12.소켓 프로그래밍 활용
12-1.개요
네트워크를 통해 데이터를 주고받으며 동작하는 네트워크 프로그램은 일반적으로 서버와 클라이언트로 역할을 구분할 수 있다. 서버는 클라이언트의 요청에 따라 다양한 서비스를 제공하는 프로그램으로 보통 Daemon 프로세스라고 한다.
예를 들어, 브라우저를 통해 웹사이트를 접속할 경우 서버에는 웹 서비스를 제공하는 HTTP 데몬이 동작하고 있어 사용자들이 웹 페이지를 볼 수 있는 것이다.
데몬 프로세스는 크게 두가지로 나눌 수 있다. 첫번째는 반복 서버로써, 데몬 프로세스가 직접 클라이언트의 요청을 처리하는 형태다.
두번째는 데몬 프로세스가 직접 서비스를 제공하지 않고, 서비스와 관련있는 다른 프로세스를 fork함수로 생성한 후 이 프로세스를 클라이언트로 연결해 서비스를 제공하는 동시 동작 서버다.
네트워크 프로그램을 처음 도입한 당시에는 반복 서버 형태로 서비스를 제공했다.
그런데 다양한 네트워크 서비스가 등장하면서 데몬 프로세스의 개수가 너무 많아지게 되었다. 이 문제를 해결할 목적으로 대표 데몬 프로세스가 동작하면서 클라이언트 요청에 적합한 서비스 프로그램을 동작시키는 동시 동작 서버를 도입했다.
대표적인 예가 inetd 데몬인데, 클라이언트의 요청에 따라 자식 프로세스를 생성하고 해당 요청을 서비스할 프로세스를 실행한다.
자주 사용하는 텔넷이나 FTP가 이와 같은 형태로 서비스하고 있다 .
이 장에서는 TCP/IP 프로토콜을 이용해 프로그램을 작성할 때 도움이 될 수 있도록 소켓 인터페이스를 이용한 다양한 예제를 다룬다. 또한 TCP 기반 네트워크 프로그램과 UDP 기반 네트워크 프로그램으로 나누어 살펴보고, 반복 서버와 동시 동작 서버의 예제를 살펴볼 것이다.
12-2. TCP 기반 프로그래밍
네트워크를 기반으로 하는 통신 프로그램은 일반적으로 서버와 클라이언트로 역할을 구분할 수 있다.
그중 서버 컴퓨터에서 동작하며, 서비스를 제공하는 프로그램을 daemon 프로세스라고 한다. 데몬 프로세스는 크게 두가지 형태로 나눌 수 있다.
- iterative server(반복 서버) : 데몬 프로세스가 직접 모든 클라이언트의 요청을 차례로 처리한다.
- concurrent server(동시 서버) : 데몬 프로세스가 직접 서비스를 제공하지 않고, 서비스와 관련 있는 다른 프로세스를 fork함수로 생성해 클라이언트와 연결한다.
12-2-1. 반복 서버
반복 서버는 서버 프로그램이 클라이언트의 요청을 직접 처리한다.
따라서 반복 서버 프로세스는 한번에 한 클라이언트의 요청만 처리할 수 있고,
여러 클라이언트가 서비스를 요청할 경우 순차적으로 처리하므로 클라이언트가 자기 순서를 기다려야 한다.
이를 방지하기 위해 반복 서버 프로세스를 여러개 동작시킬 수 있다.
반복 서버는 인터넷 소켓과 TCP(SOCK_STREAM)를 이용해 통신한다.
서버는 계속 동작하면서 클라이언트가 요청하는 서비스를 받아 처리해준다.
- 반복 서버 프로세스와 이에 접속하는 클라이언트 프로그램
이 예제 프로그램이 올바른 결과를 얻으려면 서버와 클라이언트가 통신해야한다.
서버 프로세스 실행파일을 먼저 실행해서 클라이언트의 요청을 기다리게 해놓고, 클라이언트 프로세스를 실행해야한다.
<반복 서버(서버)>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//9001 포트를 사용한다.
#define PORTNUM 9001
int main(void){
char buf[256];
struct sockaddr-in sin, cli;
int sd, ns, clientlen = sizeof(cli);
//서버 주소 구조체에 소켓의 종류를 AF_INET으로 지정하고 포트번호와 서버의 IP주소를 설정함
memset((char *)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORTNUM);
sin.sin_addr.s_addr = inet_addr("192.168.162.133");
//소켓 생성시, SOCK_STREAM으로 지정해 TCP를 사용함
if((sd==socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
//위에서 생성한 소켓을 bind함수를 이용해 IP주소/포트 번호와 연결한다.
if(bind(sd, struct sockaddr *)&sin, sizeof(sin))) {
perror("bind");
exit(1);
}
//listen함수를 호출해 클라이언트의 요청을 받을 준비를 마쳤음을 운영체제에 알린다.
//한번에 5개의 클라이언트가 접속할 수 있도록 설정한다.
if(listen(sd, 5)){
perror("listen");
exit(1);
}
//while문으로 무한반복하게 한다. (이 서버는 무한반복하면서 서비스를 제공한다.)
while(1) {
//accept함수를 사용해 클라이언트의 요청이 올때까지 기다린다.
//클라이언트의 접속 요청이 오면, 새로운 소켓 기술자 ns가 생성되고 이를 통해 클라이언트와 통신한다.
if((ns == accept(sd, (struct sockaddr *)&cli, &clientlen)) == -1){
perror("accept");
exit(1);
}
//accept함수를 통해 알아낸 클라이언트의 ip주소를 문자열로 변환해 버퍼에 저장함.
sprintf(buf, "%s", inet_ntoa(cli.sin_addr));
//어느 클라이언트가 서버에 접속했는지 출력한다.
printf("*** send a message to client(%s)\n", buf);
//서버에서 클라이언트로 보낼 간단한 메세지를 작성한다.
strcpy(buf, "welcome to network server!!!");
//send함수를 사용해 클라이언트로 메시지를 보낸다.
if(send(ns, buf, strlen(buf)+1, 0) == -1) {
perror("send");
exit(1);
}
//클라이언트가 보낸 메시지를 받아서 출력한다.
if(recv(ns, buf, strlen(buf), 0) == -1) {
perror("recv");
exit(1);
}
printf("** from client : %s\n", buf);
//작업이 끝나면 클라이언트와 접속할 때 사용한 소켓 기술자를 닫고,
//다시 accept함수를 수행해 클라이언트의 접속 요청을 기다린다.
close(ns);
}
close(sd);
return 0;
}
<반복 서버(클라이언트)>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//클라이언트도 서버와 같은 포트번호를 사용해 소켓을 생성한다.
#define PORTNUM 9001
int main(void){
int sd;
char buf[256];
struct sockaddr-in sin;
memset((char *)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htoms(PORTNUM);
//접속할 서버의 주소를 지정한다.
sin.sin_addr.s_addr = inet_addr("192.168.162.133");
//소켓을 생성하고 서버와 접속한다.
if((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
if(connect(sd, (struct sockaddr *)&sin, sizeof(sin))) {
perror("connect");
exit(1);
}
//서버에 접속한 후 서버에서 보낸 메시지를 받아 printf함수로 출력한다.
if(recv(sd, buf, sizeof(buf), 0) == -1){
perror("recv");
exit(1);
}
printf("** From server : %s\n", buf);
//서버로 보낼 메시지를 복사한 후 서버로 메시지를 전송한다.
strcpy(buf, "I want a HTTP service");
if(send(sd, buf, sizeof(buf) +1, 0) == -1) {
perror("send");
exit(1);
}
close(sd);
return 0;
}
위 두 프로그램은 socket 라이브러리와 nsl 라이브러리를 모두 링크하고 컴파일해야한다.
#gcc -o 서버.out 서버.c -lsocket -lnsl
#gcc -o 클라이언트.out 클라이언트.c -lsocket -lnsl
먼저 서버실행파일을 실행하면, 서버는 클라이언트의 요청을 기다린다.
클라이언트를 실행하면 서버와 접속하고 서버가 보낸 메시지를 받아 출력한다.
서버는 클라이언트의 접속을 요청 받으면 어느 클라이언트에서 요청했는지 출력하고
클라이언트는 메시지를 서버로 보낸다.
그후 서버는 클라이언트가 보낸 메시지를 받아 출력한다.
12-2-2. 동시 동작 서버
클라이언트의 요청에 따라 수행해야할 작업이 많은 경우에는 클라이언트가 대기하는 시간이 길어진다는 단점이 있다. 이를 해결하기 위해 동시 동작 서버 구조를 이용한다.
서버 프로그램은 클라이언트의 서비스를 대신 처리할 프로세스를 fork함수로 생성하고, 이 프로세스를 클라이언트와 연결해준 다음 다시 클라이언트의 접속을 기다린다.
이렇게 하면 동시에 여러 클라이언트에 서비스를 제공할 수 있다.
<동시동작 서버(서버)>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//9002 포트를 사용한다.
#define PORTNUM 9002
int main(void){
char buf[256];
struct sockaddr-in sin, cli;
int sd, ns, clientlen = sizeof(cli);
//서버 주소 구조체에 소켓의 종류를 AF_INET으로 지정하고 포트번호와 서버의 IP주소를 설정함
memset((char *)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORTNUM);
sin.sin_addr.s_addr = inet_addr("192.168.162.133");
if((sd==socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
if(bind(sd, (struct sockaddr *)&sin, sizeof(sin))) {
perror("bind");
exit(1);
}
//listen함수로 한번에 5개의 클라이언트가 접속할 수 있도록 설정한다.
if(listen(sd,5))[
perror("listen");
exit(1);
}
while(1){
//accept함수를 사용해 클라이언트의 접속 요청을 받는다.
if((ns = accept(sd, (struct sockaddr *)&cli,&clientlen)) == -1){
error("accept");
exit(1);
}
//fork함수를 수행해 자식 프로세스를 생성하고,
//자식 프로세스가 클라이언트의 응답을 처리하게 한다.
switch (fork()){
case 0:
close(sd);
strcpy(buf, "welcome to server");
if(send(ns, buf, strlen(buf)+1,0) == -1){
perror("send");
exit(1);
}
if(recv(ns, buf, strlen(buf),0)== -1){
perror("recv");
exit(1);
}
printf("** from client : %s \n",buf);
sleep(5);
exit(0);
}
close(ns);
}
return 0;
}
* 클라이언트 실행프로그램은 반복서버(클라이언트) 예제를 그대로 사용하는데 포트번호만 9002로 수정하여 컴파일한다.
* 실제 클라이언트 접속시 서버의 실행상태를 보면 서버 프로그램이 하나더 동작함을 알 수 있다.
* 서비스 목록 조회
#ps -ef | grep pts/2
12-2-3. 동시동작서버: 서비스 프로세스 호출1
위 예시 프로그램은 fork 함수로 생성한 자식 프로세스가 클라이언트와 통신하게한다.
이때 클라이언트에서 수행하는 부분이 부모 프로세스와 같은 파일에 작성되어 있다.
따라서 예제 프로그램 실행 결과를 보면 같은 서버 실행파일의 프로세스가 2개 동작하고 있음을 알 수 있다.
다음 프로그램 예시는 서버 프로세스가 클라이언트의 요청을 받으면 fork함수를 호출해 자식 프로세스를 생성한 후 exec함수로 다른 프로세스를 수행한다.
이는 inetd 같은 대표 데몬 프로세스가 텔넷이나 FTP와 같은 서비스 요청을 받아 해당 서비스 프로세스를 호출하는 것과 유사하다.
이 예제에서는 자식 프로세스가 han이라는 프로그램을 호출한다.
<동시 동작 서버-exec함수 사용하기(서버)>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//9002 포트를 사용한다.
#define PORTNUM 9002
int main(void){
struct sockaddr-in sin, cli;
int sd, ns, clientlen = sizeof(cli);
//socket함수로 소켓을 생성한다.
if((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(0);
}
printf("** create socket\n");
//특정 IP주소와 연결한다.
memset((char *)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORTNUM);
sin.sin_addr.s_addr = inet_addr("192.168.162.133");
if(bind(sd, (struct sockaddr *)&sin, sizeof(sin))){
perror("bind");
exit(1);
}
printf("** Bind Socket\n");
//listen함수로클라이언트의 접속을 5개가지 처리하도록 설정한다.
if(listen(sd, 5)) {
perror("listen");
exit(1);
}
printf("**listen socket\n");
while(1){
if((ns = accept(sd, (struct sockaddr *)&cli, &clientlen)) == -1){
perror("accept");
exit(1);
}
printf("** Accept client\n");
//fork함수로 자식 프로세스를 생성해, 클라이언트의 응답을 처리한다.
switch(fork()){
//클라이언트는 소켓 기술자(ns)를 표준 출력/입력으로 복사한 후
// execl함수로 han프로세스를 실행한다.
case 0:
printf("** fork client \n");
close(sd);
dup2(ns, STDIN_FILENO);
dup2(ns, STDOUT_FILENO);
close(ns);
execl("./han", "han", (char *)0);
}
close(ns);
}
return 0;
}
#include <unistd.h>
#include <stdio.h>
int main(void){
//이 프로세스에서 표준 출력으로 메세지를 출력하면
//기본 표준 출력인 모니터가 아닌 클라이언트 프로세스로 메세지가 전달된다.
//이는 부모 프로세스인 서버 프로그램에서 exec함수를 호출하기 전에
//소켓 기술자를 표준 출력으로 복제했기 때문이다.
printf("welcome to server, from han!");
//sleep함수는 프로세스 목록을 확인할 수 있게 시간을 지연시킨다.
sleep(5);
return 0;
}
<동시 동작 서버- exec 함수 사용하기 (클라이언트)>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define PORTNUM 9003
int main(void){
int sd, len;
char buf[256];
struct sockaddr-in sin;
memset((char *)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htoms(PORTNUM);
sin.sin_addr.s_addr = inet_addr("192.168.162.133");
if((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
printf("==> create socket\n");
//connect함수를 호출해 서버와 연결을 요청한다.
if(connect(sd, (struct sockaddr *)&sin, sizeof(sin))) {
perror("connect");
exit(1);
}
printf("==> connect server\n");
//서버에서 메시지를 받아 출력한다.
if((len = recv(sd, buf, sizeof(buf), 0)) == -1){
perror("recv");
exit(1);
}
buf[len] = '\0';
printf("==>from server : %s\n", buf);
close(sd);
return 0;
}
서버 실행파일을 실행한 후 클라이언트를 실행하면, han 프로그램의 메시지를 서버에서 받아 출력됨을 확인 할 수 있다.
이 예제를 실행 후 프로세스 목록을 확인해보면 # ps
han프로세스가 함께 동작하고 있음을 알 수 있다.
12-2-4. 동시동작 서버: 서비스 프로세스 호출 2
위의 예제 프로그램은 fork함수로 생성한 자식 프로세스와 소켓 기술자를 표준 출력으로 복사해 서비스 프로세스가 클라이언트와 통신하게 했다.
이와 달리 소켓 기술자를 서비스 프로세스의 명령행 인자로 전달할 수도 있다.
다음 예제 프로그램은 서버 프로그램에서 클라이언트와 연결하는 소켓 기술자를 exec함수로 호출하는 프로세스에 명령형 인자로 전달하도록 지정한다.
따라서 exec함수로 실행한 서비스 프로세스는 명령행 인자로 전달받은 소켓 기술자로 클라이언트와 통신한다.
<명령행 인자로 소켓 기술자 전달하기(서버)>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define PORTNUM 9004
int main(void){
char buf[256];
struct sockaddr-in sin, cli;
int sd, ns, clientlen = sizeof(cli);
if((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(0);
}
printf("** create socket\n");
memset((char *)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORTNUM);
sin.sin_addr.s_addr = inet_addr("192.168.162.133");
if(bind(sd, (struct sockaddr *)&sin, sizeof(sin))){
perror("bind");
exit(1);
}
printf("** Bind Socket\n");
//listen함수로클라이언트의 접속을 5개가지 처리하도록 설정한다.
if(listen(sd, 5)) {
perror("listen");
exit(1);
}
printf("**listen socket\n");
while(1){
if((ns = accept(sd, (struct sockaddr *)&cli, &clientlen)) == -1){
perror("accept");
exit(1);
}
printf("** Accept client\n");
switch(fork()){
case 0:
printf("** fork client \n");
close(sd);
//클라이언트와 통신할 수 있는 소켓을 버퍼에 저장.
sprintf(buf, "%d", ns);
//execlp 함수를 사용해 bit 프로세스를 호출할 때
//위에서 저장한 소켓 정보를 인자로 전송한다.
//exfeclp 함수를 호출함으로써 자식 프로세스는
//bit 프로세스 이미지로 바뀌고 buf로 지정한 인자는
// bit프로세스의 명령행 인자로 전달된다.
execlp("./bit", "bit", buf, (char *)0);
close(ns);
}
close(ns);
}
ruturn 0;
}
execlp함수로 호출한 bit 프로세스는 명령행 인자로 받은 소켓 기술자로 클라이언트와 통신하며, 간단한 환영 메시지를 클라이언트로 전송하고, 응답메세지를 받아 출력하는 간단한 프로그램이다.
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv[]) {
char buf[256];
int len, ns;
//명령행 인자(문자열)로 전달받은 소켓 기술자를 정수형으로 변환한다.
ns = atoi(argv[1]);
strcpy(buf, "welcome to server, from bit");
if((send(ns, buf, strlen(buf) +1, 0)) == -1) {
perror("send");
exit(1);
}
if((len=recv(ns, buf, strlen(buf), 0)) == -1) {
perror("recv");
exit(1);
}
printf("@@ [bit] from client : %s/n", buf);
close(ns);
ruturn 0;
}
<명령행 인자로 소켓 기술자 전달하기 (클라이언트)>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define PORTNUM 9004
int main(void){
int sd, len;
char buf[256];
struct sockaddr-in sin;
memset((char *)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htoms(PORTNUM);
sin.sin_addr.s_addr = inet_addr("192.168.162.133");
if((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
printf("==> create socket\n");
if(connect(sd, (struct sockaddr *)&sin, sizeof(sin))) {
perror("connect");
exit(1);
}
printf("==> connect server\n");
//서버에서 메시지를 받아 출력한다.
if((len = recv(sd, buf, sizeof(buf), 0)) == -1){
perror("recv");
exit(1);
}
buf[len] = '\0';
printf("==>from server : %s\n", buf);
//답장 메시지를 작성해 send 함수를 통해 서버로 전송한다.
strcpy(buf, "I want a Telnet service");
if(send(sd, buf, sizeof(buf)+1, 0) == -1) {
perror("send");
exit(1);
}
close(sd);
return 0;
}
실행 결과를 보면 클라이언트는 bit 프로세스가 보낸 메시지를 받고, bit프로세스도 클라이언트가 보낸 메시지를 받아 출력함을 확인할 수 있다.
12-3 UDP 기반 프로그래밍
UDP 기반 프로그래밍은 TCP와 달리 클라이언트로부터 사전에 연결 요청을 받지 않는다.
따라서 클라이언트는 connect 함수를 사용하지 않는다.
클라이언트의 연결 요청이 별도로 없을 것이기 대문에 서버도 bind함수를 수행한 다음에 listen 함수를 사용하지 않는다.
클라이언트가 보낸 데이터를 수신한 후 서버의 동작은 TCP를 사용할 때와 같다.
단지 서버에서 클라이언트로 데이터를 보낼 때 클라이언트의 주소를 구조체로 지정해야하는 점이 다를 뿐이다.
- UDP 기반 클라이언트/서버로, 간단한 메시지를 전송하는 프로그램
<UDP 프로그래밍 (서버)>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define PORTNUM 9005
int main(void) {
char buf[256];
struct sockaddr_in sin, cli;
int sd, clinetlen = sizeof(cli);
//인터넷 소켓을 사용하는 소켓을 생성한다. 통신 방식은 SOCK_DGRAM으로 지정해 UDP를 사용한다.
if((sd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
exit(1);
}
//소켓 주소 구조체에 서버의 주소를 지정한다.
memset((char *)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORTNUM);
sin.sin_addr.s_addr = inet_addr("192.168.162.133");
//위에서 생성한 소켓을 bind함수를 이용해 설정한 IP주소/포트 번호와 연결한다.
if(bind(sd, (struct sockaddr 8)&sin, sizeof(sin))) {
perror("bind");
exit(1);
}
//recfrom 함수를 호출해 클라이언트에서 오는 메시지를 받는다.
while(1) {
if((recvfrom(sd, buf, 255, 0,
(struct sockaddr *)&cli, &clientlen)) == -1) {
perror("recvfrom");
exit(1);
}
printf("** From client : %s\n", buf);
strcpy(buf, "Hello Client");
//클라이언트로 간단한 메시지를 보낸다. sendto함수에는 목적지의 주소를 인자로 지정해야한다.
if((sendto(sd, buf, strlen(buf)+1, 0,
(struct sockaddr *)&cli, sizeof(cli)) == -1) {
perror("sendto");
exit(1);
}
}
return 0;
}
<UDP 프로그래밍 (클라이언트)>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define PORTNUM 9005
int main(void) {
int sd, n;
char buf[256];
struct sockaddr_in sin;
//소켓을 생성한다. TCP기반 프로그램과 달리 connect 함수를 호출하지 않는다.
if((sd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
exit(1);
}
//서버의 주소를 소켓 주소 구조체에 설정한다.
memset((char *)&sin, '\0', sizof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORTNUM);
sin.sin_addr.s_addr = inet_addr("192.168.162.133");
//sendto함수를 호출해 메시지를 서버로 전송한다.
strcpy(buf, "I am a client");
if(sendto(sd, buf, strlen(buf)+1, 0 ,
(struct sockaddr *)&sin, sizeof(sin)) == -1) {
perror("sendTo");
exit(1);
}
//서버에서 전송한 응답 메세지를 이 함수로 읽어 출력한다.
n = recvfrom(sd, buf, 255, 0, NULL, NULL);
buf[n] = '\0';
printf("** From Server : %s\n", buf);
return 0;
}