前面的文章中介绍的服务器程序称为迭代服务器,在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