Docker #1 Docker로 Ubuntu, Apache, Php 환경의 이미지 만들기
TCP, UDP 서버 공부 #1 Nodejs / C로 TCP, UDP 서버 만들어 보기
2020-05-13
Explanation
이번 주제는 TCP/UDP 랍니다.
간단하게 이론적으로 TCP는 3-way-handshaking 라거나 흐름 제어라거나 높은 신뢰성을 보장하고, UDP는 비연결형으로 낮은 신뢰성을 가지고 있지만 TCP보다 빠르다. 뭐 이정도만 알고 있는데요. 직접 TCP / UDP 서버를 만들어서 비교해서 이것저것 실험해보면 둘의 차이를 조금 더 잘 알고 이해할 수 있지 않을까 하는 생각이 들어 한번 해보게 되었답니다.
그리고 오늘 적을 내용은 간단한 메세지를 주고 받는 TCP / UDP 서버를 만들어 본 내용을 적어 보려합니다.
우선은 그나마 가장 익숙한 Nodejs를 이용해서 아주 간단하게 UDP 서버를 만들어보면 아래와 같답니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Server const dgram = require('dgram'); const server = dgram.createSocket('udp4'); const port = 4000; server.on('message', (message, info) => { console.log(`message: ${message.toString()}`); console.log(`from address: ${info.address} port: ${info.port}`); }); server.on('listening', () => { const address = server.address(); console.log(`listening ${address.address}:${address.port}`); }); server.bind(port); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Client const dgram = require('dgram'); const toHost = '0.0.0.0'; const toPort = 4000; const client = dgram.createSocket('udp4'); const dataIdx = 1; let sum = ''; for(let i=0; i<9216; i++) { sum += dataIdx.toString(); } const data = Buffer.from(sum); client.send(data, toPort, toHost, (err) => { if (err) console.log(err); else console.log('ok'); client.close(); }); |
생각해보니까, 저는 한번도 UDP 서버를 사용해본 적이 없더라고. 그래서인지 막연히 UDP 서버를 구성하는게 굉장히 복잡할 줄 알았는데 생각보다 Nodejs에서 구성하는 건 엄청 간단했어요.
어떤걸 테스트해야 TCP와 차이를 확실히 알 수 있을까… 생각해봤는데 마땅히 방법이 안떠오르더라고요. 그나마 생각한 방법은 UDP의 패킷은 헤더 8Bytes를 제외하고 최대 크기가 65,527Bytes니까 65,528Bytes의 데이터를 보내보자!
하지만… 결과는…
1 2 3 4 5 6 7 8 9 10 11 |
Error: send EMSGSIZE 0.0.0.0:4000 at doSend (dgram.js:679:16) at defaultTriggerAsyncIdScope (internal/async_hooks.js:301:12) at afterDns (dgram.js:625:5) at processTicksAndRejections (internal/process/task_queues.js:81:21) { errno: 'EMSGSIZE', code: 'EMSGSIZE', syscall: 'send', address: '0.0.0.0', port: 4000 } |
그냥 오류가 뜹니다.
하지만 그안에서도 별거 아닐 수 있지만, 새롭게 알게 된 사실이라면!
Mac OS 에서는 UDP 패킷의 데이터를 9,217Bytes만 보내도 오류가 출력된답니다.
왜 그런지 찾아보니 맥 기본 설정값이 그렇게 되어 있다고 하더라고요.
참고 링크. https://github.com/jaegertracing/jaeger-client-node/issues/124#issuecomment-324222456
그리고 고민을 하다가 Javascript는 하이 레벨의 언어이고 NodeJS도 굉장히 최근의 플랫폼이다보니 상대적으로 조금 로우한 레벨의 언어로 해봐야겠다! 라는 생각이 들어서 C 언어로 만들어봐야겠다. 했어요.
우선 관련해서 회사 동생에게 물어봤는데, 회사 동생이 윤성우님의 “열혈 TCP/IP 소켓 프로그래밍” 책을 빌려줘서 아래의 코드는 해당 책에 나오는 예제를 참고해서 약간 수정한 코드입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
// Server #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> void error_handling(char *message); int main(int argc, char *argv[]) { int serv_sock; char message[1024] = {0}; socklen_t clnt_adr_sz; int str_len; struct sockaddr_in serv_adr, clnt_adr; if(argc != 2) { printf("usage: %s <port>\n", argv[0]); exit(1); } serv_sock = socket(PF_INET, SOCK_DGRAM, 0); if(serv_sock == -1) { char err_message[] = "UDP socket creation error"; error_handling(err_message); } memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) { char err_message[] = "bind() error"; error_handling(err_message); } while(1) { clnt_adr_sz = sizeof(clnt_adr); str_len = recvfrom(serv_sock, message, 1024, 0, (struct sockaddr*)&clnt_adr, &clnt_adr_sz); printf("%s", message); sendto(serv_sock, message, str_len, 0, (struct sockaddr*)&clnt_adr, clnt_adr_sz); } close(serv_sock); return 0; } void error_handling(char *message) { fputs(message, stderr); exit(1); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// Client #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> int main(int argc, char *argv[]) { int sock; char message[1024] = {0}; char res_message[1024] = {0}; int str_len; socklen_t adr_sz; struct sockaddr_in serv_adr, from_adr; if(argc != 3) { printf("Usage : %s <IP> <port>\n", argv[0]); exit(1); } sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = inet_addr(argv[1]); serv_adr.sin_port=htons(atoi(argv[2])); char msg[] = "Request message: "; fputs(msg, stdout); fgets(message, sizeof(message), stdin); sendto(sock, message, sizeof(message), 0, (struct sockaddr*)&serv_adr, sizeof(serv_adr)); adr_sz = sizeof(from_adr); str_len = recvfrom(sock, res_message, 1024, 0, (struct sockaddr*)&from_adr, &adr_sz); printf("Response message: %s", res_message); return 0; } |
한줄 한줄 코드에 대해 이야기하면 좋을 것 같지만,
(사실 저도 C 언어를 잘 모른 답니다.)
그리고 비교를 위해 TCP 서버도 만들어봤는데요.
TCP는 아래의 글의 예제를 참고해서 약간 수정한 코드랍니다.
참고 링크. https://medium.com/from-the-scratch/http-server-what-do-you-need-to-know-to-build-a-simple-http-server-from-scratch-d1ef8945e4fa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
// Server #include <stdio.h> #include <sys/socket.h> #include <unistd.h> #include <stdlib.h> #include <netinet/in.h> #include <string.h> #define PORT 6666 int main(int argc, char const *argv[]) { int server_fd, new_socket; long valread; struct sockaddr_in address; int addrlen = sizeof(address); char buffer[1024] = {0}; if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("In socket"); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons( PORT ); memset(address.sin_zero, '\0', sizeof address.sin_zero); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("In bind"); exit(EXIT_FAILURE); } if (listen(server_fd, 10) < 0) { perror("In listen"); exit(EXIT_FAILURE); } while(1) { if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { perror("In accept"); exit(EXIT_FAILURE); } valread = read(new_socket, buffer, 1024); printf("%s", buffer); write(new_socket, buffer, strlen(buffer)); } return 0; close(new_socket); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
// Client #include <stdio.h> #include <sys/socket.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h> #include <string.h> #include <arpa/inet.h> #define PORT 6666 int main(int argc, char const *argv[]) { int sock = 0; long valread; struct sockaddr_in serv_addr; char message[1024] = {0}; char buffer[1024] = {0}; int new_socket; if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("\n Socket creation error \n"); return -1; } memset(&serv_addr, '0', sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { printf("\nInvalid address/ Address not supported \n"); return -1; } if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { printf("\nConnection Failed \n"); return -1; } char msg[] = "Request message: "; fputs(msg, stdout); fgets(message, sizeof(message), stdin); write(sock, message, strlen(message)); valread = read(sock, buffer, 1024); printf("Response message: %s", buffer); return 0; } |
짜잔!
(굉장히 간단한 코드지만.. 개인적으로는 오랜만의.. 엄청난 삽질의 결과물이랍니다..)
하지만, 역시나.. 비교 테스트 예제를 만들기가 어려운 거 같아요.
우선은 여기까지하고, 다음에 짬짬히 더 공부해서 다음 포스팅에 TCP/UDP의 비교 예제를 포스팅 하도록 하겠습니다.
혹시, 이 가련한 개발자 중생을 위하여 UDP와 TCP의 차이를 확인할 수 있는 예제를 알려주시는 용자님이 계신다면, 성은이 망극할 것 같사옵니다. 주륵..