网络编程实战
盛延敏
前大众点评云平台首席架构师
立即订阅
6034 人已学习
课程目录
已完结 39 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 学好网络编程,需要掌握哪些核心问题?
免费
第一模块:基础篇 (9讲)
01 | 追古溯源:TCP/IP和Linux是如何改变世界的?
02 | 网络编程模型:认识客户端-服务器网络模型的基本概念
03丨套接字和地址:像电话和电话号码一样理解它们
04 | TCP三次握手:怎么使用套接字格式建立连接?
05 | 使用套接字进行读写:开始交流吧
06 | 嗨,别忘了UDP这个小兄弟
07 | What? 还有本地套接字?
08 | 工欲善其事必先利其器:学会使用各种工具
09丨答疑篇:学习网络编程前,需要准备哪些东西?
第二模块:提高篇 (10讲)
10 | TIME_WAIT:隐藏在细节下的魔鬼
11 | 优雅地关闭还是粗暴地关闭 ?
12 | 连接无效:使用Keep-Alive还是应用心跳来检测?
13 | 小数据包应对之策:理解TCP协议中的动态数据传输
14丨UDP也可以是“已连接”?
15 | 怎么老是出现“地址已经被使用”?
16 | 如何理解TCP的“流”?
17 | TCP并不总是“可靠”的?
18 | 防人之心不可无:检查数据的有效性
19丨提高篇答疑:如何理解TCP四次挥手?
期中复习周 (2讲)
期中大作业丨动手编写一个自己的程序吧!
免费
期中大作业丨题目以及解答剖析
免费
第三模块:性能篇 (12讲)
20 | 大名⿍⿍的select:看我如何同时感知多个I/O事件
21 | poll:另一种I/O多路复用
22 | 非阻塞I/O:提升性能的加速器
23 | Linux利器:epoll的前世今生
24 | C10K问题:高并发模型设计
25 | 使用阻塞I/O和进程模型:最传统的方式
26 | 使用阻塞I/O和线程模型:换一种轻量的方式
27 | I/O多路复用遇上线程:使用poll单线程处理所有I/O事件
28 | I/O多路复用进阶:子线程使用poll处理连接I/O事件
29 | 渐入佳境:使用epoll和多线程模型
30 | 真正的大杀器:异步I/O探索
31丨性能篇答疑:epoll源码深度剖析
第四模块:实战篇 (4讲)
32 | 自己动手写高性能HTTP服务器(一):设计和思路
33 | 自己动手写高性能HTTP服务器(二):I/O模型和多线程模型实现
34 | 自己动手写高性能HTTP服务器(三):TCP字节流处理和HTTP协议实现
35 | 答疑:编写高性能网络编程框架时,都需要注意哪些问题?
结束语 (1讲)
结束语丨我相信这不是结束,让我们江湖再见
网络编程实战
登录|注册

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

盛延敏 2019-08-12
你好,我是盛延敏,这里是网络编程实战第 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/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《网络编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(69)

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

    作者回复: 都是强人😄

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

    作者回复: 👍

    2019-08-13
    4
    10
  • fjpcode
    1. 缓冲区搞大一些,在一定程度上能够减少write/send等系统调用,减少用户空间和内核空间的切换。但是和吞吐量并没有直接关系,
    还可能导致数据都挤压到内核缓冲区得不到处理,造成内存消耗的问题。
    2. 发送方:用户空间-->内核缓冲区-->报文封装 三次拷贝,接收方反向过来,所以应该是6次。
    2019-08-13
    4
    10
  • Geek_Wison
    老师可以将完整的代码的github地址贴出来吗,我想自己编译调试运行一下。

    作者回复: 正在进行中

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

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

    2019-09-03
    3
  • Sweety
    对C不了解,有点不好理解.
    只能将就的看懂方法.
    第一次认真跟课,就给自己挖了一个坑.
    理解思想吧

    作者回复: 答疑篇会稍微点拨一下C语言

    2019-08-12
    3
  • 禾桃
    13-14 行表示的是非阻塞 I/O 的情况下,没有数据可以读,需要继续调用 read。

    EINTR The call was interrupted by a signal before any data was read;
                  see signal(7).

    貌似这个场景是因为read这个函数在执行过程中被一个信号中断,而没有执行完提前退出了,

    您的意思是只有在socket被设置为非阻塞的前提下,才会出现?

    如果socket之前被设成阻塞,read就不会返回,直到这个信号被处理了,如果处理后返回这个进程,读取的操作会继续进行下去,直到read函数返回?

    多谢!

    作者回复: 阻塞那部分确实是这样的,当然,可以为read设置超时。

    2019-08-16
    2
    2
  • 传说中的成大大
    还有就是通过现象说明 应用程缓冲区虽然是10240000但是套接字缓冲区应该不是10240000这么大它一次性没写完,这也是为啥服务器端在不停的打印收到的字节数 这说明在客户端send未完全拷贝到套接字缓冲区之前他是不会返回的
    2019-08-13
    2
  • 业余爱好者
    网络程序的性能瓶颈一般在于服务端,所以老师说的增加缓冲区大小应该指的是服务端。如果应用程序的处理速度跟不上,即使缓冲区再大,也不能在整体上提高太多的吞吐率。性能,从来都只能从整体上优化,简单粗暴地提升某一个指标的效果一般。

    首先讲客户端发送的数据拷贝到缓冲区,
    然后操作系统讲数据从客户端的缓冲区中发往服务端的缓冲区。服务端操作系统最后把数据从缓冲区搬移到应用程序。客户端应用--客户端缓冲区--服务端缓冲区--服务端应用程序。一共3次?
    2019-08-12
    1
    2
  • 学怪
    网络编程中为什么要循环读取数据呢?

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

    2019-12-06
    1
  • 衬衫的价格是19美元
    就像老师说的仓库的例子,在write的时候,如果缓冲区设定的很大,那么应用程序每次往缓冲区写数据的时候,都会返回完整的数据大小,应用程序以为数据发送没问题,于是接着往缓冲区写,但实际呢?可能由于网络的原因,实际数据发送的很慢,数据都堆积在缓冲区,并没有发出去,但是在达到缓冲区上限之前,应用程序都是无法感知的,而一旦感知到的时候,发现已经有山一样的数据堆积在缓冲区了

    作者回复: 应该是网卡或者网络原因。

    2019-10-01
    1
    1
  • Kepler
    没有答案吗,数据从应用程序发送端,到应用程序接收端到底为啥是6次?

    作者回复: 在答疑篇中有一个我认为的答案,其实这是一个开放性问题,不用纠结这个数据,主要是明白拷贝发生的场景,为什么为发生拷贝。

    2019-09-18
    1
  • 传说中的成大大
    我通过百度把示例代码跑起来了 当客户端连接成功服务器过后 开始发送数据 服务器的终端不停打印,然后我发现一个现象就是 因为打印太多 能看到的是 9723 每一秒涨1 第二秒就是 9724 9725 9726等一直到1000我想问的是为什么一秒比一秒多1呢?当服务器端打印完过后客户端打印 send into buffer 10240000然后程序退出
    2019-08-13
    2
    1
  • 徐凯
    我想问一下 用户态缓存是否指的是运行库的缓存 我的意思就是 write最终是要调用系统调用的 而我们使用的是运行库的write函数 为了避免像发一个字节就立马调用系统调用 运行库也会有缓存来尽量减少系统调用的次数 这个是不是就是指的是用户态缓存 而不是指用户在程序中自己定义的一段buffer数组对吧

    作者回复: 我理解不是这样的,咱们调用write就是一个系统调用,就会有用户态-内核态的上下文切换,你说的这个问题,确实是实战中应该尽量避免的,我在后面的提高篇中会针对你说的这个情况讲到一些技巧。

    2019-08-12
    1
  • 范龙dragon
    第一个readn函数中第24行返回实际字节数的地方,应该是size-nleft吧,通篇没有看到变量n

    作者回复: 已经修复

    2019-08-12
    1
    1
  • 天蝎座的狮子狗
    我遇到一个问题,发送函数send函数返回值竟然比我写的时候传入的buff的bufflen大,不知道为什么😳。

    作者回复: 出错了.... 代码贴出来看看

    2019-08-12
    1
  • 在路上
    个人的想法:
    单纯的提高缓冲区应该不是可行的方法,以一台计算机思考,内存毕竟是有限的,为每个套接字都开辟一大块内存,那相应可以创建的套接字的就减少了把

    作者回复: 相当于让仓库变大,可以存储了更多的货物,如果出货的速度有限,会有更多的货物烂在仓库里。

    2019-08-12
    1
  • 星亦辰
    有这么一个问题:

    假如客户端往服务端发送了1025个字节,而只有1024个字节是有用的。
    我在服务端用read 读取1024个字节,最后一定是返回1024。
    然后,剩下的没有用了,不打算读了,怎么样抛弃多余的内容呢。

    作者回复: 那你为啥要传这第1025个字节呢?如果是消息的边界,例如换行,还是要读到这个字符的,读完以后不拷贝到应用程序的缓冲区就可以认为是丢弃了。

    2019-08-12
    2
    1
  • itgou
    老师,求助, 我跟着你的思路,自己写了 一遍服务器和客户端, 怎么就连接不上了, 也对比了你的代码, 没发现哪里不对, 客户端使用connect连接服务器端时老返回0, 能帮我看看吗? 代码在https://github.com/itgou/socketProgramStudy
    2019-12-11
  • 我叫徐小晋
    老师您好,服务端都不打印数据是怎么回事?

    作者回复: 确定连接成功了么?不凡自己多打印一些输出信息,比如连接成功就打印一下。

    2019-11-29
收起评论
69
返回
顶部