zoukankan      html  css  js  c++  java
  • 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)显式定义所支持数据类型的二进制格式(位数,大端或小端字节序),并以这样的格式在客户与服务器之间传递所有数据。

  • 相关阅读:
    MYSQL数据丢失讨论【转】
    MySQL 5.6 新功能之 Index Condition Pushdown (ICP)
    MySQL Binlog 【ROW】和【STATEMENT】选择
    MySQL open_files_limit相关设置
    Python颜色输出和random的学习
    Python 之 【二进制、十进制、八进制、十六进制 】之间的转换【转】
    初识 MySQL 5.5 新功能、参数
    Oracle Inventory关键词解释
    plsql 输出当月的所有日期
    ORA29279: SMTP permanent error: 550 XXX@XX.com... No such user
  • 原文地址:https://www.cnblogs.com/biyeymyhjob/p/2624007.html
Copyright © 2011-2022 走看看