멀티 프로세스 에코 서버
-
커널은 파일 디스크립터에 대한 정보를 파일 디스크립터 테이블에 관리한다. 파일 디스크립터 테이블은 프로세스당 하나씩 생성된다.
-
파일 디스크립터 테이블은 프로세스당 하나씩 생성된다. 또한 커널은 시스템 상에 열려 있는 모든 열린 파일에 대한 정보를 저장하고 있으며, 시스템당 하나만 존재한다.
-
파일 디스크립터 테이블은 열린 파일 테이블에 대한 링크를 가지고 있다.
-
프로세스가
fork()
를 이용하여 자식 프로세스를 생성하면 커널은 자식 프로세스를 위한 파일 디스크립터 테이브을 생성하고, 이때 모든 값이 복사되기 때문에 두 파일 디스크립터 테이블은 같은 값을 가지게 된다. -
커널의 열린 파일 테이블 입장에서보면, 하나의 열린 파일 엔트리를 가리키는 파일 디스크립터 테이블의 엔트리가 두개 가 된다.
-
이 상황에서, 하나의 프로세스가
close()
함수를 실행하면 열린 파일을 가리키고 있는 파일 디스크립터 테이블의 엔트리가 존재하는 한close
함수는 열린 파일 디스크립터 테이블의 해당 엔트리만 삭제한다.
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
void error_proc();
void error_print();
int main(int argc, char* argv[]) {
int server_sd, client_sd;
struct sockaddr_in server_addr, client_addr;
int client_addr_len, read_len;
char read_buffer[BUFSIZ];
pid_t pid;
if (argc != 2) {
printf("usage: %s [port] \n", argv[0]);
exit(1);
}
printf("server start...\n");
server_sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (server_sd == -1) error_proc();
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[1]));
if (bind(server_sd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) error_proc();
if (listen(server_sd, 5) < 0) error_proc();
client_addr_len = sizeof(client_addr);
while(1) {
client_sd = accept(server_sd, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_sd == -1) {
error_print();
continue;
}
printf("client %s: %d is connected...\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
pid = fork();
if (pid == 0) { /* child process */
close(server_sd);
while (1) {
read_len = read(client_sd, read_buffer, sizeof(read_buffer) - 1);
if (read_len == 0) break;
read_buffer[read_len] = '\0';
printf("client (%d): %s\n", ntohs(client_addr.sin_port), read_buffer);
write(client_sd, read_buffer, strlen(read_buffer));
}
printf("client (%d) : is disconnected\n", ntohs(client_addr.sin_port));
close(client_sd);
return 0;
} else if (pid == -1) error_proc("fork");
else {
close(client_sd);
}
}
close(server_sd);
return 0;
}
간단한 UDP 멀티 프로세스 채팅 프로그램
채팅 서버의 역할은 다음과 같다.
- 클라이언트의 접속 처리
- 클라이언트가 전송하는 문자열을 다른 클라이언트들에게 전송
- 클라이언트의 접속 종료 처리
-
이 중에서, 클라이언트가 전송하는 문자열을 다른 클라이언트들에게 전송하는 기능은 여러 클라이언트들의 데이터를 하나의 소켓으로 처리할 수 있다.
-
recvfrom
함수를 통해서 전달받은 문자열을sendto
함수로 전송한 클라이언트를 제외한 모든 클라이언트 들에게 전송하면 작업을 처리할 수 있다.
채팅 서버
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <arpa/inet.h>
#define MAX_CLIENT 10
void error_proc(const char*);
int check_sock_list(struct sockaddr_in *entry, struct sockaddr_in *list, int count);
int main(int argc, char* argv[]) {
int my_sock, read_len, n_recv, res;
char buff[BUFSIZ];
char name_buffer[50];
char *str_addr;
struct sockaddr_in src_addr, dest_addr;
socklen_t addr_len;
int n_client = 0, i = 0, port;
struct sockaddr_in sockets[MAX_CLIENT];
if (argc != 2) {
fprintf(stderr, "usage: %s port", argv[0]);
return 0;
}
memset(&sockets, 0, sizeof(sockets[0])*MAX_CLIENT);
my_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (my_sock == -1) error_proc("socket");
memset(&src_addr, 0, sizeof(src_addr));
src_addr.sin_addr.s_addr = htonl(INADDR_ANY);
src_addr.sin_family = AF_INET;
src_addr.sin_port = htons(atoi(argv[1]));
res = bind(my_sock, (struct sockaddr*)&src_addr, sizeof(src_addr));
if (res == -1) error_proc("bind");
addr_len = sizeof(dest_addr);
while (1) {
n_recv = recvfrom(my_sock, buff, BUFSIZ - 1, 0,
(struct sockaddr *) &dest_addr, &addr_len);
if (n_recv == -1) error_proc("recvfrom");
res = check_sock_list(&dest_addr, sockets, n_client);
if (res == n_client) {
if (res == MAX_CLIENT) continue;
else {
memcpy(&sockets[res],
&dest_addr, sizeof(dest_addr));
n_client++;
}
}
printf("n_client: %d\n", n_client);
str_addr = inet_ntoa(dest_addr.sin_addr);
sprintf(name_buffer, "%s: %d >> ", str_addr, ntohs(dest_addr.sin_port));
for (i = 0; i < n_client; i++) {
if (i == res) continue; // sender == receiver skip
sendto(my_sock, name_buffer, strlen(name_buffer), 0, (struct sockaddr *) &sockets[i], addr_len);
sendto(my_sock, buff, n_recv, 0, (struct sockaddr *) &sockets[i], addr_len);
}
}
return 0;
}
채팅 클라이언트
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <signal.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
void error_proc(const char*);
int main(int argc, char* argv[]) {
int my_sock, read_len, n_sent, n_recv;
char buff[BUFSIZ];
char str_addr;
struct sockaddr_in dest_addr;
socklen_t addr_len;
pid_t pid;
my_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.sin_addr.s_addr = inet_addr(argv[1]);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(atoi(argv[2]));
addr_len = sizeof(dest_addr);
pid = fork();
if (pid == -1) error_proc("fork");
if (pid == 0) {
while (1) {
n_recv = recvfrom(my_sock, buff, BUFSIZ - 1, 0, (struct sockaddr*) &dest_addr, &addr_len);
if (n_recv == -1) error_proc("read");
write(1, buff, n_recv);
}
} else {
while (1) {
fgets(buff, BUFSIZ - 1, stdin);
read_len = strlen(buff);
if (read_len == 0) error_proc("fgets");
n_sent = sendto(my_sock, buff, read_len, 0, (struct sockaddr*) &dest_addr, addr_len);
if (n_sent == -1) error_proc("write");
buff[read_len - 1] = '\0';
if (!strcmp(buff, "END")) break;
}
kill(pid, SIGTERM);
}
return 0;
}
void error_proc(const char* str) {
fprintf(stderr, "%s:%s \n", str, strerror(errno));
exit(1);
}
서버 화면
클라이언트1
클라이언트2
- 클라이언트 간의 통신이 이루어지는 것을 확인할 수 있다.
참고 문헌
>> Home