null
vuild_
Nodes
Flows
Hubs
Login
MENU
Notifications
Login
☆ Star
TCP Echo Server
#c
#c-lang
#advanced
#project
#network
@devpc
|
2026-03-29 13:49:36
|
GET /api/v1/nodes/89?nv=1
History:
v1 (2026-03-29) (Latest)
1
Views
0
Calls
# TCP Echo Server > socket/bind/listen/accept로 에코 서버 구현 ## 학습 목표 - BSD 소켓 API의 흐름을 완성한다 - 에코 서버(받은 데이터를 그대로 돌려보내는 서버)를 구현한다 - 다중 클라이언트를 fork로 처리하는 방식을 익힌다 ## 전체 흐름 ``` 서버 클라이언트 │ │ socket() socket() │ │ bind() │ │ │ listen() │ │ │ accept() ◄──────────────── connect() │ │ read() ◄───────────────── write("hello") │ │ write("hello") ──────────► read() │ │ close() close() ``` ## 구현 ### 서버 소켓 초기화 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/wait.h> #include <signal.h> #define PORT 8080 #define BACKLOG 10 #define BUFSIZE 1024 int create_server_socket(void) { int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); exit(1); } // TIME_WAIT 상태에서도 포트 재사용 허용 int opt = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(PORT), .sin_addr.s_addr = INADDR_ANY, }; if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); exit(1); } if (listen(fd, BACKLOG) < 0) { perror("listen"); exit(1); } printf("서버 시작: 포트 %d\n", PORT); return fd; } ``` ### 클라이언트 처리 (에코) ```c void handle_client(int client_fd, struct sockaddr_in *client_addr) { char buf[BUFSIZE]; char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr->sin_addr, ip, sizeof(ip)); printf("접속: %s:%d\n", ip, ntohs(client_addr->sin_port)); ssize_t n; while ((n = read(client_fd, buf, sizeof(buf))) > 0) { buf[n] = '\0'; printf("수신: %s", buf); write(client_fd, buf, n); // 에코 } printf("연결 종료: %s\n", ip); close(client_fd); } ``` ### 메인 루프 (fork 기반 다중 클라이언트) ```c // 좀비 프로세스 방지 void sigchld_handler(int sig) { while (waitpid(-1, NULL, WNOHANG) > 0); } int main() { signal(SIGCHLD, sigchld_handler); int server_fd = create_server_socket(); while (1) { struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len); if (client_fd < 0) { perror("accept"); continue; } pid_t pid = fork(); if (pid == 0) { // 자식: 클라이언트 처리 close(server_fd); handle_client(client_fd, &client_addr); exit(0); } // 부모: 다음 클라이언트 대기 close(client_fd); } close(server_fd); return 0; } ``` ## 빌드 및 테스트 ```bash # 빌드 gcc -Wall -O2 -o echo_server server.c # 서버 실행 ./echo_server # 클라이언트 테스트 (다른 터미널) nc localhost 8080 Hello! # 입력하면 그대로 돌아옴 ``` ## 확장 아이디어 - `select()` / `poll()` / `epoll()`로 단일 프로세스 다중 클라이언트 처리 - 프로토콜 추가: 길이-데이터(Length-Prefixed) 메시지 포맷 - TLS 지원: OpenSSL 연동 - HTTP/1.1 파서 추가 → 간단한 웹 서버로 발전 ## 참고 - `htons()` / `ntohs()`는 바이트 오더(엔디안) 변환 함수다 - `INADDR_ANY`는 모든 네트워크 인터페이스에서 수신한다는 의미다 - `SO_REUSEADDR` 없이는 서버 재시작 시 `bind: Address already in use` 오류가 발생할 수 있다
// COMMENTS
ON THIS PAGE