利用fork实现并发服务器

(1) fork 浅析

linux 中, 一个进程可以通过fork()系统调用来创建一个与自己相同的子进程, 这个子进程是父进程的克隆, 他继承了父进程的整个地址空间, 包括进程上下文, 堆栈地址, 内存信息, 进程控制块等。值得注意的是, 调用fork一次, 他却返回两次, 一次是在父进程中返回子进程的进程id, 一次是在子进程中返回0, 这看起来有点难理解, 我们先看下面这段程序:

 #include <unistd.h>
 #include <stdio.h>
 #include <sys/types.h>
 #include <string.h>
 int main()
 {
     pid_t pid = fork();
     if(pid == -1)
     {
         perror("fork error");
         return -1;
     }
     if(pid > 0)
     {
         printf("parent: child pid is %d\n", pid);
     }
     else if(pid == 0)
     {
         printf("child: parent pid is %d \t, self pid id %d \n",     getppid(), getpid());
     }
     printf("after fork\n");
     return 0;
}

运行结果如下:

这个结果很容易理解, 当父进程调用fork后, 系统创建了一个与父进程同样的子进程, 他们拥有一样的上下文, 在父进程中, fork()返回了子进程的id, 在子进程中返回了0, 然后他们分别往下运行, 父进程走入了if(pid > 0) 程序段中, 而子进程走入了else if(pid == 0) 程序段中, 然后他们又分别继续往下运行, 都打印了after fork\n。这里需要注意的是, fork()出来的子进程并不是从头开始运行, 因为他跟父进程有一样的上下文, 这也是为什么他会返回两次(父子进程中各返回一次),  同时父子进程的运行顺序是不确定的, 多核机器上可能交替执行也可能同时运行, 所以打印顺序没有太大意义。

(2) 利用fork实现服务器的并发

简单了解了一下fork, 我们知道了调用fork以后, 会创建一个与父进程一样的子进程, 他们拥有一样的资源, 于是我们就可以利用这个特性来实现一个简单的并发服务器。

首先来看一下下面这段代码:

 1 #include <sys/socket.h>
 2 #include <sys/types.h>
 3 #include <arpa/inet.h>
 4 #include <netinet/in.h>
 5 #include <stdio.h>
 6 #include <stdlib.h>
 7 #include <string.h>
 8 #include <unistd.h>
 9
10 #define SERV_PORT 4333
11 #define MAXLINE 1024
12
13 /*读入客户端的输入, 然后带上处理进程id加以返回*/
14 void dosomething(int sockfd)
15 {
16     pid_t pid = getpid();
17     int n;
18     char buff[MAXLINE], sendbuff[MAXLINE];
19     while(true)
20     {
21         n = read(sockfd, buff, MAXLINE);
22         if(n > 0)
23         {
24             snprintf(sendbuff, MAXLINE, "pid: %d\t %s\n", getpid(), buff);
25             write(sockfd, sendbuff, strlen(sendbuff));
26         }
27         else if(n < 0)
28         {
29             perror("read error");
30             exit(-1);
31         }
32         else
33             break;
34     }
35 }
36
37 int main()
38 {
39     int servfd, connfd;
40     sockaddr_in servaddr;
41     pid_t pid;
42
43     if((servfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
44     {
45         perror("create socket error");
46         exit(-1);
47     }
48
49     // 初始化监听地址
50     bzero(&servaddr, sizeof(servaddr));
51     servaddr.sin_family = AF_INET;
52     servaddr.sin_port = htons(SERV_PORT);
53     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
54
55     // 绑定监听地址
56     if(bind(servfd, (sockaddr*)&servaddr, sizeof(servaddr)) < 0)
57     {
58         perror("bind error");
59         exit(-1);
60     }
61
62     if(listen(servfd, 5) < 0)
63     {
64         perror("listen error");
65         exit(-1);
66     }
67
68     for(;;)
69     {
70         connfd = accept(servfd, (sockaddr*)nullptr, nullptr);
71         if((pid = fork()) == 0)
72         {
73             close(servfd); //减少一次引用
74             dosomething(connfd);
75             close(connfd); //在子进程中真正关闭套接字
76             exit(0);
77         }
78         close(connfd); //减少一次引用
79     }
80
81
82     return 0;
83 }

这是一个并发服务器的简单例子,它的功能是把客户端发来的字符串带上处理的进程id然后发回给客户端, 在代码71行, 我们调用fork创建了一个子进程, 这样对客户端的响应就交给了子进程。第73行我们关闭了一次servfd, 他并没有真正关闭套接字, 而仅仅减少了一次套接字的引用, 只有套接字的引用减为零才会执行四次挥手来真正关闭连接。创建子进程后, servfd的引用加了1, 如果不在这里对其关闭一次, 那么当子进程退出后, servfd的引用将无法减至0, 这将导致套接字servfd永远无法真正关闭。第78行和75行意义与此相同。

接下来给出客户端的代码:

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>

#define MAXLINE 1024
#define SERV_PORT 4333

void dosomething(FILE* fp, int sockfd)
{
    char sendline[MAXLINE], recvline[MAXLINE];
    while(fgets(sendline, MAXLINE, fp) != NULL)
    {
        write(sockfd, sendline, strlen(sendline));
        bzero(recvline, MAXLINE);
        if(read(sockfd, recvline, MAXLINE) <= 0)
        {
            perror("read error");
            exit(-1);
        }
        fputs(recvline, stdout);
    }
}

int main(int argc, char** argv)
{
    int connfd;
    sockaddr_in servaddr;

    if((connfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("create socket error");
        exit(-1);
    }

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr.s_addr);

    if((connect(connfd, (sockaddr*)&servaddr, sizeof(servaddr))) < 0)
    {
        perror("connect error");
        exit(-1);
    }
    dosomething(stdin, connfd);
    return 0;
}

编译后, 我们首先运行服务端程序, 之后我们运行两次客户端程序来看效果

至此, 我们利用fork系统调用, 实现了一个简单的并发服务器

原文地址:https://www.cnblogs.com/bzaq/p/9908568.html

时间: 2024-07-31 00:58:47

利用fork实现并发服务器的相关文章

UNIX网络编程卷1 服务器程序设计范式1 并发服务器,为每个客户请求fork一个进程

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.传统并发服务器调用 fork 派生一个子进程来处理每个客户 2.传统并发服务器的问题在于为每个客户现场 fork 一个子进程比较耗费 CPU 时间. /* include serv01 */ #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; void

TCP/IP 网络编程 (抄书笔记 3) -- 僵尸进程和多任务并发服务器

TCP/IP 网络编程 (抄书笔记 3) – 僵尸进程和多任务并发服务器 TCP/IP 网络编程 (抄书笔记 3) – 僵尸进程和多任务并发服务器 Table of Contents 僵尸进程的产生 避免僵尸进程 信号 多任务的并发服务器 僵尸进程的产生 子进程先退出, 父进程没有退出 ==> 僵尸进程 父进程先退出, 子进程没有退出 ==> 子进程被 0 号进程回收, 不会产生僵尸进程 pid_t pid = fork(); if (pid == 0) { // child printf(&

并发服务器

本文摘自<UNIX网络编程 卷1>. fork和exec函数 fork函数是Unix/Linux中派生新进程的唯一方法.其定义如下: #include <unistd.h> pid_t fork(void); // 返回:若成功则在子进程中返回0,在父进程中返回子进程ID,若出错则返回-1 fork函数调用一次,返回两次.它在调用进程(称为父进程)中返回一次,返回值是新派生进程(称为子进程)的进程ID号:在子进程中返回一次,返回值为0.因此,返回值本身告知当前进程是子进程还是父进程

Linux 并发服务器雏形总结

如下介绍一个并发回射客户端/服务器的雏形,所谓回射:就是客户端输入一条数据,服务器端读取并显示,然后服务器端再把刚读取的信息发送回客户端进行显示.示意图如下: 所谓并发服务器:就是一个服务器可以同时为多个连入的客户端提供服务,示意图如下: 如下主要介绍两种实现并发回射服务器的方式,一种是通过子进程方式实现并发,一种是通过I/O多路转接实现并发. 并发服务器(1)[子进程方式] 1 [[email protected] tcp]# cat echoserv_childprocess.c 2 #in

Linux 高并发服务器

高并发服务器 一.多进程并发服务器 1. 实现示意图 2. 使用多进程并发服务器时要考虑以下几点: 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符) 系统内创建进程个数(与内存大小相关) 进程创建过多是否降低整体服务性能(进程调度) 3. 使用多进程的方式, 解决服务器处理多连接的问题:     (1)共享 读时共享, 写时复制 文件描述符 内存映射区 -- mmap     (2)父进程 的角色是什么? 等待接受客户端连接 -- accept 有链接: 创建一

TCP并发服务器,每个客户一个子进程

在阅读完<unix 网络编程:卷一>之后,感觉作者真是unix下编程的大师级的人物.而对于我个人而言,每次阅读完一本技术书籍之后,一定还是得自己重新再写一遍程序(换点内容),复习书本中的内容(大致结构,或者说思想,相同),否则,你很难做到真的理解并掌握的地步. Okay,今天我带来的是服务器模型中的第一种,也是最基本最常用的一种模型–TCP并发服务器,每个客户一个子进程. 先简单介绍一下:TCP并发服务器,每个客户一个子进程,也就是说并发服务器调用fork派生一个子进程来处理每个子进程,使得服

利用LoadRunner判断HTTP服务器的返回状态

利用LoadRunner判断HTTP服务器的返回状态第一种方法:是利用LR的内置函数web_get_int_property.举例:#include "web_api.h"Action(){int HttpRetCode;web_url("网易",       "URL=http://www.163.com",       "TargetFrame=_TOP",       LAST);HttpRetCode = web_ge

4高并发服务器:UDP局域网服务器(组播)

 1 UDP局域网服务器 A读出每一个客户端发送过来的数据包,然后fork出子进程,由子进程去处理客户端请求. B客户端与服务器段交换多个数据报,服务器为每一个客户端连接创建新的socket,在其上bind一个临时端口,然后用该socket处理对应客户端上的所有应答,这个办法要求在客户查看服务器第一个应答中的源端口号.然后后面利用此端口号和服务器进行交互. 2函数声明 int bind(int sockfd, const struct sockaddr*addr,socklen_t addr

1高并发服务器:多进程服务器

 1多进程并发服务器 使用多进程并发服务器时要考虑以下几点: A.父最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符) B.系统内创建进程个数(和内存大小相关) C.进程创建过多是否降低整体服务性能(进程调度) 2.案例说明 server.c,代码如下: #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #i