TCP/IP网络编程笔记Chapter I -9基于多任务的并发服务器实现

TCP/IP网络编程笔记Chapter I -9基于多任务的并发服务器实现

  • 1.基于进程的并发服务器模型
  • 2.实现并发服务器
    • (1)服务器端
    • (2)客户端
    • (3)运行
  • 3.分割TCP的I/O模型

在上节我们了解了子进程的创建以及僵尸进程的处理方法,现有知识已经足够我们编写一个多任务的并发服务器了,这节我们就来实现它。

1.基于进程的并发服务器模型

之前的回声服务器端每次只能向一个客户端提供服务,因此,我们将拓展回声服务器端,使其可以向多个客户端提供服务。下图给出了回声服务器端的实现模型。

TCP/IP网络编程笔记Chapter I -9基于多任务的并发服务器实现_第1张图片
可以看出每当有客户端请求服务时,服务器端都将创建子进程提供服务,其流程如下:

  • step1:服务器端通过调用accept函数受理请求
  • step2:把获取的套接字文件描述符创建并传递给子进程
  • step3:子进程利用文件描述符提供服务

2.实现并发服务器

(1)服务器端

与很早之前回声服务器端相比,修改的地方主要有两方面:

  • 其一是每受理一次客户端请求就创建子进程。
while (1)
    {
     
    	//accept 
        adr_sz = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
        //fork
        pid = fork();
        if (pid == 0)//子进程 
        {
     
            close(serv_sock);//关闭子进程服务器套接字
			//write 
            while ((str_len = read(clnt_sock, buf, BUF_SIZE)) != 0)
                write(clnt_sock, buf, str_len);
 
            close(clnt_sock);//关闭子进程客户端套接字
            puts("client disconnected...");
            return 0;
        }
        else		//父进程 
            close(clnt_sock);//关闭父进程客户端套接字
    }
    close(serv_sock);		//关闭父进程服务器套接字
}

有的朋友这里可能会迷惑,为什么有这么多close()函数?
fork()函数调用后父进程将服务端套接字与客户端连接的套接字的文件描述符复制给子进程,所以两个文件描述符指向了同一套套接字。只有两个文件描述符都终止时才能销毁套接字,所以需要把无关的套接字描述符关掉。
TCP/IP网络编程笔记Chapter I -9基于多任务的并发服务器实现_第2张图片
TCP/IP网络编程笔记Chapter I -9基于多任务的并发服务器实现_第3张图片
创建子进程以及关闭不需要的文件描述符是第一点修改的地方。

  • 其二是信号处理函数——sigaction()
	//准备及注册 sigaction
 	struct sigaction act;
    act.sa_handler = read_childproc;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    state = sigaction(SIGCHLD, &act, 0);

	......
	
	//信号处理函数
	void read_childproc(int sig) 
	{
     
    	pid_t pid;
    	int status;
    	pid = waitpid(-1, &status, WNOHANG);//回收子进程 
    	printf("removed proc id: %d \n", pid);
	}

仔细研究发现都是上节利用信号处理技术消灭僵尸进程的东西。没错,我们只是把它又写了一遍,这节没有新知识点。
在分解了新增的代码后,服务器端的实现已经呼之欲出了,下面就给出服务器端的完整代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define BUF_SIZE 30
void error_handling(char *message);
void read_childproc(int sig);
 
int main(int argc, char *argv[])
{
     
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    pid_t pid;
    
    socklen_t adr_sz;
    int str_len, state;
    char buf[BUF_SIZE];
    if (argc != 2) {
     
        printf("Usage : %s \n", argv[0]);
        exit(1);
    }
    
 	//准备及注册 sigaction
 	struct sigaction act;
    act.sa_handler = read_childproc;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    state = sigaction(SIGCHLD, &act, 0);
    //socket 
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    //bind
    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)
        error_handling("bind() error");
    //listen 
    if (listen(serv_sock, 5) == -1)
        error_handling("listen() error");
 
    while (1)
    {
     
    	//accept 
        adr_sz = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
        if (clnt_sock == -1)
            continue;
        else
            puts("new client connected...");
        //fork
        pid = fork();
        if (pid == -1)					//子进程创建失败 
        {
     
            close(clnt_sock);
            continue;
        }
        if (pid == 0)					//子进程 
        {
     
            close(serv_sock);			//关闭子进程服务器套接字
			//write 
            while ((str_len = read(clnt_sock, buf, BUF_SIZE)) != 0)
                write(clnt_sock, buf, str_len);
 
            close(clnt_sock);			//关闭子进程客户端套接字
            puts("client disconnected...");
            return 0;
        }
        else							//父进程 
            close(clnt_sock);			//关闭父进程客户端套接字
    }
    close(serv_sock);					//关闭父进程服务器套接字
    return 0;
}
 
void read_childproc(int sig)//信号处理函数 
{
     
    pid_t pid;
    int status;
    pid = waitpid(-1, &status, WNOHANG);//回收子进程 
    printf("removed proc id: %d \n", pid);
}
void error_handling(char *message)
{
     
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

(2)客户端

客户端代码与 4实现一个简单TCP服务器端与客户端 中回声客户端完全相同,在此不赘述

#include
#include
#include
#include
#include
#include
#define BUF_SIZE 1024
  
void error_handling(char *message);
int main(int argc,char* argv[])
{
     
	int sock;
	int str_len,recv_len,recv_cnt;
	char message[BUF_SIZE];
	
	struct sockaddr_in serv_adr;

	if(argc != 3)
	{
     
		printf("Usage : %s  \n",argv[0]);
		exit(1);
	}
	
	sock = socket(PF_INET,SOCK_STREAM,0);
	if(sock == -1)
		error_handling("socket() error");
		
	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]));//端口号 
	
	if(connect(sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1)
		error_handling("connect() error");
	else
		puts("connected......");
	while(1)
	{
     
		fputs("Input Message(Q to quit): ",stdout);
		fgets(message,BUF_SIZE,stdin);
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
			break;
		str_len=write(sock,message,strlen(message));
		recv_len = 0;
		while(recv_len < str_len)
		{
     
			recv_cnt = read(sock,&message[recv_len],BUF_SIZE - 1);
			if(recv_cnt == -1)
				error_handling("read() error");
			recv_len += recv_cnt;

		}
		message[str_len] = 0;
		printf("Message from server: %s",message);
		
	}
	
	close(sock);
	return 0;
}

void error_handling(char *message)
{
     
    fputs(message, stderr);
    fputc('\n', stderr);
	exit(1);
}

(3)运行

运行服务器端并打开多个控制台,分别连接服务器端,不同客户端可以同时与服务器端发送信息并回显发送的消息。下图可以发现每受理一个客户端,会输出new client connected,每关闭一次连接,都会输出子进程的ID号。
TCP/IP网络编程笔记Chapter I -9基于多任务的并发服务器实现_第4张图片

3.分割TCP的I/O模型

我们已经实现的回声客户端的数据回声方式为:向服务端传输数据,并等待服务端回复。无条件等待,直到接收完服务端的回声数据后,才能传输下一批数据。
TCP/IP网络编程笔记Chapter I -9基于多任务的并发服务器实现_第5张图片

现在我们可以让客户端的父进程负责接收数据,额外创建的子进程负责发送数据。分割后,不同进程分别负责输入和输出,这样,无论客户端是否从服务端接收完数据都可以进程传输,程序的实现更加简单。

TCP/IP网络编程笔记Chapter I -9基于多任务的并发服务器实现_第6张图片
分割的对象是回声客户端,父进程只需编写接收数据的代码,子进程只需编写发送数据的代码。

	pid = fork();
    if (pid == 0)
        write_routine(sock, buf);
    else
        read_routine(sock, buf);
void read_routine(int sock, char *buf)
{
     
    while (1)
    {
     
        int str_len = read(sock, buf, BUF_SIZE);
        if (str_len == 0)
            return;
 
        buf[str_len] = 0;
        printf("Message from server: %s", buf);
    }
}

void write_routine(int sock, char *buf)
{
     
    while (1)
    {
     
        fgets(buf, BUF_SIZE, stdin);
        if (!strcmp(buf, "q\n") || !strcmp(buf, "Q\n"))
        {
     
            shutdown(sock, SHUT_WR);
            return;
        }
        write(sock, buf, strlen(buf));
    }
}

完整的客户端如下:

#include 
#include 
#include 
#include 
#include 
#include 
 
#define BUF_SIZE 30
void error_handling(char *message);
void read_routine(int sock, char *buf);
void write_routine(int sock, char *buf);
 
int main(int argc, char *argv[])
{
     
    int sock;
    pid_t pid;
    char buf[BUF_SIZE];
    struct sockaddr_in serv_adr;
    if (argc != 3) {
     
        printf("Usage : %s  \n", argv[0]);
        exit(1);
    }
 	//socket
    sock = socket(PF_INET, SOCK_STREAM, 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]));
 	//connect
    if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
        error_handling("connect() error!");
 
    pid = fork();
    if (pid == 0)
        write_routine(sock, buf);
    else
        read_routine(sock, buf); 
        
    close(sock);
    return 0;
}
 
void read_routine(int sock, char *buf)
{
     
    while (1)
    {
     
        int str_len = read(sock, buf, BUF_SIZE);
        if (str_len == 0)
            return;
 
        buf[str_len] = 0;
        printf("Message from server: %s", buf);
    }
}
void write_routine(int sock, char *buf)
{
     
    while (1)
    {
     
        fgets(buf, BUF_SIZE, stdin);
        if (!strcmp(buf, "q\n") || !strcmp(buf, "Q\n"))
        {
     
            shutdown(sock, SHUT_WR);
            return;
        }
        write(sock, buf, strlen(buf));
    }
}
void error_handling(char *message)
{
     
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

使用并发服务端与分割I/O的客户端进行通信,实现了本节的目标。

你可能感兴趣的:(TCP/IP网络编程,socket,linux,多进程)