Linux下多进程服务端客户端模型一(单进程与多进程模型)

时间:2020-12-31 14:54:44

本文将会简单介绍Linux下如何利用C库函数与系统调用编写一个完整的、初级可用的C-S模型。

一、基本模型:

    1.1   首先服务器调用socket()函数建立一个套接字,然后bind()端口,开始listen()监听,此时,套接字变成了被动的套接字,用于侦听客户端的请求。然后accept(),开始阻塞监听客户端的请求。

  1.2   客户端以服务端的参数为参数,用socket()建立套接字,然后connect()连接服务端。

    1.3  服务端收到连接请求,accept()返回一个标识符,继续执行后面的指令。具体如图:

Linux下多进程服务端客户端模型一(单进程与多进程模型)

       1.4  服务端创建套接字与绑定端口代码(打码处为点分十进制的IP字符串,注意,端口号用函数转换成网络传输的大端的Long类型,点分十进制IP转换成二进制。):

          Linux下多进程服务端客户端模型一(单进程与多进程模型)

         1.5 服务端监听与accept,并且循环读写代码:

         Linux下多进程服务端客户端模型一(单进程与多进程模型)

       while结束后close(conn)  close(sockfd)即可。

       1.6 客户端(打码处为服务端的IP与端口)

         Linux下多进程服务端客户端模型一(单进程与多进程模型)

二、多进程

      2.1

      由于accept是阻塞的函数,如果用单进程进行,那么将会只能接受一个客户端的请求。而对于其他客户端的请求,则代码不会返回去重新执行accept();

     要注意的是,其他客户端连接服务器时,即使服务器只接受一个客户端的业务请求,但是内核会帮我们完成TCP的三次握手连接,放入已连接的队列中等待服务器的accept去取走,不过,由于我们没有开启新的进程,所以,后来的这些连接没有机会被取走了。如图:

Linux下多进程服务端客户端模型一(单进程与多进程模型)

       

       2.2  

       针对以上问题,可以将accept()放入while循环中,然后每一次接收到了连接,则创建一个子进程,然后子进程去执行读写操作,而父进程则继续监听。代码如下:

      

Linux下多进程服务端客户端模型一(单进程与多进程模型)Linux下多进程服务端客户端模型一(单进程与多进程模型)
 1 #include <unistd.h>
 2 #include <sys/stat.h>
 3 #include <sys/wait.h>
 4 #include <sys/types.h>
 5 #include <fcntl.h>
 6 
 7 #include <stdlib.h>
 8 #include <stdio.h>
 9 #include <errno.h>
10 #include <string.h>
11 #include <signal.h>
12 
13 #include <arpa/inet.h>
14 #include <sys/socket.h>
15 #include <netinet/in.h>
16 
17 int main(void)
18 {
19     int sockfd;
20     // 创建一个Socket
21     sockfd = socket(AF_INET,SOCK_STREAM,0);
22     if(sockfd == -1){
23         perror("error");
24         exit(0);
25     }
26 
27 
28     ///////////////////////////////////////////////////////////
29 //    struct sockaddr addr; // 这是一个通用结构,一般是用具体到,然后转型
30     struct sockaddr_in sockdata;
31     sockdata.sin_family = AF_INET;
32     sockdata.sin_port = htons(8001);
33     sockdata.sin_addr.s_addr = inet_addr(我是IP字符串);
34     
35     int optval = 1;
36     if(setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) == -1)
37     {
38         perror("error");
39         exit(0);
40     }
41     if(bind(sockfd,(struct sockaddr *)&sockdata,sizeof(sockdata)) < 0){
42         perror("error");
43         exit(0);
44     }
45     
46     ////////////////////////////////////////////////////////////
47     if(listen(sockfd,5) == -1){
48         perror("error");
49         exit(0);
50     }
51     
52     //////////////////////////////////////////////////////////
53     struct sockaddr_in peeradr;
54     socklen_t peerlen = sizeof(peeradr); // 得有初始值
55 
56 
57     /////////////////////////////////////////////////////////
58     char recvBuff[1024]={0};
59     int conn = 0;
60     while(1){
61         conn = accept(sockfd,(struct sockaddr *)&peeradr,&peerlen);
62         if(conn == -1){
63             perror("error");
64             exit(0);
65         }    
66         // 每来一个链接fork一个进程。
67         pid_t pid;
68         pid = fork();
69 
70         if(pid == 0){
71             int ret = 0;
72             close (sockfd); // 由于子进程复制来sockfd,所以关掉它。 不干涉父进程来,因为这是一个副本。由父进程继续监听
73             printf("收到的IP %s\n 客户端端口是:%d\n",inet_ntoa(peeradr.sin_addr),ntohs(peeradr.sin_port));
74             while((ret = read(conn,recvBuff,sizeof(recvBuff))) && ret > 0){
75                 // 服务器收到打印数据,然后回发
76                 fputs(recvBuff,stdout);
77                 write(conn,recvBuff,ret);
78             }
79             exit(0);
80         }
81         else if(pid > 0){
82             close(conn); // 父进程只管监听,不需要链接套接字!!
83         }
84         else{
85             perror("error");
86             close(conn);
87             close(sockfd);
88             exit(0);
89         }
90     }
91     close(conn);
92     close(sockfd);
93     return 0;
94 }
server.c

       而客户端没有什么变化,代码如下:

Linux下多进程服务端客户端模型一(单进程与多进程模型)Linux下多进程服务端客户端模型一(单进程与多进程模型)
 1 #include <unistd.h>
 2 #include <sys/stat.h>
 3 #include <sys/wait.h>
 4 #include <sys/types.h>
 5 #include <fcntl.h>
 6 
 7 #include <stdlib.h>
 8 #include <stdio.h>
 9 #include <errno.h>
10 #include <string.h>
11 #include <signal.h>
12 
13 #include <arpa/inet.h>
14 #include <sys/socket.h>
15 #include <netinet/in.h>
16 
17 int main(void)
18 {
19         int sockfd;
20     // 创建一个Socket
21     sockfd = socket(AF_INET,SOCK_STREAM,0);
22     if(sockfd == -1){
23         perror("error");
24         exit(0);
25     }
26 
27 
28     ///////////////////////////////////////////////////////////
29 //    struct sockaddr addr; // 这是一个通用结构,一般是用具体到,然后转型
30     struct sockaddr_in sockdata;
31     sockdata.sin_family = AF_INET;
32     sockdata.sin_port = htons(8001);
33     sockdata.sin_addr.s_addr = inet_addr("我是IP字符串");
34     if(connect(sockfd,(struct sockaddr *)&sockdata,sizeof(sockdata)) == -1){
35         perror("error");
36         exit(0);
37     }
38 
39     char sendBuff[1024] = {0};
40     char recvBuff[1024] = {0};
41 
42     while(fgets(sendBuff,sizeof(sendBuff),stdin) != NULL){
43         // 将输入到数据送到服务端
44         write(sockfd,sendBuff,sizeof(sendBuff));
45         // 从服务器读数据
46         read(sockfd,recvBuff,sizeof(recvBuff));
47 
48         // 显示在屏幕上
49         fputs(recvBuff,stdout);
50         // 清零
51         memset(recvBuff,0,sizeof(recvBuff));
52         memset(sendBuff,0,sizeof(sendBuff));
53     }
54 
55     close(sockfd);
56     return 0;    
57 }
client.c