网络编程实战
盛延敏
前大众点评云平台首席架构师
44207 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 40 讲
网络编程实战
15
15
1.0x
00:00/00:00
登录|注册

18 | 防人之心不可无:检查数据的有效性

读取字符时的处理
一次性读取最多512字节到临时缓冲区
超时后的处理
设置超时时间
读取字符时的长度处理
读取字符的效率问题
对报文长度进行判断
修改读取字符的方式
利用多路复用技术自带的超时能力
添加对连接是否正常的检测
给套接字的read操作设置超时
调用shutdown关闭连接的一端
文章中的例子所分配的缓冲是否可以动态分配
给应用程序最终缓冲区分配大小的讲究
异常边界的检测对程序稳定性的影响
缓冲区溢出的问题
读操作无法感知异常的情况处理
read函数返回0字节时的处理
思考题
总结
缓冲区处理
对端的异常状况
防人之心不可无:检查数据的有效性

该思维导图由 AI 生成,仅供参考

你好,我是盛延敏,这里是网络编程实战第 18 讲,欢迎回来。
在前面一讲中,我们仔细分析了引起故障的原因,并且已经知道为了应对可能出现的各种故障,必须在程序中做好防御工作。
在这一讲里,我们继续前面的讨论,看一看为了增强程序的健壮性,我们还需要准备什么。

对端的异常状况

在前面的第 11 讲以及第 17 讲中,我们已经初步接触过一些防范对端异常的方法,比如,通过 read 等调用时,可以通过对 EOF 的判断,随时防范对方程序崩溃。
int nBytes = recv(connfd, buffer, sizeof(buffer), 0);
if (nBytes == -1) {
error(1, errno, "error read message");
} else if (nBytes == 0) {
error(1, 0, "client closed \n");
}
你可以看到这一个程序中的第 4 行,当调用 read 函数返回 0 字节时,实际上就是操作系统内核返回 EOF 的一种反映。如果是服务器端同时处理多个客户端连接,一般这里会调用 shutdown 关闭连接的这一端。
上一讲也讲到了,不是每种情况都可以通过读操作来感知异常,比如,服务器完全崩溃,或者网络中断的情况下,此时,如果是阻塞套接字,会一直阻塞在 read 等调用上,没有办法感知套接字的异常。
其实有几种办法来解决这个问题。
第一个办法是给套接字的 read 操作设置超时,如果超过了一段时间就认为连接已经不存在。具体的代码片段如下:
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, (const char *) &tv, sizeof tv);
while (1) {
int nBytes = recv(connfd, buffer, sizeof(buffer), 0);
if (nBytes == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("read timeout\n");
onClientTimeout(connfd);
} else {
error(1, errno, "error read message");
}
} else if (nBytes == 0) {
error(1, 0, "client closed \n");
}
...
}
这个代码片段在第 4 行调用 setsockopt 函数,设置了套接字的读操作超时,超时时间为在第 1-3 行设置的 5 秒,当然在这里这个时间值是“拍脑袋”设置的,比较科学的设置方法是通过一定的统计之后得到一个比较合理的值。关键之处在读操作返回异常的第 9-11 行,根据出错信息是EAGAIN或者EWOULDBLOCK,判断出超时,转而调用onClientTimeout函数来进行处理。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了网络编程中数据有效性的重要性以及相关技术细节。首先,文章提到了防范对端异常的方法,包括设置套接字的读操作超时和对连接是否正常的检测,以及利用多路复用技术自带的超时能力。其次,强调了缓冲区处理的重要性,指出设计良好的网络程序应该能够在随机输入的情况下表现稳定,并且需要时刻提醒自己面对各种复杂异常的场景,甚至是别有用心的攻击者。最后,通过具体的代码片段和实例,帮助读者更好地理解了网络编程中数据有效性的重要性和相关技术细节。文章内容深入浅出,通过具体的代码片段和实例,帮助读者更好地理解了网络编程中数据有效性的重要性和相关技术细节。文章内容深入浅出,通过具体的代码片段和实例,帮助读者更好地理解了网络编程中数据有效性的重要性和相关技术细节。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《网络编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(25)

  • 最新
  • 精选
  • LDxy
    1,最终缓冲区的大小应该比预计接收的数据大小大一些,预防缓冲区溢出。2,完全可以动态分配,但是要记得在return前释放缓冲区

    作者回复: 👍

    2019-09-12
    33
  • 郑祖煌
    (1).第一道,我们在读数据的时候,一般都需要给应用程序最终缓冲区分配大小,这个大小有什么讲究吗? 有讲究的。如果分配的太小,那就会频繁的从用户太切换到内核态,这样其实非常损耗CPU的时间。同时如果设置的太大的话,那就会长期阻塞在read或者recv函数上,造成可以先服务或者先完成的内容没完成。再次,也得比实际的数据稍微大一些以免缓冲区溢出,边界的问题要想办法做好的调整。。 (2). 第二道,你能分析一下,我们文章中的例子所分配的缓冲是否可以换成动态分配吗?比如调用 malloc 函数来分配缓冲区?是可以动态分配,就是new的话要记得及时的去delete,以免造成内存泄露。

    作者回复: 回答的这么赞,我还能说啥呢 :)

    2020-07-02
    21
  • 超大红细胞
    一开始不理解为什么设置了 timeout 的 recv 会返回 EAGAIN 错误,在我的知识体系中 EAGAIN 一般出现在非阻塞的 socket 中,后来 man 了一下 SO_RCVTIMEO,发现确实如此,给后面的同学提个醒: Specify the receiving or sending timeouts until reporting an error. The argument is a struct timeval. If an input or output function blocks for this period of time, and data has been sent or received, the return value of that function will be the amount of data transferred; if no data has been transferred and the timeout has been reached then -1 is returned with errno set to EAGAIN or EWOULDBLOCK, or EINPROGRESS (for connect(2)) just as if the socket was specified to be nonblocking. 总之一句话,SO_RCVTIMEO 会导致 recv 返回 EAGAIN

    作者回复: 👍

    2020-01-11
    16
  • 郭晓朋
    你好,对于最后一个例子我感觉好像有问题,--length这种写法会读不到012345678\n这个字符串的,最终导致读到的字符串没有结束符\0。length--导致越界的原因是*buffer++ = c;。不应该先执行buffer++,应该放到if语句之后。

    作者回复: 你是说样例的readline程序有问题么?

    2021-01-28
    2
    2
  • J.M.Liu
    老师,第二个例子中,及时加上了msg_length和缓冲区length的大小比较,如果msg_length写得很大(但小于length)而实际数据没有那么大时,服务器也会阻塞在read上吧?所以说判断msg_length<=length并不能接read阻塞的问题呀,只能解内存溢出的问题。

    作者回复: 是的,如果是这样,只能说我们双方的通信协议没有得到严格的遵守。

    2019-09-12
    2
  • 纪神籽
    第三个例子有点疑问,如果第一次调用readline,读取512字节,查到第100字节是换行符,然后就返回结果,这样子剩下的412字节不就会丢失了。是不是应该先把上次readline的数据处理完在进行新的recv。

    作者回复: 你说的没错,是需要把剩下的412字节保存下来的。这个例子只是对readline该注意的事项进行了模拟分析。

    2022-03-31
    1
  • 黄毅
    关于思考题的第二个问题,有个疑问,如果通过动态分配read_buffer,假设recv能读取512个字节,进一步假设第200个字符是\n,那么在read_line退出前能delete吗?如果delete的话,会不会第201到512字节的数据丢失?

    作者回复: 这要看你的程序是怎么设计的,如果是预先读取了512字节,那确实200后的数据,你得想办法重新"缓冲"起来,以便和后面的数据拼凑成完整意义的字节流;如果你是一个一个字节读的,知道读到\n,那么则不必了。 一般情况下,我们是用缓冲来做这件事情,你可以跳到后面的Buffer设计部分,通过指针来指向当前消费的位置,和数据读取的位置做一个比较,就可以不用每次重新生成动态分配的buffer。

    2021-04-10
    1
  • 无名氏
    临时缓存区,那个“微小瑕疵”,前后两段程序程序没有区别啊😄?

    作者回复: 你细品: int nBytes = recv(connfd, buffer, sizeof(buffer), 0); int nBytes = recv(connfd, buffer, sizeof(buffer)-1, 0);

    2022-05-22
  • 苏志辉
    第三个例子修改后的版本,如果length为10发送的也是012345678\n一共10个,由于--length,所以一次最多读9个,要读完完整的消息,需要读两次吧

    作者回复: 读取的时候,是一次性尝试读取最多 512 字节。 最外面的length循环实际上控制的是拷贝临时缓冲区字符的工作。 所以,还是读取一次。

    2021-10-29
  • 吃猫的鱼
    第三个例子中,如果recv读取出的数据格式如下 “xxx\n yyy\n”,然后length=4,此时在不就只读出 “xxx\n”,但是下一次调用recv,“yyy\n”没被应用就被丢弃了。。。

    作者回复: 这个例子的假设就是\n为报文的分界符。

    2021-08-06
收起评论
显示
设置
留言
25
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部