네트워크

TCP 기반 Multi-Process 서버 구현

kidmillionaire1998 2023. 5. 15. 23:29

2023-1학기 숭실대 소프트웨어학부 조효진 교수님의 네트워크 프로그래밍  수업을 재구성하였습니다. 

예제코드는 글 맨 아래에 있습니다.

 

단일 프로세스의 한계 

단일 Process로만 TCP 소켓 프로그래밍을 수행하면, 새로운 프로세스의 클라이언트와 동시에 통신을 하기 힘들어진다는 한계를 가진다. 

이 한계를 극복하고자  Multi-Process 서버를 구현해보고자한다. 

 

새로운 Process 생성 

Multi Process를 생성하려면, fork()함수를 이용하여, Child Process를 새로 생성하여야한다. 

 

fork() 이후

-fork()이전 서버는 srvSd 파일디스크립터를 이용하여 클라이언트의 요청을 accept()하고 있으며,

accept()함수를 통해, 클라이언트와의 연결을 진행하게 되면 새로운 연결 소켓인 clntSd를 생성한다. 

 

-이후, fork()함수를 이용하여 Parent Process정보를 복제하여 Child Process를 생성한다. srvSd와 clntSd 두 소켓이 그대로 fork() 되어 Child Process에 복사되어 나타난다. 

 

-Child Process 생성 이후에는 Parent Process와 Child Process가 독립적으로 진행되는데,

 

1. Parent Process

클라이언트와 accept()이후에 만들어진 clntSd를 close하여 연결된 클라이언트와의 연결을 끊고,

srvSd만 남기며, 이를 이용하여 connect()함수를 이용한 다른 클라이언트의 요청를 accept()함수를 통해 기다리게 한다. 

 

2. Child Process 

다른 클라이언트와의 연결에 필요한 srvSd를 close()하여 connect()를 이용한 새로운 클라이언트의 연결요청을 더이상 받지 않으며,

이미 클라이언트와의 연결이 되어있는 clntSd를 이용해 read()와 write()만 진행하게 된다. 

 

결국, 

Parent Process => accept() & srvSd를 이용한 클라이언트와의 연결 

Child Process => read() , write() & clntSd를 이용하여 이미 연결된 클라이언트와의 글자 송수신

과 같이 역할이 나눠지는 것이다. 이를 그림으로 나타내면, 

 

다음과 같이 Parent process는 새로운 클라이언트의 요청을 기다린다. 이러한 상황에서 다른 process ID를 가진 client의 요청이 들어온다면, 

 

새로운 클라이언트 process가 서버의 Parent Process에 연결을 시도하고, 

 

 

Parent Process는 연결을 수행한 후, fork()하여 새로운 Child Process2를 만든다,

앞에서 설명한 Parent & Child Process가 필요한 기능의 파일 디스크립트만 남기고 close()하는 과정 처럼, Parent Process는 clntSd를 , Child Process2는 srvSd를 close()하여, 

Parent Process는 다시 새로운 클라이언트를 accept(), srvSd를 이용해 기다리고, Child Process2는 새로 연결된 pid 20000인 클라이언트와 통신하여 read & write 작업을 수행하게 된다.  

 

[예제 코드] 

//tcpfirstclnt.c
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>

void err_proc();
int main(int argc, char** argv)
{
	int clntSd;
	struct sockaddr_in clntAddr;
	int clntAddrLen, readLen, recvByte, maxBuff;
	char wBuff[BUFSIZ];
	char rBuff[BUFSIZ];
	if(argc != 3) {
		printf("Usage: %s [IP Address] [Port]\n", argv[0]);
	
	}
	clntSd = socket(AF_INET, SOCK_STREAM, 0);
	if(clntSd == -1) err_proc();
	printf("==== client program =====\n");

	memset(&clntAddr, 0, sizeof(clntAddr));
	clntAddr.sin_family = AF_INET;
	clntAddr.sin_addr.s_addr = inet_addr(argv[1]);
	clntAddr.sin_port = htons(atoi(argv[2]));

	if(connect(clntSd, (struct sockaddr *) &clntAddr,
			    sizeof(clntAddr)) == -1)
	{
		close(clntSd);
		err_proc();	
	}

	while(1)
	{
		fgets(wBuff,BUFSIZ-1,stdin);
		readLen = strlen(wBuff);
		if(readLen < 2) continue;		
		write(clntSd,wBuff,readLen-1);
		recvByte = 0;
		maxBuff = BUFSIZ-1;
		do{
			recvByte += read(clntSd,rBuff,maxBuff);
			maxBuff -= recvByte;
		}while(recvByte < (readLen-1));
		rBuff[recvByte] = '\0';
		printf("Server: %s\n", rBuff);
		wBuff[readLen-1]='\0';	
		if(!strcmp(wBuff,"END")) break;
	}
	printf("END ^^\n");
	close(clntSd);

	return 0;
}

void err_proc()
{
	fprintf(stderr,"Error: %s\n", strerror(errno));
	exit(errno);
}
//multiProcessTcpServer.c
#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>

void errProc();
void errPrint();
int main(int argc, char** argv)
{
	int srvSd, clntSd;
	struct sockaddr_in srvAddr, clntAddr;
	int clntAddrLen, readLen, strLen;
	char rBuff[BUFSIZ];
	pid_t pid;

	if(argc != 2) {
		printf("Usage: %s [port] \n", argv[0]);
		exit(1);
	}
	printf("Server start...\n");

	srvSd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(srvSd == -1 ) errProc();

	memset(&srvAddr, 0, sizeof(srvAddr));
	srvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	srvAddr.sin_family = AF_INET;
	srvAddr.sin_port = htons(atoi(argv[1]));

	if(bind(srvSd, (struct sockaddr *) &srvAddr, sizeof(srvAddr)) == -1)
		errProc();
	if(listen(srvSd, 5) < 0)
		errProc();
	clntAddrLen = sizeof(clntAddr);
	while(1)
	{
		clntSd = accept(srvSd, (struct sockaddr *)
					 &clntAddr, &clntAddrLen);
		if(clntSd == -1) {
			errPrint();
			continue;	
		}
		printf("client %s:%d is connected...\n",
			inet_ntoa(clntAddr.sin_addr),
			ntohs(clntAddr.sin_port));	
		pid = fork();
		if(pid == 0) { /* child process */
			close(srvSd);
			while(1) {
				readLen = read(clntSd, rBuff, sizeof(rBuff)-1);
				if(readLen == 0) break;
				rBuff[readLen] = '\0';
				printf("Client(%d): %s\n",
					ntohs(clntAddr.sin_port),rBuff);
				write(clntSd, rBuff, strlen(rBuff));
			}
			printf("Client(%d): is disconnected\n",
				ntohs(clntAddr.sin_port));
			close(clntSd);
			return 0;
		}	
		else if(pid == -1) errProc("fork");
		else { /*Parent Process*/
			close(clntSd);
		}	
	}
	close(srvSd);
	return 0;
}

void errProc(const char *str)
{
    fprintf(stderr,"%s: %s \n",str, strerror(errno));
    exit(1);
}

void errPrint(const char *str)
{
	fprintf(stderr,"%s: %s \n",str, strerror(errno));
}