读写socket套接字的正确姿势
我们就擦肩而过了
有趣
有用
有态度
导学问题:
1. 增加缓冲区,可以提高程序的吞吐量吗?
2. 网络数据从发送到接受的过程,总共拷贝了多少次?
发送数据
ssize_t write (int socketfd, const void *buffer, size_t size)
ssize_t send (int socketfd, const void *buffer, size_t size, int flags)
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
这三个都是发送程序时常用的接口,大部分人可以都很熟悉write函数了,经典的写文件接口。我们这里重点给大家介绍后两种。
如果我们想发送紧急数据,那么我们就可以使用send接口,给发送的数据加上紧急标记,那么我们的这一帧数据就会快速被发送出去。
如果想指定多重缓冲区进行数据传输,就需要使用第三个函数,以结构体msghdr的方式发送数据。
发送缓冲区
当tcp连接成功后,在linux内核里面会维护一个发送缓存区。它的大小可以通过调整套接字选项来进行设置。当我们应用程序调用write函数时,实际上是把数据从应用程序拷贝到操作系统内核的发送缓存区,它并不一定马上就会发送出去。具体情况如下:
-
发送缓存区足够大,那么write的系统调用会马上退出,返回写入的字节数就是应用程序的大小。 -
发送缓存区不足以容纳应用程序的数据,那么write函数会一直阻塞,直到应用数据完全填充到缓存区后返回,缓存区的数据由操作系统内核负责把它发送出去。
如下图:
所以无限增大缓冲区肯定不行,内核缓冲区总是充满数据时会产生粘包问题,同时网络 的传输大小MTU也会限制每次发送的大小,最后由于数据堵塞需要消耗大量内存资源,资 源使用效率不高。相当于让仓库变大,可以存储了更多的货物,如果出货的速度有限,会有更多的货物烂在仓库里。
读取数据
最简单的数据读取方式就是read函数了,它的函数原型如下:
ssize_t read (int socketfd, void *buffer, size_t size)
read函数要求操作系统内核从套接字描述子socketfd读取固定数量的字节,并将读取的结果存储到buffer中。返回值告诉我们读取的实际字节数量。特殊情况下,如果它的返回值为0,那么代表EOF,表示网络中对端发送了FIN,我们也要相应地进行断开连接处理。而如果返回-1,则代表出错。
阻塞式套接字最终发送返回的实际写入字节数和请求字节数是相等的。发送成功仅仅表示的是数据被拷贝到了发送缓冲区中,并不意味着连接对端已经收到所有的数据。至于什么时候发送到对端的接收缓冲区,或者更进一步说,什么时候被对方应用程序缓冲所接收,对我们而言完全都是透明的。
总结
对于send发送数据来说,返回成功仅代表数据写到发送缓存区成功,并不代表对端已经接收成功。而对于read来说,需要循坏读取数据,并且要考虑EOF等异常条件
●
●
●
●
扫码二维码
获取更多精彩
just enjoy it!
点在看~
捧个人场就行~