2011년7월6일_main( )의 인수를 이용하여 TCP서버 & 클라이언트 기동시 IP와 Port번호를 입력받을 수 있도록 코드수정, TCP와 UDP비교, TCP echo 서버/클라이언트(Server & Client) 만들기
1. main( )의 인수를 이용하여 TCP서버 & 클라이언트 기동시 IP와 Port번호를 입력받을 수 있도록 코드수정
1: //argc는 명령어라인의 문자열의 갯수
2: //argv는 명령어라인의 문자열 (0 = 실행파일, 1 = 다음 문자열, 2, 3, 4....다음 문자열)
3: int main(int argc, char *argv[])
상기와 같이 main( )에 인수를 써넣으면 argc에는 명령어의 갯수가 들어가고,
argv[]에는 명령어 문자열이 순서대로 들어간다.
아래와 같이 명령어 라인에 명령을 치면,
# ./client 127.0.0.1 9190 |
argc는 3이 되고,
*argv[0]는 “./client” 문자열을 가리키고,
*argv[1]는 “127.0.0.1” 문자열을 가리키고,
*argv[2]는 “9190” 문자열을 가리킨다.
argc의 값으로 명령어라인의 문자열수를 파악하여 예외처리를 해줘야 한다.
예외처리를 하지 않으면 치명적인 에러가 발생한다.
● 명령어라인 문자열수가 3개 미만이면 에러메세지(사용법)출력
1: if(3 > argc)
2: {
3: fprintf(stderr, "How to Use : ./%s [IP주소] [Port번호]\n", argv[0]);
4: return -1;
5: }
● IP주소와 Port번호를 담은 *argv[]
dotted decmal IP주소를 32bit 주소로 바꾸어 구조체에 저장하는 함수인 inet_addr( )은 문자열입력을 받으니 그대로 argv[1]을 넣으면 되나, Port번호를 Big endian으로 바꾸는 함수인 htons( )와 Port가 담기는 serv_addr구조체의 sin_port멤버는 정수만 취급한다.
그래서 문자열을 정수로 바꾸어 줄 필요가 있다.
# man atoi
조금 허접한 매뉴얼이지만 입력받은 문자열을 int형 정수로 바꾸어 준다는 것을 알 수 있다.
상기의 IP주소와 Port번호문자열을 주소구조체에 값을 입력하는 코드는 아래와 같다.
1: memset(&serv_addr, 0, sizeof(serv_addr));
2: serv_addr.sin_family = AF_INET;
3: serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
4: serv_addr.sin_port = htons(atoi(argv[2]));
2. TCP와 UDP비교
TCP |
UDP |
1.연결형 프로토콜
-두 시스템간에 연결이 성공한 후에 데이터 통신 시작 ※ 3-Way handshaking 2.신뢰성 보장 - 순차적으로 데이터 전달 - 데이터 재전송 3. 데이터의 경계를 구분하지 않음. - Byte stream서비스 |
1.비연결형프로토콜
-연결없이 통신가능. 2.비신뢰적인 데이터전송 -전송순서에 상관없이 가장 빠른 전송을 취함. -전송 도중 데이터가 손실되어도 재전송하지 않음. 3.데이터의 경계를 구분함 -Datagram서비스 |
3. TCP echo 서버 만들기
1: // interactive server (다중 접속시 순서대로 1:1통신)
2:
3: #include <stdio.h>
4: #include <stdlib.h>
5: #include <string.h>
6: #include <unistd.h>
7: #include <arpa/inet.h>
8: #include <sys/types.h>
9: #include <sys/socket.h>
10:
11: void error_handling(char *);
12: //void sleep(unsigned int dd);
13:
14: int main(int argc, char *argv[])
15: {
16: int serv_sock;
17: int clnt_sock;
18: struct sockaddr_in serv_addr;
19: struct sockaddr_in clnt_addr;
20: int clnt_addr_size;
21: int iCnt = 0;
22: int str_len = 0;
23: char buffer[100];
24: char buffer_echo[512];
25: char message[] = "수만: Hello SM World!\n";
26:
27: //파라메터 체크
28: if(2 != argc)
29: {
30: fprintf(stderr, "How to Use : %s [Port번호]\n", argv[1]);
31: return -1;
32: }
33:
34: //서버 소켓 생성
35: serv_sock = socket(PF_INET, SOCK_STREAM, 0);
36:
37: if(-1 == serv_sock)
38: {
39: error_handling("socket() error");
40: }
41:
42: memset(&serv_addr, 0, sizeof(serv_addr));
43: serv_addr.sin_family = AF_INET;
44: serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
45: serv_addr.sin_port = htons(atoi(argv[1]));
46:
47: //소켓에 주소 할당
48: if(-1 == bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))
49: {
50: error_handling("bind() error");
51: }
52:
53: //연결요청 대기 큐에 존재하는 clients의 연결요청을 순차적으로 수락
54: //연결 요청 대기상태로 진입
55: if(-1 == listen(serv_sock, 10))
56: {
57: error_handling("listen() error");
58: }
59:
60: for(;;)
61: {
62: //연결 요청 수락
63: clnt_addr_size = sizeof(clnt_addr);
64: clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
65:
66: if(-1 == clnt_sock)
67: {
68: error_handling("accept() error");
69: }
70:
71: sleep(5);
72:
73: str_len = read(clnt_sock, buffer_echo, sizeof(buffer_echo));
74:
75: printf("%3d번 ", ++iCnt);
76: printf("클라이언트 주소 %s\n", inet_ntoa(clnt_addr.sin_addr));
77: printf(" 클라이언트 포트 %d\n", ntohs(clnt_addr.sin_port));
78:
79: printf("%s", buffer_echo);
80: write(clnt_sock, buffer_echo, str_len);
81:
82: }
83:
84: return 0;
85: }
86:
87: void error_handling(char * message)
88: {
89: fputs(message, stderr);
90: fputc('\n', stderr);
91: exit(1);
92: }
93:
4. TCP 클라이언트 (IP와 Port입력추가한 소스)
1: // helloworld_client.c
2:
3: #include <stdio.h>
4: #include <stdlib.h>
5: #include <string.h>
6: #include <unistd.h>
7: #include <arpa/inet.h>
8: #include <sys/types.h>
9: #include <sys/socket.h>
10:
11: void error_handling(char *);
12:
13: int main(int argc, char *argv[])
14: {
15: int sock;
16: struct sockaddr_in serv_addr;
17: char message[100];
18: int str_len;
19:
20: if(3 > argc)
21: {
22: fprintf(stderr, "How to Use : ./%s [IP주소] [Port번호]\n", argv[0]);
23: return -1;
24: }
25:
26: //서버 접속을 위한 소켓 생성
27: sock = socket(PF_INET, SOCK_STREAM, 0);
28: if(-1 == sock)
29: {
30: error_handling("socket() error");
31: }
32:
33: memset(&serv_addr, 0, sizeof(serv_addr));
34: serv_addr.sin_family = AF_INET;
35: serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
36: serv_addr.sin_port = htons(atoi(argv[2]));
37:
38: //서버로 연결 요청
39: if(-1 == connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))
40: {
41: error_handling("connect() error");
42: }
43:
44: fputs("send message (q to quit) : ", stdout);
45: fgets(message, sizeof(message), stdin);
46:
47: write(sock, message, strlen(message));
48: str_len = read(sock, message, sizeof(message));
49:
50: printf("message from server : %s\n", message);
51:
52: //연결 종료
53: close(sock);
54:
55: return 0;
56: }
57:
58: void error_handling(char * message)
59: {
60: fputs(message, stderr);
61: fputc('\n', stderr);
62: exit(1);
63:
64: return ;
65: }
66:
67:
68:
<실행결과>
서버와 클라이언트 프로그램 실행시 아무것도 입력하지 않으면 어떻게 입력해야 하는지 설명이 나오고,
TCP echo서버를 먼저 9190포트를 열어 기동하고,
TCP echo클라이언트를 loopback주소와 9190포트를 넣어 기동한 뒤에,
메세지를 클라이언트에서 서버로 날리면 서버는 메세지를 받아 바로 돌려준다.
클라이언트는 메세지를 받아 화면에 출력한다.
서버측에서 출력된 메세지와 IP, Port출력은 궁금해서 넣었다.