책정리/열혈 TCP,IP

4장 TCP 기반 서버/클라이언트1

GONII 2015. 8. 5. 16:09
  • TCP와 UDP에 대한 이해

    TCP는 Transmission Control Protocol의 약자로써 '데이터 전송과정의 컨트롤'이라는 뜻을 담고 있다.

    • TCP/IP 프로토콜 스택

      다음 그림은 TCP/IP프로토콜 스택(Stack, 계층)을 보여준다

      TCP/IP 스택이 총 네 개의 계층으로 나뉨을 알 수 있는데, 이는 데이터 송수신의 과정을 네 개의 영역으로 계층화했다는 의미로 받아들일 수 있다. 즉 '인터넷 기반의 효율적인 데이터 전송'이라는 커다란 하나의 문제를 하나의 덩치 큰 프로토콜 설계로 해결한 것이 아니라, 그 문제를 작게 나눠서 계층화하려는 노력이 시도되었고, 그 결과로 탄생한 것이 'TCP/IP 프로토콜 스택'인 것이다. 따라서 TCP 소켓을 생성해서 데이터를 송수신할 경우에는 다음 네 계층의 도움을 통해서 데이터를 송수신하게 된다.

      그리고 각각의 계층을 담당하는 것은 운영체제와 같은 소프트웨어이기도 하고, NIC와 같은 물리적인 장치이기도 하다.

    • TCP/IP 프로토콜의 탄생배경

      네트워크는 소프트웨어만 가지고 해결할 수 있는 문제가 아니다. 소프트웨어가 존재하기 전에 하드웨어적으로 시스템이 구축되어 있어야 하고 그러한 물리적 환경을 기반으로 각종 소프트웨어적인 알고리즘을 필요로 한다. 즉 '인터넷을 통한 효율적인 데이터의 송수신'이라는 이슈의 해결을 위해서는 아주 많은 분야의 전문가가 필요하며 이들간의 상호 논의로 만들어진 다양힌 약속이 또한 필요하다. 따라서 이 문제는 작은 문제로 나눠서 해결히는 것이 효율적이다

      결국엔 문제를 영역별로 나눠서 '인터넷을 통한 효율적인 데이터의 송수신'에 대한 결론을 얻게 되었다. 문제를 영역별로 나눠서 해결하다 보니 프로토콜이 여러 개 만들어졌으며 이들은 계층구조를 통해서 상호간에 관계를 맺게 되었다

    • LINK 계층

      LINK계층은 물리적인 영역의 표준화에 대한 결과이다. 이는 가장 기본이 되는 영역으로 LAN, WAN, MAN과 같은 네트워크 표준과 관련된 프로토콜을 정의하는 영역이다.

    • IP 계층

      이제 물리적인 연결이 형성되었으니 데이터를 보낼 기본준비가 되었다. 그런데 복잡하게 연결되어 있는 인터넷을 통한 데이터의 전송을 위해 선행되어야 할 일은 경로의 선택이다. 목적지로 데이터를 전송하기 위해서 중간에 어떤 경로를 거쳐갈 것인가? 이 문제를 해결하는 것이 IP계층이고, 이 계층에서 사용하는 프로토콜이 IP(Internet Protocol)이다.

      IP 자체는 비 연결지향적이며 신뢰할 수 없는 프로토콜이다. 데이터를 전송할 때마다 거쳐야할 경로를 선택해 주지만, 그 경로는 일정치 않다. 특히 데이터 전송 도중에 경로상에 문제가 발생하면 다른 경로를 선택해 주는데, 이 과정에서 데이터가 손실되거나 오류가 발생하는 등의 문제가 발생한다고 해서 이를 해결해주지 않는다.

    • TCP/UDP 계층

      TCP와 UDP 계층은 IP계층에서 알려준 경로정보를 바탕으로 데이터의 실제 송수신을 담당한다. 때문에 이 계층을 가리켜 '전송(Transport) 계층'이라고 한다. TCP는 신뢰성 있는 데이터의 전송을 담당한다. 그런데 TCP가 데이터를 보낼 때 기반이 되는 프로토콜이 IP이다.(이것이 프로토콜이 스택의 구조로 계층화되어 있는 이유이다.)

      IP는 오로지 하나의 데이터 패킷(데이터 전송의 기본단위)이 전송되는 과정에만 중심을 두고 설계되었다. 따라서 여러 개의 데이터 패킷을 전송한다 하더라도 각각의 패킷이 전송되는 과정은 IP에 의해서 진행되므로 전송의 순서는 물론이거니와 전송 그 자체를 신뢰할 수 없다.

      TCP 프로토콜이 추가되어 데이터를 주고받는 과정에서 서로 데이터의 주고 받음을 확인한다. 그리고 분실된 데이터에 대해서 재전송하여 신뢰할 수 있는 데이터 송수신이 된다.

      위 그림은 TCP의 역할을 간단히 표현한 것이다. 결론적으로 IP의 상위계층에서 호스트 대 호스트의 데이터 송수신 방식을 약속하는 것이 TCP 그리고 UDP이며, TCP는 확인절차를 걸쳐서 신뢰성 없는 IP에 신뢰성을 부여한 프로토콜이라 할 수 있다.

    • APLPLICATION 계층

      최종적으로 소켓이라는 도구가 주어졌고, 이 도구를 이용해서 무언가를 만들면 된다. 이렇게 무엇인가를 만드는 과정에서 프로그램의 성격에 따라 클라이언트와 서버간의 데이터 송수신에 대한 약속(규칙)들이 정해지기 마련인데, 이를 가리켜 APPLICATION 프로토콜이라 한다. 그리고 대부분의 네트워크 프로그래밍은 APPLICATION 프로토콜의 설계 및 구현이 상당부분 차지한다.

  • TCP기반 서버, 클라이언트 구현
    • TCP 서버에서의 기본적인 함수호출 순서

      대부분의 TCP 서버 프로그램은 이 순서로 구현이 된다.

      제일 먼저 socket 함수의 호출을 통해서 소켓을 생성한다. 그리고 주소정보를 담기 위한 구조체 변수를 선언 및 초기화해서 bind 함수를 호출하여 소켓에 주소를 할당한다.

    • 연결요청 대기상태로의 진입

      bind 함수호출을 통해서 소켓에 주소까지 할당했다면, 이번에는 listen 함수호출을 통해서 '연결요청 대기상태'로 들어갈 차례이다. 그리고 listen 함수가 호출되어야 클라이언트가 연결요청을 할 수 있는 상태가 된다. 즉, listen 함수가 호출되어야 클라이언트는 연결요청을 위해서 connect 함수를 호출할 수 있다.

#include <sys/type.h>

   

int listen(int sock, int backlog);

// 성공 : 0

// 실패 : -1

sock : 연결요청 대기 상태에 두고자 하는 소켓의 파일 디스크립터 전달, 이 함수의 인자로 전달된 디스크립터의 소켓이 서버 소켓이 된다.

backlog : 연결요청 대기 큐(Queue)의 크기정보 전달, 5가 전달되면 큐의 크기가 5가 되어 클라이언트의 연결요청을 5개까지 대기시킬 수 있다.

서버가 '연결요청 대기상태'에 있다는 것은 클라이언트가 연결요청을 했을 때 연결이 수락될 때까지 연결요청 자체를 대기시킬 수 있는 상태에 있다는 것을 의미한다.

위 그림을 보면, listen 함수의 첫 번째 인자로 전달된 파일 디스크립터의 소켓이 어떤 용도로 사용되는지 알 수 있다. 클라이언트의 연결요청도 인터넷을 통해서 흘러 들어오는 일종의 데이터 전송이기 때문에, 이것을 받아들이려면 당연히 소켓이 하나 있어야 한다. 서버 소켓의 역할이 바로 이것이다. 즉, 연결 요청을 맞이하는, 일종의 문지기 또는 문의 역할을 한다고 볼 수 있다.

listen 함수가 호출되면 문지기의 역할을 하는 서버 소켓이 만들어지고, listen 함수의 두 번째 인자로 전달되는 정수의 크기에 해당하는 대기실이 만들어진다. 이 대기실을 가리켜 '연결요청 대기 큐'라 하며, 서버 소켓과 연결 요청 대기 큐가 완전히 준비되어서 클라이언트의 연결요청을 받아들일 수 있는 상태를 가리켜 '연결요청 대기상태'라 한다.

listen 함수의 두 번째 인자로 전달될 적절한 인자의 값은 서버의 성격마다 다르지만, 웹 서버와 같이 잦은 연결요청을 받는 서버의 경우에는 최소 15 이상을 전달해야 한다. 참고로 연결요청 대기 큐의 크기는 어디까지나 실험적 결과에 의존해서 결정하게 된다.

  • 클라이언트의 연결요청 수락

    listen 함수호출 이후에 클라이언트의 연결요청이 들어왔다면, 들어온 순서대로 연결요청을 수락해야 한다. 연결요청을 수락한다는 것은 클라이언트와 데이터를 주고받을 수 있는 상태가 됨을 의미한다. 따라서 이러한 상태가 되기 위해 무엇이 필요한지 짐작할 수 있을 것이다. 소켓이 필요하다. 그런데 서버 소켓은 문지기이다. 클라이언트와의 데이터 송수신을 위해 이것을 사용하면 문은 누가 지키겠는가? 때문에 소켓을 하나 더 만들어야 한다. 하지만 우리가 소켓을 직접 만들 필요는 없다. 다음 함수의 호출결과로 소켓이 만들어지고, 이 소켓은 연결요청을 한 클라이언트 소켓과 자동으로 연결된다.

#include <sys/socket.h>

   

int accept(int sock, struct sockaddr* addr, socklen_t* addrlen);

// 성공 : 생성된 소켓의 파일디스크립터

// 실패 : -1

sock : 서버 소켓의 파일 디스크립터 전달

addr : 연결요청 한 클라이언트의 주소정보를 담을 변수의 주소, 함수호출이 완료되면 인자로 전달된 주소의 변수에는 클라이언트의 주소정보가 채워진다.

addrlen : 두 번째 매개변수 addr에 전달된 주소의 변수 크기를 바이트 단위로 전달, 단 크기정보를 변수에 저장한 다음에 변수의 주소 값을 전달한다. 그리고 함수호출이 완료되면 크기정보로 채워져 있던 변수에는 클라이언트의 주소정보 길이가 바이트 단위로 계산되어 채워진다.

accept 함수는 '연결요청 대기 큐'에서 대기중인 클라이언트의 연결요청을 수락하는 기능의 함수이다. 따라서 accept 함수는 호출성공 시 내부적으로 데이터 입출력에 사용할 소켓을 생성하고, 그 소켓의 파일 디스크립터를 반환한다. 중요한 점은 소켓이 자동으로 생성되어 연결요청을 한 클라이언트 소켓에 연결까지 이뤄진다는 점이다.

위 그림에서는 대기 큐(Queue)에 존재하던 연결요청 하나를 꺼내서 새로운 소켓을 생성한 후에 연결요청을 완료함을 보이고 있다. 이렇듯 서버에서 별도로 생성한 소켓과 클라이언트 소켓이 직접 연결되었으니 이제 데이터를 주고 받는 일만 남았다.

  • hello world 서버 프로그램 리뷰

// 소켓의 생성

int serv_sock = socket(PF_INET, SOCK_STREAM, 0);

   

// 소켓의 주소 할당을 위해 구조체 변수 초기화

struct sockaddr_in serv_addr;

memset(&serv_addr, 0, sizeof(serv_addr));

serv_addr.sin_family = AF_INET;

serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

serv_addr.sin_port = htons(atoi(argv[1]));

// bind함수 호출

if( bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)

error_handling("bind() error");

   

// 연결 대기 상태로 들어가기 위해 listen 함수 호출

if( listen(serv_sock, 5) == -1 )

error_handling("listen() error");

   

// accept함수 호출, 대기 큐에서 첫 번째로 대기 중에 있는 연결요청을 참조하여 클라이언트와의 연결을 구성하고, 이 때 생성된 소켓의 파일디스크립터를 반환

// 이 함수가 호출되었을 때 대기 큐가 비어있다면 대기큐가 찰 때까지(클라이언트의 연결요청이 들어올 때까지) accept 함수는 반환하지 않는다

int clnt_sock;

struct sockaddr_in clnt_addr;

socklen_t clnt_addr_size;

clnt_addr_size = sizeof(clnt_addr);

clnt sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

if( clnt_sock == -1 )

error_handling("accept() error");

   

// write 함수 호출

// 클라이언트에게 데이터 전송

write( clnt_sock, message, sizeof(message) );

   

// 접속 종료

close(clnt_sock);

close(serv_sock);

  • TCP 클라이언트의 기본적인 함수호출 순서

    서버의 구현과정과 비교하면 '연결요청' 과정에 차이가 있다. 이는 클라이언트 소켓을 생성한 후에 서버로 연결을 요청하는 과정이다. 서버는 listen 함수를 호출한 이후부터 연결요청 대기 큐를 만들어 놓는다. 따라서 그 이후부터 클라이언트는 연결요청을 할 수 있다.

    클라이언트는 다음 함수호출을 통해 연결요청을 한다.

#include <sys/socket.h>

   

int connect(int sock, struct sockaddr* servaddr, socklen_t addrlen);

// 성공 : 생성된 소켓의 파일 디스크립터

// 실패 : -1

sock : 클라이언트 소켓의 파일디스크립터 전달

servaddr : 연결요청 할 서버의 주소정보를 담은 변수의 주소 값 전달, 함수호출이 완료되면 인자로 전달되니 주소의 변수에는 클라이언트의 주소정보가 채워진다.

addrlen : 두 번째 매개변수 servaddr에 전달된 주소의 변수 크기를 바이트 단위로 전달, 크기정보를 변수에 저장한 다음에 변수의 주소 값을 전달한다. 그리고 함수호출이 완료되면 크기정보로 채워져 있던 변수에는 클라이언트의 주소정보 길이가 바이트 단위로 계산되어 채워진다.

클라이언트에 의해서 connect 함수가 호출되면 다음 둘 중 한가지 상황이 되어야 함수가 반환된다.

  • 서버에 의해 연결요청이 접수되었다
  • 네트워크 단절 등 오류상황이 발생해서 연결요청이 중단되었다

여기서 주의할 사실은 위에서 말하는 '연결요청의 접수'는 서버의 accept 함수호출을 의미하는 것이 아니라는 점이다. 이는 클라이언트의 연결요청 정보가 서버의 연결요청 대기 큐에 등록된 상황을 의미하는 것이다. 때문에 connect함수가 반환했더라도 당장 서버스가 이뤄지지 않을 수도 있다.

  • hello world 클라이언트 프로그램 리뷰

    // TCP 소켓 생성

    int sock = socket(PF_INET, SOCK_STREAM, 0);

       

    // serv_addr에 IP, PORT정보 초기화, 초기화되는 값은 연결을 목적으로 하는 서버 소켓의 IP와 PORT정보

    memset(&serv_addr, 0, sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;

    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);

    serv_addr.sin_port = htones(atoi(argv[2]));

       

    // connect 함수호출을 통해 서버로 연결요청

    if( connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)

    error_handling("connect() error");

       

    // 연결 요청을 성공한 후에 서버로부터 전송되는 데이터를 수신

    str_len = read(sock, message, sizeof(message)-1);

    if ( str_len == -1 )

    error_handling("read() error");

       

    // 데이터 수신한 후 close함수호출로 소켓 닫기, 서버와의 연결 종료

    close(sock);

       

  • TCP기반 서버, 클라이언트의 함수호출 관계

    TCP서버, TCP클라이언트 프로그램은 서로 독립된 과정이 아니기 때문에 하나의 과정으로 머리속에 그릴 수 있어야 한다.

    서버는 소켓 생성 이후 bind, listen 함수의 연이은 호출을 통해 대기상태에 들어간다

    클라이언트는 connect 함수호출을 통해서 연결요청을 하게 된다. 특히 클라이언트는 서버 소켓의 listen 함수 호출 이후에 connect 함수호출이 가능하다는 사실을 기억할 필요가 있다. 뿐만 아니라 클라이언트가 connect 함수를 호출하기에 앞서 서버가 accept 함수를 먼저 호출할 수 있다는 사실도 함께 기억하기 바란다. 물론 이때는 클라이언트가 connect 함수를 호출할 때까지 서버는 accept 함수가 호출된 위치에서 블로킹 상태에 놓이게 된다.

  • Iterative 기반의 서버, 클라이언트 구현
    • Iterative 서버의 구현

      큐의 크기까지 설정해 놓았다면, 연결요청을 하는 모든 클라이언트에게 약속되어 있는 서비스를 제공해야 한다.

      accept 함수가 호출된 다음에 입출력 함수인 read, write 함수를 호출하고 있다. 그리고 이어서 close 함수를 호출하고 있는데, 이는 서버 소켓을 대상으로 하는 것이 아니다, accept 함수의 호출과정에서 생성된 소켓을 대상으로 하는 것이다.

      close 함수까지 호출되었다면 한 클라이언트에 대한 서비스가 완료된 것이다.

    • Iterative 에코 서버, 에코 클라이언트

      서버는 한 순간에 하나의 클라이언트와 연결되어 에코 서비스를 제공한다

      서버는 총 다섯 개의 클라이언트에게 순차적으로 서비스를 제공하고 종료한다.

      클라이언트는 프로그램 사용자로부터 문자열 데이터를 입력 받아서 서버에 전송한다.

      서버는 전송 받은 문자열 데이터를 클라이언트에게 재전송한다. 즉, 에코 시킨다

      서버와 클라이언트간의 문자열 에코는 클라이언트가 Q를 입력할 때까지 계속한다.

      • eco_server.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

   

#define BUF_SIZE 1024

void error_handling(char* message);

   

int main(int argc, char* argv[])

{

int serv_sock, clnt_sock;

char message[BUF_SIZE];

int str_len, i;

   

struct sockaddr_in serv_adr, clnt_adr;

socklnet_t clnt_adr_sz;

   

if( argc != 2 )

{

printf("Usage : %s<port>\n", argv[0]);

exit(1);

}

   

serv_sock = socket(PF_INET, SOCK_STREAM, 0);

if( serv_sock == -1 )

error_handling("socket() error");

   

memset(&serv_adr, 0, sizeof(serv_adr));

serv_adr.sin_family = AF_INET;

serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);

serv_adr.sin_port = htons(atoi(argv[1]));

   

if( bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)

error_handling("bind() error");

   

if( listen(serv_sock, 5) == -1 )

error_handling("listen() error");

   

clnt_adr_sz = sizeof(clnt_adr);

   

for( i = 0 ; i < 5 ; i++ )

{

clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);

if( clnt_sock == - 1 )

error_handling("accept() error");

else

printf("connected %d\n", i+1);

   

while(str_len = read(clnt_sock, message, BUF_SIZE)) != 0 )

write(clnt_sock, message, str_len);

}

   

close(serv_sock);

return 0;

}

   

void error_handling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

  • echo_client.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

   

#define BUF_SIZE 1024

void error_handling(char* message);

   

int main(argc, char* argv[])

{

int sock;

char message[BUF_SIZE];

int str_len;

struct sockaddr_in serv_adr;

   

if( argc != 3 )

{

printf("Usage : %s<IP> <port>\n", argv[0]);

exit(1);

}

   

sock = socket(PF_INET, SOCK_STREAM, 0);

if( sock == -1 )

error_handling("socket() error");

   

memset(&serv_adr, 0, sizeof(serv_adr));

serv_adr.sin_family = AF_INET;

serv_adr.sin_addr.s_addr = inet_addr(argv[1]);

serv_adr.sin_port = htons(atoi(argv[2]));

   

if( connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == 1)

error_handling("connect() error");

else

puts("connected.......");

   

while(1)

{

fputs("Input message(Q to quit): ", stdout);

fgets(message, BUF_SIZE, stdin);

   

if( !strcmp(message, "q\n") || !strcmp(message, "Q\n"))

break;

   

write(sock, message, strlen(message));

str_len = read(sock, message, BUF_SIZE-1);

message[str_len] = 0;

printf("Message from server:%s", message);

}

   

close(sock);

return 0;

}

   

void error_handling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

  • 에코 클라이언트의 문제점

    다음은 echo_client.c 의 45~48행에 있는 입출력 문장이다

    wirte(sock, message, strlen(message));

    str_len = read(sock, message, BUF_SIZE-1);

    message[str_len] = 0;

    printf("Message from server: %s", message);

    위의 코드는 다음과 같은 잘못된 가정이 존재한다.

    "read, write 함수가 호출될 때마다 문자열 단위로 실제 입출력이 이루어진다."

    위에서 구현한 클라이언트는 TCP클라이언트이기 때문에 둘 이상의 write 함수호출로 전달된 문자열 정보가 묶여서 한번에 서버로 전송될 수 있다. 그리고 그러한 상황이 발생하면 클라이언트는 한번에 둘 이상의 문자열 정보를 서버로부터 되돌려 받아서, 원하는 결과를 얻지 못할 수 있다. 그리고 서버가 다음과 같이 판단하는 상황도 생각해봐야 한다.

    "문자열의 길이가 제법 긴 편이니, 문자열을 두 개의 패킷에 나눠서 보내야겠군"

    서버는 한번의 write 함수호출로 데이터 전송을 명령했지만, 전송할 데이터의 크기가 크다면, 운영체제는 내부적으로 이를 여러 개의 조각으로 나눠서 클라이언트에게 전송할 수도 있는 일이다. 그리고 이 과정에서 데이터의 모든 조각이 클라이언트에게 전송되지 않았음에도 불구하고, 클라이언트는 read 함수를 호출할지도 모른다.

    이 모든 문제가 TCP의 데이터 전송특성에서 비롯된 것이다.

  • 윈도우 기반으로 구현하기
    • 윈도우 기반 에코 서버

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

   

#define BUF_SIZE 1024

void error_handling(char* message);

   

int main(int argc, char* argv[])

{

WSADATA wsaData;

int serv_sock, clnt_sock;

char message[BUF_SIZE];

int str_len, i;

   

SOCKADDR_IN serv_adr, clnt_adr;

int clnt_adr_sz;

   

if( argc != 2 )

{

printf("Usage : %s<port>\n", argv[0]);

exit(1);

}

   

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

error_handling("WSAStartup() error");

   

serv_sock = socket(PF_INET, SOCK_STREAM, 0);

if( serv_sock == INVALID_SOCKET )

error_handling("socket() error");

   

memset(&serv_adr, 0, sizeof(serv_adr));

serv_adr.sin_family = AF_INET;

serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);

serv_adr.sin_port = htons(atoi(argv[1]));

   

if( bind(serv_sock, (SOCKADDR*)&serv_adr, sizeof(serv_adr)) == SOCKET_ERROR)

error_handling("bind() error");

   

if( listen(serv_sock, 5) == SOCKET_ERROR )

error_handling("listen() error");

   

clnt_adr_sz = sizeof(clnt_adr);

   

for( i = 0 ; i < 5 ; i++ )

{

clnt_sock = accept(serv_sock, (SOCKADDR*)&clnt_adr, &clnt_adr_sz);

if( clnt_sock == -1 )

error_handling("accept() error");

else

printf("connected %d\n", i+1);

   

while((str_len = recv(clnt_sock, message, BUF_SIZE, 0)) != 0 )

send(clnt_sock, message, str_len, 0);

   

closesocket(clnt_sock);

}

   

closesocket(serv_sock);

   

WSACleanup();

return 0;

}

   

void error_handling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

  • 윈도우 기반 에코 서버

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

   

#define BUF_SIZE 1024

void error_handling(char* message);

   

int main(int argc, char* argv[])

{

WSADATA wsaData;

int sock;

char message[BUF_SIZE];

int str_len;

SOCKADDR_IN serv_adr;

   

if( argc != 3 )

{

printf("Usage : %s<IP> <port>\n", argv[0]);

exit(1);

}

   

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

error_handling("WSAStartup() error");

   

sock = socket(PF_INET, SOCK_STREAM, 0);

if( sock == -1 )

error_handling("socket() error");

   

memset(&serv_adr, 0, sizeof(serv_adr));

serv_adr.sin_family = AF_INET;

serv_adr.sin_addr.s_addr = inet_addr(argv[1]);

serv_adr.sin_port = htons(atoi(argv[2]));

   

if( connect(sock, (SOCKADDR*)&serv_adr, sizeof(serv_adr)) == SOCKET_ERROR)

error_handling("connect() error");

else

puts("connected.......");

   

while(1)

{

fputs("Input message(Q to quit): ", stdout);

fgets(message, BUF_SIZE, stdin);

   

if( !strcmp(message, "q\n") || !strcmp(message, "Q\n"))

break;

   

send(sock, message, strlen(message), 0);

str_len = recv(sock, message, BUF_SIZE-1, 0);

message[str_len] = 0;

printf("Message from server:%s", message);

}

   

closesocket(sock);

WSACleanup();

return 0;

}

   

void error_handling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

 

반응형