我们知道,服务器通常是要同时服务多个客户端的,如果我们运行上一篇实现的server和client之后,再开一个终端运行client试试,新的client就不能能得到服务了。因为服务器之支持一个连接。

网络服务器通常用fork来同时服务多个客户端,父进程专门负责监听端口,每次accept一个新的客户端连接就fork出一个子进程专门服务这个客户端。但是子进程退出时会产生僵尸进程,父进程要注意处理SIGCHLD信号和调用wait清理僵尸进程。

下面是代码框架:

  1. listenfd = socket(...);
  2. bind(listenfd, ...);
  3. listen(listenfd, ...);
  4. while (1) {
  5. connfd= accept(listenfd, ...);
  6. n= fork();
  7. if(n == -1) {
  8. perror("callto fork");
  9. exit(1);
  10. }else if (n == 0) {
  11. close(listenfd);
  12. while(1) {
  13. read(connfd,...);
  14. ...
  15. write(connfd,...);
  16. }
  17. close(connfd);
  18. exit(0);
  19. }else
  20. close(connfd);
  21. }

现在做一个测试,首先启动server,然后启动client,然后用Ctrl-C使server终止,这时马上再运行server,结果是:

binderror: Address already in use

这是因为,虽然server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监听同样的server端口。server终止时,socket描述符会自动关闭并发FIN段给client,client收到FIN后处于CLOSE_WAIT状态,但是client并没有终止,也没有关闭socket描述符,因此不会发FIN给server,因此server的TCP连接处于FIN_WAIT2状态。

现在用Ctrl-C把client也终止掉,再观察现象结果是:

binderror: Address already in useclient

终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同,在Linux上一般经过半分钟后就可以再次启动server了。

在server的TCP连接没有完全断开之前不允许重新监听是不合理的,因为,TCP连接没有完全断开指的是connfd(127.0.0.1:8000)没有完全断开,而我们重新监听的是listenfd(0.0.0.0:8000),虽然是占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。在server代码的socket()和bind()调用之间插入如下代码:

  1. int opt = 1;
  2. setsockopt(listenfd, SOL_SOCKET,SO_REUSEADDR, &opt, sizeof(opt));

select是网络程序中很常用的一个系统调用,它可以同时监听多个阻塞的文件描述符(例如多个网络连接),哪个有数据到达就处理哪个,这样,不需要fork和多进程就可以实现并发服务的server。

    1. /* server.c */
    2. #include <stdio.h>
    3. #include <stdlib.h>
    4. #include <string.h>
    5. #include <netinet/in.h>
    6. #include "wrap.h"
    7. #define MAXLINE 80
    8. #define SERV_PORT 8000
    9. int main(int argc, char **argv)
    10. {
    11. inti, maxi, maxfd, listenfd, connfd, sockfd;
    12. intnready, client[FD_SETSIZE];
    13. ssize_tn;
    14. fd_setrset, allset;
    15. charbuf[MAXLINE];
    16. charstr[INET_ADDRSTRLEN];
    17. socklen_tcliaddr_len;
    18. structsockaddr_in  cliaddr, servaddr;
    19. listenfd= Socket(AF_INET, SOCK_STREAM, 0);
    20. bzero(&servaddr,sizeof(servaddr));
    21. servaddr.sin_family      = AF_INET;
    22. servaddr.sin_addr.s_addr= htonl(INADDR_ANY);
    23. servaddr.sin_port        = htons(SERV_PORT);
    24. Bind(listenfd,(struct sockaddr *)&servaddr, sizeof(servaddr));
    25. Listen(listenfd,20);
    26. maxfd= listenfd;               /* initialize */
    27. maxi= -1;                            /* indexinto client[] array */
    28. for(i = 0; i < FD_SETSIZE; i++)
    29. client[i] = -1;     /* -1 indicates available entry */
    30. FD_ZERO(&allset);
    31. FD_SET(listenfd,&allset);
    32. for( ; ; ) {
    33. rset= allset;    /* structure assignment */
    34. nready= select(maxfd+1, &rset, NULL, NULL, NULL);
    35. if(nready < 0)
    36. perr_exit("selecterror");
    37. if(FD_ISSET(listenfd, &rset)) { /* new client connection */
    38. cliaddr_len= sizeof(cliaddr);
    39. connfd= Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    40. printf("receivedfrom %s at PORT %d\n",
    41. inet_ntop(AF_INET, &cliaddr.sin_addr,str, sizeof(str)),
    42. ntohs(cliaddr.sin_port));
    43. for(i = 0; i < FD_SETSIZE; i++)
    44. if(client[i] < 0) {
    45. client[i]= connfd; /* save descriptor */
    46. break;
    47. }
    48. if(i == FD_SETSIZE) {
    49. fputs("toomany clients\n", stderr);
    50. exit(1);
    51. }
    52. FD_SET(connfd,&allset);         /* add newdescriptor to set */
    53. if(connfd > maxfd)
    54. maxfd= connfd; /* for select */
    55. if(i > maxi)
    56. maxi= i;   /* max index in client[] array */
    57. if(--nready == 0)
    58. continue; /* no more readable descriptors */
    59. }
    60. for(i = 0; i <= maxi; i++) { /* check allclients for data */
    61. if( (sockfd = client[i]) < 0)
    62. continue;
    63. if(FD_ISSET(sockfd, &rset)) {
    64. if( (n = Read(sockfd, buf, MAXLINE)) == 0) {
    65. /*connection closed by client */
    66. Close(sockfd);
    67. FD_CLR(sockfd,&allset);
    68. client[i]= -1;
    69. }else {
    70. intj;
    71. for(j = 0; j < n; j++)
    72. buf[j]= toupper(buf[j]);
    73. Write(sockfd,buf, n);
    74. }
    75. if(--nready == 0)
    76. break;       /* no more readable descriptors */
    77. }
    78. }
    79. }
    80. }

socket服务器并发处理的更多相关文章

  1. Socket服务器整体架构概述

    转载:http://www.cnblogs.com/tianzhiliang/archive/2010/10/28/1863684.html Socket服务器主要用于提供高效.稳定的数据处理.消息转 ...

  2. 转:Socket服务器整体架构概述

    Socket服务器主要用于提供高效.稳定的数据处理.消息转发等服务,它直接决定了前台应用程序的性能.我们先从整体上认识一下Socket服务器,Socket服务器从架构上一般分为:网络层.业务逻辑层.会 ...

  3. 通过监控线程状态来保证socket服务器的稳定运行

    云平台中使用的socket服务器是我们自己定义一套通信协议,并通过C#实现的一个socket服务. 该服务目前是和web服务一起运行在IIS容器中,通过启动一个永不退出的新线程来监听端口. 在开发的初 ...

  4. Java NIO 非阻塞Socket服务器构建

    推荐阅读IBM developerWorks中NIO的入门教程,尤其是对块I/O和流I/O不太清楚的开发者. 说到socket服务器,第一反应是java.net.Socket这个类.事实上在并发和响应 ...

  5. socket服务器开发中的SO_REUSEADDR选项与让人心烦的TIME_WAIT

    1 发现问题 我在开发一个socket服务器程序并反复调试的时候,发现了一个让人无比心烦的情况:每次kill掉该服务器进程并重新启动的时候,都会出现bind错误:error:98,Address al ...

  6. workerman是一个高性能的PHP socket服务器框架

    workerman-chatorkerman是一款纯PHP开发的开源高性能的PHP socket服务器框架.被广泛的用于手机app.手游服务端.网络游戏服务器.聊天室服务器.硬件通讯服务器.智能家居. ...

  7. java的nio之:java的bio流下实现的socket服务器同步阻塞模型和socket的伪异步的socket服务器的通信模型

    同步I/O模型的弊端===>每一个线程的创建都会消耗服务端内存,当大量请求进来,会耗尽内存,导致服务宕机 伪异步I/O的弊端分析===>当对Socket的输入流进行读取操作的时候,它会一直 ...

  8. 以C#编写的Socket服务器的Android手机聊天室Demo

    内容摘要 1.程序架构 2.通信协议 3.服务器源代码 4.客户端源代码 5.运行效果 一.程序架构 在开发一个聊天室程序时,我们可以使用Socket.Remoting.WCF这些具有双向通信的协议或 ...

  9. 再次回首 TCP Socket服务器编程

    转载:http://www.cnblogs.com/zc22/archive/2010/06/27/1766007.html ------------------ 前言 --------------- ...

随机推荐

  1. 百度地图SDK调试SDKInitializer.initialize(getApplicationContext())错误

    首先描写叙述下问题出现的原因.開始的时候写了一个百度地图SDK的demo来试功能,由于最開始用的是Eclipse自带的AVD来调试,一切正常. 都能够正常验证,可是由于受不了重复的重新启动AVD设备, ...

  2. java性能监控工具jcmd-windows

    jcmd Sends diagnostic command requests to a running Java Virtual Machine (JVM). Synopsis jcmd [-l|-h ...

  3. Linux退出时出现there are stopped jobs如何解决?

    Linux 使用exit时出现there are stopped jobs如何解决? 这是因为一些命令被挂起了, 在后台驻留,需要关闭. 解决问题: 输入命令jobs -l显示停止进程的详细列表 可以 ...

  4. Irrlicht 3D Engine 笔记系列之 教程4 - Movement

    作者: i_dovelemon 日期: 2014 / 12 / 16 来源: CSDN 主题: Event Receiver, Animator, Framerate independent move ...

  5. 两种IO模式:Proactor与Reactor模式

    在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作. 在比较这两个模式之前,我们首先的搞明白 ...

  6. C递归算法与栈的分析,非全然二叉树遍历分析---ShinePans

                 对于递归,这里面的分析最好当然是用图形的方式来分析了.这里来总结一下 1.首先对于栈的理解: 先进后出,后进先出 先进后出 2.在进行非全然二叉树的存储之后,我们要做的是对其 ...

  7. (转)Understanding C parsers generated by GNU Bison

    原文链接:https://www.cs.uic.edu/~spopuri/cparser.html Satya Kiran PopuriGraduate StudentUniversity of Il ...

  8. java 对账关键点

    原理:双方交易信息对比是否平账 注意:对账bean必须重写 equals 方法 如图: //对账方法

  9. [听听音乐]X-Ray Dog

    X-Ray Dog是一个专门为好莱坞电影预告配乐的音乐工作室. https://music.163.com/#/artist?id=105618 有空的时候听听.

  10. gitlab 外网 无法访问 查端口 看文档

    云服务器安装成功后 curl   页面可以正常跳转 重置密码的token  页面可以生成 但是  外网无法 访问 [root@test ~]# curl 127.0.0.1:18021 <htm ...