如何对回显服务器进行改进_1

上一篇中写了一个基本的回显服务器,最基本的功能是有了,但是并不够健壮,那么如何对它进行改进呢?我们需要考虑以下几种情况。

增加socket函数的错误处理

之前的程序中,使用的socket相关的api都没有进行错误判断,一旦某个函数发生错误,程序可能就会崩溃,所以我们需要给原生api包裹一层,添加错误判断,就像下面这样:

1
2
3
4
5
6
int Socket(int family, int type, int protocol){
int n;
if ( (n = socket(family, type, protocol)) < 0)
err_sys("socket error");
return(n);
}

其他的函数可以仿照这个分别写wrap包裹函数。

改写read/write函数

当read和write用在字节流套接字上时和读写普通的文件不太一样,read或write的字节数量可能会比实际的少。原因是内核中用于套接字的缓冲区已经达到极限,所以我们可能需要多次调用read/write才能完成I/O。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ssize_t readn(int fd, void *vptr, size_t n){
size_t nleft;
ssize_t nread;
char *ptr;

ptr = (char *)vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0; /* and call read() again */
else
return(-1);
} else if (nread == 0)
break; /* EOF */

nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}

僵死进程的处理

服务端进程在检测到一个连接后,就fork一个子进程来处理这个连接。
当客户端程序关闭后,系统就会检测到,然后会关闭该程序打开的所有描述符,然后给服务器发送一个FIN。
服务端fork的子进程接收到FIN后,以ACK响应。(此时服务器套接字处于CLOSE_WAIT状态,客户端套接字处于FIN_wait_2状态)
(可以通过netstat -a 命令来查看所有套接字的状态)
服务端子进程响应完ACK后,会给父进程发送一个SIGCHLD中断,但是该信号默认的行为是忽略,所以子进程就会进入僵死状态。
如果这种僵死进程特别多,就会占用大量资源,所以我们得处理这种情况。
(处理僵死进程,涉及到UNIX的信号处理,需要先对此有个了解。)
我们需要给服务端父进程添加对SIGCHLD信号的处理,其中有wait和waitpid两个函数可以用来终止子进程。
如果使用第一个的话,还有种情况会出问题,就是如果客户端一次发起了5个连接,然后客户端进程被关闭,然后服务端的5个子进程会几乎同时收到这个消息,然后同时给父进程发送5个SIGCHLD信号。
由于wait没有排队机制,只能处理一个,其他几个子进程仍可能会变成僵死进程。所以需要使用waitpid,需要如下三步操作:
1.编写wrap函数Signal():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Sigfunc * signal(int signo, Sigfunc *func){
struct sigaction act, oact;

act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}

2.在accept前调用处理信号的函数:

1
Signal(SIGCHLD,sig_chld);

3.编写sig_chld函数:

1
2
3
4
5
6
7
8
void sig_chld(int signo){
pid_t pid;
int stat;
while( (pid=waitpid(-1,&stat,WNOHANG)) > 0){
std::cout<<"child "<<pid<<" terminated"<<std::endl;
}
return ;
}

客户服务器之间传递二进制结构

之前的代码都是传送字符串,不会有什么问题。但在不同的系统上,字节序可能不一样,所以传送二进制数据时(比如int型数值)可能无法得到正确的数据。
第一个常用的方法是把数值转为文本串来传递。第二种是显式地指出所支持的数据类型的二进制格式,包括位数,大端或小端。

其他问题

除了以上几个问题,以下几个问题现在还无法解决,需要学习其他知识后才能来解决。

  • 三路握手建立连接后,客户TCP发送了一个RST复位
  • 在两者正常通信时,服务器子进程被杀死,这时候客户端正阻塞在fgets函数上,无法马上作出反应
  • 服务器子进程被杀死后,服务器主机会给客户端发送FIN,然后客户端会关闭对应套接字,这时候客户端进程并不知道,如果继续向该套接字写入内容,那么会收到系统发出的SIGPIPE信号(默认操作是杀死进程)。
  • 服务器主机崩溃时(不是进程崩溃,也不是执行关机命令)。
  • 服务器主机崩溃后重启,此时再收到客户端发送的信息,会给客户端返回RST,然后导致正阻塞在redline的客户返回ECONNRESET错误。
  • 服务器主机关机,客户端应当能立马知道(跟服务器子进程被杀死时类似)

目前三个文件内容如下

  • wrapfun.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    #ifndef WRAP_FUN
    #define WRAP_FUN

    #include <iostream>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <sys/wait.h>

    typedef void Sigfunc(int);

    void err_sys(const char * str){
    std::cout<<str<<std::endl;
    return ;
    }

    int Socket(int family, int type, int protocol)
    {
    int n;

    if ( (n = socket(family, type, protocol)) < 0)
    err_sys("socket error");
    return(n);
    }

    int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
    {
    int n;

    again:
    if ( (n = accept(fd, sa, salenptr)) < 0) {
    #ifdef EPROTO
    if (errno == EPROTO || errno == ECONNABORTED)
    #else
    if (errno == ECONNABORTED)
    #endif
    goto again;
    else
    err_sys("accept error");
    }
    return(n);
    }

    void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
    {
    if (bind(fd, sa, salen) < 0)
    err_sys("bind error");
    }

    void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
    {
    if (connect(fd, sa, salen) < 0)
    err_sys("connect error");
    }

    void Listen(int fd, int backlog)
    {
    char *ptr;

    /*4can override 2nd argument with environment variable */
    if ( (ptr = getenv("LISTENQ")) != NULL)
    backlog = atoi(ptr);

    if (listen(fd, backlog) < 0)
    err_sys("listen error");
    }

    ssize_t /* Read "n" bytes from a descriptor. */
    readn(int fd, void *vptr, size_t n)
    {
    size_t nleft;
    ssize_t nread;
    char *ptr;

    ptr = (char *)vptr;
    nleft = n;
    while (nleft > 0) {
    if ( (nread = read(fd, ptr, nleft)) < 0) {
    if (errno == EINTR)
    nread = 0; /* and call read() again */
    else
    return(-1);
    } else if (nread == 0)
    break; /* EOF */

    nleft -= nread;
    ptr += nread;
    }
    return(n - nleft); /* return >= 0 */
    }
    /* end readn */

    ssize_t
    Readn(int fd, void *ptr, size_t nbytes)
    {
    ssize_t n;

    if ( (n = readn(fd, ptr, nbytes)) < 0)
    err_sys("readn error");
    return(n);
    }

    ssize_t /* Write "n" bytes to a descriptor. */
    writen(int fd, const void *vptr, size_t n)
    {
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;

    ptr = (const char *)vptr;
    nleft = n;
    while (nleft > 0) {
    if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
    if (nwritten < 0 && errno == EINTR)
    nwritten = 0; /* and call write() again */
    else
    return(-1); /* error */
    }

    nleft -= nwritten;
    ptr += nwritten;
    }
    return(n);
    }
    /* end writen */

    void
    Writen(int fd, void *ptr, size_t nbytes)
    {
    if (writen(fd, ptr, nbytes) != nbytes)
    err_sys("writen error");
    }

    ssize_t
    readline(int fd, void *vptr, size_t maxlen)
    {
    ssize_t n, rc;
    char c, *ptr;

    ptr = (char*)vptr;
    for (n = 1; n < maxlen; n++) {
    again:
    if ( (rc = read(fd, &c, 1)) == 1) {
    *ptr++ = c;
    if (c == '\n')
    break; /* newline is stored, like fgets() */
    } else if (rc == 0) {
    *ptr = 0;
    return(n - 1); /* EOF, n - 1 bytes were read */
    } else {
    if (errno == EINTR)
    goto again;
    return(-1); /* error, errno set by read() */
    }
    }

    *ptr = 0; /* null terminate like fgets() */
    return(n);
    }
    /* end readline */

    ssize_t
    Readline(int fd, void *ptr, size_t maxlen)
    {
    ssize_t n;

    if ( (n = readline(fd, ptr, maxlen)) < 0)
    err_sys("readline error");
    return(n);
    }

    Sigfunc * signal(int signo, Sigfunc *func){
    struct sigaction act, oact;

    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if (signo == SIGALRM) {
    #ifdef SA_INTERRUPT
    act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
    #endif
    } else {
    #ifdef SA_RESTART
    act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
    #endif
    }
    if (sigaction(signo, &act, &oact) < 0)
    return(SIG_ERR);
    return(oact.sa_handler);
    }
    /* end signal */

    Sigfunc *
    Signal(int signo, Sigfunc *func) /* for our signal() function */
    {
    Sigfunc *sigfunc;

    if ( (sigfunc = signal(signo, func)) == SIG_ERR)
    err_sys("signal error");
    return(sigfunc);
    }


    #endif
  • echoserv.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    #include <iostream>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <cstring>
    #include "wrapfun.h"
    #include "unp.h"

    using namespace std;

    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){
    cout<<"str_echo: read error"<<endl;
    }
    }

    void sig_chld(int signo){
    pid_t pid;
    int stat;
    while( (pid=waitpid(-1,&stat,WNOHANG)) > 0){
    std::cout<<"child "<<pid<<" terminated"<<std::endl;
    }
    return ;
    }

    int main(){
    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);
    Signal(SIGCHLD,sig_chld);
    int i=0;
    for(;;){
    clilen=sizeof(cliaddr);
    connfd=Accept(listenfd,(SA*)&cliaddr,&clilen);
    if((childpid=fork())==0){
    close(listenfd);
    str_echo(connfd);
    exit(0);
    }
    close(connfd);
    i++;
    cout<<i<<" "<<endl;
    }


    return 0;
    }
  • echoclie.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #include <iostream>
    #include "wrapfun.h"
    #include "unp.h"

    using namespace std;

    int main(int argc,char **argv){
    int i,sockfd[1005];
    struct sockaddr_in servaddr;
    if(argc!=2){
    cout<<"usage:echoclie <ip addr>"<<endl;
    return 0;
    }
    for(i=0;i<10;i++){
    sockfd[i]=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[i],(SA*)&servaddr,sizeof(servaddr));
    }
    str_cli(stdin,sockfd[0]);
    return 0;
    }

    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){
    if(Readline(sockfd,recvline,MAXLINE)<0){
    cout<<"str_cli:server terminated prematurely"<<endl;
    }
    fputs(recvline,stdout);
    }
    }

参考

  • 《UNIX网络编程(卷一第三版)》

欢迎与我分享你的看法。
转载请注明出处:http://taowusheng.cn/