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

05 | 使用套接字进行读写:开始交流吧

缓冲区实验
read
发送缓冲区
sendmsg
send
write
思考题
总结
读取数据
发送数据
使用套接字进行读写

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

你好,我是盛延敏,这里是网络编程实战第 5 讲,欢迎回来。
在前面的章节中,我们讲述了套接字相关的知识,包括套接字的格式,套接字的创建以及 TCP 连接的建立等。在这一讲里,我来讲一下如何使用创建的套接字收发数据。
连接建立的根本目的是为了数据的收发。拿我们常用的网购场景举例子,我们在浏览商品或者购买货品的时候,并不会察觉到网络连接的存在,但是我们可以真切感觉到数据在客户端和服务器端有效的传送, 比如浏览商品时商品信息的不断刷新,购买货品时显示购买成功的消息等。
首先我们先来看一下发送数据。

发送数据

发送数据时常用的有三个函数,分别是 write、send 和 sendmsg。
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)
每个函数都是单独使用的,使用的场景略有不同:
第一个函数是常见的文件写函数,如果把 socketfd 换成文件描述符,就是普通的文件写入。
如果想指定选项,发送带外数据,就需要使用第二个带 flag 的函数。所谓带外数据,是一种基于 TCP 协议的紧急数据,用于客户端 - 服务器在特定场景下的紧急处理。
如果想指定多重缓冲区传输数据,就需要使用第三个函数,以结构体 msghdr 的方式发送数据。
你看到这里可能会问,既然套接字描述符是一种特殊的描述符,那么在套接字描述符上调用 write 函数,应该和在普通文件描述符上调用 write 函数的行为是一致的,都是通过描述符句柄写入指定的数据。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入浅出地介绍了套接字的读写操作,着重介绍了发送数据时常用的三个函数:write、send和sendmsg,并详细解释了它们的使用场景和区别。同时,重点讲解了发送缓冲区的概念,以及在不同情况下数据的处理方式。读取数据的方法也得到了详细解释,特别是对read函数的使用和非阻塞I/O的特点进行了重点讲解。通过一个客户端-服务器的例子,展示了读取缓冲区和发送缓冲区的概念,并给出了服务器端读取数据的程序。总结中强调了send和read的使用要点,并提出了思考题,引发读者深入思考。整篇文章以实际案例为基础,适合网络编程初学者快速了解套接字的基本知识和操作方法。

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

全部留言(116)

  • 最新
  • 精选
  • 破晓^_^
    无限增大缓冲区肯定不行,文章中已经说过write函数发送数据只是将数据发送到内核缓冲区,而什么时候发送由内核觉定。内核缓冲区总是充满数据时会产生粘包问题,同时网络的传输大小MTU也会限制每次发送的大小,最后由于数据堵塞需要消耗大量内存资源,资源使用效率不高。 用户缓冲区到内核缓冲区 内核缓冲区IP报文,一次三拷贝,总共6次。不知对否?

    作者回复: 都是强人😄

    2019-08-12
    19
    73
  • 莫珣
    无限大肯定是不行的,这要从为什么使用缓存这个角度考虑。内核协议栈不确定用户一次要发多少数据,如果用户来一次就发一次,如果数据多还好说,如果少了,那网络I/O很频繁,而真正发送出去的数据也不多,所以为了减少网络I/O使用了缓存的策略。但为啥不呢无限大呢,网卡一次发出去的数据报它是有一个最大长度的,所以你不管累积再多数据最后还是要分片发送的,这样一来缓冲区太大也没什么意义,而且数据传输也是有延时要求的,不可能总是在缓冲区里待着等数据,这样就总会有空出来的缓冲区存放新数据,所以无限大缓冲区也没意义,反而还浪费资源。 发送端,假设数据能一次性复制完,那么从用户态内存拷贝到内核态内存是一次(这里应该直接拷贝到发送换冲区了),传输层组TCP包是第二次拷贝,因为要加包头,而发送缓冲区的都是紧凑内存全是应用层数据,那么分装包就需要一次拷贝,第三次,一个TCP包封装为IP报文这里可能也会需要一次拷贝,毕竟这里走到协议栈的下一层了。

    作者回复: 总结的很牛

    2020-01-15
    6
    46
  • cool
    什么是粘包问题?怎么解决

    作者回复: TCP是流协议,根本不存在所谓粘包一说。应用层协议在设计的时候,是需要充分考虑到数据解析和还原的问题,如果设计不好,导致数据无法还原,那是应用层协议设计不佳,并不是说TCP天然有粘包问题。

    2020-04-26
    29
  •  
    尝试着照着老师贴出来的代码写了一个,可以跑起来 https://github.com/yingcheng-zhou/socket-reading-and-writing

    作者回复: 👍

    2019-08-13
    8
    22
  • WhatAKitty
    不涉及协议栈层面,应该是4次: 用户缓冲区 -> 内核缓冲区 -> 网卡 -> 对端网卡 -> 内核缓冲区 -> 用户缓冲区 老师这里提及复制几次,主要是为了引出零拷贝吧。直接由用户缓冲区复制到网卡DMA区域。减少了中间经由内核缓冲区中转的过程。

    作者回复: DMA区域都知道,🐂。

    2020-04-12
    5
    17
  • itschenxiang
    关于write函数的返回值那里还是不太懂,当它的返回值(数值大小)小于期望写入的字节数,那它的值代表什么呢???

    作者回复: 表示缓冲区就那么大,装不下你要的那么大的字节流,就返回了目前能装下的部分,剩下的部分应用程序要自己接着往里装。

    2019-09-03
    4
    11
  • 何赫赫
    while (remaining) { int n_written = send(sockfd, cp, remaining, 0); fprintf(stdout, "send into buffer %ld \n", n_written); if (n_written <= 0) { error(1, errno, "send failed"); return; } remaining -= n_written; cp += n_written; } 老师你好,send函数不是会等所有的数据都放入缓冲区后才返回吗,那返回的n_written不是应该等于remaining呀,为什么还需要while循环

    作者回复: 在非阻塞I/O的情况下,send函数是"能写多少写多少",所以n_written就不等于remaining了,而send函数为了同时对阻塞I/O和非阻塞I/O起作用,就用while循环了。

    2020-03-07
    10
  • 郑祖煌
    增大一些是可以提高系统的效率,一定程度上减少了write/send调用,减少了用户空间和内核之间的切换。但是并不能增大吞吐量,毕竟内核的缓冲区并不能跟用户空间的缓冲区保持同步增大。把内核缓冲区总是满满的会增加粘包的频率和概率。

    作者回复: 👍

    2020-06-12
    7
  • tongmin_tsai
    老师,如果客户端和服务端要求是一次短链接,并且是一次性发完所有数据,那如果客户端的缓冲区大,服务端的缓冲区小,那么服务端如何能知道客户端这次数据完全发送完毕的?比如客户端发送1000字节,客户端的缓存区大小为1200字节,那客户端可以一次性把数据放到缓冲区,服务端这边,缓冲区大小为400字节,那么服务端是否就相当于要从缓冲区读取数据3次了,那么服务端是如何知道客户端数据发送完毕的?

    作者回复: 首先,TCP的报文会被封装成一个一个TCP包,每个包都有一个sequence序列号,每个包里包含了一定的字节,当这个包被接收端接收(放到接收缓冲区中),接收端发送一个ACK,这个ACK和sequence对应,这样服务端就可以知道哪些包被接收,哪些包没有被接收。 按照你的例子,我们以400为包大小,发送了三个ACK,就可以认为1200字节发送结束。 服务端是不需要知道数据是否发送完毕的,因为TCP是一个流式的,没有办法知道客户端下个时刻还会不会发送数据,服务端只要告诉客户端我收到了1200字节就可以了。

    2019-09-30
    4
    6
  • 学怪
    网络编程中为什么要循环读取数据呢?

    作者回复: 因为数据像流水一样,不会结束,所以叫做stream流。

    2019-12-06
    2
    5
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部