并发服务器(对服务器模型的补充)

原文链接

在网络通信过程中,服务端通常需要处理多个客户端。由于多个客户端的请求可能会同时到来,服务器端可采用不同的方法来处理。总体上来说,服务器端可采用两种模型来实现:循环服务器模型和并发服务器模型

        循环服务器模型是指服务器端依次处理每个客户端,直到当前客户端的所有请求处理完毕,再处理下一个客户端。这类模型的优点是简单,缺点显而易见。特别是TCP循环服务器模型,由于必须先处理完当前客户端,容易造成其他客户端等待时间较长的情况。

        为了提高服务器的并发处理能力,又引入了并发服务器模型。其基本思想是在服务器端此阿勇多任务机制(多进程或多线程),分别为每个客户端创建一个任务来处理,极大地提高了服务器的并发处理能力。

        下面具体介绍循环服务器模型和并发服务器模型的流程及实现。为了更好的进行对比。本节均以TCP为例讨论相关模型。

一、循环服务器(TCP)

1、运行介绍

       TCP循环服务器是一种常用的模型,其工作流程如下:

1)服务器端从连接请求队列中提取请求,建立连接并返回新的已连接套接字;

2)服务器端通过已连接套接字循环接收数据,处理并发送给客户端,知道客户端关闭连接;

3)服务器端关闭已连接套接字,返回步骤1;

2、特点分析

       通过上面对服务器执行过程的介绍,可以得到以下结论。

1)服务器端采用循环嵌套来实现。外层循环依次提取每个客户端的连接请求,建立TCP连接。内层循环接受并处理当前客户端的所有数据,知道客户端关闭连接;

2)如果当前客户端没有处理结束,其他客户端必须一直等待。

注意:采用这种模型的服务器无法同时为多个客户端服务。

3、编程示例

下面实现 TCP ECHO 服务器端和客户端。服务器端接收到客户端数据后,原封不动发送回去(回射服务);客户端运行时,用户从键盘输入字符串,发送给服务器端并接受返回的数据,直到用户输入quit 后退出。

server.c

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <string.h>

  4. #include <sys/types.h>

  5. #include <sys/socket.h>

  6. #include <unistd.h>

  7. #include <netinet/in.h>

  8. #include <arpa/inet.h>

  9. #define BUFFER_SIZE 128

  10. #define PORT 8888

  11.  
  12. int main()

  13. {

  14. int listenfd, clientfd;

  15. int n;

  16. struct sockaddr_in serveraddr,clientaddr;

  17. socklen_t peerlen;

  18. char buffer[BUFFER_SIZE];

  19.  
  20. if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

  21. {

  22. perror("socket error");

  23. exit(-1);

  24. }

  25. else

  26. {

  27. printf("listenfd:%d\n",listenfd);

  28. }

  29.  
  30. memset(&serveraddr,0,sizeof(serveraddr));

  31. serveraddr.sin_family = AF_INET;

  32. serveraddr.sin_port = htons(PORT);

  33. serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

  34.  
  35. if(bind(listenfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) //绑定IP地址和端口号

  36. {

  37. perror("bind error");

  38. exit(-1);

  39. }

  40. else

  41. {

  42. printf("bind successfully!\n");

  43. }

  44.  
  45. if(listen(listenfd,10) == -1)

  46. {

  47. perror("listen error");

  48. exit(-1);

  49. }

  50. else

  51. {

  52. printf("listening....\n");

  53. }

  54.  
  55. peerlen = sizeof(clientaddr);

  56. while(1)

  57. {

  58. if((clientfd = accept(listenfd,(struct sockaddr *)&clientaddr,&peerlen)) < 0) //循环等待客户端的连接

  59. {

  60. perror("accept error");

  61. exit(-1);

  62. }

  63. else

  64. {

  65. printf("connection from [%s:%d]\n",inet_ntoa(clientaddr.sin_addr)

  66. ,ntohs(clientaddr.sin_port));

  67. }

  68.  
  69. memset(buffer,0,sizeof(buffer));

  70. while(1)

  71. {

  72. if((n = recv(clientfd,buffer,BUFFER_SIZE,0)) == -1) //循环接收客户端发送的数据

  73. {

  74. perror("recv error");

  75. exit(-1);

  76. }

  77. else if(n == 0) //此时,客户端断开连接

  78. {

  79. break;

  80. }

  81. else

  82. {

  83. printf("Received message:%s\n",buffer);

  84. if(send(clientfd, buffer, n, 0) == -1)

  85. {

  86. perror("send error");

  87. exit(-1);

  88. }

  89. else

  90. {

  91. printf("sendmessage:%s\n",buffer);

  92. }

  93. }

  94. }

  95. close(clientfd); //客户端断开连接后,服务端也断开

  96. }

  97. close(listenfd);

  98.  
  99. return 0;

  100. }

client.c

 
  1. #include <stdio.h>

  2. #include <string.h>

  3. #include <stdlib.h>

  4. #include <unistd.h>

  5. #include <sys/socket.h>

  6. #include <sys/types.h>

  7. #include <netinet/in.h>

  8. #include <arpa/inet.h>

  9. #define BUFFER_SIZE 128

  10. #define PORT 8888

  11.  
  12. int main()

  13. {

  14. int n;

  15. int serverfd, clientfd;

  16. struct sockaddr_in serveraddr;

  17. char buffer[BUFFER_SIZE];

  18.  
  19. if((clientfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

  20. {

  21. perror("socket error");

  22. exit(-1);

  23. }

  24. else

  25. {

  26. printf("clientfd:%d\n",clientfd);

  27. }

  28. //设置服务端的IP地址和端口号

  29. memset(&serveraddr,0,sizeof(serveraddr));

  30. serveraddr.sin_family = AF_INET;

  31. serveraddr.sin_port = htons(PORT);

  32. serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

  33.  
  34. if(connect(clientfd,(struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1)

  35. {

  36. perror("connect error");

  37. exit(-1);

  38. }

  39. else

  40. {

  41. printf("connect successfully!\n");

  42. }

  43.  
  44. while(1)

  45. {

  46. printf("input > ");

  47. fgets(buffer,sizeof(buffer),stdin);

  48. if(strcmp(buffer,"quit\n") == 0) //遇到quit,退出

  49. break;

  50. buffer[strlen(buffer) - 1] = '\0'; //将'\n'去掉

  51. if(send(clientfd,buffer,sizeof(buffer),0) == -1)

  52. {

  53. perror("send error");

  54. exit(-1);

  55. }

  56. memset(buffer,0,sizeof(buffer)); //清空buffer

  57. if((n = recv(clientfd,buffer,sizeof(buffer),0)) == -1)

  58. {

  59. perror("recv error");

  60. exit(-1);

  61. }

  62. else if(n == 0)

  63. {

  64. break; //若服务端意外关闭

  65. }

  66. else

  67. {

  68. printf("echo:%s\n",buffer);

  69. }

  70. }

  71. close(clientfd);

  72.  
  73. return 0;

  74. }

运行结果如下:

server

 
  1. fs@ubuntu:~/qiang/netprogram/2$ ./server

  2. listenfd:3

  3. bind successfully!

  4. listening....

  5. connection from [127.0.0.1:57366]

  6. Received message:xiao

  7. sendmessage:xiao

  8. Received message:zhi

  9. sendmessage:zhi

  10. Received message:qiang

  11. sendmessage:qiang

  12.  
  13. //继续等待下一个客户端的连接

  14.  

client

 
  1. fs@ubuntu:~/qiang/netprogram/2$ ./client

  2. clientfd:3

  3. connect successfully!

  4. input > xiao

  5. echo:xiao

  6. input > zhi

  7. echo:zhi

  8. input > qiang

  9. echo:qiang

  10. input > quit

  11. fs@ubuntu:~/qiang/netprogram/2$

二、并发服务器(TCP)

1、运行介绍

       TCP并发服务器模型在网络通信中被广泛使用,既可以采用多进程也可以采用多线程来实现。以多进程为例,其工作流程如下:

1)服务器端父进程从连接请求队列中提取请求,建立连接并返回新的已连接套接字。

2)服务器端父进程创建子进程为客户端服务。客户端关闭连接时,子进程结束。

3)服务器端父进程关闭已连接套接字,返回步骤1;

2、特点分析

通过上面对服务器执行过程的介绍,可以得到以下结论。

1)服务器端父进程一旦接受到客户端的连接请求,建立好连接并创建新的子进程。这意味着每个客户端在服务器端有一个专门的子进程为其服务。

2)服务器端的多个子进程同时运行(宏观上),处理多个客户端。

3)服务器端的父进程不具体处理每个客户端的数据请求。

注意:采用这种模型的服务器端需要避免僵死进程。

3、编程示例

下面采用并发模型实现 TCP ECHO服务器端。

server.c

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <string.h>

  4. #include <sys/types.h>

  5. #include <sys/socket.h>

  6. #include <unistd.h>

  7. #include <netinet/in.h>

  8. #include <arpa/inet.h>

  9. #include <signal.h>

  10. #define BUFFER_SIZE 128

  11. #define PORT 8888

  12. #define IP "192.168.3.51"

  13.  
  14. void handler(int signo);

  15.  
  16. int main()

  17. {

  18. int listenfd, clientfd;

  19. int n;

  20. pid_t pid;

  21. struct sockaddr_in serveraddr,clientaddr;

  22. socklen_t peerlen;

  23. char buffer[BUFFER_SIZE];

  24.  
  25. if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

  26. {

  27. perror("socket error");

  28. exit(-1);

  29. }

  30. else

  31. {

  32. printf("Socket successfully!\nlistenfd:%d\n",listenfd);

  33. }

  34.  
  35. memset(&serveraddr,0,sizeof(serveraddr));

  36. serveraddr.sin_family = AF_INET;

  37. serveraddr.sin_port = htons(PORT);

  38. serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

  39.  
  40. if(bind(listenfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0)

  41. {

  42. perror("bind error");

  43. exit(-1);

  44. }

  45. else

  46. {

  47. printf("bind successfully!\n");

  48. printf("local IP:%s port:%d\n",IP,PORT);

  49. }

  50.  
  51. if(listen(listenfd,10) == -1)

  52. {

  53. perror("listen error");

  54. exit(-1);

  55. }

  56. else

  57. {

  58. printf("listening....\n");

  59. }

  60.  
  61. signal(SIGCHLD,handler);//捕捉SIGCHLD信号,子进程死亡后,调用handler回收

  62. peerlen = sizeof(clientaddr);

  63. while(1)

  64. {

  65. if((clientfd = accept(listenfd,(struct sockaddr *)&clientaddr,&peerlen)) < 0)

  66. {

  67. perror("accept error");

  68. exit(-1);

  69. }

  70. else

  71. {

  72. printf("connection from [%s:%d]\n",inet_ntoa(clientaddr.sin_addr)

  73. ,ntohs(clientaddr.sin_port));

  74. }

  75.  
  76. memset(buffer,0,sizeof(buffer));

  77. if((pid = fork()) < 0)

  78. {

  79. perror("fork error");

  80. exit(-1);

  81. }

  82. else if(pid == 0)

  83. {

  84. close(listenfd);

  85. while(1)

  86. {

  87. if((n = recv(clientfd,buffer,BUFFER_SIZE,0)) == -1)

  88. {

  89. perror("recv error");

  90. exit(-1);

  91. }

  92. else if(n == 0)

  93. {

  94. break;

  95. }

  96. else

  97. {

  98. printf("Received message:%s\n",buffer);

  99. }

  100.  
  101. if(send(clientfd, buffer, n, 0) == -1)

  102. {

  103. perror("send error");

  104. exit(-1);

  105. }

  106. else

  107. {

  108. printf("sendmessage:%s\n",buffer);

  109. }

  110. }

  111. printf("client is closed\n");

  112. exit(0);

  113. }

  114. else

  115. {

  116. close(clientfd);

  117. }

  118. }

  119. close(listenfd);

  120.  
  121. return 0;

  122. }

  123. //接收到SIFCHLD信号后,回收子进程

  124. void handler(int signo)

  125. {

  126. pid_t pid;

  127. while((pid = waitpid(-1, NULL, WNOHANG)) > 0)

  128. {

  129. printf("child(%d) is over!\n",pid);

  130. }

  131. }

client

 
  1. #include <stdio.h>

  2. #include <string.h>

  3. #include <stdlib.h>

  4. #include <unistd.h>

  5. #include <sys/socket.h>

  6. #include <sys/types.h>

  7. #include <netinet/in.h>

  8. #include <arpa/inet.h>

  9. #define BUFFER_SIZE 128

  10. #define PORT 8888

  11.  
  12. int main()

  13. {

  14. int n;

  15. int serverfd, clientfd;

  16. struct sockaddr_in serveraddr;

  17. char buffer[BUFFER_SIZE];

  18.  
  19. if((clientfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

  20. {

  21. perror("socket error");

  22. exit(-1);

  23. }

  24. else

  25. {

  26. printf("clientfd:%d\n",clientfd);

  27. }

  28.  
  29. memset(&serveraddr,0,sizeof(serveraddr));

  30. serveraddr.sin_family = AF_INET;

  31. serveraddr.sin_port = htons(PORT);

  32. serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

  33.  
  34. if(connect(clientfd,(struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1)

  35. {

  36. perror("connect error");

  37. exit(-1);

  38. }

  39. else

  40. {

  41. printf("connect successfully!\n");

  42. }

  43.  
  44. while(1)

  45. {

  46. printf("input > ");

  47. fgets(buffer,sizeof(buffer),stdin);

  48. if(strcmp(buffer,"quit\n") == 0)

  49. break;

  50. buffer[strlen(buffer) - 1] = '\0';

  51. if(send(clientfd,buffer,sizeof(buffer),0) == -1)

  52. {

  53. perror("send error");

  54. exit(-1);

  55. }

  56. memset(buffer,0,sizeof(buffer));

  57. if((n = recv(clientfd,buffer,sizeof(buffer),0)) == -1)

  58. {

  59. perror("recv error");

  60. exit(-1);

  61. }

  62. else if(n == 0)

  63. {

  64. break;

  65. }

  66. else

  67. {

  68. printf("echo:%s\n",buffer);

  69. }

  70. }

  71. close(clientfd);

  72.  
  73. return 0;

  74. }

执行结果如下:

client1:

 
  1. fs@ubuntu:~/qiang/netprogram/3$ ./client

  2. clientfd:3

  3. connect successfully!

  4. input > I am client1!

  5. echo:I am client1!

  6. input > I am client1, how are you!

  7. echo:I am client1, how are you!

  8. input > quit

  9. fs@ubuntu:~/qiang/netprogram/3$

clinet2:

 
  1. fs@ubuntu:~$ telnet 192.168.3.51 8888

  2. Trying 192.168.3.51...

  3. Connected to 192.168.3.51.

  4. Escape character is '^]'.

  5. I am client2! I am telnet

  6. I am client2! I am telnet

  7. I am telnet, I am still alive!

  8. I am telnet, I am still alive!

server:

 
  1. fs@ubuntu:~/qiang/netprogram/3$ ./server

  2. Socket successfully!

  3. listenfd:3

  4. bind successfully!

  5. local IP:192.168.3.51 port:8888

  6. listening....

  7. connection from [127.0.0.1:45890]

  8. Received message:I am client1!

  9. sendmessage:I am client1!

  10. connection from [192.168.3.51:35721]

  11. Received message:I am client2! I am telnet

  12.  
  13. sendmessage:I am client2! I am telnet

  14.  
  15. Received message:I am client1, how are you!

  16. sendmessage:I am client1, how are you!

  17. client is closed

  18. child(7216) is over!

  19. Received message:I am telnet, I am still alive!

  20.  
  21. sendmessage:I am telnet, I am still alive!

  22.  
  23.  

可以看到实现了并发!

三、I/O多路复用并发服务器

        I/O多路复用模型可以解决资源限制的问题。此模型实际上时将UDP循环模型用在了TCP上面。服务器用单进程循环处理请求(客户端有限的情况下)。

但是,其存在同样的问题:由于服务器是依次处理客户的请求,所以可能会导致有的客户等待时间过长。

server:

 
  1. #include <stdio.h>

  2. #include <string.h>

  3. #include <stdlib.h>

  4. #include <unistd.h>

  5. #include <sys/types.h>

  6. #include <sys/socket.h>

  7. #include <sys/select.h>

  8. #include <netinet/in.h>

  9. #include <arpa/inet.h>

  10. #define PORT 8888

  11. #define MAXSIZE 128

  12.  
  13. int main()

  14. {

  15. int i,nbyte;

  16. int listenfd, confd, maxfd;

  17. char buffer[MAXSIZE];

  18. fd_set global_rdfs, current_rdfs;

  19. struct sockaddr_in addr,clientaddr;

  20. int addrlen = sizeof(struct sockaddr_in);

  21. int caddrlen = sizeof(struct sockaddr_in);

  22.  
  23. if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

  24. {

  25. perror("socket error");

  26. exit(-1);

  27. }

  28. else

  29. {

  30. printf("socket successfully!\n");

  31. printf("listenfd : %d\n",listenfd);

  32. }

  33.  
  34. memset(&addr, 0 ,addrlen);

  35. addr.sin_family = AF_INET;

  36. addr.sin_port = htons(PORT);

  37. addr.sin_addr.s_addr = htonl(INADDR_ANY);

  38. if(bind(listenfd,(struct sockaddr *)&addr,addrlen) == -1)

  39. {

  40. perror("bind error");

  41. exit(-1);

  42. }

  43. else

  44. {

  45. printf("bind successfully!\n");

  46. printf("listen port:%d\n",PORT);

  47. }

  48.  
  49. if(listen(listenfd,5) == -1)

  50. {

  51. perror("listen error");

  52. exit(-1);

  53. }

  54. else

  55. {

  56. printf("listening...\n");

  57. }

  58.  
  59. maxfd = listenfd;

  60. FD_ZERO(&global_rdfs);

  61. FD_SET(listenfd,&global_rdfs);

  62.  
  63. while(1)

  64. {

  65. current_rdfs = global_rdfs;

  66. if(select(maxfd + 1,&current_rdfs, NULL, NULL,0) < 0)

  67. {

  68. perror("select error");

  69. exit(-1);

  70. }

  71.  
  72. for(i = 0; i <= listenfd + 1; i++)

  73. {

  74. if(FD_ISSET(i, &current_rdfs)) //fd 就绪

  75. {

  76. if(i == listenfd) //有新的连接

  77. {

  78. if((confd = accept(listenfd,(struct sockaddr *)&clientaddr,&caddrlen)) == -1)

  79. {

  80. perror("accept error");

  81. exit(-1);

  82. }

  83. else

  84. {

  85. printf("Connect from [IP:%s PORT:%d]\n",

  86. inet_ntoa(clientaddr.sin_addr),clientaddr.sin_port);

  87. FD_SET(confd,&global_rdfs);

  88. maxfd = (maxfd > confd ? maxfd : confd);

  89. }

  90. }

  91. else

  92. {

  93. if((nbyte = recv(i, buffer, sizeof(buffer),0)) < 0)

  94. {

  95. perror("recv error");

  96. exit(-1);

  97. }

  98. else if(nbyte == 0) //客户端close

  99. {

  100. close(i);

  101. FD_CLR(i,&global_rdfs); //将其清出

  102. }

  103. else

  104. {

  105. printf("recv:%s\n",buffer);

  106. send(i, buffer, sizeof(buffer),0);

  107. }

  108. }

  109. }

  110. }

  111. }

  112.  
  113. return 0;

  114. }

这里使用多路连接TCP服务器,执行结果如下:

 
  1. fs@ubuntu:~/qiang/select$ ./select

  2. socket successfully!

  3. listenfd : 3

  4. bind error: Address already in use

  5. fs@ubuntu:~/qiang/select$ ./select2

  6. socket successfully!

  7. listenfd : 3

  8. bind successfully!

  9. listen port:8888

  10. listening...

  11. Connect from [IP:192.168.3.51 PORT:40075]

  12. recv:xiao

  13.  
  14. Connect from [IP:192.168.3.51 PORT:40331]

  15. Connect from [IP:192.168.3.84 PORT:36233]

  16. Connect from [IP:192.168.3.33 PORT:2499]

  17.  

猜你喜欢

转载自blog.csdn.net/weixin_42048417/article/details/81163529