Explanation
이번 주제는 TCP/UDP 랍니다.
간단하게 이론적으로 TCP는 3-way-handshaking 라거나 흐름 제어라거나 높은 신뢰성을 보장하고, UDP는 비연결형으로 낮은 신뢰성을 가지고 있지만 TCP보다 빠르다. 뭐 이정도만 알고 있는데요. 직접 TCP / UDP 서버를 만들어서 비교해서 이것저것 실험해보면 둘의 차이를 조금 더 잘 알고 이해할 수 있지 않을까 하는 생각이 들어 한번 해보게 되었답니다.
그리고 오늘 적을 내용은 간단한 메세지를 주고 받는 TCP / UDP 서버를 만들어 본 내용을 적어 보려합니다.
2024.09.08 추가사항
Node.js를 사용해서 TCP와 UDP 서버를 만들고 그 특징을 비교한 포스팅을 새로 하였습니다!!
아래의 링크의 글이 조금 더 도움이 될 거 같아요!
링크: /node-js를-사용한-websocket-tcp-udp-서버를-만들어서-tcp와-udp의-차이를-확/
1. UDP Server - Node
우선은 그나마 가장 익숙한 Nodejs를 이용해서 아주 간단하게 UDP 서버를 만들어보면 아래와 같답니다.
// 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);
// 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의 데이터를 보내보자!
하지만... 결과는...
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 언어로 만들어봐야겠다. 했어요.
2. UDP Server - C
우선 관련해서 회사 동생에게 물어봤는데, 회사 동생이 윤성우님의 "열혈 TCP/IP 소켓 프로그래밍" 책을 빌려줘서 아래의 코드는 해당 책에 나오는 예제를 참고해서 약간 수정한 코드입니다.
// 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);
}
// 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 언어를 잘 모른 답니다.)
3. TCP Server - 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
// 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);
}
// 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의 차이를 확인할 수 있는 예제를 알려주시는 용자님이 계신다면, 성은이 망극할 것 같사옵니다. 주륵..



