工学1号馆

home

并发服务器fork实现

By Wu Yudong on October 27, 2016

前面的文章中介绍的服务器程序称为迭代服务器,在Unix中,编写并发服务器最简单的方法是fork一个子进程来服务每一个客户。

本文地址:http://wuyudong.com/2016/10/25/2913.html,转载请注明出处。

下面所示的是一个典型的并发服务器的框架:

pid_t pid;
int listenfd, connfd;
listenfd = Socket(...);
bind(listenfd,...);
Listen(listenfd, LISTENQ);
for (;;) {
    connfd = Accept(listenfd,...); //阻塞
    if ((pid = Fork()) == 0) {
        Close(listenfd);  //子进程关闭监听套接字
        doit(connfd);  //处理请求
        Close(connfd);  //关闭连接套接字
        exit(0);  //子进程终止
    }
    Close(connfd);  //父进程关闭连接套接字
}

基本TCP套接字编程 给出了TCP网络编程的函数,本文使用那些基本函数编写一个完成的TCP客户/服务器程序示例。

该例子执行的步骤如下 :

1、客户从标准输入读入一行文本,并写给服务器。

2、服务器从网络输入读入这行文本,并回射给客户。

3、客户从网络输入读入这行回射文本,并显示在标准输出上。

TCP服务器程序:main函数

#include    "unp.h"

int
main(int argc, char **argv)
{
    int                  listenfd, connfd;
    pid_t                childpid;
    socklen_t            clilen;
    struct sockaddr_in   cliaddr, servaddr;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(SERV_PORT);

    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ);

    for ( ; ; ) {
        clilen = sizeof(cliaddr);
        connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);

        if ( (childpid = Fork()) == 0) {   
            Close(listenfd);   
            str_echo(connfd);  
            exit(0);
        }
        Close(connfd);          
    }
}

代码基本上与上述并发服务器的框架中的一致,处理请求部分通过str_echo函数实现

TCP服务器程序:str_echo函数

#include    "unp.h"

void
str_echo(int sockfd)
{
    ssize_t        n;
    char        buf[MAXLINE];

again:
    while ( (n = read(sockfd, buf, MAXLINE)) > 0)
        Writen(sockfd, buf, n);

    if (n < 0 && errno == EINTR)
        goto again;
    else if (n < 0)
        err_sys("str_echo: read error");
}

TCP客户程序:main函数

#include    "unp.h"

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

    if (argc != 2)
        err_quit("usage: tcpcli <IPaddress>");

    sockfd = Socket(AF_INET, SOCK_STREAM, 0);

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

    Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));

    str_cli(stdin, sockfd);      

    exit(0);
}

TCP服务器程序:str_cli函数

#include    "unp.h"

void
str_cli(FILE *fp, int sockfd)
{
    char    sendline[MAXLINE], recvline[MAXLINE];

    while (Fgets(sendline, MAXLINE, fp) != NULL) {
        Writen(sockfd, sendline, strlen(sendline));

        if (Readline(sockfd, recvline, MAXLINE) == 0)
            err_quit("str_cli: server terminated prematurely");

        Fputs(recvline, stdout);
    }
}

程序通过Fgets函数将标准输入写入sendline数组,接着使用write函数将sendline中的数据写入套接字,接着使用Readline函数将套接字中的数据写入recvline数组,最后使用fputs函数将recvline数组中的数据写到终端

正常启动

wu@ubuntu:~/opt/unpv13e/tcpcliserv$ ./tcpserv01&
[1] 3436
wu@ubuntu:~/opt/unpv13e/tcpcliserv$ lsof -Pnl +M -i4
COMMAND    PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
tcpserv01 3436     1000    3u  IPv4  42050      0t0  TCP *:9877 (LISTEN)
wu@ubuntu:~/opt/unpv13e/tcpcliserv$ ./tcpcli01 127.0.0.1
wu@ubuntu:~/opt/unpv13e/tcpcliserv$ lsof -Pnl +M -i4
COMMAND    PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
tcpserv01 3436     1000    3u  IPv4  42050      0t0  TCP *:9877 (LISTEN)
tcpcli01  3441     1000    3u  IPv4  42146      0t0  TCP 127.0.0.1:42751->127.0.0.1:9877 (CLOSE_WAIT)

正常终止

wu@ubuntu:~/opt/unpv13e/tcpcliserv$ ./tcpcli01 127.0.0.1
hello,wuyudong
hello,wuyudong
goodbye
goodbye
^D     <Ctrl+D>是我们的终端EOF字符
wu@ubuntu:~/opt/unpv13e/tcpcliserv$ netstat -a|grep 9877
tcp        0      0 *:9877                  *:*                     LISTEN
tcp        0      0 localhost:42758         localhost:9877          TIME_WAIT

当前连接的客户端(本地端口号为42758)进入  TIME_WAIT 状态,而监听服务器仍在等待下一个客户连接

正常终止客户和服务器的步骤:

(1) 当我们键入EOF字符时,fgets返回一个空指针,于是str_cli函数返回。

(2) 当str_cli返回到客户的main函数时,main通过调用exit终止。

(3) 进程终止处理的部分工作是关闭所有打开的描述符,因此客户打开的套接字由内核

关闭。这导致客户TCP发送一个FIN给服务器,服务器TCP则以ACK响应,这就是TCP连接终止序列的前半部分。因此服务器套接字处于CLOSE_WAIT状态,客户套接字则处于FIN_WAIT_2状态。

(4) 当服务器TCP接收FIN时,服务器子进程阻塞与readline调用,于是readline返回0,

这导致str_echo函数返回服务器子进程的main函数。

(5) 服务器子进程通过调用exit来终止。

(6) 服务器子进程中打开的所有描述符随之关闭,由子进程来关闭已连接套接字会引发TCP连接终止序列的最后两个分节:一个从服务器到客户的FIN和一个从客户到服务器的ACK。至此,连接完全终止,客户套接字进入TIME_WAIT状态。

(7) 进程终止处理的另一部分内容是:在服务器子进程终止时,给父进程发送一个SIGCHLD信号。

如果文章对您有帮助,欢迎点击下方按钮打赏作者

Comments

No comments yet.
To verify that you are human, please fill in "七"(required)