관리 메뉴

Gyejoong's Information

TCP/IP 프로토콜 소켓프로그래밍 - UDP로 daytime서비스 구현하기 본문

Study/Socket programming

TCP/IP 프로토콜 소켓프로그래밍 - UDP로 daytime서비스 구현하기

연계중 2016. 10. 27. 19:02
반응형

1. udp daytime 클라이언트 소스코드

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>


void err_quit(const char *message);

void err_sys (const char *message);


#define MAXLINE 4096 /* max text line length */ 


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

{

if (argc != 2)

err_quit("usage: daytimeudpcli <IPaddress>");


// 비연결지향형 udp 소켓 생성 (socket)

int sockfd;

if ( (sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0)

err_sys("socket error");


// 서버에 더미(dummy) 데이터그램 전송 (sendto)

struct sockaddr_in servaddr;

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

servaddr.sin_family = AF_INET;

servaddr.sin_port   = htons(13); /* daytime port */

int ret;

if ( (ret = inet_pton(AF_INET,argv[1],&servaddr.sin_addr)) < 0)

err_sys("inet_pton error");

else if (!ret)

err_quit("<IPaddress> error: not a valid network address string");


const char *m = "GIVE ME A DAYTIME STRING. :)";

if (sendto(sockfd,m,strlen(m),0,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)

err_sys("sendto error");


// 날짜/시간 문자열 수신 (recvfrom) 및 출력

int n;

char recvline[MAXLINE+1];

struct sockaddr_in fromaddr;

socklen_t len = sizeof(fromaddr);

if ((n = recvfrom(sockfd,recvline,MAXLINE,0,(struct sockaddr*)&fromaddr,&len)) < 0)

err_sys("recvfrom error");


if (memcmp(&servaddr,&fromaddr,len) != 0)

err_quit("reply from different host (failed)");


recvline[n] = 0; /* null terminate */

if (fputs(recvline,stdout) == EOF)

err_sys("fputs error");


// 접속 종료/프로그램 종료 (close, exit)

close(sockfd);

exit(0);

}


void err_quit(const char *message)

{

fputs(message,stderr);

fputc('\n',stderr);

exit(1);

}


void err_sys (const char *message)

{

perror(message);

exit(1);

}


TCP소켓과 다른점은 UDP소켓은 비연결지향서비스라는 점이다. 그렇기 때문에 더미 데이터그램을 서버측에 보내야한다. 우선 UDP 소켓을 만들기 위해서  socket(AF_INET,SOCK_DGRAM,0) 이 부분에 SOCK_DGRAM으로 바꾸었다. 기존에 TCP 소켓은 SOCK_STREAM이였다. 그리고 TCP에서는 write와 read함수로 통신을 주고 받았다면

UDP에서는 sendto함수와 recvfrom함수를 쓰게된다. 그리고 connect는 하지않는다.(비연결)


sendto함수의 인자.


ssize _t sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);


recvfrom함수의 인자


ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len);


그리고 연결이 안되있기 때문에 통신을 하기위해서는 memcmp라는 함수를 이용해서 보낼 서버의 주소와 더미데이터그램을 받은 서버의 주소와 일치해야만 출력을 하게 해야한다.


2. udp daytime 서버 소스코드

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

#include <time.h>

void err_quit(const char *message);

void err_sys (const char *message);

#define MAXLINE 4096 /* max text line length */


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

{

// 비연결지향형 udp 소켓 생성 (socket)

int listenfd;

if ( (listenfd = socket(AF_INET,SOCK_DGRAM,0)) < 0)

err_sys("socket error");


// 서버주소와 소켓을 연결 (bind)

struct sockaddr_in servaddr;

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

servaddr.sin_family = AF_INET;

servaddr.sin_port   = htons(13); /* daytime port */

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)

err_sys("bind error");


for ( ; ; ) {

// 클라이언트의 더미(dummy) 데이터그램 수신 (recvfrom)

char buff[MAXLINE];

struct sockaddr_in cliaddr;

socklen_t len = sizeof(cliaddr);

if (recvfrom(listenfd,buff,MAXLINE,0,(struct sockaddr*)&cliaddr,&len) < 0)

err_sys("recvfrom error");


// 접속한 클라이언트의 정보를 화면에 출력

char cliname[INET_ADDRSTRLEN];

if ( inet_ntop(AF_INET,&cliaddr.sin_addr,cliname,sizeof(cliname)) )

printf("Handling client %s:%d\n",cliname,ntohs(cliaddr.sin_port));

else

err_sys("inet_ntop error");


// 클라이언트(cliaddr에 저장된)에게 daytime 문자열을 송신 (sendto)

time_t ticks = time(NULL);

snprintf(buff,sizeof(buff),"%.24s\r\n",ctime(&ticks));

if (sendto(listenfd,buff,strlen(buff),0,(struct sockaddr*)&cliaddr,len) < 0)

err_sys("sendto error");


} // 무한 반복 (for, while)

}


void err_quit(const char *message)

{

fputs(message,stderr);

fputc('\n',stderr);

exit(1);

}


void err_sys (const char *message)

{

perror(message);

exit(1);

}



우선 UDP소켓이라하여도 bind는 해야한다. 서버주소와 소켓을 연결을 해야되기 때문이다. 대신에 listen과 accept는 하지 않는다. 연결이 되어있지 않기 때문이다.

우선 클라이언트 측에서 데이터그램을 먼저 보냈기때문에(sendto) 서버측에서는 데이터그램을 수신을 한다.(recvfrom) 그 후에 수신이 확인되면 daytime문자열을 만들어서 클라이언트측에 보낸다.(sendto)

이러면 UDP daytime서비스는 끝이난다.


다음은 연결된 UDP daytime클라이언트(connected-UDP daytime client)를 구현해보겠다.


3. connected-udp daytime 클라이언트 소스코드

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

void err_quit(const char *message);

void err_sys (const char *message);

#define MAXLINE 4096 /* max text line length */ 


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

{

if (argc != 2)

err_quit("usage: daytimeudpconcli <IPaddress>");


// 비연결지향형 udp 소켓 생성 (socket)

int sockfd;

if ( (sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0)

err_sys("socket error");


// 서버에 접속 (connect)

struct sockaddr_in servaddr;

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

servaddr.sin_family = AF_INET;

servaddr.sin_port   = htons(13); /* daytime port */

int ret;

if ( (ret = inet_pton(AF_INET,argv[1],&servaddr.sin_addr)) < 0)

err_sys("inet_pton error");

else if (!ret)

err_quit("<IPaddress> error: not a valid network address string");


if (connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)

err_sys("connect error");


// 서버에 더미(dummy) 데이터그램 전송 (sendto -> write or send)

const char *m = "GIVE ME A DAYTIME STRING. :)";

if (write(sockfd,m,strlen(m)) < 0)

err_sys("write error");

// 날짜/시간 문자열 수신 (recvfrom -> read or recv) 및 출력

int n;

char recvline[MAXLINE+1];

if ((n = read(sockfd,recvline,MAXLINE)) < 0)

err_sys("read error");


recvline[n] = 0; /* null terminate */

if (fputs(recvline,stdout) == EOF)

err_sys("fputs error");


// 접속 종료/프로그램 종료 (close, exit)

close(sockfd);

exit(0);

}


void err_quit(const char *message)

{

fputs(message,stderr);

fputc('\n',stderr);

exit(1);

}


void err_sys (const char *message)

{

perror(message);

exit(1);

}



우선 소켓은 UDP 소켓을 만들고, connect를 한다. 이유는 연결된 udp 소켓이기 때문이다.  연결 되었지만 UDP소켓이므로 더미데이터그램을 전송해준다. 하지만 연결이 되어있으므로 write를 쓰기로 했다. 그리고 마찬가지로 문자열을 수신할 때도 recvfrom 대신에 read를 썼다. 연결이 안되어있을 때는, memcmp함수로 주소끼리 비교해주었지만, 연결된 상태에서는 주소가 다를 수가 없기 때문에 memcmp함수로 주소를 비교하지 않아도 된다.

반응형
Comments