TCP 프로토콜을 이용한 간단한 서버/클라이언트 애플리케이션을 만들어보자

서버, 클라이언트의 동작방식, 필요한 함수의 사용방법, 네트워크 연결과정, 패킷의 흐름까지 공부할 수 있겠다

 

우선 결과를 먼저 보고 코드를 아래쓰겠다

 

TCP 서버와, TCP 클라이언트의 모습

 

상단 : 서버

하단 : 클라이언트

클라이언트는 VMWARE로 돌린거 당연히 서버로 접속을 시도하고 입력한 문자를 서버로 전송하는 프로그램, 클라이언트가 메시지를 전송하고 서버는 메시지를 받는다.

 

이걸 도식화하면

서버가 먼저 실행되어 (메인모듈) 클라이언트가 접속되기를 기다린다. 잘 들어봐 Listen()

클라이언트가 서버에 접속하고

서버로 데이터를 보낸다 send()

서버는 클라이언트 접속을 허용 accept() 하고

클라이언트가 보낸 메시지를 받는다 recv()

서버는 처리한 데이터를 다시 클라이언트에게 보낸다 send()

클라이언트는 서버가 보낸 데이터를 받아서 자신의 목적에 맞게 사용한다. recv()



 

 

2. Connect()

 - Connect함수는 지정된 소켓을 지정된 원격지로 접속을 이루는 함수입니다.

 - 형식

   : int connect()

     {

         SOCKET                            s;

         const struct sockaddr FAR* name;

         int                                     namelen;

      };

   : s - 접속되지 않은 소켓을 구분하는 식별자

     name - 어디로 접속할 것인지에 대한 정보를 담은 SOCKADDR 구조체

     namelen - name 매개변수의 크기

 - 리턴값 : 에러없으면 0, 에러생기면 에러코드 리턴, 에러내용은 WSAGetLastError 호출로 알아볼 수 있다, 에러코드는 생략

 - 동기소켓(blocking socket)에서 리턴값은 접속시도의 성공 or 실패값을 의미

 - 비동기소켓(nonblocking socket)에서 접속시도는 바로 완료될 수 없다, 때문에 connect()함수는 에러를 리턴하고, WSAGetLastError 함수는 WSAEWOULDBLOCK 에러코드를 리턴할거야, 이련 경우 접속처리의 완료를 위해 취할 수 있는 방법이 세가지가 있다.

  * 접속 요청에 의한 완료를 결정짓기 위해 select 함수 사용

  * 만약 어플리케이션이 접속 이벤트 발생을 위해 WSAAsyncSelect 함수를 사용했다면, 어플리케이션은 접속처리가 완료(성공이던 실패던)되었다는 것을 의미하는 FD_CONNECT 통지 메시지를 받을 것이야

  * 만약 어플리케이션이 접속 이벤트 발생을 위해 WSAEventSelect 함수를 사용했다면, 엮어져 있는 이벤트 객체는 접속처리가 완료(성공이던 실패던)되었다는 신호를 받게 될 것이야

 

3. send()

 - send()함수는 접속되어 있는 상대방 소켓으로 데이터를 보내는 함수

 - 형식

   : int send

     {

         SOCKET             s;

         const char FAR * buf;

         int                      len;

         int                      flags;

     }

   : s - 접속되어 있는 소켓의 서술자(descriptor)를 서술한다.

   : buf - 전송하려는 데이터를 갖고있는 버퍼

   : len - buf 안에 있는 데이터의 길이

   : flags - 함수의 호출이 어떤일을 할 지 나타내는 플래그

 - 에러가 발생하지 않으면 send함수는 전송된 데이터의 총 크기를 리턴, 비동기 소켓의 경우 len 매개변수로 보낸 수치보다 작을수도있음, 에러가 발생한 경우는 SOCKET_ERROR를 리턴, WSAGetLastError를 이용하여 에러코드 확인가능

 

 - send() 함수는 접속된 소켓에서 데이터를 접속한 상대방으로 전송하는데 사용되는 함수, 메시지 지향 소켓(message-oriented sockets)에서 최대 패킷크기(getsockopt 함수로, SO_MAX_MSG_SIZE 옵션의 값을 얻어내어 최대 크기를 구할 수 있다)를 초과하지 않도록 주의해야한다. 데이터가 전송되기에 너무 크다면 데이터는 전송되지 않고, WSAEMSGSIZE 에러코드를 발생한다.

 - send()함수의 성공적인 종료가 성공적인 데이터의 전송을 의미하진 않는다.

 - 전송 시스템에 사용 가능한 버퍼가 없다면, 소켓이 비동기(nonblocking)모드일 경우를 제외하고 send를 블록합니다. 비동기 스트림 소켓(nonblocking stream oriented socket)에서 전송된 바이트의 크기는 1부터 요청된 데이터의 길이 사이의 수가 될 수 있습니다. select, WSAAsyncSelect, WSAEventSelect 함수는 한번 데이터를 전송하고 언제 다른 데이터를 보낼 수 있는지를 결정하는데 사용됩니다. 데이터는 아무때나 보낼 수 있는게 아닙니다, 물론 비동기 소켓에서 연속된 send를 사용할 수 있지만, 안정적인 데이터 송수신을 기대할 수 없다, 그래서 소켓은 데이터를 전송할 수 있는 상태가 되었을 때 데이터를 전송해야 한다.

 - len 매개변수를 0으로 하여 send함수를 호출하는것도 가능하다, 하지만 send는 0을 리턴하고 메시지 지향 소켓(message-oriented sockets)일 경우는 0 길이의 데이터그램이 전송됩니다.

 

 

이제 예제를 보자

 

4. TCP 클라이언트

#include <Winsock2.h>

#include <stdlib.h>

#include <stdio.h>

 

#define BUFSIZE 512

 

// 사용자 정의 데이터 수신 함수

int recvn(SOCKET s, char *buf, int len, int flags)

{

    int received;

    char *ptr = buf;

    int left = len;

 

    while (left > 0)

    {

       received = recv(s, ptr, left, flags);

       if (received == SOCKET_ERROR)

            return SOCKET_ERROR;

       else if (received == 0)

               break;

       left -= received;

       ptr += received;

    }

 

   return (len - left);

}

 

int main()

{

   int retval;

  

   // 윈속초기화

   WSADATA wsa;

   if(WSAStartup(MAKEWORD(2, 2), &wsa) != 0)

          return -1;

 

   // socket()

   SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

   if(sock ==  INVALID_SOCKET) printf("소켓() 에러염");

 

   // connect()

   SOCKADDR_IN serveraddr;

   ZeroMemory(&serveraddr, sizeof(serveraddr));

   serveraddr.sin_family = AF_INET;

   serveraddr.sin_port = htons(9000);

   serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");

   retval = connect(sock, (SOCKADDR*)&serveraddr, sizeof(serveraddr));

   if(retval == SOCKET_ERROR) printf("연결() 에러염");

 

   // 데이터 통신에 사용할 변수

   char buf[BUFSIZE + 1];

   int len;

 

   // 서버와 데이터 통신

   while(1)

   {

      // 데이터 입력

      ZeroMemory(buf, sizeof(buf));

      printf("\n보낼데이터");

      if (fgets(buf, BUFSIZE+1, stdin) == NULL)

              break;

 

      // '\n' 문자 제거

      len = strlen(buf);

      if (buf[len - 1] == '\n')

        buf[len - 1] = '\0';

      if (strlen(buf) == 0)

        break;

 

      // 데이터 보내기

      retval = send(sock, buf, strlen(buf), 0);

      if (retval == SOCKET_ERROR)

      {

          printf("송신() 에러");

          break;

       }

      printf ("TCP 클라이언트 %d 바이트를 보냈습니다.", retval);

      

      // 데이터 받기

      retval = recvn(sock, buf, retval, 0);

      if (retval == SOCKET_ERROR)

      {

          printf("수신() 에러");

          break;

       }

       else if (retval == 0)

             break;

 

       // 받은 데이터 출력

       buf[retval] = '\0';

       printf("TCP 클라이언트 %d 바이트를 받았습니다.", retval);

       printf("받은 데이터 %s", buf);

    }

 

    // closesocket()

    closesocket(sock);

 

    // 윈속 종료

    WSACleanup();

     return 0;

}

 

 

5. TCP 서버

#include <Winsock2.h>

#include <stdlib.h>

#include <stdio.h>

 

#define BUFSIZE 512

 

int main()

{

   int retval;

  

   // 윈속초기화

   WSADATA wsa;

   if(WSAStartup(MAKEWORD(2, 2), &wsa) != 0)

          return -1;

 

   // socket()

   SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);

   if(listen_sock ==  INVALID_SOCKET) printf("소켓() 에러염");

 

   // bind()

   SOCKADDR_IN serveraddr;

   ZeroMemory(&serveraddr, sizeof(serveraddr));

   serveraddr.sin_family = AF_INET;

   serveraddr.sin_port = htons(9000);

   serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

   retval = bind(listen_sock, (SOCKADDR*)&serveraddr, sizeof(serveraddr)); // connect가 아니라 bind

   if(retval == SOCKET_ERROR) printf("바인딩() 에러염");

 

   // listen()

   retval = listen(listen_sock, SOMAXCONN);

   if(retval == SOCKET_ERROR) printf("리슨() 에러염");

 

   // 데이터 통신에 사용할 변수

   SOCKET client_sock;

   SOCKADDR_IN clientaddr;

   char buf[BUFSIZE + 1];

   int addrlen;

 

   // 서버와 데이터 통신

   while(1)

   {

      // accept()

      addrlen = sizeof(clientaddr);

      client_sock = accept(listen_sock, (SOCKADDR*)&clientaddr, &addrlen);

      if (client_sock == INVALID_SOCKET){

              printf("accept() 에러\n");

              continue;

      }

      printf ("TCP 서버, 클라이언트 접속 : IP 주소 = %s, 포트번호 = %d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

 

      // 클라이언트와 데이터 통신

      while(1)

      {

          retval = recv(client_sock, buf, BUFSIZE, 0);

          if (retval == SOCKET_ERROR)

         {

             printf("수신() 에러\n");

             break;

          }

          else if (retval == 0)

                 break;

            

          // 받은 데이터 출력

          buf[retval] = '\0';

          printf("[TCP /%s:%d] %s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buf);

 

          // 데이터 보내기

          retval = send(client_sock, buf, retval, 0);

          if (retval == SOCKET_ERROR)

         {

             printf("송신() 에러\n");

             break;

          }

      }

 

      // closesocket()

      closesocket(client_sock);

      printf("TCP 서버, 클라이언트 종료 : IP 주소 = %s, 포트번호 = %d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

   }

 

   closesocket(listen_sock);

 

   // 윈속 종료

   WSACleanup();

   return 0;

}

 

 

 

 

TCP로 파일전송하는 부분은 생략

'Web, Network Programming > Socket' 카테고리의 다른 글

TCP, UDP의 특징 비교  (0) 2014.11.07
멀티 스레드 TCP 서버/클라이언트  (0) 2014.06.16
도메인 네임 서버  (0) 2014.06.16
바이트 정렬  (0) 2014.05.29
Socket 생성  (0) 2014.05.29
Posted by 긍정왕오킹