페이지의 컨텐츠를 보아하니, 따로 페이지를 뽑아내도 될것 같아 [문서구조조정] 하였습니다. 원래 페이지 이름은 '''데블스캠프2005/Socket Programming in Unix/Windows Implementation'''였습니다. - [임인택] 주제 : Socket Programming의 기초적인 부분을 알아본다. [[TableOfContents]] = 기본적인 함수/개념들 = == Socket == ※ 소켓이란? {{{~cpp #include #include int socket(int domain, int type, int protocol); // 성공 시 파일 디스크립터, 실패 시 -1 리턴 }}} === domain: === PF_INET : 인터넷 프로토콜 체계 사용 PF_INET6 IPv6 : 프로토콜 체계 사용 PF_UNIX : 유닉스 방식의 프로토콜 체계 사용 (프로세스간 통신) PF_NS XEROX : 네트워크 시스템의 프로토콜 체계 사용 PF는 Protocol Family PF대신 AF를 사용해도 무방. (ex. PF_INET -> AF_INET) === type: 서비스 타입 === ※ TCP/IP, UDP란? SOCK_STREAM : 스트림 방식의 소켓 생성 (TCP/IP) SOCK_DGRAM : 데이터그램 방식의 소켓 생성 (UDP) SOCK_RAW : raw 모드의 소켓 생성 === protocol: 프로토콜 === IPPROTO_TCP : TCP 기반. 값은 0이다. IPPROTO_UDP : UDP 기반. 값은 0이다. // 우리가 사용하는 프로토콜인 TCP, UDP가 0이므로 0으로 써도 무방하다. // 구체적인 프로토콜을 선택할 때 사용하는데 대부분의 응용 프로그렘에서는 0으로 지정하면 된다. {{{~cpp 예제) main(){ int sockfd; sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1) fprintf(stderr, "socket 함수에서 에러"), exit(1); // 에러가 났을경우( sockfd == -1) 에러를 출력하고 프로그램 종료. } }}} == 네트워크 정보 == {{{~cpp struct sockaddr { u_short sa_family; /* address family */ char sa_data[14]; /* 주소(IP 주소 + 포트 번호) */ }; }}} {{{~cpp struct sockaddr_in { short sin_family; // 주소 체계를 나타낸다. u_short sin_port; // port 번호 struct in_addr sin_addr; // ip 주소 char sin_zero[8]; // 쓰지 않는 주소 }; }}} ※ 왜 sockaddr과 sockaddr_in의 structure가 같을까? === sin_family: // 주소체계 === AF_INET : 인터넷 주소 체계 AF_UNIX : 유닉스 파일 주소 체계 AF_NS XEROX : 주소 체계 // sockaddr_in 은 TCP/IP체제 이므로 AF_INET만 사용한다. -> TCP/IP는 인터넷 기반이므로. // AF_INET/PF_INET -> 인터넷 프로토콜 체계 사용. {{{~cpp 데이터를 Big-Endian으로 변환 시켜주는 체계. unsigned short integer 변환 (2바이트 크기) htons(): host-to-network 바이트 변환 (Big-Endian으로 변환) ntohs(): network-to-host 바이트 변환 (해당 시스템) unsigned long integer 변환 (4바이트 크기) htonl(): host-to-network 바이트 변환 (Big-Endian으로 변환) ntohl(): network-to-host 바이트 변환 (해당 시스템) ※ 왜 우리는 데이터를 Big-Endian으로 변환 시켜주어야할까? ※ 그렇다면 우리가 전송하는 데이터 모두 Big-Endian으로 변환 시켜주어야할까? }}} === sin_port: === NULL : 임의의 포트를 할당한다. client에서 사용한다. // u_short sin_port 은 Big-Endian을 사용한다. // 따라서 Little_Endian을 사용하는 시스템에서는 Big-Endian으로 바꿔줘야한다. === sin_addr: === INADDR_ANY : 자기 자신의 주소를 할당한다. (== 0) // sin_addr은 인터넷 주소를 담고 있으므로 4 바이트가 필요하다. cf. 도메인 네임을 통한 연결은 설명하지 않겠습니다. {{{~cpp struct in_addr { unsigned long s_addr; }; }}} {{{~cpp /* inet_addr(): 주소를 long형으로 계산하고 htonl()를 사용해 Big-Endian으로 변환 후 값을 return 한다. // 165.194.27.129 -> 165*256*256*256 + 194*256*256 + 27*256 + 129 = 2780961665 // 2780961665 의 값은 Little-Endian 체계에서는 811BC2A5이다. // 이것을 A5C21B81로 바꿔 저장한다. 예제 ) ina.sin_addr.s_addr = inet_addr("127.0.0.1"); */ }}} === sin_zero[''''''8'''''']: === // sin_zero 배열은 항상 0으로 채워져 있어야한다. ※ 왜 sin_zero가 만들어졌을까요? {{{~cpp 예제) #define PORT 9999 #define SERVER_IP "165.194.27.129" main(){ struct sockaddr_in ina; memset((struct sockaddr *)&ina, 0, sizeof(struct sockaddr)); // sin_zero를 0으로 채운다. // bzero라는 함수도 있지만 초기에 0으로 채우는 것이 편하다. // bzero(&(ina.sin_zero), 8); ina.sin_port = htons(PORT); // PORT의 경우 정수를 넣어야한다. ina.sin_addr.s_addr = inet_addr(SERVER_IP); // 클라이언트의 경우 // SERVER_IP의 경우 문자열 포인터를 넣어야한다. // 165.194.27.129 -> 165*256*256*256 + 194*256*256 + 27*256 + 129 = 2780961665 로 저장이 된다. // ina.sin_addr.s_addr = INADDR_ANY; // 서버의 경우 } }}} = Server 가 될 프로그램에 필요한 함수 = [[HTML(
)]] == Bind - socket과 네트워크 정보를 연결하는 Bind!!! == {{{~cpp #include int bind(int sockfd, struct sockaddr *myaddr, int addrlen); // 성공 시 0, 실패 시 -1 리턴 }}} {{{~cpp 예제) if( bind(sockfd, (struct sockaddr *)&ina, sizeof(struct sockaddr) == -1 ) fprintf(stderr, "bind에서 에러가 났습니다.n")), exit(1); }}} == listen - client의 요청을 기다린다! == {{{~cpp #include int listen(int sockfd, int backlog); // 성공 시 0, 실패 시 -1 리턴 // backlog는 서버에 접속할 사람의 대기자 Maximum을 의미한다. }}} {{{~cpp 예제) #deinfe BACKLOG 5 // 대기자가 5명이 넘으면 접속 불가능하다. if( listen(sockfd, BACKLOG) == -1 ) fprintf(stderr, "listen에서 에러가 났습니다.n"), exit(1); }}} == accpet - client의 요청을 받아들인다! == {{{~cpp #include int accept(int sockfd, struct sockaddr *addr, int *addrlen); // 성공 시 파일 디스크립터, 실패 시 -1 리턴 // *addrlen에 주의. accept는 client의 인터넷 정보가 들어오면 addrlen의 크기(struct sockaddr_in의 크기)와 // 비교를 하여 크다면 받아들이지 않고, 작다면 크기를 줄일것이다. }}} // child process를 생성해 다중 연결을 하는 것은 설명하지 않습니다. {{{~cpp 예제 ) // int server_sock, client_sock // struct sockaddr_in server_addr, client_sock int sizeof_sockaddr_in; sizeof_sockaddr_in = sizeof(struct sockaddr_in); client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &sizeof_sockaddr_in); if( client_sock == -1 ) fprintf(stderr, "accept에러. client가 서버에 접속 할 수 없습니다."); }}} = Client 가 될 프로그램에 필요한 함수 = [[HTML(
)]] == connect - Server에 연결한다. == ※ connect와 server 함수중 어떠한 함수가 닮았는지 이야기 해보자. ※ 이를 이야기 해보고 client의 프로그램의 네트워크 정보(struct sockaddr_in)에는 무엇이 들어가야하는지 이야기해보자. {{{~cpp #include #include int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); // 성공 시 0, 실패 시 -1 리턴 }}} {{{~cpp 예제 ) if( connect(client_sock, (struct sockaddr *)&client_addr, sizeof(struct sockaddr)) == -1 ) fprintf(stderr, "서버에 connect 할 수 없습니다."), exit(1); }}} = 정리에 필요한 함수 = == close 파일을 닫는다. == {{{~cpp #include int close(int fildes); // 성공 시 0, 실패 시 -1 리턴 }}} {{{~cpp 예제 ) close(sockfd); }}} = server/client 공통 - 입출력 함수 = [[HTML(
)]] == send/write - 상대에게 데이터를 보낸다. == {{{~cpp #include ssize_t send(int fildes, const void * buf, size_t nbytes, unsigned int flags); ssize_t write(int fildes, const void * buf, size_t nbytes); // 성공 시 전달 한 바이트 수, 실패 시 -1 리턴 }}} {{{~cpp 예제 ) char buf1[] = "Hello, World!"; char *buf2 = "Hello, World!"; if( send(client_sock, buf1, sizeof(buf), 0) == -1 ) fprintf(stderr, "send error"); if( write(client_sock, buf2, strlen(buf2)) == -1 ) fprintf(stderr, "write error"); // 타 시스템으로 이식을 위해 되도록 send를 사용하는 것이 좋다. }}} == recv/read - 상대에게 데이터를 받는다. == {{{~cpp #include ssize_t recv(int fildes, void *buf, size_t nbytes, unsigned int flags); ssize_t read(int fildes, void *buf, size_t nbytes); // 성공 시 수신 한 바이트 수(단 EOF만나면 0), 실패 시 -1 리턴 }}} {{{~cpp 예제 ) char buf1[200]; char buf2[200]; int str_len; str_len = recv(sockfd, buf1, sizeof(buf1), 0); str_len = read(sockfd, buf2, sizeof(buf2)); buf1[str_len] = 0; // 배열의 끝을 설정해준다. 하지 않으면 뒤의 쓰레기 값까지 접근된다. buf1[str_len] = 0; // 타 시스템으로 이식을 위해 되도록 send를 사용하는 것이 좋다. }}} = ※ 윈도우 기반에서는... = {{{~cpp ◎ Visual C++에서 빈 소스 파일 하나를 연다. Project -> Setting -> LINK 메뉴 -> Object/library modules: 의 끝부분에 ws2_32.lib 를 추가한다. ◎ #include -> #include ◎ UNIX 체계에서 사용하던 함수들의 헤더파일이 Windows 기반에서는 존재하지 않을 수도 있다. ◎ struct sockaddr_in -> SOCKADDR_IN ◎ int sockfd; -> SOCKET sockfd; // 소켓 디스크립터 // 소켓 핸들 ◎ main() 함수 내부에 WSADATA wsaDATA; // 추가. WSADATA형의 변수를 선언한다. if( WSAStartup(MAKEWORD(2,2), &wsaData) != 0) fprintf(stderr, "WSAStartup Error"), exit(1); // 추가. WSAStartup() 은 socket의 버젼을 ws2_32 라이브러리에 전달한다. // 프로그램이 끝날 때, 항상 WSACleanup()으로 리소스를 해제해야한다. }}} {{{~cpp 예제) #include main(){ WSADATA wsaDATA; SOCKET sockfd; // UNIX 기반의 int sockfd; SOCKADDR_IN ina; // UNIX 기반의 struct sockaddr_in ina; if( WSAStartup(MAKEWORD(2,2), &wsaData) != 0) fprintf(stderr, "WSAStartup Error"), exit(1); ... 내용 WSACleanup(); } }}} = server 예제 = {{{~cpp #include #include #define PORT 9999 // 서버의 9999번 포트를 연다 #define BACKLOG 5 void error(char *buf) { puts(buf), exit(1); } main(){ WSADATA wsaData; SOCKET server_sock; // 서버의 socket을 생성 SOCKET client_sock; // 클라이언트의 socket을 생성 SOCKADDR_IN server_addr; // 네트워크의 정보를 담을 structure 생성. SOCKADDR_IN client_addr; int sizeof_sockaddr_in; char msg[] = "Hello, World!"; if( WSAStartup(MAKEWORD(2,2), &wsaData) == -1 ) error("WSAStartup Error"); // socket 설정 // 프로그래머는 이것을 통해 네트워크와 대화를 한다. server_sock = socket(AF_INET, SOCK_STREAM, 0); if( server_sock == -1 ) error("server socket error"); // 네트웍 정보 설정 // 이것은 프로그램이 socket과 연결할 정보를 담고있다. memset((SOCKADDR_IN *)&server_addr, 0, sizeof(SOCKADDR_IN)); // struct sockaddr_in -> SOCKADDR_IN server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; // 자신의 주소로 설정한다. server_addr.sin_port = htons(PORT); // socket과 네트웍 정보를 bind()로 연결한다. if( bind(server_sock, (sockaddr *)&server_addr, sizeof(SOCKADDR_IN)) == -1 ) error("bind() 에러"); // 준비는 끝났다! // listen()으로 client의 연결 요청을 기다리자. // client의 요청이 올 때 까지 서버는 여기서 기다린다. if( listen(server_sock, BACKLOG) == -1 ) error("listen() 에러"); // client의 요청이 오면 server는 accept() 함수로 요청을 받아들인다. // client와의 데이터 전송을 위해 client 소켓 디스크립터가 필요하다. sizeof_sockaddr_in = sizeof(SOCKADDR_IN); while(1) { client_sock = accept(server_sock, (sockaddr *)&client_addr, &sizeof_sockaddr_in); if( client_sock == -1 ) { // accept가 실패하면 while의 처음으로 돌아가 다시 client를 기다린다. fprintf(stderr, "accept() 에러"); continue; } send(client_sock, msg, sizeof(msg), 0); //close(client_sock); WSACleanup(); } exit(0); // exit로 종료를 하면 모든 파일 디스크립터를 자동으로 닫고 종료한다. } }}} ※ 컴퓨터는 하나의 처리밖에 하지 못한다. 따라서 위의 소스는 하나의 client밖에 받아 들일 수 밖에 없다. 어떻게 하면 여러 client를 동시에 받아들일 수 있을까? = 실습 = 위의 server 에 접속 하는 client 프로그램을 짜고, Server가 보내는 메세지인 "Hello, World!"란 문장을 clinet 화면에 출력하도록 한다. = 참고 사이트 = An Introduction to Socket Programming : [http://www.uwo.ca/its/doc/courses/notes/socket/] Beej's Guide to Network Programming : [http://www.ecst.csuchico.edu/~beej/guide/net/html/] Beej's 번역판 : 서치엔진에서 찾아보세요. (무책임) Socket Programming in Python : [http://www.amk.ca/python/howto/sockets/] IPv6 Socket Programming : [http://www.joinc.co.kr/modules/moniwiki/wiki.php/article_IPv6_Programing?action=print2] ---- [프로그래밍분류]