
C 언어에서 fork()를 활용한 다중 프로세스 소켓 서버를 구축하면 여러 클라이언트 연결을 동시에(concurrent) 처리할 수 있습니다. fork() 시스템 콜을 이용하면 접속하는 클라이언트마다 자식 프로세스를 생성해 각각 독립적인 통신 채널을 확보하게 됩니다. 덕분에 하나의 서버가 여러 클라이언트와 동시에 통신할 수 있습니다.
이번 글에서는 C 언어로 fork() 기반의 다중 프로세스 소켓 서버를 구현하는 방법을 살펴보겠습니다. 서버 측 프로그램과 클라이언트 측 프로그램 예제 코드를 통해 단계별로 자세히 설명하겠습니다.
fork() 시스템 콜이란?
실행 중인 기존 프로세스를 복제(clone)하는 시스템 콜을 fork()라고 합니다. fork()는 실행 중인 프로그램을 두 개의 별도 프로세스, 즉 부모(parent)와 자식(child)으로 분리합니다. 두 프로세스는 PID(Process ID)만 다를 뿐 나머지 속성은 모두 동일합니다. 이 시스템 콜은 다음과 같은 값을 반환합니다.
- 부모 프로세스에는 자식 프로세스의 PID
- 자식 프로세스에는 0
이 반환값의 차이를 이용하면 부모와 자식이 각자 다른 작업을 수행하도록 분기 처리할 수 있습니다.
예제 1: 서버 측 프로그램
아래 서버 코드는 소켓을 생성하고 특정 주소·포트에 바인딩한 뒤 연결 요청을 대기합니다. 클라이언트가 접속할 때마다 fork()로 자식 프로세스를 만들어 해당 클라이언트 전담 처리를 맡깁니다.
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define PORT 8888
int main()
{
int serSoc, cliSoc;
struct sockaddr_in serverAddr, clientAddr;
socklen_t addrSize = sizeof(clientAddr);
char buffer[1024];
int clientCount = 0;
// 서버 소켓 생성
serSoc = socket(AF_INET, SOCK_STREAM, 0);
if (serSoc < 0) {
perror("Error in socket creation");
exit(1);
}
printf("Server socket created.\n");
// 서버 주소 설정
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr = INADDR_ANY;
if (bind(serSoc, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
perror("Error in binding");
exit(1);
}
// 들어오는 연결 대기 시작
if (listen(serSoc, 5) == 0) {
printf("Listening for connections...\n");
} else {
perror("Error in listening");
exit(1);
}
while (1) {
// 클라이언트 연결 수락
cliSoc = accept(serSoc, (struct sockaddr*)&clientAddr, &addrSize);
if (cliSoc < 0) {
perror("Error in accepting connection");
exit(1);
}
// 클라이언트 수 증가 및 접속 정보 출력
clientCount++;
printf("Accepted connection from Client %d: %s:%d\n", clientCount,
inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
printf("Total clients connected: %d\n", clientCount);
pid_t pid = fork();
if (pid == 0) {
// 자식 프로세스
close(serSoc);
// 클라이언트로부터 메시지 수신
while (1) {
memset(buffer, 0, sizeof(buffer));
if (recv(cliSoc, buffer, sizeof(buffer), 0) < 0) {
perror("Error in receiving data");
exit(1);
}
printf("Received data from Client %d: %s\n", clientCount, buffer);
// 받은 메시지를 그대로 클라이언트에 되돌려 보냄(Echo)
if (send(cliSoc, buffer, strlen(buffer), 0) < 0) {
perror("Error in sending data");
exit(1);
}
}
close(cliSoc);
exit(0);
} else if (pid > 0) {
// 부모 프로세스
close(cliSoc);
} else {
perror("Error in forking");
exit(1);
}
}
// 서버 소켓 닫기
close(serSoc);
return 0;
}예제 2: 클라이언트 측 프로그램
클라이언트는 서버에 접속한 후 사용자 입력을 받아 전송하고, 서버가 되돌려준 응답(Echo)을 화면에 출력합니다.
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define PORT 8888
#define SERVER_IP "127.0.0.1"
int main()
{
int cliSoc;
struct sockaddr_in serverAddr;
char buffer[1024];
// 클라이언트 소켓 생성
cliSoc = socket(AF_INET, SOCK_STREAM, 0);
if (cliSoc < 0) {
perror("Error in socket creation");
exit(1);
}
printf("Client %d socket created.\n", getpid());
// 서버 주소 설정
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
// 서버에 연결
if (connect(cliSoc, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
perror("Error in connecting to server");
exit(1);
}
printf("Connected to server.\n");
while (1) {
// 사용자 입력 읽기
printf("Client %d - Enter a message: ", getpid());
fgets(buffer, sizeof(buffer), stdin);
if (send(cliSoc, buffer, strlen(buffer), 0) < 0) {
perror("Error in sending data");
exit(1);
}
// 서버로부터 응답 수신
memset(buffer, 0, sizeof(buffer));
if (recv(cliSoc, buffer, sizeof(buffer), 0) < 0) {
perror("Error in receiving data");
exit(1);
}
printf("Client %d - Server response: %s\n", getpid(), buffer);
}
// 클라이언트 소켓 닫기
close(cliSoc);
return 0;
}컴파일 및 실행 결과
**서버 프로그램 컴파일**
$ gcc ser.c -o ser
$ ./ser
Server socket created.
Listening for connections...
Accepted connection from Client 1: 127.0.0.1:59074
Total clients connected: 1
Received data from Client 1: hii admin
Accepted connection from Client 2: 127.0.0.1:40192
Total clients connected: 2
Received data from Client 2: hello everybody
**클라이언트 1 프로그램 컴파일**
$ gcc cel.c -o cel
$ ./cel
Client 4007 socket created.
Connected to server.
Client 4007 - Enter a message: hii admin
**클라이언트 2 프로그램 컴파일**
$ gcc cel.c -o cel
$ ./cel
Client 4024 socket created.
Connected to server.
Client 4024 - Enter a message: hello everybody코드 설명
위 예제는 C 언어에서 fork()를 활용해 다중 프로세스 소켓 서버를 구현한 것입니다. 서버 측 프로그램은 먼저 서버 소켓을 생성하고, 지정된 주소와 포트에 바인딩한 뒤 들어오는 연결을 대기합니다. 클라이언트가 접속하면 fork()를 호출해 자식 프로세스를 생성하고, 이 자식 프로세스가 해당 클라이언트의 메시지를 전담해서 수신하며 클라이언트 ID와 함께 화면에 출력합니다. 부모 프로세스는 계속 accept()만 반복하며 새로운 연결을 받아들이기 때문에, 여러 클라이언트가 동시에 접속해도 각각의 통신이 독립적으로 이루어집니다.
실무에서 참고할 점도 있습니다. fork() 기반 서버에서는 클라이언트가 접속을 끊은 뒤 자식 프로세스가 종료될 때 좀비(zombie) 프로세스가 남을 수 있습니다. 이를 방지하려면 부모 프로세스에서 signal(SIGCHLD, 핸들러)을 등록해 waitpid()로 자식을 회수하거나, SIGCHLD를 SIG_IGN으로 설정하는 것이 좋습니다. 또한 다수의 클라이언트를 장기간 처리해야 한다면 프로세스 생성 오버헤드가 적은 스레드(pthread)나 epoll 같은 이벤트 기반 모델을 고려하는 것도 좋은 선택입니다.
결론
이번 글에서는 C 언어에서 fork() 시스템 콜을 사용해 다중 프로세스 소켓 서버를 구현하는 방법을 알아보았습니다. fork()를 활용하면 여러 클라이언트 연결을 동시에 처리할 수 있으며, 각 클라이언트와의 통신이 독립적인 프로세스에서 이루어져 안정적인 동시 통신과 효율적인 요청 처리가 가능합니다. 예제 코드를 직접 컴파일하고 실행하면서 fork() 기반 서버의 동작 원리를 확실히 익혀 보시기 바랍니다.
저자 소개

Bamdeb Ghosh
Bamdeb Ghosh는 무선 네트워킹 분야에서 풍부한 실무 경험을 갖춘 전문가입니다. 유·무선 네트워크 환경에서 Wireshark 패킷 캡처 분석 전문가이며, Android, Bluetooth, Linux 명령어, Python 등에 대한 폭넓은 지식을 보유하고 있습니다. 개인 사이트: wifisharks.com