얇지만얇지않은 TCP/IP 소켓프로그래밍 C 2 판 Chap 2. Basic TCP Sockets
Chap. 2 Basic TCP Sockets 2.1 IPv4 TCP 클라이언트 2.2 IPv4 TCP 서버 2.3 소켓의생성과해지 2.4 주소지정 2.5 소켓에연결 2.6 소켓을주소와바인딩하기 2.7 클라이언트의연결요청처리 2.8 데이터주고받기 2.9 IPv6의사용
소켓 (Socket) 소켓이란무엇인가? 응용프로그램이데이터를주고받는추상화개념 ( 자료구조 ) 네트워크플러그인인터페이스 TCP/IP 를포함하여다양한프로토콜인터페이스지원 소켓의구분 프로토콜 (TCP, UDP 혹은기타프로토콜 ), 주소, 포트별로구분 하나의응용프로그램이여러개의소켓을가질수있으며반대로여러개의프로그램이하나의소켓의공유가가능
소켓통신과정 간략화한소켓통신과정 소켓생성 TCP or UDP 소켓에주소정보할당 IP address, Port number 소켓연결 클라이언트소켓과서버소켓연결 bind(), listen(), connect(), accept() 데이터전송 send(), recv()
TCP/IP 소켓의생성 소켓생성 어떠한소켓을생성할것인가를명시 ( 프로토콜종류 ) TCP/IP 소켓의경우 int socket(int family,int type,int proto); Family Type Protocol TCP SOCK_STREAM IPPROTO_TCP PF_INET UDP SOCK_DGRAM IPPROTO_UDP Socket 식별자 UNIX의파일식별자와동일 Windows의경우, WinSock에서사용하는소켓핸들 Windows 의경우, 파일핸들과같지않음 반환값 : 소켓식별자인양의정수, 에러의경우 -1
TCP/IP 소켓식별자 유닉스 / 리눅스계열에서식별자공간 0 1 2 3 4 Descriptor Table Data structure for file 0 Data structure for file 1 Family: PF_INET Service: SOCK_STREAM Local IP: 111.22.3.4 Remote IP: 123.45.6.78 Local Port: 2249 Remote Port: 3726
TCP/IP 소켓의주소지정 (1) struct sockaddr 사용 여러가지프로토콜을사용하기때문에 1) 프로토콜종류, 2) 주소를지정해야함 TCP/IP 의경우는인터넷주소임을알리는 AF_INET, IP 주소, Port 번호가필요 IP : IPv4 주소형식과 IPv6 주소형식으로나뉨 Ports : TCP/UDP 관계없이 0~65535 사이의값사용 well-known (port 0-1023) dynamic or private (port 1024-65535)
TCP/IP 소켓의주소지정 (2) 범용 (Generic) 소켓주소구조체 struct sockaddr { }; unsigned short sa_family; /* Address family (e.g., AF_INET) */ char sa_data[14]; /* Protocol-specific address information */ IPv4 에사용되는소켓주소구조체 struct sockaddr_in { unsigned short sin_family; /* Internet protocol (AF_INET) */ unsigned short sin_port; /* Port (16-bits) */ struct in_addr sin_addr; /* Internet address (32-bits) */ char sin_zero[8]; /* Not used */ }; struct in_addr sockaddr Family Blob { unsigned long s_addr; /* Internet address (32-bits) */ }; 2 bytes 2 bytes 4 bytes 8 bytes sockaddr_in Family Port Internet address Not used 교재본문과비교
TCP/IP 소켓의주소지정 (3) IPv6 에사용되는소켓주소구조체 struct sockaddr_in6{ sa_family_t sin6_family; // Internet protocol(af_inet6) in_port_t sin6_port; // Address port(16bits) uint32_t sin6_flowinfo; // Flow information struct in6_addr sin6_addr; // IPv6 address(128bits) uint32_t sin6_scope_id; // Scope identifier }; struct in_addr{ uint32_t s_addr[16]; }; // Internet address(128bits) 모든종류의 sockaddr 을수용하기위한구조체 struct sockaddr_storage { sa_family_t };
주소정보를소켓에할당 bind() 를사용하여주소정보를생성된소켓에할당 int bind( int sockfd, struct sockaddr *localaddr, int addrlen); 성공시 0, 실패시 -1 int mysock,err; struct sockaddr_in myaddr; char* servip; /* ex)203.252.164.143 */ mysock = socket(pf_inet,sock_stream,0); myaddr.sin_family = AF_INET; myaddr.sin_port = htons( portnum ); myaddr.sin_addr.s_addr = inet_addr(servip); err=bind(mysock, (sockaddr *) &myaddr, sizeof(myaddr));
클라이언트 서버통신 클라이언트가먼저서버에게연결요청 서버의프로세스는미리소켓을열고대기하고있어야함 서버는특정포트를열고대기하여야하며클라이언트는이포트를알고있어야함 클라이언트는서버에연결 이때클라이언트는서버의 IP, Port 정보를응용프로그램에게명시하여접속가능
UDP 연결흐름도 서버는이부분에서멈추고클라이언트의연결요청을기다림
TCP 연결흐름도 연결요청 다음클라이언트로부터연결요청을기다림
서버의연결대기함수 - listen() TCP 와같은연결지향서버에사용 소켓의상태를대기상태로바꿈 int listen(int socket, int queuelimit) socket: 생성된소켓의식별자 queuelimit : 연결을수행중에다른연결이들어오면연결요청을 queue 에넣고보류, 이때사용하는 queue 의크기
서버의연결대기함수 - Accept() int accept(int socket, struct sockaddr *clientdaddress, int addr_len) listen() 호출후, accept() 를수행하면클라이언트의연결요청 (connect()) 에대해응답합 passive open 클라이언트와데이터송수신 (send/recv) 이가능한새로운소켓식별자를반환
클라이언트의연결함수 - Connect() int connect(int socket, struct sockaddr *foreignaddress, int addr_len) 클라이언트는 connect() 를호출하여연결의상태를 active open 으로만듬 foreignaddress 는서버의 IP, port 를담고있는주소구조체
Send(to), Recv(from) 연결이이루어진후에는 send/recv 를이용하여데이터의송수신이가능 int send(int socket, char *message, int msg_len, int flags) 주어진소켓을통하여 messge 의송신이가능 int recv(int scoket, char *buffer, int buf_len, int flags) 주어진소켓을통해주어진버퍼에데이터를수신
클라이언트와서버의통신 클라이언트 : 연결을초기화하는주체 Client: Bob Server: Jane Hi. I m Bob. Hi, Bob. I m Jane Nice to meet you, Jane. 서버 : 수동적으로연결을기다림
TCP 상의서버 / 클라이언트통신 서버는클라이언트의연결을받아들일준비를하고시작 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 상의서버 / 클라이언트통신 /* Create socket for incoming connections */ if ((servsock = socket(pf_inet, SOCK_STREAM, IPPROTO_TCP)) < 0) DieWithSystemMessage("socket() failed"); 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 상의서버 / 클라이언트통신 echoservaddr.sin_family = AF_INET; /* Internet address family */ echoservaddr.sin_addr.s_addr = htonl(inaddr_any);/* Any incoming interface */ echoservaddr.sin_port = htons(echoservport); /* Local port */ if (bind(servsock,(struct sockaddr *) &echoservaddr, sizeof(echoservaddr)) < 0) DieWithSystemMessage("bind() failed"); 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 상의서버 / 클라이언트통신 /* Mark the socket so it will listen for incoming connections */ if (listen(servsock, MAXPENDING) < 0) DieWithSystemMessage("listen() failed"); 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 상의서버 / 클라이언트통신 for (;;) /* Run forever */ { clntlen = sizeof(echoclntaddr); if ((clntsock=accept(servsock,(struct sockaddr *)&echoclntaddr,&clntlen)) < 0) DieWithError("accept() failed"); 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 상의서버 / 클라이언트통신 서버는이시점에서클라이언트의연결을처리하기위해서대기 클라이언트는서버에연결시도 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 상의서버 / 클라이언트통신 /* Create a reliable, stream socket using TCP */ if ((sock = socket(pf_inet, SOCK_STREAM, IPPROTO_TCP)) < 0) DieWithSystemMessage("socket() failed"); 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 상의서버 / 클라이언트통신 echoservaddr.sin_family = AF_INET; /* Internet address family */ echoservaddr.sin_addr.s_addr = inet_addr(servip); /* Server IP address */ echoservaddr.sin_port = htons(echoservport); /* Server port */ if (connect(sock,(struct sockaddr *)&echoservaddr, sizeof(echoservaddr)) < 0) DieWithSystemMessage ("connect() failed"); 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 상의서버 / 클라이언트통신 if ((clntsock=accept(servsock,(struct sockaddr *)&echoclntaddr,&clntlen)) < 0) DieWithError("accept() failed"); 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 상의서버 / 클라이언트통신 echostringlen = strlen(echostring); /* Determine input length */ /* Send the string to the server */ if (send(sock, echostring, echostringlen, 0)!= echostringlen) DieWithUserMessage ("send() sent a different number of bytes than expected"); 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 상의서버 / 클라이언트통신 /* Receive message from client */ if ((recvmsgsize = recv(clntsocket, echobuffer, RCVBUFSIZE, 0)) < 0) DieWithSystemMessage("recv() failed"); 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 상의서버 / 클라이언트통신 close(sock); close(clntsocket) 서버 클라이언트 2. 연결설정 3. 데이터송수신 4. 연결종료 2. 소켓에포트할당 3. 소켓상태를대기 (listen) 로변경 4. 다음을반복적으로수행 a. 새로운연결을받아들임 b. 데이터송수신 c. 연결을종료
TCP 데이터교환 클라이언트는사전에서버의주소정보 (IP, port) 를알아야함 서버는클라이언트가접속할포트만정하고있음 send() 와 recv() 간에는어떠한정해진룰이없음 Client send( Hello Bob ) recv() -> Hi Jane Server recv() -> Hello recv() -> Bob send( Hi ) send( Jane )
연결종료 연결을종료하기위해서 close() 를사용 파일의 EOF 와유사 send(string) Echo Client while (not received entire string) recv(buffer) print(buffer) Echo Server recv(buffer) while(client has not closed connection) send(buffer) recv(buffer) close(socket) close(client socket)
Practical.h #ifndef PRACTICAL_H_ #define PRACTICAL_H_ #include <stdbool.h> #include <stdio.h> #include <sys/socket.h> // Handle error with user msg void DieWithUserMessage(const char *msg, const char *detail); // Handle error with sys msg void DieWithSystemMessage(const char *msg); // Print socket address void PrintSocketAddress(const struct sockaddr *address, FILE *stream); // Test socket address equality bool SockAddrsEqual(const struct sockaddr *addr1, const struct sockaddr *addr2); // Create, bind, and listen a new TCP server socket int SetupTCPServerSocket(const char *service); // Accept a new TCP connection on a server socket int AcceptTCPConnection(int servsock); // Handle new TCP client void HandleTCPClient(int clntsocket); // Create and connect a new TCP client socket int SetupTCPClientSocket(const char *server, const char *service); enum sizeconstants { MAXSTRINGLENGTH = 128, BUFSIZE = 512, }; #endif // PRACTICAL_H_
TCPEchoClient4.c 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <arpa/inet.h> 9 #include "Practical.h" 10 11 int main(int argc, char *argv[]) { 12 13 if (argc < 3 argc > 4) // Test for correct number of arguments 14 DieWithUserMessage("Parameter(s)", 15 "<Server Address> <Echo Word> [<Server Port>]"); 16 17 char *servip = argv[1]; // First arg: server IP address (dotted quad) 18 char *echostring = argv[2]; // Second arg: string to echo 19 20 // Third arg (optional): server port (numeric). 7 is well-known echo port 21 in_port_t servport = (argc == 4)? atoi(argv[3]) : 7; 22 23 // Create a reliable, stream socket using TCP 24 int sock = socket(af_inet, SOCK_STREAM, IPPROTO_TCP); 25 if (sock < 0) 26 DieWithSystemMessage("socket() failed");
TCPEchoClient4.c 27 28 // Construct the server address structure 29 struct sockaddr_in servaddr; // Server address 30 memset(&servaddr, 0, sizeof(servaddr)); // Zero out structure 31 servaddr.sin_family = AF_INET; // IPv4 address family 32 // Convert address 33 int rtnval = inet_pton(af_inet, servip, &servaddr.sin_addr.s_addr); 34 if (rtnval == 0) 35 DieWithUserMessage("inet_pton() failed", "invalid address string"); 36 else if (rtnval < 0) 37 DieWithSystemMessage("inet_pton() failed"); 38 servaddr.sin_port = htons(servport); // Server port 39 40 // Establish the connection to the echo server 41 if (connect(sock, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) 42 DieWithSystemMessage("connect() failed"); 43 44 size_t echostringlen = strlen(echostring); // Determine input length 45 46 // Send the string to the server 47 ssize_t numbytes = send(sock, echostring, echostringlen, 0); 48 if (numbytes < 0) 49 DieWithSystemMessage("send() failed"); 50 else if (numbytes!= echostringlen) 51 DieWithUserMessage("send()", "sent unexpected number of bytes");
TCPEchoClient4.c 52 53 // Receive the same string back from the server 54 unsigned int totalbytesrcvd = 0; // Count of total bytes received 55 fputs("received: ", stdout); // Setup to print the echoed string 56 while (totalbytesrcvd < echostringlen) { 57 char buffer[bufsize]; // I/O buffer 58 /* Receive up to the buffer size (minus 1 to leave space for 59 a null terminator) bytes from the sender */ 60 numbytes = recv(sock, buffer, BUFSIZE - 1, 0); 61 if (numbytes < 0) 62 DieWithSystemMessage("recv() failed"); 63 else if (numbytes == 0) 64 DieWithUserMessage("recv()", "connection closed prematurely"); 65 totalbytesrcvd += numbytes; // Keep tally of total bytes 66 buffer[numbytes] = '\0'; // Terminate the string! 67 fputs(buffer, stdout); // Print the echo buffer 68 } 69 70 fputc('\n', stdout); // Print a final linefeed 71 72 close(sock); 73 exit(0); 74 }
TCPEchoServer4.c 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/socket.h> 6 #include <netinet/in.h> 7 #include <arpa/inet.h> 8 #include "Practical.h" 9 10 static const int MAXPENDING = 5; // Maximum outstanding connection requests 11 12 int main(int argc, char *argv[]) { 13 14 if (argc!= 2) // Test for correct number of arguments 15 DieWithUserMessage("Parameter(s)", "<Server Port>"); 16 17 in_port_t servport = atoi(argv[1]); // First arg: local port 18 19 // Create socket for incoming connections 20 int servsock; // Socket descriptor for server 21 if ((servsock = socket(af_inet, SOCK_STREAM, IPPROTO_TCP)) < 0) 22 DieWithSystemMessage("socket() failed"); 23
TCPEchoServer4.c 24 // Construct local address structure 25 struct sockaddr_in servaddr; // Local address 26 memset(&servaddr, 0, sizeof(servaddr)); // Zero out structure 27 servaddr.sin_family = AF_INET; // IPv4 address family 28 servaddr.sin_addr.s_addr = htonl(inaddr_any); // Any incoming interface 29 servaddr.sin_port = htons(servport); // Local port 30 31 // Bind to the local address 32 if (bind(servsock, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0) 33 DieWithSystemMessage("bind() failed"); 34 35 // Mark the socket so it will listen for incoming connections 36 if (listen(servsock, MAXPENDING) < 0) 37 DieWithSystemMessage("listen() failed"); 38 39 for (;;) { // Run forever 40 struct sockaddr_in clntaddr; // Client address 41 // Set length of client address structure (in-out parameter) 42 socklen_t clntaddrlen = sizeof(clntaddr); 43 44 // Wait for a client to connect 45 int clntsock = accept(servsock, (struct sockaddr *) &clntaddr, &clntaddrlen); 46 if (clntsock < 0) 47 DieWithSystemMessage("accept() failed");
TCPEchoServer4.c 48 49 // clntsock is connected to a client! 50 51 char clntname[inet_addrstrlen]; // String to contain client address 52 if (inet_ntop(af_inet, &clntaddr.sin_addr.s_addr, clntname, 53 sizeof(clntname))!= NULL) 54 printf("handling client %s/%d\n", clntname, ntohs(clntaddr.sin_port)); 55 else 56 puts("unable to get client address"); 57 58 HandleTCPClient(clntSock); 59 } 60 // NOT REACHED 61 }
HandleTCPClient() 1 void HandleTCPClient(int clntsocket) { 2 char buffer[bufsize]; // Buffer for echo string 3 4 // Receive message from client 5 ssize_t numbytesrcvd = recv(clntsocket, buffer, BUFSIZE, 0); 6 if (numbytesrcvd < 0) 7 DieWithSystemMessage("recv() failed"); 8 9 // Send received string and receive again until end of stream 10 while (numbytesrcvd > 0) { // 0 indicates end of stream 11 // Echo message back to client 12 ssize_t numbytessent = send(clntsocket, buffer, numbytesrcvd, 0); 13 if (numbytessent < 0) 14 DieWithSystemMessage("send() failed"); 15 else if (numbytessent!= numbytesrcvd) 16 DieWithUserMessage("send()", "sent unexpected number of bytes"); 17 18 // See if there is more data to receive 19 numbytesrcvd = recv(clntsocket, buffer, BUFSIZE, 0); 20 if (numbytesrcvd < 0) 21 DieWithSystemMessage("recv() failed"); 22 } 23 24 close(clntsocket); // Close client socket 25 }
컴파일방법 리눅스환경 Native 리눅스 /VMware 리눅스 / Cygwin 환경 유닉스기반 (iris.mmu.ac.kr) 환경변수에다음디렉토리추가 (.profile PATH에추가 ) export PATH=/usr/bin:/usr/sbin:/opt/solarisstudio12.3/bin $ cc < 컴파일옵션 > -o < 실행파일 > < 소스파일들 > -lsocket lnsl 리눅스기반 (lily.mmu.ac.kr) $ gcc < 컴파일옵션 > -o < 실행파일 > < 소스파일들 >
참고 Makefile CC = cc CFLAGS = -g CLI_OBJS = TCPEchoClient4.o DieWithMessage.o SRV_OBJS = TCPEchoServer4.o TCPServerUtility.o addressutility.o DieWithMessage.o echo_cli: $(CLI_OBJS) $(CC) -o echo_cli $(CLI_OBJS) -lnsl -lsocket echo_srv: $(SRV_OBJS) $(CC) -o echo_srv $(SRV_OBJS) -lnsl -lsocket clean: rm echo_cli $(CLI_OBJS)
과제 클라이언트 서버프로그램작성 ( 총 200 점 ) 동작확인 (100 점 ) echo_srv 6000 echo_cli 203.232.252.110 Test 6000 (lily) echo_cli 203.232.252.109 Test 6000 (iris) 프로그램설명 ( 발표자료또는보고서형식 100 점 ) 컴파일과정 주요헤더파일설명 프로그램코드설명 클라이언트프로그램개선 1 (100 점 ) echo_cli 주소포트번호문장순서로변경 echo_cli 203.232.252.110 6000 This is a test 클라이언트프로그램개선 2 (200 점 ) echo_cli 주소포트번호 키보드로부터입력을받아서버에게전달 서버로부터응답받은후다시반복 제출기한 : 4 월 29 일자정