zoukankan      html  css  js  c++  java
  • Linux系统编程(33)—— socket编程之TCP程序的错误处理

    上一篇的例子不仅功能简单,而且简单到几乎没有什么错误处理,我们知道,系统调用不能保证每次都成功,必须进行出错处理,这样一方面可以保证程序逻辑正常,另一方面可以迅速得到故障信息。

    为使错误处理的代码不影响主程序的可读性,我们把与socket相关的一些系统函数加上错误处理代码包装成新的函数,做成一个模块wrap.c:

    #include <stdlib.h>
    #include <errno.h>
    #include <sys/socket.h>
     
    void perr_exit(const char *s)
    {
             perror(s);
             exit(1);
    }
     
    int Accept(int fd, struct sockaddr *sa,socklen_t *salenptr)
    {
             intn;
     
    again:
             if( (n = accept(fd, sa, salenptr)) < 0) {
                       if((errno == ECONNABORTED) || (errno == EINTR))
                                gotoagain;
                       else
                                perr_exit("accepterror");
             }
             returnn;
    }
     
    void Bind(int fd, const struct sockaddr*sa, socklen_t salen)
    {
             if(bind(fd, sa, salen) < 0)
                      perr_exit("bind error");
    }
     
    void Connect(int fd, const struct sockaddr*sa, socklen_t salen)
    {
             if(connect(fd, sa, salen) < 0)
                       perr_exit("connecterror");
    }
     
    void Listen(int fd, int backlog)
    {
             if(listen(fd, backlog) < 0)
                       perr_exit("listenerror");
    }
     
    int Socket(int family, int type, intprotocol)
    {
             intn;
     
             if( (n = socket(family, type, protocol)) < 0)
                       perr_exit("socketerror");
             returnn;
    }
     
    ssize_t Read(int fd, void *ptr, size_tnbytes)
    {
             ssize_tn;
     
    again:
             if( (n = read(fd, ptr, nbytes)) == -1) {
                       if(errno == EINTR)
                                gotoagain;
                       else
                                return-1;
             }
             returnn;
    }
     
    ssize_t Write(int fd, const void *ptr,size_t nbytes)
    {
             ssize_tn;
     
    again:
             if( (n = write(fd, ptr, nbytes)) == -1) {
                       if(errno == EINTR)
                                gotoagain;
                       else
                                return-1;
             }
             returnn;
    }
     
    void Close(int fd)
    {
             if(close(fd) == -1)
                       perr_exit("closeerror");
    }


    慢系统调用accept、read和write被信号中断时应该重试。connect虽然也会阻塞,但是被信号中断时不能立刻重试。对于accept,如果errno是ECONNABORTED,也应该重试。详细解释见参考资料。

    TCP协议是面向流的,read和write调用的返回值往往小于参数指定的字节数。对于read调用,如果接收缓冲区中有20字节,请求读100个字节,就会返回20。对于write调用,如果请求写100个字节,而发送缓冲区中只有20个字节的空闲位置,那么write会阻塞,直到把100个字节全部交给发送缓冲区才返回,但如果socket文件描述符有O_NONBLOCK标志,则write不阻塞,直接返回20。为避免这些情况干扰主程序的逻辑,确保读写我们所请求的字节数,我们实现了两个包装函数readn和writen,也放在wrap.c中:

    ssize_t Readn(int fd, void *vptr, size_t n)
    {
             size_t  nleft;
             ssize_tnread;
             char   *ptr;
     
             ptr= vptr;
             nleft= n;
             while(nleft > 0) {
                       if( (nread = read(fd, ptr, nleft)) < 0) {
                                if(errno == EINTR)
                                         nread= 0;
                                else
                                         return-1;
                       }else if (nread == 0)
                                break;
     
                       nleft-= nread;
                       ptr+= nread;
             }
             returnn - nleft;
    }
     
    ssize_t Writen(int fd, const void *vptr,size_t n)
    {
             size_tnleft;
             ssize_tnwritten;
             constchar *ptr;
     
             ptr= vptr;
             nleft= n;
             while(nleft > 0) {
                       if( (nwritten = write(fd, ptr, nleft)) <= 0) {
                                if (nwritten < 0 && errno ==EINTR)
                                         nwritten= 0;
                                else
                                         return-1;
                       }
     
                       nleft-= nwritten;
                       ptr+= nwritten;
             }
             returnn;
    }


    如果应用层协议的各字段长度固定,用readn来读是非常方便的。例如设计一种客户端上传文件的协议,规定前12字节表示文件名,超过12字节的文件名截断,不足12字节的文件名用''补齐,从第13字节开始是文件内容,上传完所有文件内容后关闭连接,服务器可以先调用readn读12个字节,根据文件名创建文件,然后在一个循环中调用read读文件内容并存盘,循环结束的条件是read返回0。

    字段长度固定的协议往往不够灵活,难以适应新的变化。比如,以前DOS的文件名是8字节主文件名加“.”加3字节扩展名,不超过12字节,但是现代操作系统的文件名可以长得多,12字节就不够用了。那么制定一个新版本的协议规定文件名字段为256字节怎么样?这样又造成很大的浪费,因为大多数文件名都很短,需要用大量的''补齐256字节,而且新版本的协议和老版本的程序无法兼容,如果已经有很多人在用老版本的程序了,会造成遵循新协议的程序与老版本程序的互操作性(Interoperability)问题。如果新版本的协议要添加新的字段,比如规定前12字节是文件名,从13到16字节是文件类型说明,从第17字节开始才是文件内容,同样会造成和老版本的程序无法兼容的问题。

    现在重新看看上一节的TFTP协议是如何避免上述问题的:TFTP协议的各字段是可变长的,以''为分隔符,文件名可以任意长,再看blksize等几个选项字段,TFTP协议并没有规定从第m字节到第n字节是blksize的值,而是把选项的描述信息“blksize”与它的值“512”一起做成一个可变长的字段,这样,以后添加新的选项仍然可以和老版本的程序兼容(老版本的程序只要忽略不认识的选项就行了)。

    因此,常见的应用层协议都是带有可变长字段的,字段之间的分隔符用换行的比用''的更常见,例如本节后面要介绍的HTTP协议。可变长字段的协议用readn来读就很不方便了,为此我们实现一个类似于fgets的readline函数,也放在wrap.c中:

    static ssize_t my_read(int fd, char *ptr)
    {
             staticint read_cnt;
             staticchar *read_ptr;
             staticchar read_buf[100];
     
             if(read_cnt <= 0) {
             again:
                       if( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
                                if(errno == EINTR)
                                         gotoagain;
                                return-1;
                       }else if (read_cnt == 0)
                                return0;
                       read_ptr= read_buf;
             }
             read_cnt--;
             *ptr= *read_ptr++;
             return1;
    }
     
    ssize_t Readline(int fd, void *vptr, size_tmaxlen)
    {
             ssize_tn, rc;
             char    c, *ptr;
     
             ptr= vptr;
             for(n = 1; n < maxlen; n++) {
                       if( (rc = my_read(fd, &c)) == 1) {
                                *ptr++= c;
                                if(c  == '
    ')
                                         break;
                       }else if (rc == 0) {
                                *ptr= 0;
                                returnn - 1;
                       }else
                                return-1;
             }
             *ptr  = 0;
             returnn;
    }
     

  • 相关阅读:
    使用H5Stream实现rtsp流播放,并整合到web项目中
    浏览器通过RTSP协议取流实时显示在web页面(海康威视大华摄像机实时监控)
    Python-----获取excel的所有sheet页,并获取每个sheet页的内容
    MySQL表结构导出成Excel
    Hive 是什么?场景? vs RDBMS
    Scala “_” 的用法总结
    Hadoop主要组件知识点梳理
    javaIO:RandomAccessFile
    javaIO:IO和File
    java io 详细代码实现 纪录
  • 原文地址:https://www.cnblogs.com/new0801/p/6176968.html
Copyright © 2011-2022 走看看