UNP总结 Chapter 5 TCP客户/服务器程序实例

1.概述

这章的TCP客户/服务器模型

2.TCP回射服务器程序

1).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) {                  /* child process */
            Close(listenfd);                              /* close listening socket */
            str_echo(connfd);                             /* process the request */
            exit (0);
        }
        Close(connfd);                                    /* parent closes connected socket */
     }
}

 

2).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");
}

 

 

 

3.TCP回射客户程序

1).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);     /* do it all */

    exit(0);
}

 

2).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);
    }
}

 

 

 

4.POSIX 信号处理

每个信号都有一个处理办法(disposition),也称为与此信号关联的行为(action)。我们通过调用函数sigaction来设置一个信号的处理办法。
1).可以提供一个函数,在信号发生时随即调用。这个函数称为信号处理函数(signal handler),而此行为便称为捕获(catching)信号,有两个信号不能捕获SIGKILL和SIGSTOP,函数由信号值这一单一参数来调用且无返回值,函数原型为

void handler(int signo);

信号SIGIO,SIGPOLL,SIGURG还要求捕获它的进程有其它动作。

2).可以通过设置信号的处理办法为SIG_IGN来忽略它,但是SIGKILL和SIGSTOP不能忽略。

3).可以设置信号的处理办法为SIG_DFL来为它设置缺省处理办法

函数signal的函数原型层次复杂

void ( * signal (int signo, void ( * func)(int) ) )(int);

用typedef简化函数原型

typedef void Sigfunc(int); // 它说明信号处理程序是带有一个整形参数且无返回值的函数

这样signal的函数原型就变为

Sigfunc * signal (int signo, Sigfunc * func); // 此函数的第二个参数和返回值都是指向信号处理函数的指针

 

5.处理SIGCHLD信号

设置僵尸(Zombie)状态的目的就是维护子进程的信息,以便父进程在稍后的某个时候取回。如果一个进程终止,且该进程有子程序处于僵尸状态,则所有僵尸子进程的父进程ID均置为1(init进程),init进程将作为这些子进程的继父,并负责清除他们(也就是说,init进程将wait它们,从而去除僵尸进程),有些Unix系统给僵尸进程输出的COMMAND列为<defunct>(ps命令输出)。

另外:

  • 如果fork子进程,那么就要wait它们,以防止它们变成僵死进程。
  • 捕获SIGCHLD信号,并在信号处理函数中wait子进程,我们始终应该调用waitpid而非wait来处理子进程
  • 我们始终应该检查慢系统调用是否返回EINTR错误。并决定是否重启这些系统调用。(一些系统会自动重启被中断的系统调用)。
  • connect不能被重启,当connect函数被信号中断且不自动重启时,我们必须调用select来等待连接完成

 

 

 

6.wait和waitpid函数

可以调用如下两个函数处理已终止的子进程

#include <sys/wait.h>
 
pid_t wait (int *statloc);
 
pid_t waitpid (pid_t pid, int *statloc, int options);
//返回值:成功返回进程ID,出错返回返回0或-1;

对于参数pid 想等待的进程ID号。-1表示等待第一个结束的子进程,options附加选项,常用的是WNOHANG,告知内核在没有以终止子进程时不要阻塞

函数wait和waitpid均返回两个值: 函数的返回值是终止子进程的进程ID号,子进程的终止状态(一个整数)则是通过指针statloc返回的。

wait和waitpid的区别: wait 等待第一个结束的子进程,如果没有结束的子进程,wait将阻塞。waitpid 通过参数设置,可以在没有子进程结束时waitpid不阻塞

 

 

7.accept返回前连接终止

 Berkeley 的实现在内核中处理终止的连接。POSIX 规定返回一个ECONNABORTED 的 errno(详见UNP3)

 

 

8.服务进程终止

 如果向一个服务进程已终止的服务器发起连接,服务器将返回一个RST 信号

 PS RST:(Reset the connection)用于复位因某种原因引起出现的错误连接,也用来拒绝非法数据和请求

 

9.SIGPIPE信号

  • 当一个进程向某个已收到RST的套接字执行写操作时,内核向该进程发送一个SIGPIPE信号,
  • SIGPIPE信号的默认行为是终止进程

 

10.服务器主机崩溃

如果,客户端和服务器已经建立了连接的时候,此时服务器崩溃(达到这一标准可以把服务器的网线拔掉,这个时候,服务器就不能发送FIN数据报了,和关机不一样的)

  • 如果这时客户端向服务器发送数据的时候,因为服务器已经不存在了,那么客户端就不能接受到服务器给客户端的ack信息,这个时候,客户端建立的是TCP连接,就会重发数据报,而服务器对客户的数据分节根本没有响应,那么所返回的错误就是ETIMEDOUT。
  • 如果中间某个路由判断目的主机不可到达,从而响应一个"destination ETINEDOUT"(目的地不可达)ICMP消息,所返回的错误是EHOSTUNREACH或者ENETUNREACH

 

11.服务器主机崩溃后重启

当客户端和服务器已经建立连接的时候,服务器发生崩溃,重新启动的时候,丢失了原来和客户端的连接信息,这个时候,当客户端向服务器发送数据的时候(客户端并不知道,服务器已经忘记三次握手了),此时服务器发送RST数据报,就结束了客户端的发送

 

12.服务器主机关机

 Unix系统关机时,init进程通常先给所有进程发送SIGTERM信号。等待5-20秒后给所有仍然在运行的进程发送SIGKILL信号,这么做的目的是给进程一小段时间来清除和终止。

 

13.TCP程序例子小结

需要通信的客户/服务器程序在通信之前都要指定套接字对:本地IP地址,本地端口号,外地IP地址,外地端口。

客户程序的本地IP地址和本地端口号通常是内核分配。服务程序的本地IP地址和端口号有bind函数指定

 

14.数据格式

网络传递数据存在三个潜在问题:

(1)不同的实现以不同的格式存储二进制数,最常见的是大端字节序和小端字节序。

(2)不同的实现在存储相同的C数据类型上可能存在差异,例如32位系统中的long 为32位,64位系统中的long为64位。

(3)不同的实现给结构打包的方式存在差异,取决于各种数据类型所用的位数以及机器的对齐限制,因此,穿越套接字传送二进制结构绝不明智。

解决上述问题的两个常用方法:

(1)把所有的数值数据作为文本串来传递,前提是客户和服务器机器具有相同的字符集。

(2)显式定义所支持数据类型的二进制格式(位数,大端或小端字节序),并以这样的格式在客户与服务器之间传递所有数据。

 

 

posted on 2012-08-05 17:58  as_  阅读(746)  评论(0编辑  收藏  举报

导航