精华内容
下载资源
问答
  • Linux环境下应用进程和Socket套接字设计并发服务器,完成用户聊天应用程序,本文档给出了系统设计模型、进程通信接口。
  • Linux环境下应用进程和Socket套接字设计并发服务器,完成用户聊天应用程序,这里给出了在Linux环境下的C语言实现完整代码。
  • 多进程并发服务器

    千次阅读 2019-02-03 15:45:42
    多进程并发服务器 在 Linux 环境下多进程的应用很多,其中最主要的就是网络/客户服务器。多进程服务器是当客户有请求时,服务器用一个子进程来处理客户请求。父进程继续等待其它客户的请求。这种方法的优点是当客户有...

    多进程并发服务器

    在 Linux 环境下多进程的应用很多,其中最主要的就是网络/客户服务器。多进程服务器是当客户有请求时,服务器用一个子进程来处理客户请求。父进程继续等待其它客户的请求。这种方法的优点是当客户有请求时,服务器能及时处理客户,特别是在客户服务器交互系统中。对于一个 TCP 服务器,客户与服务器的连接可能并不马上关闭,可能会等到客户提交某些数据后再关闭,这段时间服务器端的进程会阻塞,所以这时操作系统可能调度其它客户服务进程,这比起循环服务器大大提高了服务性能

    TCP多进程并发服务器

    TCP 并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是由服务器创建一个子进程来处理。
    在这里插入图片描述

    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>						
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>	
    
    
    int main(int argc, char *argv[])
    {
    
    	unsigned short port = 8080;		// 本地端口	
    
    	// 创建tcp套接字
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0);   
    	if(sockfd < 0)
    	{
    		perror("socket");
    		exit(-1);
    	}
    
    	
    
    	// 配置本地网络信息
    	struct sockaddr_in my_addr;
    	bzero(&my_addr, sizeof(my_addr));	  // 清空   
    	my_addr.sin_family = AF_INET;		  // IPv4
    	my_addr.sin_port   = htons(port);	  // 端口
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip
    
    	// 绑定
    	int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    
    	if( err_log != 0)
    	{
    		perror("binding");
    		close(sockfd);		
    		exit(-1);
    	}
    
    	// 监听,套接字变被动
    	err_log = listen(sockfd, 10); 
    	if(err_log != 0)
    	{
    		perror("listen");
    		close(sockfd);	
    		exit(-1);
    	}
    
    	
    
    	while(1) //主进程 循环等待客户端的连接
    	{
    
    		
    		char cli_ip[INET_ADDRSTRLEN] = {0};
    		struct sockaddr_in client_addr;
    		socklen_t cliaddr_len = sizeof(client_addr);
    
    		// 取出客户端已完成的连接
    		int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
    
    		if(connfd < 0)
    		{
    			perror("accept");
    			close(sockfd);
    			exit(-1);
    		}
    
    		pid_t pid = fork();
    		if(pid < 0)
    		{
    			perror("fork");
    			_exit(-1);
    
    		}
    		else if(0 == pid)
    		{ //子进程 接收客户端的信息,并发还给客户端
    
    			/*关闭不需要的套接字可节省系统资源,
    			  同时可避免父子进程共享这些套接字
    			  可能带来的不可预计的后果
    
    			*/
    
    			close(sockfd);   // 关闭监听套接字,这个套接字是从父进程继承过来
    
    			char recv_buf[1024] = {0};
    			int recv_len = 0;
    
    			// 打印客户端的 ip 和端口
    
    			memset(cli_ip, 0, sizeof(cli_ip)); // 清空
    			inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
    			printf("----------------------------------------------\n");
    			printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
    
    			
    			// 接收数据
    			while( (recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0 )
    			{
    				printf("recv_buf: %s\n", recv_buf); // 打印数据
    				send(connfd, recv_buf, recv_len, 0); // 给客户端回数据
    			}
    
    			printf("client closed!\n")
    			close(connfd);    //关闭已连接套接字
    			exit(0);
    		}
    		else if(pid > 0)
    		{	// 父进程
    			close(connfd);    //关闭已连接套接字
    		}
    
    	}
    
    
    	close(sockfd);
        return 0;
    
    }
    
    

    运行结果:
    在这里插入图片描述
    本文学习资料来自(https://blog.csdn.net/tennysonsky/article/details/45671215#t4):

    展开全文
  • 随着物联网、智能电网、智能移动设备的发展,我们将能在任何时候任何地方获取我们所需的信息,本文设计一款基于ARM-Linux嵌入式系统的多进程并发服务器。它拥有传统服务器的功能,可远程访问和操作,同时又具有体积...
  • 然而在实际应用中,不可能让一个服务器长时间地为一个客户服务,而需要其具有同时处理 个客户请求的能力,这种同时可以处理个客户请求的服务器称为并发服务器,其效率很 高却实现复杂。在实际应用中,并发服

    前面的话

      服务器按处理方式可以分为迭代服务器和并发服务器两类。平常用C写的简单Socket客户端服务器通信,服务器每次只能处理一个客户的请求,它实现简单但效率很低,通常这种服务器被称为迭代服务器。 然而在实际应用中,不可能让一个服务器长时间地为一个客户服务,而需要其具有同时处理 多个客户请求的能力,这种同时可以处理多个客户请求的服务器称为并发服务器,其效率很 高却实现复杂。在实际应用中,并发服务器应用的最广泛。
    迭代服务器与并发服务器

      linux有3种实现并发服务器的方式:多进程并发服务器,多线程并发服务器,IO复用,先来看多进程并发服务器的实现。


    创建进程

    Linux下的进程

      在创建新进程时,要进行资源拷贝。Linux 有三种资源拷贝的方式:

    1. 共享:新老进程共享通用的资源。当共享资源时,两个进程共同用一个数据结构,不需要为新进程另建。
    2. 直接拷贝:将父进程的文件、文件系统、虚拟内存等结构直接拷贝到子进程中。子进程创建后,父子进程拥有相同的结构。
    3. Copy on Write:拷贝虚拟内存页是相当困难和耗时的工作,所以能不拷贝就最好不 要拷贝,如果必须拷贝,也要尽可能地少拷贝。为此,Linux 采用了 Copy on Write 技术,把真正的虚拟内存拷贝推迟到两个进程中的任一个试图写虚拟页的时候。如 果某虚拟内存页上没有出现写的动作,父子进程就一直共享该页而不用拷贝。

    进程创建函数fork与vfork

      下面介绍创建新进程的两个函数:fork()和 vfork()。
      其中,fork 用于普通进程的创建,采用的是 Copy on Write 方式;而 vfork 使用完全共享的创建,新老进程共享同样的资源,完全没有拷贝。

    ● fork函数原型如下:

      #include <unistd.h>
      pid_t fork (void);

      函数调用失败会返回-1。fork 函数调用失败的原因主要有两个:

    1. 系统中已经有太多的进 程;
    2. 该实际用户 ID 的进程总数超过了系统限制。

       而如果调用成功,该函数调用会在父子进程中分别返回一次。在调用进程也就是父进程中,它的返回值是新派生的子进程的 ID 号,而在子进程中它的返回值为 0。因此可以通过返回值来区别当前进程是子进程还是父进程。

      为什么在 fork 的子进程中返回的是 0,而不是父进程 id 呢?
      原因在于:没有子进程都只 有一个父进程,它可以通过调用 getppid 函数来得到父进程的 ID,而对于父进程,它有很多 个子进程,他没有办法通过一个函数得到各子进程的ID。如果父进程想跟踪所有子进程的ID, 它必须记住 fork 的返回值。

    ● vfork函数原型如下:

      #include <unistd.h>
      pid_t vfork (void);

      vfork 是完全共享的创建,新老进程共享同样的资源,完全没有拷贝。当使用 vfork()创 建新进程时,父进程将被暂时阻塞,而子进程则可以借用父进程的地址空间运行。这个奇特 状态将持续直到子进程要么退出,要么调用 execve(),至此父进程才继续执行。
      可以通过下面的程序来比较 fork 和 vfork 的不同。

    #include <sys/types.h> 
    #include <unistd.h> 
    int main(void) 
    {
        pid_t   pid; 
        int status;
         if ((pid = vfork()) == 0) 
        { 
           sleep(2); 
            printf("child running.\n"); 
             printf("child sleeping.\n"); 
             sleep(5); 
            printf("child dead.\n"); 
             exit(0); 
        } 
        else if ( pid > 0) 
        { 
             printf("parent running .\n"); 
             printf("parent exit\n"); 
             exit(0); 21.     
        } 
        else
        {
             printf("fork error.\n"); 
            exit(0); 
        } 
    } 

      程序运行结果如下:

    child running. 
    child sleeping. 
    child dead. 
    parent running . 
    parent exit

      如果将 vfork 函数换成 fork 函数,该程序运行的结果如下:

    parent running . 
    parent exit 
    [root@localhost test]# 
    child running. 
    child sleeping. 
    child dead. 

    使用fork函数实现多进程并发服务器

      fork 调用后,父进程和子进程继续执行 fork 函数后的指令,是父进程先执行还是子进程 先执行是不确定的,这取决于系统内核所使用的调度算法。
      而在网络编程中,父进程中调用 fork 之前打开的所有套接字描述符在函数 fork 返回之后都是共享。如果父、子进程同时对同一个描述符进行操作, 而且没有任何形式的同步,那么它们的输出就会相互混合。

      fork函数在并发服务器中的应用:
      父、子进程各自执行不同的程序段,这是非常典型的网络服务器。父进程等待客户 的服务请求。当这种请求到达时,父进程调用 fork 函数,产生一个子进程,由子进程对该请求作处理。父进程则继续等待下一个客户的服务请求。并且这种情况下,在 fork 函数之后,父、子进程需要关闭各自不使用的描述符,即父进程将不需要的 已连接描述符关闭,而子进程关闭不需要的监听描述符。这么做的原因有3个:

    1. 节省系统资源
    2. 防止上面提到的父、子进程同时对共享描述符进程操作
    3. 最重要的一点,是确保close函数能够正确关闭套接字描述符

      我们在socket编程中调用 close 关闭已连接描述符时,其实只是将访问计数值减 1。而描述符只在访 问计数为 0 时才真正关闭。所以为了正确的关闭连接,当调用 fork 函数后父进程将不需要的 已连接描述符关闭,而子进程关闭不需要的监听描述符。

      好了,有了上面的知识,我们现在可以总结出编写多进程并发服务器的基本思路:

    1. 建立连接
    2. 服务器调用fork()产生新的子进程
    3. 父进程关闭连接套接字,子进程关闭监听套接字
    4. 子进程处理客户请求,父进程等待另一个客户连接。

    服务器端代码示例(来源于网络):

    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <sys/types.h>
    #include <netdb.h>
    #define SERV_PORT 1113
    #define LISTENQ  32
    #define MAXLINE 1024
    /***连接处理函数***/
    void str_echo(int fd);
    int 
    main(int argc, char *argv[]){
      int listenfd,connfd;
      pid_t childpid;
      socklen_t clilen;
      struct sockaddr_in servaddr;
      struct sockaddr_in cliaddr;
      if((listenfd = socket(AF_INET, SOCK_STREAM,0))==-1){
         fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
         exit(1);
      }
      /* 服务器端填充 sockaddr结构*/ 
      bzero(&servaddr, sizeof(servaddr));
      servaddr.sin_family = AF_INET;
      servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
      servaddr.sin_port = htons(SERV_PORT);
      /* 绑定listenfd描述符  */ 
      if(bind(listenfd,(struct sockaddr*)(&servaddr),sizeof(struct sockaddr))==-1){
        fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
        exit(1);
       }
       /* 监听listenfd描述符*/
        if(listen(listenfd,5)==-1){
            fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
            exit(1);
        }
      for ( ; ; )  {
        clilen = sizeof(cliaddr);
        /* 服务器阻塞,直到客户程序建立连接  */
        if((connfd=accept(listenfd,(struct sockaddr*)(&cliaddr),&clilen))==-1){
            fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
            exit(1);
        }
        //有客户端建立了连接后
        if ( (childpid = fork()) == 0) { /*子进程*/
         close(listenfd);    /* 关闭监听套接字*/
         str_echo(connfd);   /*处理该客户端的请求*/
         exit (0);
        }
        close(connfd);/*父进程关闭连接套接字,继续等待其他连接的到来*/
     }
    }
    void str_echo(int sockfd){
     ssize_t n;
        char  buf[MAXLINE];
        again:
          while ( (n = read(sockfd, buf, MAXLINE)) > 0)
              write(sockfd, buf, n);
          if (n < 0 && errno == EINTR)//被中断,重入
              goto again;
          else if (n < 0){//出错
            fprintf(stderr,"read error:%s\n\a",strerror(errno));
            exit(1);
          }
    }

      传统的网络服务器程序大都在新的连接到达时,fork一个子进程来处理。虽然这种模式很多年使用得很好,但fork有一些问题:

    1. fork是昂贵的。fork时需要复制父进程的所有资源,包括内存映象、描述字等;目前的实现使用了一种写时拷贝(copy-on-write)技术,可有效避免昂贵的复制问题,但fork仍然是昂贵的;
    2. fork子进程后,父子进程间、兄弟进程间的通信需要进程间通信IPC机制,给通信带来了困难;
    3. 多进程在一定程度上仍然不能有效地利用系统资源;
    4. 系统中进程个数也有限制。

      下一篇文章介绍基于多线程的并发服务器编程。


    扩展-进程的终止

      进程终止存在两种可能:父进程先于子进程终止;子进程先于父进程终止。

    • 如果父进程在子进程之前终止,则所有子进程的父进程被改为 init 进程,就是由 init 进 程领养。在一个进程终止是,系统会逐个检查所有活动进程,判断 这些进程是否是正要终止 的进程的子进程。如果是,则该进程的父进程 ID 就更改为 1(init 的 ID)。这就保证了每个 进程都有一个父进程。
    • 如果子进程在父进程之前终止,系统内核会为每个终止子进程保存一些信息,这样父进 程就可以通过调用 wait()或 waitpid()函数,获得子进程的终止信息。终止子进程保存的信息 包括进程 ID、该进程的终止状态,以及该进程使用的 CPU 时间总量。当父进程调用 wait() 或 waitpid()函数时,系统内核可以释放终止进程所使用的所有存储空间,关闭其所有打开文 件。一个已经终止,但是其父进程尚未对其进行善后处理的进程称为僵尸进程。

      当子进程正常或异常终止时,系统内核向其父进程发送 SIGCHLD 信号,默认情况下, 父进程忽略该信号,或者提供一个该信号发生时即被调用的函数。
      父进程可以通过调用 wait()或 waitpid()函数,获得子进程的终止信息。

    • wait 函数原型如下:
    #include <sys/wait.h> 
    pid_t wait(int *statloc);

      参数 statloc 返回子进程的终止状态(一个整数)。当调用该函数时,如果有一个子进程 已经终止,则该函数立即返回,并释放子进程所有资源,返回值是终止子进程的 ID 号。如果当前没有终止的子进程,但有正在执行的子进程,则 wait 将阻塞直到有子进程终止时才返 回。如果当前既没有终止的子进程,也没有正在执行的子进程,则返回错误-1。

      函数 waitpid 对等待哪个进程终止及是否采用阻塞操作方式方面给了更多的控制。

    • waitpid函数原型如下:
    #include <sys/wait.h> 
    waitpid(pid_t pid ,int *statloc, int option);

      当参数 pid 等于-1 而 option 等于 0 时,该函数等同于 wait()函数。 参数 pid 指定了父进程要求知道哪些子进程的状态,当 pid 取-1 时,要求知道任何一个子进程的终止状态。当 pid 取值大于 0 时,要求知道进程号为 pid 的子进程的终止状态。当 pid 取值小于-1 时,要求知道进程组号为 pid 的绝对值的子进程的终止状态。
      参数 option 让用户指定附加选项。最常用的选项是 WNO_HANG,它通知内核在没有已 终止子进程时不要阻塞。
      当前有终止的子进程时,返回值为子进程的 ID 号,同时参数 statloc 返回子进程的终止 状态。否则返回值为-1。
      和wait较大的不同是waitpid可以循环调用,等待所有任意进程结束,而wait只有一次机会。

    展开全文
  • 37-多进程并发服务器(并发测试)

    千次阅读 2017-04-21 13:36:38
    因为我们没有大量的测试机器,所以只能在单机上模拟大量客户端去连接服务器。代码托管在 gitos 上,请使用下面的命令获取:git clone https://git.oschina.net/ivan_allen/unp.git如果你已经 clone 过这个代码了,请...

    因为我们没有大量的测试机器,所以只能在单机上模拟大量客户端去连接服务器。

    代码托管在 gitos 上,请使用下面的命令获取:

    git clone https://git.oschina.net/ivan_allen/unp.git

    如果你已经 clone 过这个代码了,请使用 git pull 更新一下。

    1. 客户端与服务器程序

    • 客户端

    本文所使用的客户端程序路径是:

    unp/program/concurrent_test/ccrt

    ccrt 程序可以一次发起大量连接请求,主要通过参数 -n 指定。比如:

    $ ./ccrt -h sun -p 8000 -n 40000

    上面的命令表示发起 40000 个连接请求去连接 sun 主机上的服务器。ccrt 程序每次连接成功或失败后,会在屏幕打印如下字样:

    sockfd = xxxx, port = xxxx, client xxxx connecting success! 
    // 或者
    sockfd = xxxx, port = xxxx, client xxxx connecting failed! 

    sockfd 表示客户端的套接字,port 表示系统分配的端口号。

    • 服务器程序使用的是上一篇文章我们修复了僵尸进程的多并发服务器,它的路径是:
    unp/program/echo/processzombie

    2. 实验

    2.1 实验一:测试单机环境多进程并发量

    • 在 flower 主机上启动服务器
    [flower] $ ./echo -s -h flower
    • 在 sun 主机启动客户端,发起 40000 个连接请求
    [sun] $ su root // 切换到 root 用户
    [sun] $ ulimit -n 65535 // 修改单进程最大可打开的文件描述符数
    [sun] $ ./ccrt -h flower -n 40000


    这里写图片描述
    图1 sun 主机向 flower 发起 40000 个连接请求,在第 3618个请求上失败

    很不幸的是,服务器在第 3618 次 fork 的时候产生了错误,提示资源暂时不可用(Resource temporarily unavailable). 毕竟想让单机 fork 40000 个进程出来实在是太难为它了,这中间有一次测试直接让操作系统崩溃了……

    可见,多进程并发服务器因为受系统进程数量的限制,并发量也就在几千左右。

    接下来,在客户端按下 CTRL D 释放掉连接吧,不然现在服务器已经卡的没法使用了。

    2.2 测试单机一次最多能接收多少个连接

    ccrt 程序同时也可以扮演服务器的角色。当 ccrt 充当服务器的时候,它循环的 accept,但是并不处理客户端发来的数据。伪代码类似下面这样:

    while(1) {
      sockfd = accept(listenfd);
    }

    我们要做的就是统计这样的服务器能承受多大的压力。

    • 在 flower 主机上启动服务器
    [flower] $ ./ccrt -s -h flower
    • 在 sun 主机启动客户端,发起 40000 个连接请求
    [sun] $ su root // 切换到 root 用户
    [sun] $ ulimit -n 65535 // 修改单进程最大可打开的文件描述符数
    [sun] $ ./ccrt -h flower -n 40000


    这里写图片描述
    图2 sun 主机向 flower 发起 40000 个连接,客户机端口耗尽

    图1 左上侧是 sun 主机上的客户端,右上侧是 flower 主机上的服务器,我们看到,flower 主机上的多进程服务器经受住了考验!可是不幸的是,客户端在发起第 28232 个连接的时候失败了!!!因为系统的端口资源被耗尽(图 2 中显示最后一次分配的端口是 0!),要知道,操作系统一共只准备了 65536 个端口,其中有大量端口我们都是无法使用的,当可用端口被用完的时候,也正是 ccrt 结束生命的时候。

    另一方面,这个 28232 并不是随便就得出来的,我们可以查看 /proc/sys/net/ipv4/ip_local_port_range这个文件里的数据,内容为

    32768   60999

    它表示系统可自动分配的端口范围,刚好是 28232 个。

    好了,先不要关掉 sun 主机上的 ccrt 程序,虽然打印了 done,但是它并没有关闭 ESTABLISHED 状态的连接。

    • 接下来,在另一台主机 moon 上启动 ccrt,继续向 flower 主机发起 40000 个连接。
    [moon] $ su root // 切换到 root 用户
    [moon] $ ulimit -n 65535 // 修改单进程最大可打开的文件描述符数
    [moon] $ ./ccrt -h flower -n 40000


    这里写图片描述
    图3 moon 主机上的客户端在发起第 2991 次连接时失败

    重点看图 3 底部的数据包,moon 主机在第 2991 次发起连接请求的时候,发送了 SYN 段给 flower 主机,但是都没有得到 flower 主机的回应!!!重传了 6 次 SYN 报文后,客户端放弃,connect 函数返回,并报告超时。

    算上图 2 和图 3 中所有成功的连接,一共是 28232 + 2991 = 31223 次连接,也就是在纯粹的只 accept 的情况下,flower 主机并发量达到了 31223 次,约 3 万次左右。另一方面,你得知道,我的 flower 主机只是一个虚拟机,如果换成真实主机,配置再好点的话,10 W+ 应该不成问题。

    知乎上看到有人实现了单机 300W 并发量,玩的很溜^_^. 传送门

    注意:服务器不会有端口耗尽的情况,因为本机套接字地址始终都是固定的,变化的只是远端套接字地址,tcp 连接是一个四元组 {local ip:port <-> foreigh ip:port}。

    3. 总结

    在上面的实验中,我们有幸观察到了:

    • fork 返回错误(这真的很少见,errno = EAGAIN
    • 端口耗尽(这也不太常见,errno = EADDRNOTAVAIL
    • 单机服务器并发数影响因素
      • 进程打开描述符的描述数限制
      • ip 地址个数 232 × 端口号个数 65536 (理论最大值)
      • 机器本身所能承受的极限,比如内存的限制
      • 服务器架构,比如多进程服务器多线程服务器会受进程个数,线程个数的限制
      • 其它因素

    最后,我希望看到这篇文章的同学,也能在你的机器上测试一下,大家把数据贴出来看看,共享一下。

    欢迎入群讨论:610441700,加群记得注明来意,否则会被拒绝。

    展开全文
  • 主要介绍了c语言多进程tcp服务器示例,多进程socket,epoll实现IO复用,需要的朋友可以参考下
  • 基于信号 实现多进程并发服务器.pdf
  • Linux多进程并发服务器[定义].pdf
  • Linux多进程并发服务器[收集].pdf
  • 基于Linux的Socket编程之TCP全双工Server-Client聊天程序 一、多进程并发分析:特点: 占用资源大,但是一个进程挂了不会影响另一个。这与多线程刚好相反,多线程服务器不稳定。分析: 父进程循环accept,当父...

    同类基础博客:
    基于Linux的SOCKET编程之TCP半双工Client-Server聊天程序
    基于Linux的Socket编程之TCP全双工Server-Client聊天程序

    一、多进程并发分析:

    特点:
    占用资源大,但是一个进程挂了不会影响另一个。这与多线程刚好相反,多线程服务器不稳定。

    分析:
    父进程循环accept,当父进程接收到链接请求之后,立即fork出一个新的子进程去处理通信,而父进程继续循环等待接收accept()(没有连接请求父进程则阻塞,但是不会影响到子进程通信)。而对于自己进程回收,父进程可以用一个单独的子进程去回收用于通信的子进程。子进程也可以自己fork出新的子进程与原进程分别处理读与写(发与收),以致于读写之间不受阻塞限制。
    注意:子进程会继承父进程文件描述符,对于用不到的文件描述符listenfd需要关闭,并且父进程中在创建fork之后也需要关闭confd。防止文件描述符无意义的耗费过度。

    结构图:
    这里写图片描述

    可能需要考虑的问题:
    ①子进程资源回收:如果客户端关闭,服务器相应的子进程则要结束,不能一直阻塞等待接收信息。
    ②不用的文件描述符要回收。
    ③对于回射式客户-服务器模型,并不需要交互式客户-服务器模型那样需要子进程创建新的进程去分别处理读写操作,那么结束一个进程自然不需要发送信号去通知另一个。交互式客户-服务器模型则必须要处理读写进程中任意一个结束,信号告知另外一个进程。即对于交互式模型,多进程处理多链接,而子进程又需要多进程分别处理读与写的不同操作不至于客户端为阻塞式,对于多进程的资源回收需要注意。

    二、源代码基本实现:

    文件关系:

    这里写图片描述

    1、Server端:

    (1)、server_main.c:

    /*server_main.c*/
    #include<multiproc.h>
    #include<server.h>
    
    int main(char argc, char **argv)
    {
        if(argc < 3 ){
            printf("Too few parameter!\n");
            exit(EXIT_FAILURE);
        }
    
        socket_server_create(argv[1], argv[2]);
        return 0;
    }

    (2)、server.c:

    /*server.c*/
    #include<multiproc.h>
    #include<server.h>
    
    void sys_err(const char * ptr_err)
    {
        perror(ptr_err);
        exit(EXIT_FAILURE);
    }
    void socket_server_create(const char * ipaddr, const char * port)
    {
        struct sockaddr_in serveraddr, clientaddr;
        socklen_t addrlen;
        int confd, ret_bind, ret_listen;
    /*创建套接字socket、绑定bind、监听listen、接收accept等基本操作*/
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_addr.s_addr = inet_addr(ipaddr);
        serveraddr.sin_port = htons(atoi(port));
    
        if( (ret_bind = bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0)
            sys_err("bind");
    
        if( (ret_listen = listen(listenfd, BACKLOG_SIZE)) < 0)
            sys_err("listen");
    
        while(1){//循环监听,以保证多进程可以并发
            addrlen = sizeof(clientaddr);
    again:
            if( (confd = accept(listenfd, (struct sockaddr *)&clientaddr, &addrlen)) < 0){
                if ((errno == ECONNABORTED) || (errno == EINTR))//接收到的-1是因为无请求链接
                    goto again;
                else
                    sys_err("accept");
            }
            fork_create(listenfd, confd, &clientaddr);//对接收到的客户端链接进行处理
        }
        close(listenfd);
    }
    
    void fork_create(const int listenfd, const int confd, const struct sockaddr_in * clientaddr)
    {
        pid_t pid = fork();
        if(pid < 0)
            sys_err("fork");
        else if(pid > 0)    
            close(confd);//父进程关闭上一个接收到的链接的socket秒数符,继续循环accept
        else{
            close(listenfd);//关闭继承过来的无用的文件秒数符
            deal_connect(confd, clientaddr);//子进程处理通信
            close(confd);//推出前关闭文件描述符断开链接
            exit(EXIT_SUCCESS);//子进程结束退出
        }
    }
    void deal_connect(const int confd, const struct sockaddr_in * clientaddr)
    {
        pid_t pid = fork(); 
        if(pid < 0)
            sys_err("fork");
        else if(pid == 0){//处理读:保存信息到日志文件中
            write_logfile(clientaddr);//写日志
            sleep(10);//模拟其他操作
            /*信息写完后向父进程发送一个SIGCHILD结束信号*/
            kill(SIGCHLD, getppid());
            exit(EXIT_SUCCESS);
        }
        else{//处理写:回射登录状态信息
            int ret_write;
            signal(SIGCHLD, SIG_DFL);//处理子进程结束信号,采用默认处理方式(忽略),也可以自定义并将wait()写入自定义信号处理中
            if( (ret_write = write(confd, "connect success!\n", 17)) < 0)//连接成功将链接成功的信息回射给客户端
                sys_err("write connect success");
            wait(NULL);//等待回收子进程资源
            if( (ret_write = write(confd, "quit success!\n", 14)) < 0)//wait不再阻塞,表明子进程结束,通信结束,将结束信息会射到客户端
                sys_err("write quit success");
        }
    }
    /*处理些日志文件的操作*/
    void write_logfile(const struct sockaddr_in * clientaddr)
    {
        int logfd = open("sersock.log", O_RDWR);//以可读可写的方式打开日志文件
        if(logfd < 0)
            sys_err("open sersock.log");
    
        char addrbuf[ADDR_PORT_SIZE] = {};
        /*将登录信息写入日志文件*/
        lseek(logfd, 0, SEEK_END);
        sprintf(addrbuf, "%s:%d    ",inet_ntoa(clientaddr->sin_addr), ntohs(clientaddr->sin_port));//将IP地址与端口号拼接到一起
        write(logfd, addrbuf,strlen(addrbuf));//将拼接好的地址信息写入日志文件
    
        close(logfd);       
    }

    (3)、server.h:

    /*server.h*/
    #ifndef _SERVER_H_
    #define _SERVER_H_
    
    #include<multiproc.h>
    #include<fcntl.h>
    #include<signal.h>
    #include<sys/wait.h>
    
    void sys_err(const char *);
    void socket_server_create(const char *, const char *);
    void fork_create(const int, const int, const struct sockaddr_in *);
    void deal_connect(const int, const struct sockaddr_in *);
    void write_logfile(const struct sockaddr_in *);
    
    #endif

    2、Client端:

    (1)、client_main.c:

    /*client_main.c*/
    #include<multiproc.h>
    #include<client.h>
    
    int main(char argc, char ** argv)
    {
        if(argc < 3){
            printf("Too few parameter!\n");
            exit(EXIT_FAILURE);
        }
        socket_client_create(argv[1], argv[2]);
        return 0;
    }

    (2)、client.c:

    /*client.c*/
    #include<multiproc.h>
    #include<client.h>
    
    void sys_err(const char * ptr_err)
    {
        perror(ptr_err);
        exit(EXIT_FAILURE);
    }
    void socket_client_create(const char * ipaddr, const char * port)
    {
        struct sockaddr_in serveraddr;
    
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_addr.s_addr = inet_addr(ipaddr);
        serveraddr.sin_port = htons(atoi(port));
    
        int ret = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
        if(ret < 0)
            sys_err("connect");
    
        deal_connect(sockfd);
        close(sockfd);
    }
    
    void deal_connect(const int sockfd)
    {
        char buf[BUF_SZIE] = {};
        while(1){
            bzero(&buf, strlen(buf));//每次清空
            int ret = read(sockfd , buf, sizeof(buf));//循环读取服务器回射信息
            if(ret < 0)
                sys_err("read");
            write(STDOUT_FILENO, buf, strlen(buf));//将接收到的回射信息写到标准输出上
            if(strcmp(buf, "quit success!\n") == 0)//接收到的信息为“quit success!\n”时,表明服务器将客户端请求(sleep(10)模拟)信息处理完毕
                break;
        }
    }

    (3)、client.h:

    /*client.h*/
    #ifndef _CLIENT_H_
    #define _CLIENT_H_
    
    #include<multiproc.h>
    
    void socket_client_create(const char *, const char *);
    void deal_connect(const int);
    
    #endif
    

    3、其他文件:

    (1)、multiproc.h:

    /*multiproc.h*/
    #ifndef _MULTI_PROCESS_SOCKET_
    #define _MUTLI_PROCESS_SOCKET_
    
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<sys/stat.h>
    #include<arpa/inet.h>
    #include<netinet/in.h>
    #include<errno.h>
    
    /*数据传输接收缓冲区长度*/
    #define BUF_SZIE 1024
    /*监听队列长度*/
    #define BACKLOG_SIZE 128
    /*客户端地址信息缓冲区长度*/
    #define ADDR_PORT_SIZE 40
    
    #endif

    (2)、Makefile:

    /*Makefile*/
    CPPFLAGS= -I ../ -I ./
    CFLAGS= -g -Wall
    #LDFLAGS= 
    CC= gcc
    
    src = $(wildcard *.c)
    obj = $(patsubst %.c,%.o,$(src))
    #服务器端目标文件
    target = server
    
    #客户端目标文件
    target = client
    $(target):$(obj)
        $(CC) $^ $(LDFLAGS) -o $@
    
    %.o:%.c
        $(CC) -c $< $(CFLAGS) $(CPPFLAGS) -o $@
    
    .PHONY:clean
    clean:
        -rm -f server
        -rm -f *.o

    三、多进程并发功能与测试:

    服务器启动后,连接多个客户端,便可在单独的终端:
    netstat -apn | grep “port”查看连接状态等信息

    我们可以看到在处理客户端链接时,服务器创建了多个进程进行处理:

    这里写图片描述
    对于图中三个进程,其关系如下:

    这里写图片描述

    我们cat查看一下log日志文件中的信息变化(可以看到几次测试时,系统为客户端分配的端口号是不一样的,服务器接收到了链接的客户端地址信息,并将其写入日志文件):

    这里写图片描述

    server-client的一些TCP链接状态(包含有之前测试的TIME_WAIT状态、服务器的LISTEN状态以及新的链接的ESTABLISHED通信状态、新链接断开时的TIME_WAIT状态等):

    这里写图片描述

    四、多进程并发需要注意的问题:

    对于大型Socket项目中,客户端异常终止,服务器用于处理客户端的自己成还处于read阻塞,如何处理?

    1、调用setsockopt()函数,让内核自己处理:

    int setsockopt(int sockfd, int level, int optname, void * optval, socklen_t * optlen);//该函数能够设置TCP链接的存活属性
    getsockopt(int sockfd, int level, int optname, void * optval, socklen_t optlen);

    当设置连接的TCP存活属性之后,如果长时间(2小时左右(总之很长))客户端都没有向服务器发送数据请求(通信数据),那么服务器就会向客户端发送一个确认客户端是否异常断开链接的请求包,如果线路通常,客户端TCP/IP内核栈会自动发送一个携带RST复位信息的包,当服务器接收到RST复位信息后就知道客户端是异常结束的(如:Ctrl+C等),那么服务器就会自动断开该条链接。如果客户端是因为网络等问题结束的,服务器发送的包,客户端可能不会收到,也不会恢复服务器,那么服务器就重新发送,发送多次(十多次)以后,服务器依然没有收到回复,服务器就认为是客户端网络等问题导致的线路不通畅,就会关闭该条链接。服务器断开该条链接后,服务器端的阻塞的read,也会结束阻塞返回-1,并判断-1是因为连接断开返回的,而结束通信的子进程。

    优点:方便
    缺点:不能实时响应

    2、C/S心跳机制:

    服务器单独的进程/线程每隔几分钟(固定时间)发送一个心跳包给连接到的每个客户端,检测客户端是否存活。如果客户端无回复,重复发送几次,若还无回复则关闭链接。客户端也可以设置心跳进程,若果是服务器异常退出,客户端可以关闭并重新建立链接,如果重新建立几次失败,客户端就会向用户报告出错信息。(自己设置一个服务器的心跳包)。对于心跳机制,首先需要保存所有连接到服务器的客户端信息。

    展开全文
  • 基于ARM-Linux嵌入式系统的多进程并发服务器设计.pdf
  • 多进程并发服务器编程

    万次阅读 2011-12-10 22:27:11
    编写多进程并发服务器程序和客户程序,具体功能如下: 1 、服务器等待接收客户的连接请求,一旦连接成功则显示客户地址,接着接收客户端的名称并显示;然后接收来自该客户的字符串,每当收到一个字符串时,...
  • 多进程并发服务器实例

    千次阅读 2016-09-22 21:55:46
    该程序为多进程并发服务器实例。包括服务器程序和客户端程序。编译及运行的相关信息如下:  操作系统:CentOS 7  编译工具:GCC  调试工具:GDB  程序实现的功能如下:  1、服务器等候客户连接,一旦连接...
  • 二、多进程并发服务器 在Linux环境下多进程的应用很多,其中最主要的就是网络/客户服务器。多进程服务器是当客户有请求时,服务器用一个子进程来处理客户请求。父进程继续等待其它客户的请求。这种方法的优点是当...
  • 网络编程(5)多进程并发服务器

    千次阅读 2014-02-15 01:04:27
    多进程并发服务器基本流程是,accept接收一个客户端连接,就创建一个子进程来处理它,即一个子进程对应一个客户端。 简单说就是 父进程负责接收请求,子进程负责处理请求。 整理代码如下: /*********************...
  • 并发服务器-多进程服务器详解
  • 主要介绍了Python多进程服务器并发原理及用法,深入浅出的介绍了进程、并行、并发、同步、异步等相关概念与原理,并结合实例形式给出了Python多进程编程相关操作技巧,需要的朋友可以参考下
  •   在网络程序里面,一般来说都是许多客户对应一个服务器(多对一),为了...一、多进程并发服务器的概念   在 Linux 环境下多进程的应用很多,其中最主要的就是网络/客户服务器。多进程服务器是当客户有请求时,...
  • https://blog.csdn.net/lianghe_work/article/details/46503895一、tcp并发服务器概述一个好的服务器...二、多进程并发服务器在 Linux 环境下多进程的应用很多,其中最主要的就是网络/客户服务器。多进程服务器是当客...
  • 本文用fork()函数实现一个多进程并发服务器
  • 一旦客户端关闭连接,服务器进程就会退出,然而父进程仍然存在,就产生了“白发人送黑发人”的场景。如果父进程没有主动回收(wait)子进程,或者没有忽略 SIGCHLD 信号,退出的子进程就会成为僵尸进程。代码托管...
  • 操作系统实验一 多进程并发执行的实验报告,有实验目的,实验预备知识,实验代码和结论,其中还有四个父子进程的代码。
  • Unix系统编程(4) - 多进程并发服务器

    千次阅读 2016-07-14 20:42:20
    1. 迭代服务器 & 并发服务器服务器按照 处理方式 来看可以分为: - 迭代服务器 迭代服务器只能一次处理一个客户的请求。也就是说在服务器响应一个客户请求时,如果有另一个客户发起请求是不能...2. 多进程并发服务器 1
  • 多进程实现的并发服务器,c语言,socket,在linux下实现

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 264,817
精华内容 105,926
关键字:

多进程并发服务器

友情链接: IDLbiancheng.rar