Linux系统编程(33)—— socket编程之TCP程序的错误处理

时间:2021-01-24 01:10:30

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

为使错误处理的代码不影响主程序的可读性,我们把与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字节的文件名用'\0'补齐,从第13字节开始是文件内容,上传完所有文件内容后关闭连接,服务器可以先调用readn读12个字节,根据文件名创建文件,然后在一个循环中调用read读文件内容并存盘,循环结束的条件是read返回0。

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

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

因此,常见的应用层协议都是带有可变长字段的,字段之间的分隔符用换行的比用'\0'的更常见,例如本节后面要介绍的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 == '\n')
break;
}else if (rc == 0) {
*ptr= 0;
returnn - 1;
}else
return-1;
}
*ptr = 0;
returnn;
}

Linux系统编程(33)—— socket编程之TCP程序的错误处理的更多相关文章

  1. Linux系统编程(30)—— socket编程之TCP&sol;IP协议

    在世界上各地,各种各样的电脑运行着各自不同的操作系统为大家服务,这些电脑在表达同一种信息的时候所使用的方法是千差万别.就好像圣经中上帝打乱了各地人的口音,让他们无法合作一样.计算机使用者意识到,计算机 ...

  2. (54)LINUX应用编程和网络编程之九Linux网络通信实践

    3.9.1.linux网络编程框架 3.9.1.1.网络是分层的 (1)OSI 7层模型(理论指导) (2)网络为什么要分层 (3)网络分层的具体表现 3.9.1.2.TCP/IP协议引入(网络分层实 ...

  3. Linux系统编程:socket网络编程(操作篇)

    一.问题思考 问1.网络通信应用在什么场合?通信的前提是什么? 答1.主要应用在不同主机进程间的互相通信,同一主机的进程也可以使用网络进行通信.通信的前提是如何标识通信进程的唯一,由于不同主机的进程极 ...

  4. (47)LINUX应用编程和网络编程之二Linux文件属性

    Linux下的文件系统为树形结构,入口为/ 树形结构下的文件目录: 无论哪个版本的Linux系统,都有这些目录,这些目录应该是标准的.各个Linux发行版本会存在一些小小的差异,但总体来说,还是大体差 ...

  5. Linux系统编程(35)—— socket编程之TCP服务器的并发处理

    我们知道,服务器通常是要同时服务多个客户端的,如果我们运行上一篇实现的server和client之后,再开一个终端运行client试试,新的client就不能能得到服务了.因为服务器之支持一个连接. ...

  6. Linux系统编程(34)—— socket编程之TCP服务器与客户端的交互

    前面几篇中实现的client每次运行只能从命令行读取一个字符串发给服务器,再从服务器收回来,现在我们把它改成交互式的,不断从终端接受用户输入并和server交互. /* client.c */ #in ...

  7. Linux系统编程(32)—— socket编程之TCP服务器与客户端

    TCP协议的客户端/服务器程序的一般流程 服务器调用socket().bind().listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后, ...

  8. Linux系统编程(31)—— socket编程之TCP详解

    TCP有源端口号和目的端口号,通讯的双方由IP地址和端口号标识.32位序号.32位确认序号.窗口大小稍后详细解释.4位首部长度和IP协议头类似,表示TCP协议头的长度,以4字节为单位,因此TCP协议头 ...

  9. linux socket编程之TCP与UDP

    转:http://blog.csdn.net/gaoxin1076/article/details/7262482 TCP/IP协议叫做传输控制/网际协议,又叫网络通信协议 TCP/IP虽然叫传输控制 ...

随机推荐

  1. 在IIS上部署SSL

    背景: 在处理DropboxAPI开发时,其重定向的URL地址必须是https的[除了localhost],不得已在自己网站上加了ssl,下面简单介绍下添加自签名证书,毕竟只是临时使用. 1.打开II ...

  2. nginx下禁止访问robots&period;txt的设置方法

    关于robots.txt文件:搜索引擎通过一种程序robot(又称spider),自动访问互联网上的网页并获取网页信 息.您可以在您的网站中创建一个纯文本文件robots.txt,在这个文件中声明该网 ...

  3. WPF之TextBox

    1. TextBox实现文字垂直居中 TextBox纵向长度比较长但文字字体比较小的时候,在输入时就会发现文字不是垂直居中的. 而使用中我们发现,TextBox虽然可以设置文字的水平对齐方式,但却没有 ...

  4. Elasticsearch 5&period;0

    Elasticsearch 5.0 使用ES的基本都会使用过head,但是版本升级到5.0后,head插件就不好使了.下面就看看如何在5.0中启动Head插件吧! 官方粗略教程 Running wit ...

  5. 水池(DFS)

    水池数目 点我 描述 南阳理工学院校园里有一些小河和一些湖泊,现在,我们把它们通一看成水池,假设有一张我们学校的某处的地图,这个地图上仅标识了此处是否是水池,现在,你的任务来了,请用计算机算出该地图中 ...

  6. base库中的BarrierClosure

    说明说得很明白,就是等侍num_closures 为零的时候回调done_closure,代码也很简单,不加详述 #ifndef BASE_BARRIER_CLOSURE_H_ #define BAS ...

  7. iOS 7 标签栏控制器进行模态视图跳转后变成透明

    要解决此问题,需要设置tabBar的如下属性: self.tabBar.translucent = NO;

  8. usb host鼠标不能使用原因

    linux kernel 3.4.5的板子插入USB鼠标,出现例如以下错误LOG: [  191.177508] Plug in USB Port2 [  191.363516] usb 1-1: n ...

  9. 使用EF扩展EntityFramework&period;BulkInsert实现批量插入

    EntityFramework 最被人诟病的地方就是它的性能,处理大量数据时的效率.此种条件下,通常会转回使用 ADO.NET 来完成任务.而EntityFramework.BulkInsert则是利 ...

  10. python页面解析&lowbar;beautifulsoup试玩

      最近玩爬虫,先把python解析器 beautifulsoup 练练 这个 tainiu.html 是从百度网盘里拷贝一段html from bs4 importBeautifulSoup wit ...