책정리/열혈 TCP,IP

6장 UDP 기반 서버/클라이언트

GONII 2015. 8. 5. 16:12
  • UDP에 대한 이해

    TCP/IP 프로토콜 스택의 상위 두 번째 계층인 '전송(Transport) 계층'이 TCP와 UDP로 나뉜다. 이렇듯 데이터 송수신은 TCP소켓을 생성해서 TCP방식으로 송수신하는 방식과 UDP소켓을 생성해서 UDP방식으로 송수신하는 방식으로 나뉜다.

    • UDP 소켓의 특성

      UDP 소켓은 신뢰할 수 없는 전송방법을 제공한다

      신뢰성만 놓고 보면 분명 TCP가 UDP보다 좋은 프로토콜이다. 하지만 UDP는 TCP보다 훨씬 간결한 구조로 설계되어있다. ACK와 같은 응답 메시지를 보내는 일도 없으며, SEQ와 같이 패킷에 번호를 부여하는 일도 없다. 때문에 상황에 따라서 TCP보다 훨씬 좋은 성능을 발휘한다. 물론 프로그래밍의 관점에서 보더라도 UDP는 TCP보다 구현이 용이하다. 계다가 UDP도 TCP만큼은 아니지만 생각만큼 데이터의 손실이 자주 발생하는 것은 아니기 때문에 신뢰성보다 성능이 중요시되는 상황에서는 UDP가 좋은 선택이 될 수 있다.

      앞서 TCP는 신뢰성 없는 IP를 기반으로 신뢰성 있는 데이터의 송수신을 위해서 '흐름제어(Flow Control)'를 한다고 설명했는데 바로 이 흐름제어가 UDP에는 존재하지 않는다.

      흐름제어가 UDP와 TCP를 구분 지어주는 가장 큰 차이점이다.

    • UDP의 내부 동작원리

      위 그림에서 보이듯이 호스트B를 떠난 UDP패킷이 호스트A에게 전달되도록 하는 것은 IP의 역할이다. 그런데 이렇게 전달된 UDP패킷을 호스트A 내에 존재하는 UDP소켓 중 하나에게 최종 전달하는 것은 IP의 역할이 아니다. 이는 바로 UDP의 역할이다. 즉, UDP의 역할 중 가장 중요한 것은 호스트로 수신된 패킷을 PORT 정보를 참조하여 최종 목적지인 UDP소켓에 전달하는 것이다.

    • UDP의 효율적인 사용

      UDP도 나름대로 상당히 신뢰할만하다. 인터넷의 특성상 손실되는 정보들이 많음에도 불구하고 생각보다는 신뢰할만하다. 압축파일의 경우에는 반드시 TCP를 기반으로 송수신이 이뤄져야 한다. 왜냐하면 압축파일은 그 특성상 파일의 일부만 손실되어도 압축의 해제가 어렵기 때문이다. 그러나 인터넷 기반으로 실시간 영상 및 음성을 전송하는 경우에는 조금 다르다. 멀티미디어 데이터는 그 특성상 일부가 손실되어도 크게 문제되지 않는다. 실시간으로 서비스를 해야 하는 경우에도 속도가 상당히 중요한 요소가 된다. 이러한 경우가 UDP 기반의 구현을 고려할만한 상황이다. 그러나 UDP가 TCP에 비해서 언제나 빠른 속도를 내는 것은 아니다. TCP가 UDP에 비해 느린 두 가지 이유를 들 수 있다.

      • 데이터 송수신 이전, 이후에 거치는 연결설정 및 해제과정
      • 데이터 송수신 과정에서 거치는 신뢰보장을 위한 흐름제어

      따라서 송수신하는 데이터의 양은 작으면서 잦은 연결이 필요한 경우에는 UDP가 TCP보다 훨씬 효율적이고 빠르게 동작하다.

  • UDP 기반 서버/클라이언트의 구현
    • UDP에서의 서버와 클라이언트는 연결되어 있지 않습니다.

      UDP서버, 클라이언트는 TCP와 같이 연결된 상태로 데이터를 송수신하지 않는다. 때문에 TCP와 달리 연결 설정 과정이 필요 없다. 따라서 TCP 서버 구현과정에서 거쳤던 listen함수와 accept함수의 호출은 불필요하다. UDP소켓의 생성과 데이터의 송수신 과정만 존재할 뿐이다.

    • UDP에서는 서버건 클라이언트건 하나의 소켓만 있으면 됩니다.

      TCP에서는 소켓과 소켓의 관계가 일대일이었다. 때문에 서버에서 열 개의 클라이언트에게 서비스를 제공하려면 문지기의 역할을 하는 서버 소켓을 제외하고도 열 개의 소켓이 더 필요했다. 그러나 UDP는 서버건 클라이언트건 하나의 소켓만 있으면 된다.

      위 그림에서는 하나의 UDP 소켓으로 두 곳의 호스트를 대상으로 데이터의 송수신이 가능함을 보이고 있다. 이렇듯 UDP 소켓은 하나만 있으면 둘 이상의 호스트와의 통신이 가능하다.

    • UDP 기반의 데이터 입출력 함수

      UDP소켓은 연결상태를 유지하지 않으므로, 데이터를 전송할 때마다 반드시 목적지의 주소정보를 별도로 추가해야 한다. 주소정보를 써넣으면서 데이터를 전송할 때 호출하는 UDP관련함수는 다음과 같다.

#include <sys/socket.h>

   

ssize_t sendto(int sock, void* buff, size_t nbytes, int flags, struct sockaddr* to, socklen_t addrlen);

// 성공 : 전송된 바이트 수

// 실패 : -1

sock : 데이터 전송에 사용될 UDP소켓의 파일 디스크립터

buff : 전송할 데이터를 저장하고 있는 버퍼의 주소 값

nbytes : 전송할 데이터 크기를 바이트 단위로 전달

flags : 옵션 지정에 사용되는 매개변수, 지정할 옵션이 없다면 0 전달

to : 목적지 주소정보를 담고 있는 sockaddr 구조체 변수의 주소

addrlen : 매개변수 to로 전달된 주소 값의 구조체 변수 크기

위 함수가 이전에 소개한 TCP 기반의 출력함수와 가장 비교되는 것은 목적지 주소정보를 요구하고 있다는 점이다.

UDP데이터는 발신지가 일정치 않기 때문에 발신지 정보를 얻을 수 있도록 함수가 정의되어 있다. 즉 이 함수는 UDP패킷에 담겨 있는 발신지 정보를 함께 반환한다.

#include <sys/socket.h>

   

ssize_t recvfrom(int sock, void* buff, size_t nbytes, int flags, struct sockaddr* from, socklent_t* addrlen);

// 성공 : 수신한 바이트 수

// 실패 : -1

sock : 데이터 수신에 사용될 UDP 소켓의 파일 디스크립터

buff : 데이터 수신에 사용될 버퍼의 주소 값

nbytes : 수신할 최대 바이트 수 전달, 때문에 매개변수 buff가 가리키는 버퍼의 크기를 넘을 수 없다.

flags : 옵션 지정에 사용되는 매개변수, 지정할 옵션이 없다면 0 전달

from : 발신지 정보를 채워 넣을 sockaddr 구조체 변수의 주소 값 전달

addrlen : 매개변수 from으로 전달된 주소 값의 구조체 변수의 크기

  • UDP 기반의 에코 서버와 에코 클라이언트
    • uecho_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 30

void error_handling(char* message);

   

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

{

int serv_sock;

char message[BUF_SIZE];

int str_len;

socklent_t clnt_adr_sz;

   

struct sockaddr_in serv_adr, clnt_adr;

if( argc != 2 )

{

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

exit(1);

}

   

serv_sock = socket(PF_INET, SOCK_DGRAM, 0);

if( serv_sock == -1 )

error_handling("UDP socket creation 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");

   

while(1)

{

clnt_adr_sz = sizeof(clnt_adr);

str_len = recvform(serv_sock, message, BUF_SIZE, 0,

(struct sockaddr*)&clnt_adr, &clnt_adr_sz);

   

sendto(serv_sock, message, str_len, 0,

(struct sockaddr*)&clnt_adr, clnt_adr_sz);

}

close(serv_sock);

return 0;

}

   

void error_handling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

  • uecho_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 30

void error_handling(char* message);

   

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

{

int sock;

char message[BUF_SIZE];

int str_len;

socklen_t adr_sz;

   

struct sockaddr_in serv_adr, form_adr;

if( argc != 3 )

{

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

exit(1);

}

   

sock = socket(PF_INET, SOCK_DGRAM, 0);

if( sock == -1 )

error_handling("socket() error");

   

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

serv_adr.sin_family = AF_INET;

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

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

   

while(1)

{

fputs("Insert message(q to quit): ", stdout);

fgets(message, sizeof(message), stdin);

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

break;

   

sendto(sock, message, strlen(message), 0,

(struct sockaddr*)&serv_adr, sizeof(serv_adr));

adr_sz = sizeof(from_adr);

str_len = recvfrom(sock, message, BUF_SIZE, 0,

(struct sockaddr*)&from_adr, &adr_sz);

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);

}

모든 소켓에는 IP와 PORT가 할당되어야 한다. 다만 직접 할당하느냐, 자동으로 할당되느냐의 문제만 남아있을 뿐이다.

  • UDP 클라이언트 소켓의 주소정보 할당

    그런데 UDP클라이언트 프로그램을 가만히 보면, IP와 PORT를 소켓에 할당하는 부분이 눈에 띄지 않는다. TCP클라이언트의 경우에는 connect 함수호출 시 자동으로 할당되었는데, UDP클라이언트의 경우에는 그러한 기능을 대신 할만한 함수호출문 조차 보이지 않는다. 어느시점에서 IP와 PORT가 할당되는 것일까?

    UDP프로그램에서는 데이터를 전송하는 sendto 함수호출 이전에 해당 소켓에 주소정보가 할당되어 있어야 한다. 따라서 sendto함수 호출 이전에 bind함수를 호출해서 주소정보를 할당해야 한다. 물론 bind함수는 TCP프로그램에 구현에서 호출되었던 함수이다. 그러나 이 함수는 TCP와 UDP를 가리지 않으므로 UDP프로그램에서도 호출이 가능하다. 그리고 만약에 sendto 함수호출 시까지 주소정보가 할당되지 않았다면, sendto 함수가 처음 호출되는 시점에 해당 소켓에 IP와 PORT번호가 자동으로 할당된다. 또한 이렇게 한번 할당되면 프로그램이 종료될 때까지 주소정보가 그대로 유지되기 때문에 다른 UDP소켓과 데이터를 주고받을 수 있다. 물론 IP는 호스트의IP로, PORT는 사용하지 않는 PORT번호 하나를 임의로 골라서 할당하게 된다.

    이렇듯 sendto 함수호출 시 IP와 PORT번호가 자동으로 할당되기 때문에 일반적으로 UDP의 클라이언트 프로그램에서는 주소정보를 할당하는 별도의 과정이 불필요하다. 그래서 앞서 보인 예제에서도 별도의 주소정보 할당과정을 생략하였으며, 이것이 일반적인 구현방법이다.

  • UDP의 데이터 송수신 특성과 UDP에서의 connect 함수호출
    • 데이터의 경계가 존재하는 UDP 소켓

      UDP는 데이터의 경계가 존재하는 프로토콜이므로, 데이터 송수신 과정에서 호출하는 입출력함수의 호출횟수가 큰 의미를 지닌다. 때문에 입력함수의 호출횟수와 출력함수의 호출횟수가 완벽히 일치해야 송신된 데이터 전부를 수신할 수 있다. 예를 들어 세 번의 출력함수 호출을 통해서 전송된 데이터는 반드시 세 번의 입력함수 호출이 있어야 데이터 전부를 수신할 수 있다.

      • bound_host1.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

   

#define BUF_SIZE 30

void error_handling(char* message);

   

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

{

int sock;

char message[BUF_SIZE];

struct sockaddr_in my_adr, your_adr;

socklen_t adr_sz;

int str_len, i;

   

if( argc != 2 )

{

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

eixt(1);

}

   

sock = socket(PF_INET, SOCK_DGRAM, 0);

if( sock == -1 )

error_handling("socket() error");

   

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

my_adr.sin_family = AF_INET;

my_adr.sin_addr.s_addr = htonl(INADDR_ANY);

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

   

if( bind(sock, (struct sockaddr*)&my_adr, sizeof(my_adr)) == -1 )

error_handling("bind() error");

   

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

{

sleep(5); // delay 5sec

adr_sz = sizeof(your_adr);

str_len = recvfrom(sock, message, BUF_SIZE, 0,

(struct sockaddr*)&your_adr, &adr_sz);

   

printf("Message %d: %s\n", i+1, message);

}

   

close(sock);

return 0;

}

   

void error_handling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

sleep 함수를 호출하고 있는데, 인자로 전달된 시간만큼(초 단위) 프로그램을 멈추는 기능을 제공한다. 이 예제에서는 총 3회의 sendto 함수호출을 통해서 문자열 데이터를 전송한다.

  • bound_host2.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

   

#define BUF_SIZE 30

void error_handling(char* message);

   

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

{

int sock;

char msg1[] = "HI!";

char msg2[] = "I'm another UDP host";

char msg3[] = "asdflkj";

   

struct sockaddr_in your_adr;

socklen_t your_adr_sz;

if( argc != 3 )

{

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

exit(1);

}

   

sock = socket(PF_INET, SOCK_DGRAM, 0);

if( sock == -1 )

error_handling("socket() error");

   

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

your_adr.sin_family = AF_INET;

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

your_adr.sin_port = htons(argv[2]);

   

sendto(sock, msg1, sizeof(msg1), 0,

(struct sockaddr*)&your_adr, sizeof(your_adr));

sendto(sock, msg2, sizeof(msg2), 0,

(struct sockaddr*)&your_adr, sizeof(your_adr));

sendto(sock, msg3, sizeof(msg3), 0,

(struct sockaddr*)&your_adr, sizeof(your_adr));

   

close(sock);

return 0;

}

   

void error_handling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

bound_host2.c는 총 3회의 sendto 함수호출을 통해서 데이터를 전송하고, bound_host1.c는 총 3회의 recvfrom 함수호출을 통해서 데이터를 수신한다. 그런데 recvfrom 함수호출에는 5초간의 지연시간이 존재하기 때문에 recvfrom 함수가 호출되기 이전에 3회의 sendto 함수호출이 진행되어서 데이터는 이미 bound_host1.c에 전송된 상태에 놓이게된다. TCP라면 이 상황에서 단 한번의 입력함수 호출을 통해서 모든 데이터를 읽어 들일 수 있다. 그러나 UDP는 다르다. 이 상황에서도 3회의 recvfrom 함수호출이 요구된다.

  • connected UDP 소켓, unconnected UDP 소켓

    TCP 소켓에는 데이터를 전송할 목적지의 IP와 PORT번호를 등록하는 반면, UDP 소켓에는 데이터를 전송할 목적지의 IP와 PORT번호를 등록하지 않는다. 때문에 sendto 함수호출을 통한 데이터의 전송 과정은 다음과 같이 크게 세 단계로 나눌 수 있다.

    • UDP 소켓에 목적지와 IP와 PORT번호 등록
    • 데이터 전송
    • UDP소켓에 등록된 목적지 정보 삭제

    즉, sendto 함수가 호출될 때마다 위의 과정을 반복하게 된다. 이렇듯 목적지의 주소정보가 계속해서 변경되기 대문에 하나의 UDP 소켓을 이용해서 다양한 목적지로 데이터 전송이 가능한 것이다. 그리고 이렇게 목적지 정보가 등록되어 있지 않은 소켓을 가리켜 'unconnected 소켓' 이라 하고, 반면 목적지 정보가 등록되어 있는 소켓을 가리켜 'connected 소켓'이라 한다. 물론 UDP 소켓은 기본적으로 unconnected 소켓이다. 그런데 이러한 UDP 소켓은 다음과 같은 상황에서는 매우 불합리하게 동작한다

    "IP211.210.147.82 PORT82 로 준비된 총 세 개의 데이터를 세 번의 sendto 함수호출을 통해 전송한다"

    이 경우 위에서 정리한 데이터 전송 세 단계를 총 3회 반복한다. 그래서 하나의 호스트와 오랜 시간 데이터를 송수신해야 한다면, UDP 소켓을 connected 소켓으로 만드는 것이 효율적이다. 참고로 앞서 보인 1단계와 3단계가 UDP 데이터 전송과정의 약 1/3에 해당한다고 하니, 이 시간을 줄임으로 적지 않은 성능향상을 기대할 수 있다.

  • connected UDP 소켓 생성

    connected UDP 소켓을 생성하는 방법은 간단하다. UDP 소켓을 대상으로 connect 함수만 호출해주면 된다.

    sock = socket(PF_INET, SOCK_DGRAM, 0);

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

    adr.sin_family = AF_INET;

    adr.sin_addr.s_addr = ....

    adr.sin_port = ....

    connect(sock, (struct sockaddr*)&adr, sizeof(adr));

    socket 함수의 두 번째 인자가 SOCK_DGRAM이기 때문에 이는 분명 UDP 소켓의 생성과정이다. 물론 UDP 소켓을 대상으로 connect 함수를 호출했다고 해서 목적지의 UDP 소켓과 연결설정 과정을 거친다거나 하지는 않는다. 다만 UDP소켓에 목적지의 IP와 PORT 정보가 등록될 뿐이다.

    이로써 이후부터는 TCP소켓과 마찬가지로 sendto 함수가 호출될 때마다 데이터 전송의 과정만 거치게 된다. 송수신의 대상이 정해졌기 때문에 sendto, recvfrom 함수가 아닌 write, read 함수의 호출로도 데이터를 송수신할 수 있다.

    • uecho_con_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 30

void error_handling(char* message);

   

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

{

int sock;

char message[BUF_SIZE];

int str_len;

socklen_t adr_sz;                // 불필요해진 변수

   

struct sockaddr_in serv_adr, form adr;        // from_adr 불필요해짐

if( argc != 3 )

{

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

exit(1);

}

   

sock = socket(PF_INET, SOCK_DGRAM, 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(argv[2]);

   

connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));

   

while(1)

{

fputs("Insert message(q to quit): ", stdout);

fgets(message, sizeof(message), stdin);

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

break;

   

/*

sendto(sock, message, strlen(message, 0,

(struct sockaddr*)&serv_adr, sizeof(serv_adr));

*/

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

   

/*adr_sz = sizeof(from_adr);

str_len = recvfrom(sock, message, BUF_SIZE, 0

(struct sockaddr*)&from_adr, &adr_sz);

*/

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

   

message[str_len] = 0;

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

}

close(sock);

return 0;

}

   

void error_handling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

  • 윈도우 기반으로 구현하기

    윈도우 기반의 sendto 함수와 readform 함수도 사실상 리눅스와 차이는 없다

#include <winsock2.h>

   

int sendto(SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen);

// 성공 : 전송된 바이트 수

// 실패 : SOCKET_ERROR

   

#include <winsock2.h>

   

int recvfrom(SOCKET s, char* buf, int len, int flags, struct sockaddr* form int* fromlen);

// 성공 : 수신한 바이트 수

// 실패 : SOCKET_ERROR

  • 예제 uecho_server_win.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <winsock2.h>

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

   

#define BUF_SIZE 30

void error_handling(char* message);

   

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

{

WSADATA wsaData;

SOCKET servSock;

char message[BUF_SIZE];

int strLen;

int clntAdrSz;

SOCKADDR_IN servAdr, clntAdr;

   

if( argc != 2 )

{

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

exit(1);

}

   

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

error_handling("WSAStartup() error");

   

servSock = socket(PF_INET, SOCK_DGRAM, 0);

if( servSock == INVALID_SOCKET )

error_handling("socket() error");

   

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

servAdr.sin_family = AF_INET;

servAdr.sin_addr.s_addr = htonl(INADDR_ANY);

servAdr.sin_port = htons(argv[1]);

   

while(1)

{

clntAdrSz = sizeof(clntAdr);

strLen = recvfrom(servSock, message, BUF_SIZE, 0,

(SOCKADDR*)&clntAdr, &clntAdrSz);

   

sendto(servSock, message, strLen, 0,

(SOCKADDR*)&clntAdr, sizeof(clntAdr));

}

   

closesocket(servSock);

WSACleanup();

return 0;

}

   

void error_handling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

  • echo_client_win.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <winsock2.h>

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

   

#define BUF_SIZE 30

void error_handling(char* message);

   

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

{

WSADATA wsaData;

SOCKET sock;

SOCKADDR_IN servAdr;

   

char message[BUF_SIZE];

int strLen;

   

if( argc != 3 )

{

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

exit(1);

}

   

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

error_handling("WSAStartup() error");

   

sock = socket(AF_INET, SOCK_DGRAM, 0);

if( sock == INVALID_SOCKET )

error_handling("socket() error");

   

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

servAdr.sin_family = AF_INET;

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

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

connect(sock, (SOCKADDR*)&servAdr, sizeof(servAdr));

   

while(1)

{

fputs("Insert message(q to quit): ", stdout);

fgets(message, sizeof(message), stdin);

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

break;

   

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

strLen = recv(sock, message, sizeof(message)-1, 0);

message[strLen] = 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);

}

 

반응형