基本概念叫协议
什么叫协议?
协议是一个大家共同遵守的一个规则, 那么在这个网络通信当中,其实就是双方通信和解释数据的一个规则,这个概念 你也不用记,你只要心里明白就可以了,
分层模型, 物数网传会表应 每一层代表什么意思,干的什么事,你先看一看,你面试的时候 有可能会问你,别到时候 说不出来
四层模型和七层模型有个对应,每一层代表什么意思,以及他和七层的哪一层有对应关系,
2 1 1 2 2 1 1 3 加起来一共7层 这四层 是不是我们实际的 网络传输过程中
用的就是这四层啊?
数据通讯的过程。最后一句话总结出来了,什么过程?在发送端是数据什么的过程?层层打包,那么在数据的接触方是层层解包的过程,那么那个图是吧?咱们从头为是不是给你画了一遍啊?嗯,这个我想的话大家应该明白了,是不是应该明白了?那么至于说这个网络应用程序的设计模式是不是有两种, BS 模式和 CS 模式,这个优缺点你看一看就行了
好,你看一看,咱们都知道这个客户端是不是应该装在本机浏览器的话,是不是你只要本机有浏览器就可以了?是不是它的优先点其实还是本着这个方面来说的,是吧?
好,那么以太网帧格式,有同学不知道什么叫以太网,那你就把这以太网比看成网络就可以,网络你知道吧?兄弟们?那么以太网它只是一个专有名词,你就把这以太网哎比成就看成网络就行了,就是网络的出来帧格式清楚吗?同学们,不要把东西想的太复杂。
好,那么这个帧格式呢?咱们是重点跟你说的,咱们还记得以哪个跟你重点说的吗?是不是 a r p 协议?兄弟们, a r p 协议来说明这个帧格式,这个你能够照着这个图,能够把这里边的每一个段的意思能够说清楚就行了,没有人让你背着去说,而且我们写代码时候用这个吗?有没有这个东西谁做的?是不是底层给你封装好的?嗯,是吧?这个不用你管。
那么说的具体是内核做的是不是?那么这个帧格式是不是从网卡上发出去的数据就是帧格式,能理解吗兄弟们?从网卡上发出去的数据是什么?什么样的数据?是数字信号还是模拟信号的?模拟信号是不是模拟信号?是不是就高电平是不是,对吧?要记住这个,那么调制解调器是干什么的?猫德牟 猫是不是进行数模转化和模数转化的?这些东西在这个 TCP IP 分层模型里面都有,清楚了吗?兄弟们你还是要出来看一看的好。
这个那么咱们给大家讲一下这个以 ARP 为例,来给大家介绍了一下以太网真格式,当然这个以太网帧格式的话,不只是有arp,是不是还有什么rarp,是不是还有这个 IP 数据报等等,是不是有好多种类型了?好多种类型,是不是就根据这个两个字节的这个类型来进行区分的?是不是?
这个从头到尾我给你画了一遍这个里面的每一句话什么意思?也不是说哪个都需要你,你把这里面重点的我跟你说的那些你去看一看就可以了。
比如说什么叫以太网目的地址,这应该知道是不是就是对方的 Mac 地址,什么叫对方你把数据要发给谁,那个谁是不是对方啊?是不是?那么以太网的原地是不是就是自己?是不是就发送端啊?是吧?你们是不是帧类型有点类型,什么协议类型,这东西你也不用死硬背,也没人会问这些,那么后面这些什么什么以太网,以什么以太网地址这 6 个字是不是还指的是mac地址? 4 个字节是不是指的是 ip 地址?但是你得注意这个 ip 地址它实际上是一个整形值,是大端字节序的整型值。是不是这样的?ip数据段,这个你看这个图,对照这个图你看一看就可以了。
我先说这个东西,你不用背, 你背的话会,会浪费你的精力,你把你的精力用在更值得花费的地方,好吧?好,这是什么?什么协议?这玩意你自己看一看好了。UDP,你看一下从这个协议的这个字段的多少上来看的话,这个 UDP 是不是简单一些? UDP 也简单一些。这个红色的标记,咱们昨天是不是这个练习题也有啊?
现在记住了吗?像这些东西就应该把它当做一个常识,是吧? ip 地址是不是可以标识这个网络中的唯一台主机?端口号Port,端口号Port,是不是端口号?端口号是不是标识这个主机当中唯一的一个进程?是不是在这也称做服务啊?能称做服务吗?同学们,以后你们听说这个服务的话,就是进程的意思,就是程序跑起来了,是不是就是进程啊?,或者叫守护进程,是不是说的一个意思啊?好,这句话要搞清楚那 TCP 的数据流格式,这个后面咱们还有咱们今天还会说这个,这里面需要你关注的是 32位序号、 32位确认序号,以及这个里面的什么ack,syn,fin这东西,兄弟们,那么序号的话是不是跟你讲过了?是不是这个在发包的时候,我说这个包和报文字符串是一个意思,发包就是发字符串的往外,清楚了吗?发数据流的吗?知道你怎么说的,是一个意思,发包发字符串,发报文是一个意思,不要搞混了。
那么你这个网卡往外发包是不是发的字节流了?你的这个字你要你如果往外发字符串的话,是不是字符串最终也会给你转换成什么呀?高低电平吗?是不是?好,那么这个里面咱是不是需要什么意思?你再往外发包的时候,是不是对每一个包都有一个编号的?那这个编号干什么用的?嗯,接收的,那么对方接收方那边是不是可以根据这个序号把这个大的,这个把小包是不拼了?一个大包了,他是不是能够拼起来?他根据什么来拼的,就是给你这需要的拼的,那么言外之意是这意思。
那么有时候那种发包的时候是不是有可能先发的包后到?有没有可能后发的包先到?是不是这样?就像你在网上买东西,是不是你比如说你在淘宝买东西 4 天到货,在京东买车第二天到货,是不是第二天到货?你可能在京东上买是,时间靠后,但是他可能先到货了,有没有这种可能?朋友们跟这个意思是类似的,但是你如果加上序号之后呢,以后呢,不管是他先有后的到来,没关系,最后是不是可以给你这需要拼起来,是干这个用的清楚吗?那么确认序号是干什么用的?我是不是跟你说了,那么你把这个数据发给对方了,对方是不是要给你回一个确认包了?他告诉你他已经收到了,如果他没有给你回这个包怎么办?要进行重传。
今天会说好,这个先不说了,今天会说这个,那么这个是稳定、安全、可靠面向连接的,
这个上面都说过了,这个里面网络里面这些名词术语解析,咱们讲义上都有,你看一看,比如什么叫交换机、什么叫什么极限器、什么叫三字交换机等等。这个你看一看。好,这个你看一看。那么讲socket编程的时候呢
咱们跟你说过了,这个 socket a p i 函数,实际上是不是它也能够实现进程间通讯,那么只不过这个是不是跨网络的,那么原来咱们讲那些什么pipe fifo是不是在一个主机上啊?这个就不一样了,就跨主机了是不是?这要搞明白,那么本质上它是不是也用到了内核了?同学们,是不是我们这一个文件描述是不是对应两个缓存区?pipe 对应几个? pipe 是不是两个文件描述对应一个缓存区?和这个是不是正好相反的?好大端小端的基本概念,这个不难,应该这个也不难
,什么叫大端啊?是不是低位地址存款高位数据,高位地址存放低位数据啊?那个程序自己有没有看一看?同学们,让你们看的是这个面试可能会问的东西,可能会让人写,你如果说你咱们讲过,你到时候没写出来我想的话,你会非常的后悔是不是?你可能会抓耳挠腮的说,当时可不是说再敲两遍就好了,是不是?千万不要有这种后悔,如果说你实在没见过没做出来,那倒无所谓了,是意思吗?因为这个一般的面试考试的时候很少有人说全答会,是不是你答有些答复是正常的,是不是?比如说 100 分,你可能,唉,答了 50 分让你去面试了,别到时候你连 30 也答不了你就你自己最差,那肯定进不去了是不是?那后面是不是就没有展示你自己能力的机会了,对吧?你们不要太差了就行,大端小端的基本概念,搞清楚
这个程序,要求你看懂。
那么我们在测试本机一个或者一个机器的话,是大的模式还是小的模式是吧?我们用到了什么?同学们里面 是不是共用体或者联合体, 这 4 个函数?我是不是跟你说了这个技巧了?是吧?怎么记的时候怎么记? n 代表什么意思?网络, h host 主机 to 是不是到谁的意思?嗯,是吧。比如这个 h to n l 什么意思?是主机字节序转化为网络字节序长整型, l 是长整型, s 是short。不说了,这个不说了。
那么 i net pton 这个也有窍门,兄弟们,前面是不是都这样的? inet 吗?那么这个 p 指的什么意思?是不是点分 10 进制的这种字符串形式的 ip地址,n表示网络地址大端字节序的 4 字节整型的地址是不是这样的?这些东西如果你记不住,那呢?那么你看看能不能参照着这个什么呀?讲义或者是 man 配置 man文档,你看看能不能写出来。我再强再一次强调一下,这个man 配置,这个你最好是还要独立去用一用。
为什么这么说?一方面可以练一下你的英语能力,翻译能力,另一方面的话是不是也可以锻炼一下自己查文档能力?咱们讲过你会没讲过,难道说就不用了吗?是不是一定要注意这个?这是 i net pton,还有一个是 i net ntop,是不是这俩正好相反?这个咱们代码中是不是给你用过了?都用过了,
这个是那个结构体,这个结构体有些同学可能这个觉得第一次接触,因为他稍微的有点绕,不那么直观,用起来比较麻烦一些,这个只能说习惯问题用的还是少,是不是?你这么着,你把它敲上几遍,不可能还记不住,是不是?是不是功夫没到位?是不对啊?功夫没到位本身这东西都是不是太难,也没有什么理论性东西的。这几个函数我就不再一一介绍了,那么咱们直接把这个代码给打开,咱们给大家把这代码再回顾一下,那么这个在写这代码的时候,有没有人说写不出来的,或者是这个最后测不出来的?有这样的吗?
有没有写不出来的,这个咱们写这个代码的时候,是把这个流程用文字描述给你写了一下,是吧?那个图是没有第一步调什么函数,第二步调函,这个什么函数都有,是吧?咱们再快速的说一下。好,这个是我们的服务端程序,那么首先这两个头文件你要记得包含一下
这两个头文件你要包含一下,如果不包含,咱们是不是昨天试过了,是不是有些函数找不到声明,看一下这个过程。第一步首先是创建socket,那么首先创建一下,创建 socket 以后,是不是我们可以得到一个文件描述符了,那么这个文件描述符在内核当中是不是有两块缓冲区?分别是什么?缓程序?就是发送缓存区和接收缓存区,发送缓存区和接收缓存区,那么第二个参数第一层是 a f inet,表示你用的什么 ,你用的是哪种? i p v 4 是吧?你们是不是还有一个 a f _ i net 6 那个什么呀?i p v 6。
目前来说,我们仍然用的是 a f i net。你直接填这个宏就可以了,是不是直接填这个宏就可以了?你也不要去。为什么说老师为什么演用这个宏,这个宏为什么这么写,我能解答你吗?同学们,是不是?人家这么用,你就这么用就可以了。第二个sock stream 表示你使用的什么方式,是不是 TCP 协议?对,第三个是0,表示你使用的是不是 TCP 的默认协议。同学们,嗯,是吧?兄弟们,好,这个参数如这个函数如果错误的话,是不是返回一个小于 0 的数了?对,那么你直接调用 perror 打印一下错误原因就可以了。错误描述信息是不是就可以通过这个函数打印出来?第二个函数bind绑定,同学们,那绑定的意思是什么?
将这个文件描述符。
将谁和谁绑定啊?将文件描述符和 IP 端口进行绑定。兄弟们,绑定完以后,接下来后续的操作是不是我们一直在使用这个文件描述了?兄弟们,然后在这儿给你提一句,你说这个bind在服务端能不能不绑定?能不能?大家注意,你不绑定你的程序它也能跑起来,但是这个端口你不知道是不是每次你还得查,那么服务端的这个端口是固定的,大家注意是固定的,不要乱用,今天用1000,明天用1001
是不行的,因为你的服务是不是要对外提供?你要对外提供服务,你老是变的话,别人知道你吗?是不是这样的?那么绑定的时候,我们是不是要定义一个这个类型的一个变量叫 struct sock addr in 是吧?让它进行赋值,是不是只要有 3 个值?一个是这个 family 是吧?同学们,这句话的意思是不表示我们用的是ipv4,兄弟们,那么第二个是端口,大家注意这个port是端口的意思, port是端口意思 IP 是不是 ip 地址的意思?htons什么意思?是不是将主机字节序转化为网络字节序的什么什么型了?是不是短整型了?短整型,那这个 4 个 8 是不是端口号了?那表示我们对外提供服务的话,是不是有一个固定端口了?是不是同学们?有一固定端口好,在这个程序里面,后边这个后面那句话是不是在设置 ip 地址?那么其中这个宏,大家注意这个宏指的是什么呢?这个宏的值其实就是0,其实就是0 INADDR_ANY。
那这句话的意思是可以使用本机任意一个IP,当然是一个可用IP。你们现在可能还不知道一个大型服务器,它这个服务器上它能用的网卡不止一个,清楚什么意思吗?不止一个网卡。当我们的电脑是不是也有两个网卡呀?一个是Wifi,这个网卡吗?是不是还有一个什么呀?是不是插网线这网卡呀?是吧?一个有线网卡,一个无线网卡,是不是我们这两边也是两个网卡呀?是吧?大型服务器往往有两个,有的有 4 个,一个网卡上也可以配 2 个以上的IP。
清楚了吗?兄弟们总知道这句话的意思,你要清楚。就什么呀?我的这个就这个服务器上可能有多个IP,那么我这句话的意思就是我可以使用我这个服务器上任意一个可用IP。那么对外提供服务的时候,你是不是要告诉客户端这一个 ip 地址和一个端口?那么举个例子,比如说你这个机子有两个网卡,每一个网卡有一个IP,那么你对外提供服务的话,你可以告诉他这两个网卡当中任何一个网卡,这个地址也行,你全告诉他,这也可以啊?明白什么意思吗?但是端口的话是不是只有一个? 端口是不是相当于在软件层面啊?网卡是不是?网卡的的ip地址是不是相对?这个 IP 是不是配在这网卡上了?兄弟们清楚了吗?同学们,这些属于像这个一个机子上有至少有一个网卡,这也是属于常识, 两个网卡子大家可能没见过,是不是?我在这个我在现场部署的时候我见过这些东西,当时我面对的机器是上百万的服务器,我还装过系统,装红包,系统在上面,是不是 ip 地址的话我也配过,那的确见到了,确实有两个以上网卡,的确这样的。
好,那么接下来做一个绑定是吧?绑定的参数,第一个是不是lfd?这个参数是不是就是前面这个参数返回值 15? 第二个是不是我们一定要做一个强制类型转换,如果你不做转换,它会给你来一个moding?是这样的,好,最后一个是不是 这个的长度 serv?这个不用说了,那么也就是bind之后,是不是我们将文件描述服端口 IP 进行了绑定?好,
接下来调用 listen 的监听,那么什么叫监听?同学们,你这个监听,你这个服务只有监听是不是才能有他这个行为?才有主动变为被动,是不是?你的服务程序是不是让别人去连你啊?那百度服务器?百度是连你 还是你连百度啊?是不是我们主动去访百度?我们是不是相当于客户端啊? 清楚吗?同学们,客户端服务器的这个概念要逐渐建立起来,到底什么叫服务端?什么叫客户端?我举个例子,你去政府办公大楼办事,你说人家的工作人员是主动等着你,主动找你,你去找人家,你是不是找人家的?你是不是相当于客户端的?人家是不是相当于对外提供服务的那个窗口?是不是每他那里面是不是每一个办公人员有一个小窗口啊?maylick窗口你可以把它理解成port。 端口
不同的窗口是不是提供的服务不一样?言外之意,不同的端口提供的服务是不是也不一样? 1 个服务,两个服务能不能用同一端口了?那么只能打架是不行的,第二个端口根本就绑定不成功,会报错了。大家还记得昨天咱们这个测试的时候,是不是这个bind?是报错,还记得吗? 那个我是不是跟你说他过一分钟就可以了,还记得吗?那个就是这个原因。
好,这个 listen 是将这个我们的服务端程序它由主动变为被动,也就是说它是对外提供服务的,应该是由客户端主动去联系他,是不是这样的?好,接下来调用 accpet的函数,这个函数是不是要接受客户的链接?是吧?那么不严格的,你可以这样说,接受客户的链接,那么严格的说,是不是这个 accpet的其实是从已链接队列当中拿出一个可用链接来,这个是不是昨天给你试过了,还记得吗?同学们,我在 accpet 的之前是不是 sleep 30,然后的话我客户端去连,这个时候是不是 sleep 30 仍然没结束?你再用。 net ..来查看的时候,这个链接已经建立了。我是不是你试过了?同学们,这个要搞清楚。好,这个accpet
我们拿到一个新的链接以后,这个新的链接是干什么的?大家注意,这个新的链接是不是用这个文件描述来标识?CFD?是不是就标识这个新链接了?前面那个叫什么呀?叫监听的文件描述符,这个叫什么呀?通信文件描述符。所以在服务端我们拥有几类文件描述符啊?两类吧,在客户端有几类?这一概念要慢慢树立起来。
好,我们得到了一个通信的文件描述符。那么接下来我们在一个循环里面是不是循环的收发数据?嗯,兄弟们好,其中read函数有这点需要你关注一下。什么意思?锐的函数如果说返回是 0 表示什么意思?是不是对方已经关闭链接了?是不对?对方已经关闭链接了,那他关闭链接之后,那么你在这样的话,他就read还会阻塞吗?是不是立刻返回啊?是不是这点和管道是不是一样的?还记得吗?同学们,如果说对方把管道的写端给关闭了,那么你在那 read 还会阻塞吗?是不是这个返回,这个是类似的?好,那么在一个需要里面,我们调用 read 和write函数进行收发数据,在这是不是仍然也可以调 receive 或send 啊?最后一个参数填 0 就可以了?这个 有没有尝试一下?可以试一下,是不是一样的,用哪个都行?好,用哪个都行?好,这个最后你不用了,要记得干什么?文件描述是不是要记得关闭?兄弟们要记得关闭。
那么这个文件描述符,这个lfd、 cfd 这俩文件描述是不是也保存到了内核当中的文件描述表里面?这两个值我是不是给大家打出来了?还记得吗?几啊? 这俩是 一个是3 一个是4,这个整个过程就这样子,如果你目前来说,比如说还不太熟悉,没有关系,别人着急,咱们今天也会用,后天还会用。
基本上咱们只要写这个程序,这样的代码都会在用,一遍不行,来两遍,两遍不行,来三遍,来十遍行不行?还能忘吗?是不是一开始我瞧这张代码的时候我也是记不住,多瞧几遍就知道了。好吧,朋友们,这个不要灰心,这个图,大家注意这个图,我给大家找的这个图,你呢?看看这个图,
是不是同学,这个图是不是把我们这个整个流程给你介绍一遍,创建消费者 bind listen,accept,receive, send 是不是都有啊?好,那咱们看一下客户端,嗯, 这个程序的话,客户端是不是就比这个服务端要简单多了?因为它没有绑定,没有list,是不是没有 accept 好?第一步也是创建socket,创建socket以后,返回的这个文件描述是干什么的?是通信的啊?通信的好,那么接下来他是不是要 connect 对方了?对方指的谁啊?服务端,是不是服务端的就是服务器?那么好,他connect 对方,他要知道什么信息?
是不是这个?是不是类似于我们寄信啊?你要给别人发信的话,是不是你得知道谁收,你得知道他的地址,他电话是不是意思?还得知道谁?是不是在我们这?是这样的,你要想对方发发包的话,发数据的话,你得知道对方的 IP 和端口是不是这样的?你得知道对方的 IP 和端口。
再想一个问题,那么你说这个发包的时候是吧?同学们,我们往外发数据的时候,我客户端给服务器发送数据是吧?同学,那么他我们这个服务器上是不是有很多服务?他怎么会把这个数据准确无误地发送那个服务上去?他怎么知道的?仅仅通过 IP 不行吧?是不是还有端口了?因为你发数据,其实发到那个服务的端口上去了,是不是他就可以接受到了?是不是?就像那什么呀?你去办,你去这个政府办公大楼办事,你是不是得知道在哪办呢?是不是在端口上写着呢?你说计生科,比如说这个什么什么审批,什么什么项目的是不是都有?是不是?比如说户籍科是不是,你办户是不是,到户籍科是不是?等等,这些都是有明确的,有明确端口的说明这个乱不了,这个乱不了。
你再想一下,如果说假如说两个服务能绑定同一个端口,那就意味着发的这个包到时候他都不知道发给谁了,是不是了?他就无法询了?好,接着看好connect, connect 以后大家注意这个connect,它的返回值 ret,只是说成功已失败,是吧?同学们,那么这个 connect 这里面在内核当中做了一件事情,什么事情? 3 次握手建立链接,你只要一调用 connect 在内核当中他就给你做了 3 次握手过程。
今天会说这个好,那么 connect 成功之后,是不是接下来它就可以发送数据和接收数据了?整个是不是就结束了?最后不用了,要记得 close 这个文件描述是没有了,客户端是不是相对来说比较简单的,是吧?同学们,这个代码,我跟大家说这个代码如果还有不懂的课间找我们,别到时候我问你的时候你说不知道,好,要主动问好。
同学们,好,这个复习内容大家,
这个今天的学习内容给大家快速说一下,第一个掌握 3 次握手建立链接的过程,这一个过程是你在调用哪函数?那发生的是不是connect,谁连谁?客户端是不是要主动 connect 服务端的?在内核当中它就有一个三次握手过程,这个内部的过程你不用你关心,是他自己做的清楚吗?第二步掌握 4 次挥手,关闭链接过程,这个在哪一步调用?当你调用 close 的时候,你是不要关闭链接,这个时候内核就有一个 4 次握手关闭链接的过程,清楚了吗?同学们,这是 4 次回手之势, 4 的回手和 4 握手是一个意思,不要死抠这个细节,一个意思,反正就是什么经过四步是不是?那后面给大家讲一下滑动窗口的概念,那么在第一天的时候我给大家说了这个滑动窗口用途是什么呀?流量是不是进行流量控制,避免我发送的过快,你收的太慢,是不是这样的?好,第四个给大家讲一下掌握错误处理函数的封装,其实这一块就是把咱们这些 socket API 做了一封装,我们在调用的时候就不用再去做异常判断了。比如说创建socket 失败之后是不是有一个 perror 打印啊,然后咱们把它封装起来,把这perror 封装到这个里面去之后我们直接调这一个函数,结束了,这个咱们会跟你说。
那么第五、第六后面这两个是用什么呀?实现高并发服务器的两个模型,一个是多进程版,一个是多线程版,这个就比昨天有意思了,昨天那个的话是不是只能接收一个后端?两个行吗?两个不行,两个不行。因为你 accept 的函数是不是已经调过了?只有调用一次accept ,调用一次accept ,是不是才能结收一个链接?是吧? 还有这个学习目标,就这么些个,其中这两个咱们会写代码,
这个咱们把代码给你说一下,掌握错误处理函数封装 然后咱们在这两个里面去用它,知道什么意思?这代码我已经有了,直接发给你,这里面这些概念前三个,要求你这个概念不用画了。
这两个三次握手4次挥手的时候,你自己在纸上自己画一画啊,自己画一画,这给大家留的一个作业,你自己动手画一画。为什么让你画,你画一画,你自己琢磨清楚吗?这过程你要了解过程,了解这一块,我们写代码的时候用不着,什么时候用,面试是吧?面试你说你学的好,人家问你问题,你不知道人人相信吗?是不是你首先把这些,是不是这些功夫都做到,你该会得会这属于理论性的东西。
好,咱们看一下你,首先你先把这个题给思考一下,哪个题?为什么 TCP 是面向链接的安全可靠的传输?我把这个 3 次过手的过程, 4 次回收过程讲完之后,那么这个东西你应该就清楚了,心里明白了,看一下,那么咱们这 TCP 是面向链接的安全可靠的传输。
,好,那么在建立链接的时候有一个三次握手过程,那么三次握手过程完以后,这个链接就建好了,建好链接之后,接下来就可以进行数据传输了,那么数据传输结束以后,如果你这个不用了,这个链接不用了。也就是说你不想发送数据,接收数据了怎么办?是不是最后close, close 有一个啊?这个内核当中有一个 4 次挥手过程,关闭链接的过程清楚吗?咱们接下来就讲一下这个 3 次握手和 4 次挥手的过程。
好看一下这个图,看这个图,这个大的讲义上也有我从上面摘下来的。好看这个图,这个图里面一共分了三大块。一共分了三大块,哪三大块?你能看得出来?应该这是一块,兄弟们,这是一块,然后后面是不是又一块了,是吧?一共是分了三大块。好,咱们分头来介绍一下。好,其中一开始的话,客户端是不是要主动连服务端的?
好,那么客户端主动连服务端,大家想一下调到哪函数了? connect 了?好,大家注意,现在大家已经回忆起来调用,当客户端调用 connect 以后,接下来内核当中是怎么做的?内核当中有一个建立链接的三次握手过程,在客户端第一步的时候,第一次握手的他先发出一个 s y n,那么这个 s y n 哪来的? 大家还记得我给你画这个图吗?
这个图当中是不是有一个s y n?是不是这个?这个就是什么呀?发送链接请求的一个标识,大家注意这个里面,其实它本质是一个位,知道什么呢?本质上是一个位, 1 表示有, 0 表没有,为什么只有两种值啊?好,接下来继续看。
那么客户端会给服务端发送一个s y n 标识,表示他要想建链接,这个里面这个 1000 什么意思?这是一个随机序号,随机序号,这个数字怎么来?随机分配的, 1000 行, 2000 行,这个随机,你可以把它理解成一个基础值,知道什么意思吗?我这个值相当于,比如说我一开始 1000 后面是不是 这个值会增长?我是不是跟你说过了,这个发包的时候有序号,每个时候都有序号了,他序号怎么来的?是从这个基值上一点点加上去的,明白了吗?是从基值上加去的,那么这个以前就是开始的一个基础值。
好,括号里面这个 0 什么意思?表示携带数据的长度,那么在这儿的话,他携带数据了吗?没有,他现在是不是只想建链接?建链接,数据是0,数据长度是0,那么这个 0 是不是就和这个是类似的?红框内,同学们,后面这个 数据部分是0。
接下来继续看 m s s 1460 什么意思?这个 m s s 的意思是告诉对方,我这边我这一侧每次可以接收的数据最大长度是1460,知道什么意思嘛?是不是?你现在是不是客户端?是不是相当于去主动连接服务器?他是不是要告诉服务器,你服务器要给我发包的时候,给我发数据的时候,你发的数据你最多可以发1460,
你不要超过这个长度,你超过了是不是要截断了? 好,这是第一步,是不是他发送那个 s y n 标识?接着第二步看一下第二步, 这个服务端收到以后,它首先是不是得判断一下,是吧?那么这个服务端一看,你发的是 s y n标识表示的,你想构建链接了,我明白了,是吧?意思明确了。
好,那我给你回一个ACK,回一个ACK,那么这 ACK 1001 怎么来的? 是这个随机序号, 1000 的值加个1,那么这个 1 什么意思? s y n 本身也要占一个字节。唉,有同学说了,老师你不是刚才说占一位吗?那么这一位是不是在这一个字节当中, 这一位是在一个字节当中,所以你把它列成这个 SN 本身也占一个位,所以 1000 + 1 就是1001,这么来的,是不是言外之意? 言外之意,它是要告诉客户端的好,你发的这一个 s 端标识我收到了,他是不是只收了一个? 1000 嘛?他加了1。好, 1001 告诉对方,你发那一个标识我已经收到了,同时告诉客户端,如果你再发,那么你就从 1001 开始发就可以了。
这句话是这个意思,能理解吗?这句话这个意思好,同时他自己也会发送一个 s y n标识。什么意思?你不是想给我建联系吗?我这边同意,是这意思吗?同时我要请求一下你,你那边也得同意。好,有人就说了,为什么这个要双向链,为什么这个要发两次?有没有疑问?同学们,昨天我是不是跟你说过了,一个链接其实是不是一个socket 片了?是不是一个连接对啊?兄弟们还记得吗? socket 片 是不是连接对了?连接对,咱们这个用 next 命令时候查看的时候,看到了客户端服务端有一个链接的时候,其实有两项链接吧?两条双向的,是不是双向的好,那么这个服务端也会给他发送一个 s y n 请求,同时他的随机序号, 8000 携带数据长度为 0 是吧?同学,是不是他又回了ACK1001,然后这个服务端这儿是不是告诉客户端了?服务端,我这边我收到的数据长最大是多少?1024,那么你呢?你不要超过这个长度,能理解。
接下继续看,第三步的时候只回了ACK,客户端给服务端回一个 ACK 是八千零一,这个八千零一怎么来的?是不是就这 8000 加这个 sy n?这一个一位啊?一共是8001?那么是不是表示客户端告诉服务端是不是他已经收到他的请求和ACK了? s y n 和ACK了? s y n 和ACK 他收到这个了,然后给他做了一个回应,那么这样的话,这个服务端是不是就知道这个链接已经建好了?至此三次握手过程结束,链接建立了,那么你这个时候再有 next 命令的时候,是不是就看到了?有同学可能有疑问,老师我怎么看不到这个 3 次握手过程,如果你看能看到,那就显得太慢了,是不是你只要一定要调用connect,是不是瞬间就完成了?那就瞬间就完成,除非是你的网络环境非常不好,连接不上,是不是才有可能失败?是吧?你们在局域网当中这种情况是非常少见的。为什么?因为局域网是不是网络环境比较好啊?没有跨网段,没有跨网段,这个三次握手就结束了。
好,咱们看一下这个数据发送过程。好,看一下第四步到第六步。是数据的发送过程吗?好,第四步。那么客户端是不是要给服务端开始发包了?嗯,我说发包就发数据的意思啊?发数据了。好,这个 1001怎么来的?朋友们,那么是不是你这个1001,你这个客户端的1001,是不是服务端告诉你的?哪告诉你的?是不这了 ACK1001?是不是这只是这俩是一个值啊?同学们看清楚。
好,这个ACK 8001怎么来的?是不是你刚刚给对方发的ACK啊?你刚刚给对方发的 ACK是不是这个 3?所以这个值就等于这个值能看到问题没?这俩就是一样的,慢慢你把这个规律找到之后,你再画这个图的时候,你就会轻而易举画出来的。好,接着看。
那么这个服务端收到包以后,他是不是要做一个回应啊?兄弟们,他 5自己是不是也可能发包了?好,看一下啊?这个 8001怎么来的?怎么来的?是不是就是这个ACK?同学们,是不是这个ACK?人家让你发哪个,你再发哪个吗?是这个意思吗?是不是人家让你发从8001开始发呀?好,你从8001开始发吗?好,接下来括号里面10是什么意思?是不是服务端给他发了十个数据过去了?大家注意这个十个字节的意思,咱们是不是都是按字节说的,是吧?发了 10 个字节,ACK这个 1021 怎么来的?
是不是 1001 + 20?那么言外之句告诉什么呀?告诉对方你发的数据 20 个数据,我是不是全收到了,是不是,对吧?接着看好看第六步,第六步他给他回了一个ACK,那么这一步是不是这个客户端已经不再给负端发数据了?是不是没有数据啊?同学们,他是不是只回了 ACK 啊?这个 ACK 是8011 ,8011,这个 8011 怎么来的?是不是就是 8000 + 10 啊?这规律找到了嘛? 然后这个服务端收到这个包之后,是不是相当于数据传输结束了?好,接着看,接着后面看这一步,这部你需要好好听一下这一步。
那么从这个第7 8 9 10 这四步是不是要关闭链接了?那么首先发起关闭的链接是谁发起的?客户端?好,我反而问一句,服务端能不能主动关闭链接?可以,可不可以?是可以的,我举一个最常见的例子,那比如说我昨天我再跟你说过一把这个accrept函数,第二参数什么意思?保存的是不是这个客户端的 IP 端口?是吧?我服务端是不是有的时候可能会被这个客户端恶意攻击?那么我是不是可以把一些客户端的 IP 加到一个黑名单里面去?那么你这个给我发包的时候, 你给我简历连接的时候,我首先查一下这个accrept第二个参数,第二个参数它得到的 ip 地址是不是在这个黑名单里面?在黑名单里面我直接就拒绝,怎么拒绝?是不是 close 的链接就可以了?是不是close的链接之后他还能发包吗?他不能发包了,他就不能再给你发数据了。
那么制是可以的,也就是说服务端可以给客户端主动关闭链接,同理,客户端是不是也可以主动关闭链接?都行,那么这个图上画的是不是客户端主动关闭链接?好,咱看一下。那么客户端调用的是close,他关键因素他发送那个标识叫 f i n,看这个标识是不是和他不一样了?这个SYN是建链接的,叫建立链接请求的一个标识,这个是关闭链接请求的标识,叫 FIN,其实就是fihish吧?是吧?fihish的前三个字母,兄弟们接着看。好,这儿是1021,这个值怎么来的?是不是对方给你发的ACK啊?大家注意,这个是刚刚对方赶紧给你发的ACK,刚刚是不是上面第五个?是不是刚刚这个ACK?
是这,所以这个 1021 就这个值,看到了吗?他俩是不一样了,他是不是携带数据长度为0,因为这个第六步,这他已经决定不再发数据了,是不是?好,他是不是也给他回了一个ACK,这个 ACK 是不是上一次 第六步刚刚给他发的ACK?是不是这个?这个关系?你只要找到了,你自己能画出来了。
好,接着看,那么这个服务端收到这个关闭链接的这个标识的时候,他首先是不是要判断一下?他一看你给他发了一个 FIN 标识什么意思,他就明白了。噢,原来是他想给我断开链接了,好,同意,是不是?他同意了?同学,他是不是要回来ACK? ACK 是不是 1022 了?1022 怎么来的?是不是 1021 加1?finish他本身它也占一位,这一位是不是在一个字节当中一位?你,那么你算的时候肯定算一个字节,是不是?是11022 ,这是这么来的。
接着看,那么这个服务端他也不服气,是吧?你客户端想给我关闭链接,那么我也给你关闭链接。这个有的类似于什么呀?这个男女朋友吵架了,是不是?这个女的一气之下,是吧?分手是吧?那男的也正在气头上,分手就分手,同意了,然后这个过会这个男的觉得不服气,这个分手应该是由我来说,是吧?他也出来分手,那你就同意是吧?是不是双方都同意了?大家再想一下,为什么在这?我们这个这个关闭链接有 4 次?
同学们,为什么?你要搞清楚你这个链接是不是双向的?每关一个是不是都有回应?再有一个大家再想这个问题,那么你说你客户端想要给服务端主动关闭链接,那么有没有这种可能?那么服务端一开始不知道你想关,一开始不知道他还有可能是不是还给你发呢?有没有可能?他有可能还给你发你,但是你不知道他给你发是不是?
所以如果说你这个时候把这个接收的这个也给关掉了,这个链接是不是双向的?一个是你的发送端对应对方的接收端,另外一个你的接触端是不是对应对方的什么端?发送端吧?是双向的?好,那么这个时候第九步,服务端也想给客户端主动关闭链接,怎么他是不是也要发送一个FIN请求啊?携带数据是不是也是0?这个时候这个序号是8011,
这个8011是不是就是上次对方(7->)给你回的ACK,它就是8011,是这个值,是吗?同学们,好,这个 ACK是102,这个怎么来的?是不是?对,你刚刚给对方回的ACK啊?是不是这个?最后一个 ACK?第十步,8012,这怎么来?是不是就是 8011 加1啊?8012?整个过程四次挥手结束,能看到他们这个?这个里面这些序号,这些值怎么来的?如果你记不住,待会咱们一块画一个,我只负责写,你来说,好。好,这个过程我先说到这,这个里面咱们就总结一句话。
三次握手, 建立链接是不是?同学们,建立链接需要什么呀?需要 3 次握手过程。好,那么 4 次挥手呢,断开链接,需要什么? 4 次挥手过程,这个图给大家从头到尾的捋了一遍,然后咱们一块来画一下。好,这个图一块来画一下。这个里面有些名词我也给你解释过了,比如说 syn什么意思? ack 什么意思?都说过了,然后 syn 是不是表示连接请求?ack 是不是表示回应?确认啊?
这个ack 是不是就这个ack (红框内)确认信号,对,没那个需要,是不是就这个 ack 是不是?在这里面同学们说清楚,把这个和咱们前面学过结合起来,这个是他的英文单词,我给你写了一下,自己看一看这个FIN 是不是finish啊?是不是结束,关闭链接,整个就是这么一个过程。
好,下面咱们就开始把这个三四握手建链接的过程和这个 4 字挥手,断开链接的过程,给大家一块来梳理一下,画一画,这样我来画,你来说,行吧?我看看这个他们家这个数字,这个大小怎么算出来的,看看你能不能明白。好,那么这个三次握手的过程,首先是客户端是不是先发起的?先发起的客户端先发起一个建立链接的请求。好,它其实内部调用的是哪函数的?是不是 connect 的好?看看第一个,这一个的话,客户端要主动发起一个链接请求,标识是哪个?SYN,是不是 SYN?我写在这SYN是吧?同学们,好,那比如说这个序号,咱们给他来个什么呀?序号随便来一个2000,行,那么现在数据长度多少?0。先发起一个这样的一个请求,那么这个客户端服务端收到这个请求之后要干什么呀?是不是要给他回一个 ACK?同时他也会发起一个请求?对,大家注意链建立链接是 3 次。握手。
那个的话mss咱们写不写都一样, mss。是不是告诉对方啊? 多少,这个是?这个你自己定好,这个是不是根据实际情况来啊?这个不见得每次都是说多少,不见得每次都是那个数(1024)。好,这个是不是告诉服务器,我这边,我客户端这边一次性可以接收 1024 个数据, 1024 个字节数据,是不是?那服务端收到这个请求之后,他干什么?
干什么呀?是不是他要给他回就做一个回应作为回应,是不是?对不对?好?同时他是不是自己也要发出一个链接请求啊?好,咱们先写资源请求, s y n,那这个序号,比如说他定一个5000,随便携带数据长度为 0 是吧?那么ACK有多少? ACK 这个值你们算一下是不是2001?2001是吧?比如说他来这个 m s s 也是1024吧?是不是1K?好,他也过来了,这个时候大家注意,到这儿为止,以后是不是相当于这个半块链接好了? 是吧?也就说什么呢?我服务端也想给你请去链接当,而且我也同意,是不是?我也同意你刚才给我发的联系请求了。嗯,也同意了,这个时候到这儿为止,这是第一步。第一步,这是第二步,是不是第二步?到这第二步为止 ,到这为止是不是相当于这个半链接建好了?大家想一下这个链接建的是哪块链接?
是客户端给服务端的链接建好了,是不是?现在是不是客户端现在能给他发了?是吧?但是能收吗?现在收不了。好,接下来继续看接下来这客户端的话,是不是要给他回一个 ACK 回来表示你服务端的联系球我接受了,我同意了,是吧,对吧?到此为止 3 次握手是不是就完成了?是吧?同学好,他是不是只回一个ACK 就可以拉?这ACK 多少?序号多少?5001 ,这是第三次,第三次握手到这儿结束了是吧? 没那么到这个到这儿,这个第二步完成以后是不是相对半链接啊?到第三步完成以后是不是相对全链接建好了?嗯,两条链接是不是都建好了?
是不是就是咱们前面说的那个socket 片了?连接对啊?是不是?这个是不是可以用 next 命令来看?嗯,好, next 命令来看。好,这个咱们接下来往后看,下面咱们说一下这个数据传输的过程。数据传输过程我这有一个思考题,我提问一下大家看看大家明白吗?在我这个图里面,是吧?同学们,我为什么画这个线的时候不是画的直线,而是画的这个往下斜的线?
为什么?从上往下再去?从上往下其实是有时间的。那么你这个建链接过程和你这三次握手建立的每一步里面,它都需要时间,虽然这个时间很短暂,他肯定是有时间消耗的,是不是?所以我画的是什么呀?画的是斜线,画的斜线能理解吗?同学,是这个好,看一下这个数据传输的过程,你看一下,那么这个数据传输的时候,我这个服务端能给客服,客户端能给服务端发送出去,那么一开始服务端能不能客户端发送出去?只要练建立了,谁给谁发都行,清楚了。
好,咱们看看一下客户端,那么正常的情况下是不是一般都是客户端主动去请求服务啊,是吧?好,看这个这是数据传输过程,在这个过程里面它有一个序号,这个序号多少?序号叫seq,是不是 sequence 序号?我在这加一个这标识,加 一个标识,那么这个序号应该多少?多少?同学
们。
这个序号是怎么来的?这个序号是不是应该是对方给你回的那ACK啊,对方是不是指的服务端?服务端给你回的ACK,最近的一次ACK是不是2001?谁在这段?
是不是2001?那么携带的数据,我随便给你写一个20,是吧?同学们,我给你写 20 个数据,是吧?那么这个服务端最多可以接收多少个数据?同学们,是不 1024 没有超长吧?嗯,没有关系。好,接下来这个服务端收到这个数据之后,是吧?收到这数据之后,接下来他是不是要回一个ACK?回ACK。好,咱们给他来一个 ACK 回来。
好,那比如说这个回来ACK,这个序号多少?ACK的序号多少?2021?多少?2021?2021。比如说他自己也想发包,可以吗?来一个他自己的序号多少?他自己的序号。
是不是从 5001 开始发?从 5001 开始发,他发多少?发 5001 ,是吧?那么接下来这个客户端给他回这个 ACK 应该是多少呢?多少?他回 ACK之后他不再发包了,可以,他不再发包了。那这个是多少呢?现在这个序号是不是ACK?要再写,再加一个 ACK是不是?应该是多少?ACK是不是5051? 是不是?5051?是不是就是 5000 + 50?对,这个好,到此为止,是不是数据传输过程结束了?对啊,数据传输过程结束了。
好,咱们看一下,挥手过程 4 次挥手过程,是不是断开链接过程?那么断开链接过程是谁给谁?断开链接都可以。那么这个咱们可以反过来,咱们给书上不一样了。可以吧?咱们现在看,是服务端想给客户端关闭链接,可以吗?这是可以的。好,这个时候是不是服务端会发起一个关闭链接的请求?标识叫什么名了? FIN 叫FIN 标识,序号多少?是不是5051啊?这个序号是不是刚刚对方给你发的ACK啊?是不是 ?5051 这个东西?这个序号有同学搞不清楚?你记住这句话就可以了,第一次除外。
第一次是不是没有啊?第一次随机的吗?那么后面的序号,每一个序号,它这序号值怎么来的?都是上次对方刚刚给你发的 ACK的值,是吧?我现在需要是不是5051?这个值是不是刚刚对方是不是客户端?对方给你发的ACK是不是5051?这个数就这么来的,清楚了吗?
好,再看啊,这是FIN 吧?ACK 多少? ACK 是不是刚刚你给对方发的ACK啊?是不是这样的?多少2021吧?好,这个写在这,能看懂他们,这个马上,我为什么让你自己画一遍呢?我就怕你这个数字你记不清楚,你自己捋一捋就可以了。
好吧,你不要老看我在这写这么顺,是不是?我是看了好多遍,我是研究透了他了才能给你讲,对吧?好,这是第一次吧,同学们,这是四次挥手,第一次过程吗?第一次过程。好,接着看第二次过程。那么这个客户端收到了服务端给他发过来的断开连接请求之后,他干什么呀?是不是回一个 ACK 啊,他要回一个ACK 回来?
序号多少 5052啊 是不是 5052 了?这个值怎么来的?你记住这个,每一次你给对方回的这个 ACK 的值,你给对方回的 ACK 的值一定是上一次对方刚刚给你发过来的序号吗?对方刚刚给发过来序号是不是5051啊? 如果有数据是不是把这个序号加,把这个数据长度加上啊?好,这是第二步。第三步该干什么了?
好看一下。那么接下来的话,是不是客户端也要主动这个发起关闭连接请求啊?他发的这个标识是不是也是fin啊?第三步, f i n 序号多少?他这序号是不是上次对方刚刚给你回的ACK啊?对方指的谁?是服务端的?服务端上次刚刚给你回的ACK 是不是2021?是吧?同学们,2021,是不是这个?还有别的吗?是不是还有ACK? ACK 有多少?ACK 是5052,那么这个 5052 是不是就是这个 5051 加上( i f反正)这有一个啊,是吧?是5052,放这来,好看,看第四步就很简单了,第四步是服务端给他回一个 ACK 是不是结束了 ?多少啊这个数字是,行,这么画不太好看。
这是ACK 是,多少? 2022 到此为止是不是结束了?到此为止,结束了。
那我在这给你用的是什么呀?用的是具体的序号,如果让你替换成 x 和y,你能画吗?是不是也能啊?同学们,那唯一不同的是不是这里面这数字不一样,你发送数据的长度不一样?这个 4 次 3 次握手建立链接的过程。那注意和这个 3 次握手和这 4 次挥手断开链接过程,
要求你自己在这个图上,在这个纸上你可以画一下,好朋友们,让你画,一定要动着画。为什么让你画?我是想看一下这个里面的数字之间联系,你是不是真弄懂了?你不要老看着我弄懂,我能给你画出来,你自己能画出来吗?清楚了嘛,你在画之前,你可以像咱们这样,你现在把这个图先梳理一遍这个里面的意思,因为我明白了,我想的话应该是能画出来。
这是大家的一个要求,这要不要怕麻烦?那么学习东西这个的确是有点辛苦,但是挺过去以后,这个你会感觉也就那样,是吧?所以有一个词叫什么呀?同学们叫柳暗花明又一村,是不是苦尽甘来,是吧?同学们,如果你学一个东西,你是吧?大家应该这个参加过高考的应该都知道,上三年高中,如果你最后还长肉了,比如说你原来150,后来长到 180 了,这个人成绩应该好不了,是不是?如果你原来150,后来上了三级高中, 120 了,是不是?甚至那骨瘦如柴了,说明你确实下功夫了,起码你起码你对得起自己吧,是不是?你努力了,是不是?是不是?你不能说你,你上了三年高中的,你长胖了是不是?长高了可以,但是不要长胖了是吧?同学,
好,这个咱们就说到这个过程,我大家一块又捋了一遍,你自己,你在你用这个,我这个工具onenote的画图也好用,咱们 windows 带的那个什么呀? miss pen 的那个画图工具也好,总之你自己一定要画一遍,我可能会抽查,我可能会用抽出啊,别到时候你说老师我知不知道这个作业,你别这么说是吧?兄弟们,如果你没画,我现场再让你画好吧,这给大家的一个严格要求。
好的,那么首先你得知道滑动窗口,大家注意这个只是一个形象的叫法,形象的叫法,其实在我们这个里面,在我们的网络这个通讯里面并没有这个什么什么窗口的意思,它只是一个形象的称呼而已,清楚了吗?形象的称呼,看一下这个滑动窗口的主要作用是进行流量控制,在咱们昨天也跟大家说过了,那如果说你发送端发送的数据过快,接收端接收的数据比较慢,这个时候就有可能会造成数据丢失,或者是造成什么呀?接收方的接收缓冲区是不是填满了?是不是兄弟们,填满之后再发送是不是就有可能丢失了?是这意思。
好,咱们看下这个图,我就不给大家画了,因为太多了,咱把这图给你分析分析,你看看你能不能听懂。如果你把刚才我们讲的图完全搞明白了,这个图一点也不难。
第一个这个图。首先你看一下这个图一共分了几部分,一二三几部分。同学们, 123 是不是第一个过程?一直到 4 5 6 7 8 9 到哪了?到这个 12 是不是第二过程了?是不是数据传输过程?
那么最后一步是不是哪个过程?是不是断开链接过程?断开链接过程,咱们一点点来,先看前三步,前三步是不是跟刚才我们讲过的完全一样,是吧?好,syn 标识 0 是吧?窗口大小4096,这个的意思咱不知道,跟你说一下,fin4096 这个的意思是告诉对方我这边接收缓冲区可用空间是4096,知道什么意思吗?也就说你给我发送数据的时候,你如果说超过这个长度了,那我的缓冲区是不是就满了?兄弟们啊?告诉对方,是不是告诉对方?同学们,告诉谁呀?是不是告诉你接收方,接收方。
那么在这个图上这个接收方应该是什么呀?是不是也是服务端的?为什么?因为你是你客户端主动发行链接,是不是这样的?好,那这个 MSS 和上面那个一样吗?是告诉对方我这边一次性可以接收的数据长度为1460,大家注意是指的最大程度 是吧? 好,这一步大家知道吗?这有一个词叫 w i n 是 windows 的意思,窗口大小其实就是指的是他缓冲区可用空间大小,记住是可用空间,好看。
第二个这个slow receiver,这个服务端收到他的请求之后,是不是要给他回一个ACK?这个 ACK 在这1,这个 1 是不是就是这个ACK占的一位,嗯, 0 + 1,同时他自己是不是要发送一个syn请求?嗯,它的序号是 8000 长度数据长度为0,是吧?同时它是不是也告诉发送方了?我这边缓冲区大小可用空间为 6144, 6144 多少?几 k 6K 4409 是4K ,m ss 是1024,是不是告诉客户端?那么我这边接收数据长度最大一次性为 1024 个长度,你不要超过这个长度,是这意思吧,好看。
第三步,以后回来ACK8001 ? 窗大小,WIN 4096 是不是也是告诉他了?大家看这个图里面是不每次又问的 为什么每次有啊?你这个结束缓存局,对这两方的结束缓冲区是不是实时变化的啊?你发了之后它是不少了,知道这意思吗?这肯定是动态的嘛,跟你说嘛,但是一开始这个 MSS 是不是规定好了?双方规定好了,就是这点长度,你不要发超长就可以了,但是这个 w i n 这个长度它是变化的,因为你发送到对方的缓冲区以后,它是不是缓冲区就变少了,这肯定的是不是?好,那么接下来看第4步、第4步、第5步、第6步、第 7 步、第 8 步、第9步。
连续一共几次?看到没?456789,连续发了几次? 6 次,连续发了 6 次,每次发多长?是不是发 1024 是吧?这个 1 怎么来的?同学们,这个 1 是不是上次对方给你回的这个 1啊?ACK是不是一样携带数据长度1024 吧?好,ACK是8001,这个 8001 是不是上一次你刚刚跟对方回的?ACK啊?是不是8001啊?窗口大小是4096,他自己这个是不是在这 6 步当中都没有变化呀?为什么没有变化呀?因为对方并没有给你发。
是不是你的缓冲区大小是不是没有变啊?对,好,那么发了 6 次以后,你说填满对方的缓冲区了没有,是不是满了?那么正常情况下,如果说他没有接收应该是满了吧?因为 6* 164 正好是6144,已经满了,所以 456789 他连续发6次。那么我反问一下,为什么他知道连续发6次?为什么他为什么他知道?要发 6 次就可以了,他不再发第7次了?因为人家已经告诉你了,是不是6144,是不是他就告诉你了。嗯,这边我只有 6144 个大小空间,你呢?你发就行了,你不要超过这个长度。是不是他发 6 次发满了,他很自觉,是不是很自觉?
他们以后看第十步,看这个窗。这个什么意思?同学们,这黑框黑色格子什么意思?黑色格式表示已经填满了,是不是弹出缓存区大小一共是6K,是 6K ,这黑色的格子表示接收端的缓冲区已经占满了。
白格子什么意思?白格子是空闲的格子,一个格子表示1024,清楚了吗?你看懂这个意思?好,接下来看第 10 步,ACK 回这个 6145 是怎么来的?是不是 5121 加1024啊?好,这里面我给大家提个问题,假如说我第十步回带ACK,我是4097,那什么意思?
是不是最后一个包丢了?是不是包丢了以后这个发送需要补发?是不是要冲床?知道这意思吗?这样是不是可以保证我们这个数据不丢失?接着看第十步,它回了ACK 是6145,那么 w i n 是2048,什么意思?表示他是不是已经处理了 2 K的数据?处理 2 K的数据的意思是不是读走 2 K啊?嗯,走 2 K以后是不是意味着这个缓冲器里面有空闲 2 K啊?好,看格子往是不是往右?是不是又滑两个?这两个空闲是不是有两空闲的表示2K,也就是2048。
接着再看那么 11 步,他又给他回了一个ACK ,是 6145 是吧?那么这个 ACK 是不是和上一步那完全一样?为什么一样?因为这个发送端他是不是没有给他发送数据啊?是不是?好 当w i n 大小是4096,这又什么意思?是不是他又出2 k啊?那么看 11 步是不是这个窗口又往右移了两个?接下来是不是还有两个?
2 k是不是在访问区里面?现在空闲是几 k 了? 4K 了?好,接着看接下来这个发送单已经知道了,现在这个接收端有已经有这个 4K的空闲了吧?现在他又发,是不是又连续发了两个 12 13 是不是都发出数据了?是吧? 12 发送了1K, 13 发送1K,同时 13 是不是也发起了一个关闭连接请求啊?关闭连接请求是可以携带数据的,现在看到了没有?是不可以携带?好,那么看这个窗口,看 14 这窗口,那么 12 13 连续发送 2 k 以后呢?那么这个缓冲区的可用空间是不是由原来的 4K 变成 2 k 了?嗯,那么言而之意,这个窗口是不是往右又移动了?没移啊,这个缓存区是不是又填满 2 k 啊?这 2 k 是不是刚填满的?是吧?同学们,这两个是不是原来空闲,
现在是不是有了?嗯,还剩多少?能剩 2 k吧?好看第 14步,他告诉发送端窗口大概多少,是不是 2 k,也就是我这边还有 2 个空闲,你如果发的话还能再发 2 k,那么这个ACK 是8194,这个 8194 是 怎么来的?
是不是就是上面这个 7169 加 1024 啊,同时还加1,是不是正好是7194?好,接着看 14 不给他回了一个ACK ,那么 15 步是不是也回了一个? 15 步回 ACK 以后,同时这个窗口他是不是也变了?是不是?
这个空白的格子是不是又多了两个?是不是处理 2 k啊? 四个空白的是正好的 4K 能看到吗同学们,他并没有给他立刻回FIN关闭连请求,并没有立刻回,接着看接着看第 16 步,接下来他是不是要回来ACK,是不是同学们,win大小是多少? 6144 表示这个数据是不是已经处理完了?嗯,缓冲区里面还有数据没了,现在是不是已经空了?空了里面没有数据了。
16 步到这了,第 17 步接着看, 17 步接收端已经把数据接收完了,这个时候他已经也确定了,我也要关闭链接了,这个时候他是不是要发了一个 FIN请求给客户端的?这个序号是 8001 ,这个 8001 怎么来的?怎么来的?这个离得上面太远了,上面有点远了。
是不是上次对方给你回的ACK啊?上一次刚刚 大家注意是上次刚刚给你回的ACK,要记住这个词,最近的一次,最近的是不是这个?13的ACK?这个值是不是和这个值完全一样?能看到吗?好,就这个好。那么这个17的ACK怎么来的?是不是刚刚你给对方发的ACK,你刚刚给对方发的ACK,是不是这个16的?8194 ,win大小是不没变?因为你已经这个缓冲区里面是不是都空了,是吧?大小就是 6K 了。
好,到第 18 步以后,那么这个客户端给他回了一个 ACK 是8002,这个 8002怎么来的呢?是不是就8001 加1?好,最后一个回了个win, 4096 是吧?同学们?是不是告诉服务端它的大小是4096,那么至从至始至终这个整个发送过程结束了。
那么其中在我们这个里面大家应该可以看到一个现象。什么现象?这个里面是不是只是这个客户端给服务端发送数据?服务端有没有给客户端发送数据?没有,没有。能发吗?同学们,能发是吧?
但它这个图的意思是什么呢?通过这个滑动窗口是不是控制这个 对流量机控制,是吧?你看它这个窗口的话,是不是这个格子是不是一直往右移?再比实际的情况并不是这样的,这个只是说给你形象这个说法而已,
你可以把这个理解成一个队列,可以吗?可不可以?也是一样的。那么这个第一 (黑块图部分)是什么呢?我这边一开始是 6K 大小,你发了 2 k,那我这边的大小是不是空闲只有 4K 了?你再发 2 k,我现在只有,还有 2 k,再发是没有了,大家注意这个过程,这个缓存大小,这个大小变化是动态的。
为什么是动态呢?我读走之后数据是不是就空闲了?是不是缓冲区大了?那么你发了之后占满了是不是就小了?所以这个不是死的,这个值一定不是死的,是活的,是这意思吗?整个过程从头到尾就这个样子,能听懂吗?兄弟们,能听懂吗?
这个图如果你懂了,是吧?同学们,这个图并不难理解,这里面是不是多了一个窗户大小,是吧?兄弟们,就多了一个窗大小,一开始空的,后来是不是黑色的格子,是不是表示填满了,是吧?这个,这一块 1K 填满了,一个格子表示1K, 6 个黑格子表示已经填满了,那么有几个空白的格子表示有几个空闲吧?是吧?这个这一块,这个滑动窗户这一块,我就说这么多,这个图也不要求你画了,你把它里面的每一个数字是怎么计算出来的?每一个标识代表什么意思?搞清楚,这个里面 s y n很清楚吧,表示建立链接的请求,一个标识吧?ACK表示回应吗?同学们,是吧?表示回应, m s s表示告诉对方我这边最多可以一次性接收数据的最大程度,那么 w i n表示告诉对方我这边缓冲区可用空间大小,是不是?还有别的吗?没了 FIN表示什么呀?表示关闭链接请求的一个标识是吧?名词没有了。
那这个括号里面这个数字代表什么意思?数据的长度?数据长度,数据长度。好,那么这个这一块我就说这么多,
大家提一个思考题,什么思考题?这个有同学可能还不太清楚这一点,那么你们说这个发送端发送数据是发送到了这个接收端的什么里面去了。
什么缓存区?什么缓存区?是不是读缓冲区啊?好,你的这边的发送缓冲区是不是对应这边的结束缓冲区?结束缓冲区是不是读就是缓冲区,清楚了吗?这个过程是你做的吗?同学,是内核帮你做的,是不是内核调用网卡驱动程序把这个数据接到了这个内核的写缓冲区里面来,那么你调 read 或者write,调 read 函数,或者是调用 recive 函数,你是从哪读出去?同学们,你是从哪读?你记住这一点,你是从缓存区里面去读,你不是从网卡那读,你能从那读吗?你读不了。
我跟大家是不是说过,咱们这个一个文件描述是不是对应两个缓冲区啊?那么分别是发送缓区和接入缓存区,大家在想你写数据,你set数据和write数据,你写哪去了?什么缓存区?是不是你把数据写到发送缓冲区里面了?这个数据只要一到了发送缓冲区内核就帮你负责发送出去了,你用管吗?不用管,是内核帮你发送出去的,不是你发的。
搞清楚,你只是往这个缓冲区是,从这个缓冲区来读,或者写,你并没有去驱动网卡去工作,是内核做的事情清楚了吗?包括这个三次握手, 4 次挥手的过程,内部的过程都是内核在做,你也不用管,清楚了吗?这个好,那么接下来咱们还有一个点要给大家说一下,
这个给你提一提 m s s和 MTU,这个MTU什么意思?叫最大传输单元,这个 m s s 是不是咱讲过了?什么意思?是不是告诉对方我这边一次性可接收数据的最大长度是多少?是不是这样的?那这俩之间什么关系?你说这俩会相互影响吗?最大传输单元,看这个概念是指,是指一种通讯协议的某一层上面能通过的最大数据包大小,是吧?同学,是不是它也是指的是最多可以传多少数据。那么你想我举个例子,假如说你这个 m s s,你是1000,你这个MTU是1500。
走多少?是不是走少的?它俩是相互影响的。大家看一下我们这个,我们这个,我这个在讲这个以太网帧格式的时候,是不是这里面有一个数据的长度?是不是 46 是最少的?如果不足 46 是不是补空格了?还记得吧?是补齐啊,不见得补空格,是补齐啊,给你啊,至于补什么的,那咱不用关心,反正补齐了给你。
是不是那么如果说这个长,如果这个数据超过 46 字节,那但是他是不是也不能超过1500?嗯,这个 1500 大家注意,这个 1500 据说是一个经验值,这个怎么来的?肯定是经过反复测试得出这个值,用这个值比较好,知道什么意思吗?用这个值比较好,我们知道有这么回事就可以了,那么这个值是不是也会影响上面这两个值?知道什么意思?那么你这个从这个以太网,你从这个网卡上发出去的这个数据包长度是不是就是最多长1500?加上前面这几个,说明 1500 加多少?加 14 吧,加14。
那再长了是不是又再分段了要?是不是拆分拆啊?对,那比如说你两千,你两千的话能一次发送吗?是不是至少得两次?那么这个值是不是也会影响上面这个值啊?是不同的?那么举个例子,这是 1500 最大长度,那么如果说这你这个 mss 是 2 k 是不是也不行?是不是也不行?明白什么意思了吗?同学们?这里这几个值之间是不是相互影响?真是吧?你们实际上我们真正我们编码的时候,我们其实也不用关心这些事,为什么?因为这也都是不是你干的,是吧?同学们?而且不同的操作系统之间,它是不是这个值是不是略有差别,清楚吗?
同学们,这个你也不用关心,你要知道这几个之间它是有相互影响的就完了,能知道这句话的意思吗?同学们,这个怎么这个怎么这个举个例子,这个如果一个货车运货的话,每次可以运 20 吨,这是吧?同学们,最多可以运 20 吨,这个时候如果是有 30 吨货,能一次装完吗?是不是也得跑两次?跟这个类似是一样的,那么更说的更这个极端一点的,比如说你 20 吨货,正好这个有 21 吨货,说现在是不是查超载查得很严重啊?你有异怎不行?超载跑两次,是不是?知道什么一路上吗吗?同学们,这几个值是相互影响的,你知道这么回事就可以了。
这个 MSS 是不是跟你讲过了,那最大报文长度只是在建立连接的时候 告诉对方我这边最大能接收多少数据,在数据通讯的过程中就没有了吧?那么也就是说这个值是不是在建立连接的过程当中,是不是相互告诉对方了?那么后续当中还有这个值吗?就不变了,这个值是不变的,但是这个滑动窗口这个win大小是每次都变,就是因为它这个东西是动态的,
你发进去了,这个数据写进去了,是不是缓冲区大小肯定就少了?读走了是不是就多了?大家注意这个,那么你这个 read 读缓存区的时候, read 之后这个缓冲区里面这个数据就没有了,否则的话是不是就就是很容易传满?好,这个说到这,
好,那么咱们跟大家说一下这个函数封装的思想,咱们现在写这个代码的时候,是不是很多都在这个里面有一个 if 判断,是吧?有一个判断,然后这个,然后打印一个perror,是把这个错也打出来。
嗯,这些东西我们完全可以写在一个什么呀?写在一个函数库里面,这样的话我们在调用的时候,直接调用这库函数,是不是就很方便的?是不是很方便?正常情况下我们在这个做项目开发的时候也是这样的,你很少在这个项目的框架的代码里面看到什么 socket bind listent,看到这些东西都给你集成好了一个函数可以实现好几个函数的功能,明白什么意思吗?
同学们,是不是相当于在内部给你集成好了,那么这个封装思想,它结合这个errno,尤其是我们这个,嗯, socket api函数是不是都是底层函数了?是吧?都是底层函数,那么这些函数里面是不是大部分情况下是不是成功返回0,失败返回-1,有的是不是还返回一个文件描述了?对,是不是这些?那这些呢?咱们给大家把这个先给大家说一下,那么在封装的时候,我们总封装的呢?唉,你可以把这个 socket 的函数,比如说它调用了,封装成大写的s,大写的 S 是不是也是 Socket,是吧?参数一模一样,没有任何区别,只不过是在这个函数内部,他给你做了异常处理。
什么叫异常处理?这个函数有可能会调用失败吧?嗯,是不是然后调用 perrno打印出来?错误原因是不是?那这样的话你可以调可以使用这个 shift 加 k 进行搜索,shift 加 k 咱们是不是讲过,还记得吗?同学们讲搜索的时候我给你讲 4 种,但是我说你只记一种就可以了,还记得吗?在文件里面讲搜索斜杠问号,井号、 shift 加什么呀?加 k 是不是都可以?但是shift 加 k 的话,是不是搜索的时候它搜索的是命令,有的时候是吧?有的时候说的命令,好,这是一个如果说这个命令,如果说这个函数没有命令与之对应的命令,它是不是就可以搜这个函数了,是吧?那么实际上你用的时候你也可以不用这么写。
你说老师我不想用大写的socket,我封装是怎么封装?比如说我这个叫函数,叫 socket 函数,我封装可以用这个,这这个这个其实随你,比如说我这个,我是不是要这在这里面调用socket函数了?那么如果让你封装你怎么写?唉,当然第一种是不是我这么跟你说的,是不是这个可以,那么你也可以用这个,比如说 TCP_socket,
是不是也可以啊?表示你用的是 TCP 协议的 socket 通信吗?是不是这样的?也行,这个不是死的,所以这个你自己了解了解,真正你封装的时候要结合实际情况来清楚了,要结合实际情况来怎么好弄,怎么弄你,总之你封装之后是不是方便一调用?是不是这样的这个,那么还有一些东西我需要跟你说一下,在我们这代码里面经常会有一些什么,什么这些阻塞函数的异常处理,比如说 accept 是不是异常处理?不是。
accept 是不是阻塞函数了?同学们,accept 和 read 这些是不是都阻塞的?那么这些阻塞的函数如果说被信号打断,我这跟你说过吗?同学们还记得吗?如果被信号打断,那么这个信号的意愿基数比较高啊?它会优先处理信号,那么这信号处理函数完成以后,那么这些函数就会解除阻塞。
那么一个很明显的例子,我记得大家应该接触过这个,在他们的时候那 sleep 吧?嗯,sleep,比如说 sleep 10,然后呢?这个时候被信号中断了,是不是那个 sleep 立刻结束了?对,还记得吗?所以这种情况是不是我们(staipu number)上面是不是写的一个while循环里面?还记得吗?这么回事,然后就返回了,那么这个 errno设置为什么呀?
EINTR,这我给你查过,还记得吗?我是给你查过这个的,叫 EINTR,这个在我们代码里面也有体现,这个代码里面有体现,这个我会跟你说的,也就是说这些阻塞函数如果说被信号打断,有没有可能被信号打断?同学,可以我举个例子,父子进程的话,
这个父进程负责调用accept 的接入新的链接,是吧?同学们?如果没有新的链接到来,这个accept 会阻塞吧是不会阻塞,那么如果说你这个有链接到来了,这个就解除阻塞。
解除阻塞之后我们创建一个子进程,这个子进程负责接收数据和发送数据,可以吧,我们是不是将那给父子进行分工了?嗯,然后假如说我这个父进程在accept 的阻塞了,但是我子进程退出了,子进程退出会有什么东西产生?是不是内核会给他父进程发出信号啊?这个信号一发,accept 立刻返回,清楚了,accept 立刻返回,也就是说这个accept 是不是被这个 sick chat 进行打断了?这种你说属于错误吗?这种不属于错误,清楚了吗?你不要直接退出进程,这种不属于错误的。
好,这个这个咱们代码当中也有体现,我给你,我到时候跟你说这个,还有一个是这个errno =ECONNABORTED,这个表示什么呀?信号表示链接被打断异常了,是,其实是不是表示链接异常了?好,那么这个关于这个我们编写代码的时候,有的时候会结合errno ,是不是调用 perr打印错误原因,那么这个每一个错误编号对应的错误原因都在这个里面。
这个其实是不是很好找,怎么找啊?他们,你man一下,man errno,那么这个 errno下面是不是一堆?看到没?这个每一个是不是都有一个描述信息啊? 这我跟你说过,他这个什么意思?比如 EBUSY device or resource busy,是不是忙碌 ECHILD 什么呀? no child process,
是不是这个父进程也没有子进程了,是吧?等等,每一个错误编号,也就是这个errno值都对应一个错误描述,我们通过这个错误描述是不是就可以知道这个错误是怎么发生的?那么也就是错误原因。什么?是不是这样的?这个咱们是不是前面一直在用这个 perr 了?这个不用再多说了。好,那这样的话我给大家把这个代码给你分析分析,你看一看,大家把这个代码打开,你看我看着,我这也行,代码我跟你说几个,这个里面这些代码要求你自己的能看懂,然后用的时候你知道怎么用就可以了。这代码并不难,一共有这么多个代,这么这么多函数,是不是这些这么些个,我们看一下这个代码。
好,第一个函数是 perr_exit,这个什么意思?是不是就是错?是出错的时候是不是调这个函数返回就可以了?退出吗?是不它里面调那个perror(s),这个 s 是不是你指定的?明白是我们自己指定的吗?然后退出进程吗?这还是不难吧。
第二个accept,它是不是用的是大写的 A 呀?其实内部是不是调用的小写的a?是,是吧?同学,你封装的时候是不是这个accept你不这么写 t c p accept是不是也可以?这你随意指定,随你它的参数是不是和这个accept 参数完全一样?是不是这样的?你看这个行怎么用的?如果 n 等于accept,如果小于0,是不是意味着这个 accept 可能是有错误?有错误他又判断了一下这个errno 是不是等于ECONNABORTED?或者是这个EINTR是不是链接异常?
这个是不是被信号打断的?为什么?因为你这个 accept是个阻塞的,阻塞期间如果说被信号打断了,是不是这个函数会立刻返回?同时 n 是不是等于-1?是不是相当于这个返回值是-1吧?同学们并用errno标识错误原因吧?是这么意思,
那这他是不是这个是goto again 没退出的话?是不是goto again 了?是不是再次调用accept?知道什么意思吗?也就是说有些错误我们是可以容忍的,是不是这样的,因为这样像这些错误是不是很正常啊?很正常,如果说不是这两种错误干什么呀?是不是直接就退出进程就可以了?这个函数里面是不是调用这个了 exit(-1)?这个函数大家记得我跟你讲了return和exit的区别是什么呀?
return是返回这个函数,返回到调用这个函数地方,这个是退出进程,退出进程,看这个bind 是不是bind 很简单啊?内部是不是调用了一个bind 啊?是吧?
嗯,connect是不是也一样了?没什么,没什么区别。什么?什么? listen 是不是也一样了?那封装的时候你以后再用的话,你就写这个大写的 listen 就可以了吧?你还用写 perror 吗?同学们,不用写了,因为这里面是不是给你调用了?如果有异常的话,他是不是就返回了?他就返回了?
socket 是不是也一样的?它的这个函数原型是不是和那个socket 的完全一样? read 是读数据,读数据, read 是不是也是阻塞?是吧?看到没?如果说被信号打断了 干什么呀?是不是还继续读?能理解吧?这是有可能的,比如说你这个在读数据期间的这个数据量比较大,你是不是在循环读啊?你读的时候,你在外面给他发了个信号,
是不是这个 read 函数会立刻就返回了, 大家注意立刻返回了,那这种情况我们不应该把它是一个错误。如果说阻塞函数这个在阻塞期间被信号打断了,这种不应该视为一个错误,明白什么意思吗?这我记一下,这里需要你,你也得知道。
阻塞函数在阻塞期间若收到什么呀?信号会什么呀?会被信号中断,那么errno会设置为EINTR,那么这个错误不应该看成一个错误,明白什么意思嘛?这个你不应该把它看成一个错误。好,接着看。
这个代码比较多,咱们再找几个,这个write 是不是和那个完全一样? write 是不是也是阻塞的?同学们,大家想想,这个 write 在什么情况下会阻塞呢?缓冲区不可用,缓冲区写满了,不可用了还能写吗?管道是不是和这类似的?同学们,管道这个写满了还能写吗?同学们,如果你光写不读,是不是一会填满了再写的话就阻塞了?
close 是不也一样的?没的说,看这个readn,readn是什么意思?读 n 个数据?我举个例子,这个函数适用于什么场合?
比如说,比如说你的缓存区里面有 10K 的数据,但是你每次 read 你读不了那么多,你只读 500 个,是不叫你循环读?是不是循环读?这个函数就干那个用的,干那用的,你看什么意思?这个 f d 是不是读到文件描述了?通信的文件描述,这个n 的意思是 是你要读多少个数据?这个vptr是不是指的是缓存区啊?要把数据是不是读到这个缓存里面来?好,他顶一个指针,ptr指向这个,然后nleft等于n,也就是说一开始是不是剩余的数据,没有读完的数据是不是就 n 个?表示一个没有读,一个没有读?接来它是不是调用 read 函数读数据,是吧?读数据然后读到返回的值,是不是nready啊?是吧同学们,返回了多少个,表示读到了多少个数据,那么读到多少数据,剩余的数据是不是就 nread 再减这个啊?比如说一开始我,我一共想读 1000 个,一次只读 100 个,剩余是不是 900 个? 900 多怎么来的?是不是 1000 减100啊?这个是不是就是那个?然后你这个是又什么意思? p t r 加等于 nread
你这个 p t r 指向谁来看?是不是指向他呀 vptr?这个你自个还得揣摩揣摩,这个是不是我的缓冲区啊?同学们,是不是刚才 的ptr啊?然后我这个 ptr是不是指向这个第一个一开,指向这个开始的位置,你读了几个数据之后,你这个 ptr 的指针是不是要移动一下,是不是移到这来?那么再读的话是不是往后写啊?知道什么意思嘛?干这个事的?干这个事了,你自己琢磨琢磨。
那么什么情况下读完了?同学们?返回 0 的时候是不是就表示读不到数据了?表示读完了,还记得吧?读完了干什么呀?直接 break 跳出循环,是吧?跳出循环,这个write 是不是也这样的?跟着类似吧,是不是写个类似啊同学们,那么它是往一个大的缓冲区来写吧?是不是连续写?给那个道理是一样的。
还有一个myread,这个事读的量就变小了,还是 readline,这什么意思?什么意思?你看他的意思能看得出来什么呀?是不是读一行啊?那读一行他遇到什么结束?看没这个 ‘\n’,因为这一行里面它有一个结束标识,遇到 ‘\n’,他认为读的一行了,是这意思吗?他怎么读的同学们,没看到没?怎么读的?这个是?是不是一个一个读的?看到没?是不是读一个啊,是不是c嘛? c 是不是只有一个字符?这种读法是不是很慢? 你写代码时候你千万注意了,你如果读一行的话,如果你是一个一个的读,如果是你,你量小行 ,10 个、 8 个还行,你要是很多呢?是不是了?就比较慢了,比较慢了。
还有这个tcp4bind这是个是什么意思? 这个函数就集成了好几个函数,看第一个是不是创意socket的过程,有啊,有吗?第二个是不是还有这个bind过程?是不是bind过程?我问大家这个能不能把listen 也加过来?listen 可以,可以的,是可以的这个函数,其实这个函数可以封装成三个函数,你想一下我们这个服务端那个程序的话,第一步创建socket,第二步绑定,第三步监听,是不是这三步可以合成一步啊?能吗同学们,是可以的,你把这 3 个函数合在一个函数里面,你再调用的话,是不是一个函数搞定了?是不是很方便同学们?所以我再说,你真正在参加工作的时候,这些底层函数你是很少直接写的,明白了,很多都是这个,一个代码里面,一个库文件里面都写好了,你直接把这个库函数一调。哎,好几个功能实现了,你就不用关心内部的细节了。
为什么这样干?同学们有什么好处?提高开发效率,缩短开发周期?如果说什么你都从底层写,很慢,是不是这个代码的复用性不高?我们能够把一些经常用的函数合在一起,是吧?比如说我们在公司里面有的时候这个封装一些符串的函数,字符串,比如说string copy 那些东西都封装成一个函数了,一个库里面,比如这些这个业务相关的这些公共函数也放在一起,目的就是为了调用的时候方便缩短开发周期。
而且这些函数是经过严格检验的,知道什么意思吗?是经过严格检验的,那么是不是这个用的多了之后有问题就早就修复了?你再用的时候基本上就没有问题了,基本就没问题了。这这些函数,这些函数要求你这个大致都通读一遍,这里这个里面没有什么太难的,都是一些基础的,这个可能稍微麻烦一些,这个你得分析分析,这个是不是你得稍微的看一看,大家想一下这个里面用这个静态变量有什么好处?静态变量的话是不是只初始化一次?还记得吗?它是不是能有记忆功能啊?下次再调用这行的时候,这个变量是不是还存在?这个他读的话我看他这里面是不是调用了?这个没问题吧,这读嘛?然后这个是等于0,是不是读完了?好像是一个一读的,看到没?是不是加减 ?是不是一个一个读的?是一个一个读的吗?同学们, read 我看,这个你得琢磨琢磨,你琢磨琢磨, ptr read_cnt是不是这个是读到的字节数了? read_cnt<= 0
read_cnt ==0 ; retrun 0;
read_ptr等于 read buf,这个指针是不是指向这个 buffer 了?这个 buffer 哪来的?是不是这个是静态的?read_buf 158同学们,是不是静态的? return - 1 read CNT 减减 read CNT 是不是也是静态的?
这个它是一个一个读的,那么你看一下这个my_read在哪调用了?是不是可以逆向推啊?同学们,你看这个在哪调用啊? 咱找一下my_read在哪调用的?我记得有一个地方是不是调用my_read的了,readline 是不是在这?他这是不是?他是不是指这个也是这个一个读的?很显然这个函数是不是每次只读一个数据,能理解吗?这些函数里面,这些所有的函数里面你都自己通读一遍,至少做到你能够把这里面的每一句代码看懂,而且你还能调用,是不是?然后以后咱们这个下午写代码的时候,我们就用这个库里面就用这个代码里面函数咱们直接调,是不是我们就省去了很多写这个 perr的东西了,是吧?给返回东西吧?就省得写了。好,那么这个代码的介绍我就给大家说这么多,我就再说这么多。
好讲最后一个,今天上午的最后一个概念,粘包问题和解决粘包的问题。首先跟你说一下什么叫做粘包,咱们大家想一下有没有这种可能出现,那么客户端给我发送数据的时候,连续给我发两次是吧?连续发两次,第一次发了 10 个,第二次发 20 个,那么我服务端在接收的时候,我第一次只读了 8 个,第二次我是不是要读 22 个才读完?大于注意第一次读 8 个,那就意味着是不是缓冲器里面剩余了第一次的两个数据,是不是?那这个叫黏包,这个就叫黏包清楚了嘛?这是基本概念。
我给你画一下,你看见这是那块缓冲区吧?同学们啊,这是缓冲区。什么缓冲区?同学,这个是应该是什么缓冲区?人家给你发出去发在哪了?叫接收缓存区,叫或者叫读缓冲区,那么在这个接收缓区缓存区里面,对方给你发数据的时候连着发了两次,我这个大概给你写个过程,对方发送数据,是吧?那么连续发了两次只是一个特例,对吧?能不能多次啊? 都行,反正你是这样,你是做的,是这样做的,你不是说每次都把每次数据接完了,知道什么意思吗?你,人家给你发了 10 个,你接了 8 个,是不是 2 个留在了后面了?那就粘包了,你这两个数据是不是要放在后面读?是不是?这就粘包,连续发送两次,然后读数据的时候,第一次没有读完,然后剩余的数据,在第二次读完了,在第二次读走了,那么这个时候这种情况就属于黏包,能知道什么意思吗?同学们,好,那么我给大家画一个图,这个是不是那个缓?这是那个缓存区。同学们,好,你看第一次发了多少数据?比如说他第一次,来个红色的,嗯,这什么样?哈哈哈,感觉是这个线条太粗了,是吧?
划线,好,这个什么意思?我给大家简单说一下。什么意思?这个你首先把这意思明白了,那么这个在这个写的时候,看一下,那么这儿比如说他第一次发的包 是这么发的, 0123456789 是不是发了 10 个数据啊,同学们,发出 10 个数据,然后这边第二次发的是a、b、c,d、e、f、 g 随便写一个。那么但是你第一次读数据的时候怎么读的?他不一样,他读的不一样,这是第一次,第二次在注意只是发送进来的,发两次。那么但是他读的时候怎么读的可以分开,你看他读的怎么读的,这么读的
那么一开始人家读数字的时候呢,读到这儿了,我是读的,现在是读,同学们别搞混了,读这来了,也就说一开始他是不是 0123456 读完了,是不是剩下还剩下这个 789 以及后面的数据,那么后面的数据在第二次读走了,第二次走了这种就产生粘包了。也就是说第二次读的数据是不是有第一次剩余的数据?这种就叫粘包,能清楚什么意思?这是基本概念。
好,这个概念搞清楚了,大家想一下粘包会有什么危害?你搞清楚,如果说你有粘包问题,会对后面的数据它的逻辑判断产生影响。我举个例子,你比如你取款的时候,你本来是取了这个 100 块,是吧?同学,这可能例子不是十分恰当,但是由于粘包,你这个数据没有被解析出来,是不是放在了第二次这个读走的时候了?那么你说这别人存款的时候,你存了1万,正好把这 100 弄上去了,是吧?存了1万,结果显示 100 知道什么意思吗?就产生问题了。
当然如果说你这个第一次和第二次,如果说是在一个路,一个业务逻辑里面,在一个做业务逻辑里面不会产生任何问题,比如写文件,比如接收文件,那么对方发了 100 次,是吧?同学们,我每次读的时候是不是有可能读不完啊?这个没事,但是如果说你第一次和第二次它的业务逻辑完全不一样,他就产生问题了,能听明白这句话的意思吗?嗯,好。
那么如何解决这个粘包呢?如果让你解决你怎么解决?其实很简单,用的我们用的这个,我们用的手段也非常的简单。如果说你把每一次发送的数据的说多少这个量告诉他,他就是不是就知道了?那举个例子,第一次我是不是发了 10 个数据啊?好,那我就告诉你,我就发了 10 个,那么你接收你就接10 个就完了,是不是肯定不会产生粘包了?是不是?这种是我们用的最多的一种方法,也就是加一个包头,加 1包头,看一下,解决方法。
第一个是什么呀?是包头,加什么呀?加数据。好,那么既然我跟你说了这个解决方案是包头加数据,那你看一下,那么如果说放在我们这个里面,这个怎么写?包头是包头可以用什么呀?可以用 4 个字节的长度,知道什么意思? 4 个字写长度用两个行吗?同学们,用几都行,你随你定。
可以吧?你用四个字节的话,它最大可以表示的范围是多少?处理长度是不是49?用两个的话是不是99?是不是大家注意用的时候最好用长一点,因为你的数据是不是有可能会长,是吧?好,那么如果说放在我们这应该怎么用?这怎么用?怎么用?同学大胆说,我前 4 个字节就表示长度,而且我把它换成字符串, 10 个字节长度换成字符串,怎么写?能这么写吗? 0010 知道什么意思吗? 0010 是表示10,但是你注意这个是0010 ,是字符串,知道什么意思吗?如果你用的是这个整形,是不是你还得做什么呀?大小的转换?知道什么意思吧?那个麻烦一些吧?我们用这个0010表示前 4 个字节,表示数据长度。
那么好,对方收到数据之后,他先收 4 个字节,收到 4 个字他干什么呀?他是不是要计算一下,怎么计算?同学们怎么把这字符串变成整型长度啊? a two i 吗?这还用说吗?忘了,后面是什么呀?后面是不是加数据,加数据部分?那么这个最后我们弄这个这样说, 4 个字节长度加数据部分。好,那换到我们这儿这个东西应该怎么写?同学们,是不是0010?那接下来是什么呀? 0123456789 是不是?嗯,好,那么这个数据发给对方以后,对方收的话怎么收?
他是不是先收四个字节?他们收到 4G 之后,他是不算出长度来?算出一长度,他在 read 的时候他是不是就可以知道读多少字节数据了?这个时候还会粘包吗?百分之百不会,这种方法是在项目当中用的最多的方法,你把这个掌握了就够了。清楚了,那么第二次的话怎么写?第二次的话是几个数据?同学们, 7 个吧?,是不是 7 个?0007吧?然后a、b,c,d,e、f、 g 是不是就可以了?那这样的话,你说他分两次读还能读错吗?但是这样读的话是不是会有这种印象啊?他是不是要分两次读?每次读是要分两次,至少分两次吧?
如果是您数据太长的话, 9999 的话,你 read是不是一次读不完的?读完没有关系,调用我们那个什么循环读的函数是不是就可以了?是不是我们这里面有一个循环读的?哪个readn吧,是不是这个?你是不是循环读就可以读完了?是吧?然后定义一个大数组是不是把这个数据就放在这数组里面来了?知道什么意思吗?好,这个好,这是一种方法。那么另外还有第二种方法,这种方法不推荐大家用。
第二种是添加结尾标志,什么意思?这个数据是不是有一个,就像我们这个字符串有个\m结束啊?添加一个字,这样一个标记,你比如你可以杠n,什么杠$等等,你自己定,这个定的话是不是要双双协商?你定的是井号,对方收到井号之后它就结束了。但是这样做的话,是不是你得这个挨个判断?那举个例子,你他知道第几个是吗?他不知道吧?是不是读一个,然后判断一下,读一个判断一下是不是很显然这样的效率比较低?能知道什么意思吗?这是这个,添加结尾标记,这个第三个,还有一个是什么呀?发送定长数据这个能力什么意思吗?不管什么业务,我每次都给你发 100 个字节,你收的时候你就收 100 个,知道这意思吗?好,那么这样的话,他还用这个收前四个字节,然后判断长度吗?不用了,反正我已经给你协调好了,我就给你发 100 个,是吧?你收的就收 100 个就完了。
至于说这 100 个字节的数据里面每一部分代表什么意思,是不是也得事先协商?好吧?这个是不是就是相当于协议的意思?在这个开发的时候,这个不叫协议,这地方叫接口文档或叫规约,是不是相当于还是规则,也称之为协议?那么我们一般这个协议是在这个这个底层的时候称之为协议,比如 TCP 协议、 UDP 协,这也叫协议,然后在这个数据部分的协议我们自己定的。
叫什么呀?一般称之为接口,你们一般参与工作的时候就写接口,知道什么意思?你们写接口居多。接口的话比较简单,你只要把文档看懂了,毫不夸张的说你就能写,现在你们就可以写了,而且写得非常好,厉害,哈哈哈。你们这个如果说做这个,如果说你们做这个什么呢?做这个研发的话,什么研发?做产品?当然你也能看过接口的,也得看接口,那个就没有那个多了,那么这个时候就技术水平上要求要高一些。不就说你在你参与工作以后,并不是说你这个,你感觉老师我这个网络没学好,我是不是就不能工作?不是那样的,你网络编程没学好,你 QT 学好了吗?没有,是不是这个系里面的学的怎么样? c 是不是 c++呢? 。然后你说这个韩老师没教过我。
就是说你一般的情况下,在这个在公司里面做这种业务型的开发,你只要会看文档,写代码不是难度。写代码不是难度?难的是什么呀?里面的业务逻辑比较难,那个得需要你琢磨琢磨,但有的人他就有这样人,咱们这里边人一定不少。他那文档会看,技术可能稍差一些,但是也不影响他写代码,因为代码本身没有什么太难的,他业务的理解得很清楚,这样类的公司一样能够吃香,而且比做技术的还厉害,知道什么?这就是在公司里面做业务的,他在公司里面做时间长以后他干什么呀?他专门跑市场。比如说这个某个银行要这个做系统,他去给人家谈,他能够把这系统说的这个从头到尾呢这个整个都说得很清楚。你让他写的可能写不出来,但是他会说这是意思,但是他一定是理解业务才能说得出来。
明白什么意思?我给你举个例子,微信是吧?对吧?微信这个话我们支付的话是不是也有一套流程?这个流程你可能说出来,但是你内部细节可能不知道,但是不影响你去交流。细节的话不用你管,你只要给人家协调好了,这个文档生成了,然后这个召集一帮项目经理,然后咱把这个细节讨论一下,细节他们去做明白了吧。
这开发人员去做,你做的事情,把这个整个业务流程搞清楚,这样人能在公司挣大钱,比你做开发还厉害。明白,另外会吹牛逼,但是你记住,如果你一句代码没写过你这样的东西,你不可能说理解的,理解得非常透彻,知道什么意思吗?那么做技术的往往朝哪个程度发展呢?最后做最高的就是什么呀?项目总监、架构师,架构师,这个做一个产品它是不是需要一个架构,整个架构还这个他捋得非常清楚,一般这样的人工资在一般在五六万开外,年薪不超过 100 万。当然做得好的大公司的话,像阿里的话,比如说这个最高级别一年几百万都是有可能的,知道什么意思?因为刚进去的时候这个要求不要太高,先慢慢锻炼。
这个是什么呀?这个是发送定长数据,是吧?同学们,这个是定长数据,那么这个定长数据还用他这个判断接收多少个吗?反正你约定好了吗?我就每次给你发 100 个,你那收到之后就直接收 100 个就可以了,是不是?直接收 100 个就可以了?明白什么意思?同学们,再有一个,这个我给大家提到了这个发送的这 100 个数据里面,它是不是每一部分可能代表不同的含义?知道什么啊?我举个例子,举个例子。这这这个你只要这个能听懂了,你做类似的东西你一点费事不了。比如说我规定了每次给你发送 20 个字节,这是发送定好数据好,比如说我们发的这 20 个字节数据,如发送 20 个字节的定长数据,好,我给你规定好了,每这 20 个数据里面我给你分成了好几段,每一段代表什么意思我都给你协商好了。
这个是不是相对规约?是不是相对协议双方?你协商好了,这个协议一定是双方定出来的,或者是你定出来之后,你告诉人家,你不告诉人家人,家人知道吗?是不是一般情况在公司里面,像这样的公司会形成一个文档,你定好这个规则之后,你写那个文档,然后把这个文档发给人家,人家去看看这文档,人家看不懂的过来找你,是不是需要啊?沟通好发送数据怎么发呢?比如说学生信息。举个例子,学生信息好,前两个字节或前 4 节代表学生的学号可以吗? 0010 表示这个学生是不是 10 号编号的?姓名是不是?你们姓名用一位肯定不够。姓名是不是知道好几个字节?还得比如说叫 8 个字节可以吗?叫小赵,这个是不是正好 8 个字节?哎,代表姓名,现在几个字节了, 10 个字节了,而那后面还有两个家庭住址是不是可以?北京是吧?北京几个字节?现在是 7 个字节了是吧?亲们,还差 3 个,不足 3 个空格。这你自己定,你补 0 也行,你补空格也可以。
当然如果我们定义了现在几部分,是不是学号什么呀?姓名家庭住址是吧?他们是不是还有一个?还差一段了,还差三个字节。那就来个年龄吧。年龄是不是最多?一般人活在 100 一百来岁是不是一般?就是吧,那这个比如说他是 25 了025,那这样的话,对方收到之后,他是不是就可以对这个字符串进行解析啊?他就知道前四个字节, 10 小赵,他的这个编号是10,他知道了。然后他的姓名叫小赵,家庭住址的是北京,然后年龄是 25 岁了,是不是就知道了?就这么来的,你以为有多难啊?能看懂吗?你如果在公司里面能够做到这个程度,做到哪个程度呢?你开始制定这个规则了,这个接口了,那你一定是在公司里面一定是做的时间比较长了,而且业务流程非常清楚。
你如果不清楚,你知道这个里面包含哪些东西吗?你知道多长吗?不知道,得注意。行,这一块就说这么多,这是粘包的这个东西,大家说到这一共 3 种方法,同学们包头加数据,这个是我们用的最多的,还有添加结尾标记,这个不建议你去用。为什么?效率低,是不是这样的?还有一个是发送定长数据,这是可以用,但是一般情况下这种我们用的也不多,为什么呀?因为不同的业务它的这个数据量的大小是不一样的。我举个例子,同学们,你说你在银行里面取款是吧?对吧?取款是不是?我取 100 块钱是不是就发过去了?然后你再有一个,再一个业务。
那什么呀?我想查询一下我最近半年的交易记录,你说这两个交易的数据量一样吗?哪个数据量大?交易记录,你交易记录就是不是很多条了,这个数据量肯定比较大,很显然这个数据你用定长就不合适。实际上我们在进行项目开发的时候,在进行业务开发的时候,往往数据是不定行的,是不是我们可以用上面这个方法呀?包头加数据,数据长度告诉你后面是具体的内容,内容里面是不是我还得定一个细则,规则每一步代表什么意思?有多长?是不是得定好这意思?行,都回来。
咱们昨天写的那个服务器的话,是不是只能接受一个客户的链接?这样的东西有用吗?其实没有多大意义是不是?那我们的服务器是不是应该是这个能够支持高并发,是吧?那么多个客户端同时到来,我们仍然能够处理好,那么先给大家讲两种模型的,这种模型也是大家这个有点基础的,多线程 多进程咱们接触过,是不是咱们看看怎么写?大家想一下这个,我们前面讲这个第一个版本的这个服务器的时候,是不是里面调那个accept的接受一个新的链接,然后接下来是不是 read 在一个循环里面,是不是循环的收发数据啊?是吧?同学们,咱们都说过了,这个accept的函数调用一次是不是只能接收一个新的链接啊?很显然如果说你想接受多个链接的话,这个accept函数应该在哪调用?是不是循环里面?也就是说你接收到一个新链接,是不是你要处理一个链接?嗯,在这我们使用多进程版的,那么好,如果不使用多进程版会有什么?会有什么? 弊 端?大家看这代码,我给大家写个伪代码。
那么你比如说while 1 是不同意向大家先听我分析一下while 1 ,那是不是我们都刚才我们提出来的,应该在这个while 1 里面去 accept 吧,是吧?同学?嗯,好,accept,是不是返回一个新的链接,比如说cfd 等于这个,是吧?你这个我具体先不写了,accept是不是我们得到一个新的链接啊?这新链接是不是用这个c、f、 d 来标识?好,那接下来是不是开始这个有新链接之后,是不是我们就可以收发数据了?好,是不是你再写个while 语句啊?同学们能理解什么意思吗?好,是不是我们需要这个?有的时候需要循环收发,是吧?然后比如说n 等于一个read, c f d buffer size off buffer,是吧同学们,好,如果这个 n 小于等于0,这个大家知道吧?是不是需要 break ?对方关闭链接或读异常,是不是我们都需要这个跳出循环?好,是不是?按照我们的这个正常的思维,应该是这么一个逻辑啊?仔细看,如果说这个成立了,是不是 c f d 我们得到了?兄弟们,然后我们read,如果说客户端他也不关链接,但是他也不发包,他也不怕数据,你说会怎么样?那么你这是不是你这个 read 函数会阻塞?你阻塞的时候,这个时候再来一个客户端链接,能接受吗?能接受吗?同学们,所以我们这样做是不是不合理啊?那也就是说什么呢?你这个accept函数你得不到执行,那么你就调,你就获得不了新的链接嘛?是他们。那么是因为什么呀?是因为这个有的客户他链接上以后,他并没有发数据包,并没发数据,那么导致我们这个程序阻塞在这个read这个位置了吧?,是不这样的。
那么如果说咱们这个先不跟你说多进程版的,如果说这个让你解决,你怎么解决?这个怎么解决, 唉,有同学说对了,让他可以不阻塞,这个 c f d,是不是我们可以设置为非阻塞啊?怎么设置
怎么解决办法?我先给你提出一个疑问,这个疑问大家都知道什么意思了,那咱们看怎么解决。很显然我们应该将什么呀?将cfd 设置为什么呀?非阻塞就这样了。非阻塞,能理解这句话的意思吗?好,非阻塞你知道了,那非阻塞调哪个函数了?是不是这函数了?调这函数这个步骤大家还记得吧?第一步是不是先 get 下,然后去设置,然后 set吧? 是不是三步?这我先不细说了。
好,继续观察,继续观察好,他已经是非阻塞了,这没有关系,是吧?同学们,那假如说这种情况,同学们,那假如说这个连续有好几个链接发过来了,有连接就过来了,是不是?我这个 accept 是不是都可以返回啊?再看 那么你说你这个read读的时候,你知道读的是哪个连接的数据吗?是不是混了?是,那我的话听懂了没有?就是什么意思?就是说我同时有多个客户在链接到来,就这个 CFD 是不是都可以返回?最后这个 CFD 是不是只能保存一个值?嗯,这句话能不能听懂?对,为什么?因为是不是你上次 CFD 是被这次的CFD 覆盖了?这个是不是也不好啊?能理解我说的话意思吗?同学们?嗯,好。
所以说这种方法就是比较笨拙,而且相对麻烦,并不能够实现更好的这个高并发。为此我们提出了两种解决方法。这是解决方法1。跟你说一下不太可行。那么什么情况下,假如说,有多个客户端链接请求是吧?cfd 是不是只会只是最后一次有效,只会保留最后一个链?一个文件描述吧?是不是这样的?有同学就说了这个老师,我这个可以把这个 COD 保留着一个数组啊?这是可以的,但是保留成数组以后,那么你这个read这儿是不是也相对麻烦一些?那你read的时候是不是还得把这个数组遍历一遍啊?还得去接收啊?那么总知道反正这样做的不管怎么想都不太省事,都麻烦,是不是都比较麻烦?为此我给大家咱们有 2 种解决方法,更好的方法一个是使用多进程,一个是使用多线程。
咱们先说第一种,好,再想一下,那么如果我先给你提个醒,说使用多进程来解决这个问题的话,你应该朝着哪方面去想啊?fork是肯定的。好,大家想一下,咱们如果使用多进程的话,那么父亲做什么事情?子进去做什么事情?你是不是要给他分工一下?父进程做什么事情?监控回收,说对了,父进程就是在调用accept 就是监听得到一个新的文件描述符,这个新的文件描述符 谁去处理?谁去处理?要敢说,那么这个父进程,这个接收到一个新的客户端的链接以后,然后他创建一个子进程,让这个子进程去处理这个链接,那说白了处理这链接的话是不是就是手法数据啊?嗯,这不就好了吗?你再来一个链接,我再来一个子进程,再来一个,再来一个,是不是?这样的话是不是就分开了?谁出题的?是不是互不干扰啊?那么好,那么你父进程应该需要增加哪些功能?除了这个父进程,这个去accept 一个新的链接创建子进程以外,那么这个父进程是不是应该还有其他的事情要做?什么事情?它是不是可以回收子进程?朋友们,回收子进程咱们前面讲过了,最好的方式是使用什么来回收?是信号机制是不是?这样的话就是把咱们前都连贯起来了。嗯,好,这个咱们都知道了。
接下来咱们把这个整个 过程 跟大家一块捋一遍,分析分析,你看看你到底是哪不知道,嗯,或者是你哪不清楚,咱们都一块说一说。好,这个咱们提出两种方法,这个不太可行,那么解决方法2,是吧?使用什么呀?使用多进程,那大体思路这样的让谁呀?让父进程干什么啊?是不是监听接受什么呀?接受新的链接,那子进程处理链接,新的链接,其实处理新的链接是不是括弧?就是接收数据,接收和接收和发送数据是不是这样的?当然父进程还有一个功能,干什么呀?父进程还负责回收子进程,这句话能听懂什么意思吗?也就是说现在我们已经明确了父进程做的事情,子进程做的事情,那么接下来咱们代码是不是就好写了?好,咱们就大体的给大家捋一下,好处理流程。
好,咱们看一下,那么在这个父进程里面我们都做什么事情?一开始的话先干什么?你们来说我们这个是不是写的一个网络服务器?第一步干什么?第二步干什么?第三干什么?是不是都有固定的套路站到第一步? 创建 socket 是不是你们原来怎么写的,现在就还怎么样,清楚了吗?同学们,是创建socket,是不是这个好调用哪个函数了?是不是函数那么得到了一个什么东西?同学们,得到一个监听的文件描述符,叫 lf d,可以,咱得起名字。前面这几步大家都会。
第二步,干什么?干什么?是不绑定,将谁和谁绑定,将 LFD 和什么呀? IP 和端口,端口我们称之为什么呀? port 进行绑定,进行绑定,调哪函数是不是这个函数?是吧?同学们,第三步,干什么?是不是设置监听啊?同学们,设置监听这两份,是不是一个?这是不是?咱们这些是不是咱们都清楚,咱们无非是在这个原来的代码基础上进行了扩充和改进,是这样的吗?第四步干什么?accept 能在这个循环外面调用吗?你如果在循环外面调用,是不是又回到老路上去了?是不是接下来是不是进入 while循环的?
注意看这个思路,如果我把这个思路写出来,你自己能把能不能把代码写出来?好,进入外部循环先干什么?同学们,是不是应该是阻塞?等待有新的客户端连接到来,调用哪个函数?是不是 accept c fd 等一个,我只把伪代码写下这个,这个具体你去填就可以了。
好,我们接收到一个新的客户链接,接下来干什么?现在是不是我们已经有一个新的这个客户链接到来了,而且我们把这个文件描述符也拿到了,接下来做的事情,你们说 Fork 是不是要 Fork 出一个子进程来啊?对,搞清楚。 Fork 一个子进程,然后让子进程去干什么呀?去处理数据就是收发,是吧?同学们好,接下来是不是这个 p i d 等于什么呀?等于Fork ?好,那么这个 p i d < 0 的话,是不是就失败了?这是一种情况,这失败情况咱大致给你写一下这失败的情况,失败干什么?同学们退出就可以了。
else,如果说 p i d 大于0,这是什么?这个我就不再写的那么详细了,这个大家都知道了。好,pid > 0 是不是父进程啊? else 等于 0 等于 0 是不是子进程啊?好,你先把框架写上,好,咱们再看。那么我问大家,你说这个新的链接在父进程里面使用吗?不使用,不使用干什么要关掉它啊?不调肯定就关掉它。大家记得我想的管道的时候吗?父子进程通过管道来通信的话,我是不是跟你说过一句,用哪端留哪端,不用哪端关哪端。对于我们这父进程来说,他用收数据吗?不用。他用发数据吗?是不是也不用?干什么呀?关掉close?关闭谁?他是cfd,关闭通信文件描述符, 是不是直接 close 就可以了?c f d?好,那么我再问一下,那么这个子进程它用监听吗?干什么?它是不是要关掉?也就是说你这个父子进程他在做的事情是不是已经明确了,你子进程就是完成数据的出发,你父进程的就是完成什么呀?监听,有链接到来我就接收一下就可以了。
所以在这干什么?关闭谁?关闭监听文件描述符,close(lfd) 这个为什么这个关掉之后,我们这个,我们这个 c f d 的链接没有被真正关闭?同学们,为什么呀?这是不是复制过来的? 子进程是不是复制过来的?我父子进程是不是这个都有这么一个CFD,是不是你关掉一个,只是说是它的引用进程减一吧?并没有在底层真正关闭。清楚了吗,但是线程就不一样了,线程是不是共享?对,线程一关是不是就没有了?这个你得知道。
好,现在我们这个里面就这个基本写完了,那么接下来是不是你该写子进程做的事情,是不是我们也很明确了?干什么呀?就是收发数据吧?怎么写?是不是是不是进入while 语句循环啊?好,接下来是不是? read的数据,同学们读数据,是不是 n 等于什么呀? read 吧? read,这个是 c f d 是吧? buffer set off buffer 好,
那么什么情况下我们认为这个客户端就处理完了?是不是对方关闭链接或者你读异常了?嗯,是吧,这一点是不是和咱们前面讲的一样了?n小于等于 0 吧,干什么?这 break 是吧?跳出循环好,再往后看,那么他是不是收到数据之后进行处理,然后是发送数据啊?当然这个具体这个有没有什么什么大数据转换,还有其他处理?咱这不说了,咱们只说流程,大体流程发送数据给对方,对,是不是这个write?这个是 c f d,这 buffer n 后面还用写吗?同学们是不是接下来这个循环里面,他是不是一直在反复的做这件事了?好,那么如果跳出循环之后,你说我们这儿后面还写什么?这个是务必你思考清楚的,如果考虑不清楚,你的程序我很难看,一会儿就整个俩都死掉了。
怎么着?跳出之后我是不是可以?我,当然我如果把这个关闭,这个 cfd是不是也可以放在这里面了?能不能?可以吗?可以。那么如果放在外面是不是也行啊?这,这行吗?同学们是不是也可以?是不一样,反正 break 了吗?是不是跳出来了?后面还有东西?同学们,一定要想一想我们循环创建子进程,切记的一个问题,如果你想过那一层,那么你的程序会很难看的。仔细想想如果我这样写的话,有没有什么弊端?这个是不是子进程和父进程是不是都在这个 while 循环里面?怎么办?
现在我问一下大家,现在来说我这个子进程是不完成任务了,是吧?比如说对方关闭连接,对方光关闭连接以后呢,我们这个子进程是不是已经是完成任务了?完成任务他应该怎么办?他要退出啊是不是?如果你不退出会怎么样?他是不是也会去循环创建子进程啊?对,是不是没循没完了? break 包使,直接这样调用退出就可以了,这样是不是更彻底?大家注意这个错误我犯过,当时弄之后这个不停的,尤其是什么,尤其你创建的这个子进程数量也多,而且关的也多,它会来回的创建一会你的这个服务,你的这个系统里面这个进程数量就太多了,会不会有这个效果?同学看看,肯定有的,我们这个里面是不是也没有对这个数量做控制?看没,是不是,这个是肯定有的,所以这个务必加上,我们加上,下面的必须有什么呀?防止什么呀?同学们,防止子进程干什么呀?再去创建子进程,是不是这样的?这个务必要清楚,你写一个 break 行吗?同学们看看,如果你写 break 是不是也可以?但是你在这儿的话,是不是他关掉这个之后他是不是就没用了?你直接在这直接退出就可以了。
这个退出是不是对父进程没有任何影响啊?这个是不是只是他自己退出?嗯,是吧?好,这个这完成了,这完成以后这个基本的框架是不写完了,那个补充的框架,那个补充的功能你们自己去实现。那还用我写吗?那么接下来的话,这个子进程退出之后也应该父进程,它应该干什么呀?是吧?回收它,回收它的话我问大家,这里面有可能会发生一个问题,有没有可能这个函数用完了?我上午是不是讲过了accept它是不是阻塞函数了?阻塞函数它是不是有可能被信号打断?那么这个 seek child 信号它是不是也是信号?是的,它信号打断之后,这个 accept信号函数是不是会立刻返回?这个处理你得注意一下是不是会产生一个 e Inter啊。
如果你调用咱们这个 wrap. c 里面的代码,就不用考虑这问题。为什么呀?他这里面给你考虑到了,看看,是这有,所以我们下午写代码的时候,我们就按这个流程来写,调用它这里面的函数这些异常的话是不是就考虑很清楚?对,但是这个东西你得知道这个,因为这个我记得我带其他的一个班的学生,他说老师我代码和你他完全一样,他无非就不一样的地方
就是你这个地方大写,我这个地方小写,是吧?我这个怎么回收老出问题呀?后来我一看,噢,你这是肯定是被信号打断了,结果他改了以后没事了,清楚了吗?朋友们,这个是需要你这个特别注意的地方,我这给你补充一下,我给你写在后面,这是你需要注意的点。这点怎么说?这是 accept 或者read函数。是什么呀?阻塞函数吧?会被谁啊?会被信号打断?当然我们这儿的信号,肯定你能预见到的信号是不是是一个chart信号啊?你能预见的是不是?当然有没有其他的,是不是有可能为会这个在外部触发啊?比如 q 是不是可以,那么此时那么不应该视为一个错误,那么实际上这个也就是说这个errno应该是几?同学们,errno会是这个 EINTR,首先会是这个,这个逻辑是你的,判断一下,如果你如果咱们没有这个代码的话errno=EINTR,同学们没有这个代码的话,你是不是得把这个加上?一定要注意这个好多人不知道好,那么这个流程呢?这个流程大体上是不是就这些啊?是吧同学们,你们没想到的,可能这一开始你不知道,现在我告诉你了,是吧同学们,这个信号会被信号打断,这是一个。
再一个,那么我们接收到一个新链接后,之后,我们调用 fork函数创建一个子进程,然后在这个子进程里面去收发数据,说这也告诉你了,还有一个注意点,这个 exit(0)是不是告诉你了?那么注意的点呢?你都知道了,那么接下来写程序的话,我相信你能写出来,能写出来吗?同学们,这个信息应该有吧?你这样,同学们你如果你写的慢的话,你怎么写?你这个在这方面在上面写,在 UE 或者是 node Pad 上写快一点,这个代码是不是大厂就都有了?
好,这个开始上课,那么咱们接下来咱们一块来写一下这个程序,很多同学已经写出来了,可能有些同学有这些,有一些问题没有关系,那么你先停一下。好,先停一下,那么咱们一块来,写完之后你再看看字的程序到底是哪有问题。
好吧,同学们先注意听好,咱们写一下这个程序,那么第一步先干什么?同学们,这个是不是创建socket?创建 socket 是吧?调用哪个函数呢?在这咱们就调用wrap. c 里面代码可以吗?好,这个怎么写?是 int lfd 等于什么呀?是不是s?是不是这么写?是吧?兄弟们,第一个参数是什么呀? AF_INET 第二参数,SOCK_STREAM,最后一个是0,这个你还用在这写个if lfd< 0 吗?同学们,不用,因为这个 wrap . c 里面是不是给你东西都有了,是吧?你们,你就不用写那个了。
好,第二步干什么?是不是绑定啊?绑定的话是不是我们需要这个?先定一个结构体变量啊?怎么写? struct sockaddr_in serv 初始化 bzero 是不是serv?然后这个是sizeof(serv),接下来是不是要给这个解构体变量赋值了,是吧?serv. sin_family =AF_INET,第二个是这个 serv.sin_port =什么呀?咱们这设定端口 4 个 8 可以吗? htons(8888)?那么还有一个是不是 i p
serv.sin_addr.s_addr = htonl(INADDR_ANY)
这样的代码你多敲几遍你就会了。清楚了嘛,同学们,不要怕麻烦,不要怕麻烦。
好,这个已经赋值完了,接着是不是开始bind了?bind 我们这样掉可以吗?这个函数是不是只是第一个字母变了?第一个参数是什么?是不是l f d?第二参数是不是要做一个强制类型转换? struct sockaddr * ,然后这个是 & serve,后面两个是不是这个 serve 的长度?绑定完成了?
嗯,第三步干什么?第三是不是设置监听?嗯,设置监听怎么写? listen 是不是listen,然后这个是fld, 是不是这个准备工资是不是差不多了?接下来进入 while 1 循环,是不是要结束一个新的链接,是吧?那么结束一些新的链接,是不是咱们外面应该定义一些变量?同学们? int cfd 这个有吧?是吧?那么你用到哪个去加就可以了。
一开始认为你可能想不全,没有关系的,相当于什么呀?Accept 这个函数里面是不是把我们处理掉了一些异常?好,第一个参数什么?先给它设置为NULL,这个可以吗?先不用管它的,好,这个还用判断这个 c f d 小 0吗?不用不用。
接受新的链接。
好,这个链接有了,接下来该干什么了?接下来是不是要创建子进程啊?同学们,创建子进程,那么这个变量的定义你放在外面写也行,pid_t, p i d 等于一个fork。像这样的这个注释我就不再写了,因为咱们前面写过好多次了。这个咱们判断一下,如果 p i d 小于 0 的话,是不是我们认为这个fork 失败了?fork 失败干什么? perror一下可以在这时候直接退出就可以了。可以。好,那下面的话是不是成功的情况?如果 p i d 大于 0 是什么?
这是父进程吗?父进程做的事情什么样你们还记得吗?先把框架写完,这是不是这个是子进程? p i d 等于个0 是不是框架写完了?好在这个父进程里面它是不使用这个通信文件描述cfd啊,所以它有关闭,是不是?关闭什么呀?关闭通信文件描述符 c f d ,close c f d 是不是?后面等会再写?别着急再补充,这个的话,是不是子进程啊?子进程不使用谁关闭?监听文件描述符吧?是不是这个,大家能跟上思路吗?晚上反馈的时候别到时候再说快。
好,这个是close,这个是什么呀?lfd吧?对,那么基本的框架是不是写完了?那么接下来的这个操作是不是和咱们昨天讲的就一样了?对子,进程里面做的事情就是收发数据吧?同学们 好,读数据。读数据从哪读?同学们,读数据是不是从这个内核的什么缓冲区啊?叫接收缓冲区读数据吧,你不要以为是从网卡那直接读的,要记住一点,是不是 ?n=Read,这个可以吗?第一个什么呀?cfd,第二个 buf,咱们先写再定义是不是 sizeof(buf)了,那前面的话是不是我们用?我们是不是可以定在这了?同学们,这个变量是不是只是在子进程这了?所以你把这个 n 定义 在这就可以了。
int n char buf[1024],这个设置就是1024,当然我们这个读数据和发数据是不是应该写在一个循环里面?嗯,这样,是不是这个?这个它自动把这个右边}你补齐了,看没?还比较方便,是吧?同学们看一下,如果说 n 小于等于 0 什么意思?是不是这个读失败,或者是对方关闭链接了?啥玩意?多了个这玩意? 这个 print f, read error or client closed,是吧? n 等于多少?n?
这样怎么办? break 是不是退出?退出,这咱们和咱们写的这个思路上完全一样,这没有区别的。好,那读完数据之后,它是不是要发回去?那么再发回去的时候呢。我们做一个这样的操作,我们把小写转个大写可以吧?嗯,这怎么写?for循环吧? 是不是?这应该进一个 int i,这个 i 是不是一个局?是不是把这个放上面去?放这行可以吗?for i 等于个 0 i 小于个多少?n i++,是吧?这个 buf[i]等于什么呀?toupper(buf[i]) ,这是buffer,是不是?这个相当于是不是转化完成了?嗯, 将小写转换为大写。
这个转化完以后是不是接着要发送过去啊?发送数据用哪个函数呢?是不是write啊 在这 这样同学们,嗯,咱们调一下这个 receive 可以吗?是不是也行啊,那 receive 的话是不是我们这里没有,是吧?那还是调那个完事的,这个是不是大写的write?是吧 同学们 ,好,那第一个什么呀? cfd 第 二个是 buf ,第三个 n 是不是发送这个 n 个数据?这完成了吗?同学们,完成了吗?把它扣上,这个后面是不是你还得关闭它?如果 break 之后,是不是你还得把这个通信文件描述符给它关掉啊?关闭,是 cfd啊?是这个吗? close (cfd),是吧?然后在后面要这句话一定要有。exit(-1)
同学就问了,老师,这儿能不能是return?你们说能不能试啊?return 可以吗?同学们,可以是可以的,但是如果说你这个东西写在一个函数里面, return 就不行了,如果你写在这个里面,这个里面是不是相当于这个子进程的整体框架?你写在这是可以的,但这个是不是更彻底清了?这个函数写在 main函数里面是没有区别啊, return 和这个函数没有区别,但是你写在这个函数里面它有区别吗?这个原因,搞清楚是不是return 是返回到哪了?调用者的位置?好,那么这个就写完了。
那么写完了咱们看看这个代码有没有什么漏洞,这儿是不是要关闭啊?关闭谁?监听文件描述符吧是吧?监听文件,close(lfd)?好,我看看还有没有值得加的地方,咱们整体再捋一遍,创建socke没问题吧?然后绑定监听,这个时候是不是我们的程序就处于监听状态了,是不是就可以等待客户的链接了?同学们,接下来进入while循环,然后调用Accept的接受一个新的客户链接,接受一个新的链接之后,他是不是要 fork出一个子进程来啊,让这个子进程去干什么?同学们,去处理数据,是不是?接收和发送数据? 接受一个新的链接,创建一个子进程,让子进程完成数据的。
什么呀?收发操作是不是?
这是失败的情况,这是父进程。好,那么这个是子进程里面做的工作,是不是就是收发数据?同学说出去,然后是这个 read 没问题,然后是这个,如果说 read 完数据以后是不是要把小写转回大写,然后发送出去啊?整体上我代码是不完事了,你看见没加起来一共多少行, 85 行吗?如果你不使用这个里面代码是不是可能会长一些?因为你还你是不是要加一些逻辑判断异常处理?是不是这样的话,是不是我们这个wrap. c 里面是不是都有了?但是你编译的时候你得注意,你得把这个wrap. c 是不是加上啊,有的人没有加这个,暴露错误,未定义的引用或者函数没有定义是不是?好,这么这个代码写完了,咱们就编一下看看对不对?这两个代码我得把它加上去,我看看,直接拷贝一个就可以了。
cp wrap.c wrap.h 2018,现在是不是代码都有了?那咱就编一下,看一看效果,编一下,怎么编辑? g c c 杠 o server 可以吗?我起名叫server,然后01,然后是不是 wrap.c?然后呢,你写代码的时候是不是你得把这个 wrap.h 给引用上?是不是这样的?同学们你不加他的话,像这些函数他知道声明吗?是不是不知道?好,咱们编译一下看看,一步到位,看没听见没怎么着。好,启动起来,咱们看一下监听起来没有。
net step 杠 a n p 是吧? grep 8888 是不是起来了?我们这个程序,这是第一个同学们, 0 在这指的是本机的意思,是不是本机啊?你绑定肯定只能绑定本机吗?你能绑定其他的 IP 吗?我这个机子能绑定你的 IP 吗?是不是绑不了啊?你只能绑定本机IP,注意点。好,那咱就测一下对不对。 nc 127.1 8888 回车是吧?当然我们这是没有打印客户端信息啊?hello, word 是不是接收到了?
再来一个 nc 127. 1 8888 回车。你好,小吴是不是打出来了?再来一个 n c 127.1 8888去吧,是不都有啊?好,那咱再从这个我给大家呢教一下,这个工具大家拿了没有?你看我刚才给你发了个工具,你们拿不知道你们拿了没有,我找一找。嗯,在这儿。第二天这个拿了没有? sock to 这个工具我教大家用一下启动。
好,那么这个的话,我们这个工具应该是作为服务端。客户端的同学们,是不是客户端的,我们那个是不是写的服务端的?好在这点,这个点创建说这个 ip地址是不是要填一个地址啊?是吧?192.168.10. 143,这多少端口是8888?好,确定链接是不是已经连接成功了?然后我发送一个数据,大家注意,发送数据在这写,是不是这有发送啊?好,你好,小红,发送是不收到了,能理解吗?收到没?是不是收到了,那我这个服务端这边是不是没有这个?没有打印任何数据?你可以把这个打印数据的操作给加上,我们看加在哪加哪,同学们,是不是 read 完之后是不是在这? n buffer,是不是?同学们,好,那么整体上我们整个程序是没问题。
同学们,好,那么有些小的功能我们需要加上,哪个?我先给大家结束一下,那么结束之后这些 n c 命令是不是全结束了?加哪个?咱把Accept 的这加上,咱们要增加一个这样的小逻辑功能。什么功能?是不是有好多客户都连我们,我要区分一下是谁连的我们可以吗?谁发过来的?能理解什么意思吗?可以。那么在哪加呀?是不是你首先要在Accept 那加一下?看这儿我们需要定一个变量, struct sock addr_in client是这个,然后这儿怎么写明白?是不是 struct sock addr* ?然后什么呀?client 当然是不是还需要一个长度的?同学们还记得那个叫socklen_t 初始值是多少?这个值,是不是写反了?是不是 socket 的这个长度?查不会,去查怎么查?man 2 accept是这个是不是socklen_t?这个参数是不是既是一个输入也是输出?这个切记,这个,切记,这个咱们要赋一个值,是不是len啊?给个什么呀?是不 sizeof(client),大家注意,为什么我这个len赋值要写在这个while循环里面,而不是写在外面?因为len的值是不是每次都会改啊?是不是这样的?我这len是不是写在这了就可以了?说。
同学,那接下来咱们看一下这个客户端的这个地址啊,我们在这把这个客户端地址打印出来,是不是printf?好打印?客户端的 IP 怎么打印?调用哪个函数? i net,是不是 n two p?是不是网络字节序转化?为什么呀?主机字节序的什么字符串形式吧?叫 i net ntop,大家注意,很多函数都是有规律的,不是说都让你死记硬背,是不是这个得有规律?好,这个参数你记不住,你就查手册就可以了,咱查一下 man inet_ntop
第一个参数是不是 af啊?很显然这个是不是应该用 AF_INET,第二参数是不是原地址?嗯,原地址是不是我们就是 client 里面那个地址,对,是吧?这个是,嗯,那个 IP,是吧?这个是长度。再来这个,第一个是 a f,是不是这一个?第二参数是不是原地址?咱怎么写? 是不是就是client.sin_addr.s_addr么呀?是不是?然后在前面取个地址吧,是不是?同学们,第三个参数是不是IP?同学,是不是一个字符串吧? sIP这个是需要你定义,嗯,外面定义一下 char sIP[16 ] 16 就够了。
这个的初始化是不是写在里面了?是不是每一次都要初始化啊?同学们? sIP然后 0x00, sizeof(sIP),那么这儿是不是写 IP 啊?那么后面这个长度指的谁的长度啊?是不是 SIP 的长度啊? sizeof(sIP)这个有点长了。别着急,咱往后说一说。
那么后面是不是还有一个端口?端口的话怎么写?n 是不是网络?网络字节序转化成什么?主机字节序短整型 ntohs是不是这样的?那这个的话怎么写?是不是 client .sin_port,你记不住你就多敲几遍。 好,那么这儿的话是不是这个是 class 冒号百分之s吧?OK,再来一个,是不是结束了?嗯,好,那么咱们这个里面大家,咱们既然讲到了这个父子进程这一块,说到一块了,那么我就问一下大家,那么那么你说这个父子进程能够共享哪些?在这里能共享哪些?咱们前面讲过的能共享的,这是给你回顾的东西,我讲插两句,你说能共享哪些?嗯,文件标识符。
好,那么你说对了,有一个我给你记一下,父子进程能够共享的,第一个文件描述符,这个咱们都知道,我父进程里面和子进程里面都有同一个文件描述。l、f、d,是不是我这个父子进程各关闭一个,并没有将这个文件描述说真正关闭,是不是其实这个不是共,这个说共享的有点,这个不太合适,这个是不是相当复制过来的?是吧?同学们,我父进程有的,是不是子进程的也有,是不是我附近关掉一个,是不是不影响子进程的使用啊?但在线程里面就不行,线程就不行。
当然这个文件描述,也就是说其实也就是说我这父进程我操作这个文件描述符,是不是子进程也可以看到效果?大家想一下一个文件文件是不是我父进程写文件是不是子进程那个也可以看到效果?你看除这个之外还有什么呀?mmap 共享映射区是不是?有这个吗?有,是不是有这个?全局变量行吗?不行。全局变量是不是不可以啊?堆行不行?是不也不行啊?也不行啊?要切记一点,这两个是可以共享的。好,咱们接着看我的代码,那么这个代码咱们就我看写到这个位置了,是不是这个地址是不是打出来了?然后我们要在这个子进程里面,大家注意,我们要在这个子进程里面把这个也就是在这我们要打印出这个是谁发过来的。
那么我在这问一下大家,那么这个client的话,我这个子进程上面有吗?client这个变量有没有?这个变量在我子进程那边有没有?要敢说有没有?
有,有是有,因为是复制,是不是复制过来的?那么你复在fork之前有的是不是全有?是不是整个大半段是不是相对都是共享的?对,那这个 client 肯定有,所以这个 client 我能不能在?我是不是可以在这个子进程里面直接使用?那么再想一个问题,那么如果说我这个时候有来了,一共是服务进程创建了 3 个子进程,我父进程这个 client 和这每一个子进程里面这个 client 是一个吗?不是一个,他们之间有没有影响?朋友们,没有,是互不影响的,切记,是互不影响的,能理解吗?好,那么这个 client 是不是我就可以在那边直接使用它,是吧?同学们,那么也就是说我看看这句话,这句话我是不是可以直接拿过来?
能直接拿过来吗?同学们,是不是可以放这?当然我要看看这个怎么写比较好?这个,这样,咱们这个端口就,这样吧,朋友们,这个 IP 是不是都是一个?但是本身是不是都是同一个地址?咱们用这个端口来确认可以吗?不同的客户端是他分布的端口是不一样,咱们用端口来区分去,是不是就可以啊? 那知道什么意思吗?听不懂,明白怎么回事了?我是不是可以把这个端口作为区分这个子进程的一个标志啊?能不能因为你这同一个系统上是不不是同的,客户端肯定会给你随机分配一个不同端口,所以把这个打出来就可以区分了。
是不是可以写这,能知道什么意思吗?我写这,这不转了吗?是不转了? ,可以吗?同学们,也可以的,打端口是不是也行啊?都可以,那也就是说不同的子进程会有不同的端口吧,是不是?验证一下起来, 是不是打印出一个来?同学们,这个是477688,再来一个又出一个,现在有三个了,是不是三个都是不同端口,那么它分配的是不是看完 687072 他给你加了个2,是吧?这个他怎么分配咱不管,那么咱们看来看一下没问题。
一共有几个连接对了?是不是三个人连接对了?同学们,三个是不是三个 NC 啊?看到没,那么你这个服务端用的端口都是 4 个8,但是你这个子进程的话是不是不一样,不是,你这个客户端是不一样,那咱发包试试。你好,端口多少?谁发的?是不是47768?是不是第一个子进程发的?再来一个,去,你的谁发的?第二个?是不是这个4,这个 70 啊?也没问题吧?再来一个。好,没问题,都没问题。
那我们看我们这个发呢?点击是不是又来一个?这个是 8261 端口?是啊,8261,那么咱就给他发一个,是不是也没事了?是不是 8261 ?咱的程序完全没问题,后续的话再添加新功能,就添加父进程,使用 c chat (ceikechat)信号完成对子进程的回收。这个功能你晚上的时候你把它加一下,我想的话也加不了这么几句话,是不是?那么在这个使用这个信号回收的时候,你要注意哪些点?还记得吗?这个你去看一看。
好吧,因为这个咱们讲过好几次,这个代码的套路和这是这个是不是类似?同学们,你那个是创建子进程 ,这个是创建子线程,嗯,是吧?好,咱们把这个多线程版的呢,一块给大家来写一思路。写思路,多线程版本的服务器开发流程。
第一步干什么呀?兄弟们,前几步是不是都一样了?同学们,一样吗?到这是不是都一样?这我直接拷过来啊。 第一步是不是创建socket?
同学们得到一个监听的文件描述符,l f d 调用的是 socket 函数。第二步,是不是将l f d 和 i p 端口进行绑定?第3步,是不是设置监听?设置监听第4步干什么?第四步是不是进入while循环的?对,while循环,进入while干什么?是不是调用accept的函数,接受一个新的链接,接收新的客户端链接?请,请求吧同学们,是不是 c f d?等于什么呀? accept 是不是一个调这函数?好,那么现在我们拿到的拿到了一个新的链接,那么你说拿到这新链接你该干什么了?创建子线程,是不是创建子线程,是不是这样的?创建一个子线程,那么创建子线程调用哪个函数?这个咱们是刚刚讲过,同学们是不是?pthread_create 第一参数是什么? 是不是这个?&threadID
第二参数 呢? 是不是属性 啊,你可以设置为NULL,是不是这个设置分离属性?这个可以在创建线程之后也可以,是不是在创建线程的时候也行?如果让你在这个创建线程的时候去设置它,你会吗?分几步同学们还记得吗?这个细节我先不说了,因为这个不是今天咱们讲的重点。
好,第三个参数是什么?是不是线程的回调函数了,是吧? thread work 可以吗?最后一个是不是参数了?对,那么你仔细想一下。 你仔细想一下,那么这个参数应该传什么?传什么?是不?CFD?行,咱们先给他写上,可以先写上这个,待会发现问题再改,可以吗。
好,那么这个时候你重点是不是应该去写这个什么了?这个回调函数了?当然我这个的话是不是可以在这儿设置为什么呀?分离属性吗?设置线程为分离,调哪个函数了?是不是这个函数pthread_detach?这个只有一个参数?好像是吧,我也记不清了,是吧? 好,接下来是不是咱们开始写这个线程的回调函数了?子线程处理执行函数叫执行函数。
好,那这个的话是返回值是什么?void*,然后是 thread_work 参数是不是只有一个 void *arg?你先写你正常的这个思维,逻辑思维,正常思维。你这你写代码的时候切记一点,你不要说写代码的时候,我这个把所有的事情全考虑到了再写,你写边写边想,先写一个大体框架,然后再不断的去完善,这才是比较合理的。因为你有时候不写,你想不到一些东西是不是这样的,你写的时候可能这个需要改进,那个需要改进,慢慢去优化,去调节,这个代码就完善了。
好,那么首先是不是我们要先要把这个参数获取,是不是同学们·?,那这才是什么?同学们这个是不是通信文件描述符了?是这个啊,那你应该怎么写什么 int、c、f、 d 等什么呀?星括号,然后int * ,arg,是这个能看懂他们这个这一块咱们前面都讲过啊,无非就是什么呀,在这里面是不是要接收数据了?好,那么接下来是不是这个操作和咱们上面讲的就基本类似了,是不是进入while循环呢?第一步干什么?是不是先读数据?要读数据了,读数据是吧?是不是这个 n 等于什么呀?嗯, read 第一个参数是什么? cfd吧? 第二个呢buffer,第三个 sizeof(buf)是吧?同学们,咱们这个写代码写的快的话,你要跟上思路,因为这一块的话是不是和咱们上面讲的基本上类似了?那么如果说这个 n 小于等于 0 什么意思?是不是这个意味着这个对方关闭链接或读异常啊,是不是直接 break 就可以了?直接break,那么 break 之后是不是在跳出循环的时候,你要关闭哪个?是这个?是不是关闭这个?关闭 c f d,关闭 c f d。
好,那么这个是这个读完数据,读完数据之后我们干什么呀?是要发送数据,调哪个函数?write?第一参数怎么写?cfd,第二个buffer,第三个,n,说当然大于这种话你自己写就好了。好,这个整体的逻辑是不是就这样的?好,这个整体逻辑大家也都能认,能看清吗。同学们,是不是就这不一样啊?同学们,那么这样原来是多进程的时候,我们是不是创建一个子进程?在这儿的话是不是我们创建个子线程?是吧?这是一点区别。
好,我给大家提出一个问题,第一个问题,这个 l f d,同学们,这个 l f d,我能不能在这个里面去关闭?第一个问题,这是第一个问题。第二个问题是 ,那么我如果说我这个有两个或两个以上的客户端同时到来的话,这个 CFD 会有什么情况发生?首先你听懂我的意思了吗?这个 CFD 它的定义的话在哪定义的?同学们,是不是在这个主线程里面定义的?主线程里面定义的这个CFD是不是可以被所有的子线程共享?那么涉及到共享了,那么就有问题发生了,那么这个CFD保存的值是谁的值?是不是最后一个?对呀,你这样写行吗?行不行?
用你先写,是不是这样的?咱们前面说大家讲过这个循环创建多个子线程,那个,还记得那个第几个吗?同学们,是不是最后都是5啊?对,为什么?是我给你分析了,嗯?是不是?当然这个问题在我们这呢是存在的,但是他表现的不明显。为什么呀?因为我这个,我这几个子线程创建的时候一个有前有后。对对对,有前有后的话没问题,还记得那个吗?同学们,我们加一个sleep,是不是那个一二三四五 是不是就打印出来了?但是你不加的话同时过来肯定会有问题。对,能理吗,那么其实最容易给大家实验的是什么呀?我在这直接加个什么呀?加个sleep,对吧?我让这个子线程先不执行呢,然后我再来一个客户端链接。
那么这个cfd是不是被改了?当然,你这,你加 你加哪啊?是不是加在这上面啊?你要加在这句话的上面才行 105,你加在下面是不行,因为他是不是已经把这个值读出来了?这个咱们给你测。嗯,知道什么意思吗?我如果把 sleep 加在下面行不行?同学们,加在这,我如果加在这的话,是不是这个值是不是已经被他读到了?读到之后他的值再改了?还跟他有关系吗?没听懂吗。
这个是不是cfd?同学们,这个是不是可以被好几个子线程共享这个值啊?兄弟们,好,那我这几个子线程创建出来以后是不是这句话?是不是?cfd?是不是把这个值是不是已经读到了?读到之后你再改它还对它有影响吗?有没有? 但是你用的如果是用内存的话就不行吧,你读到之后是不是就没有印象了?这个我待会给你试,先把这个思路给你说清楚。这些细节,这些是细节。
好,我给大家提的 2 个问题,一个问题大家可以记一下这第一个问题,问题一,你带着问题咱们去这个去想这问题。第一个子线程能否关闭lfd?是不是这个 l fd 是监听文件描述了,好。第二问题,父进程,父线程是不是主线程,能否关闭 CFD? 理解这句话的意思吗? 注意,按照咱们前面给大家讲现成的那个理论来说,这个文件描述符是不是它不是复制的同学们?子线程是不是没有复制这个主线程的?这个文件描述表啊? 它是共享的,你只要有一个关了,它就彻底关了。嗯,它的引用 是不是为1啊?你这么一 close 是不是成 0 了?成 0 是不是这个整个就关掉了?嗯,好,待会咱们给你试一下是不是这样的。
第三步,第三个问题, 第三个, 这个什么来着?子线程是吧?那么使用的cfd是吧?这样多个子线程多个多个子线程,是不是共享?这个cfd 同学们,是不是共享?这个会有什么问题发生?这 3 个问题,你现在先说,你听明白了没有?意思,知道什么意思了吗?这个呢?咱们先写一个基本的代码,待会给你试,你边测边看,这样效果更明显清楚。有的时候我们空想,可能有些东西想不到,但是我通过测试,我看测东西和我们想的是不是一致,如果一致,那证明我们想的对,如果不一致是不是我们想到哪想错了?是吧?同学们, 我们学东西要这么来学好这个,这个整体思路呢。
我先跟你说到这个思路和他上面比起来是不是差不多?同学们,是不是就这变了一下?是吧?就这变了一下?好,这个咱们先说到这,当然我这主线程,主线的最后是不是干什么啊?同学们,最后是不是要close?谁啊?是不是 l f t 是吧?当然我这个的话,比如说这个整个流程这样的把它括起来,是这么样的,这是不是主流程?同学们,这个是主线程,是吧?同学们,这个是不是子线程啊?是不是子线程?
好,那这个咱们先说到这好吧, 那么咱们开始写一下,那么咱们这个版本是不是要使用多线程版本呀?首先使用多限制版本,你要记得增加一个头文件,哪个头文件啊?哪个?是不是pthread.h
?是不是这个头文件?同学们,有同学在编辑的时候上说,老师,我怎么老编不过去,一看没加什么,这个库是吧?你们好,咱们看一下前面几步是不都一样的?创建socket,给他,然后绑定这个就不用写了。
监听是不是都是一样的?嗯,这些都是一样的,这是前期准备工作,那到这为止我们的程序是不是就可以处于监听状态了?对,好,接下来下面该干什么了?循环,是不是进入循环的?进入循环?好,进入循环,接下来是不是调用accept?accept,所以我们需要一个什么呀? cfd int cfd ,咱们想,你写,你想的先写着。那cfd 等于什么呀? accept 第一个参数等于什么呀?是不是 lfd
第二个先传NULL,先不管他,那是吧?第三个也是NULL,是不是?好,这个接受新的链接,我们有了,后面该干什么了?现在这个新的链接有了,那么接下来是不是我们要让这个子线上去处理这个链接,是吧?要让子线上去处理它干什么呀?创建子线程,是吧同学们,那创建子线程的话,是不是我们需要一个什么呀?是不变量啊 同学们,pthread_t tgreazd 可以吧,这个,是创建子线程,第一个参数是什么呀?是不是这个这玩意啊&threadID?
第二个呢?这个怎么颜色?是这个颜色我加个i d 还变吗? ,可能是吧?嗯,是吧。第二个是不是属性啊 NULL?第三个是线程的回调函数吧?叫thread_work可以吗?那么这个提示在这蹦跶的,这个第四最后一个是不是参数 &cfd?先你先这么写好吧,遇到问题咱们再改好了,现在是创建子线程,那么这个子线程创建完了之后,那么 这个主线程还做什么事情?
同学们,是不是后面我们应该把这个子线程设置什么呀?分离属性吧?设置子线程为分离属性,怎么设置? p thread detach,这怎么写? threadID 是不是 thread ID 能清楚吗?同学们,嗯,好,那么接下来咱们继续往后写。那么你如果让你在写的话,你该朝着哪方面去写了?同学们,这个主线程里面最后是不是跳 出循环以后他干什么呀?这要记得关闭什么呀?监听为描述符,关闭监听文件描述符close。哪个? l f d 是不是这样的?
你先把能写的写完,return 0。
好,接下来我们要写的是不是开始写这个线程的回调函数了,是不是这个?这是子线程回调函数。好,是不是void*?同学们,void*,然后thread_work, 它是不是有个参数了?arg?好,那接下来是不是我们这个要接收这个参数了?嗯,我们定义个变量可以吗?int,cfd 等一个什么呀?int *, 是不是做一个强制类型转化啊?那么这个cfd有了,是不是后续的工作和咱们前面的一模一样了,是吧?while1,去循环的收发数据吧好读数据。read,数据,怎么读?是不是咱等一个 read 用这个可以,用这个函数,
,大家能跟上这个思路吗?能跟上吗?因为这一块和前面完全一样,是不是没有必要在这个浪费太长时间?好,那么接下来同学判断一下这个n。
n 小于等于一个0,哪个初始化呀?buf 是不是每次都要初始化呀?当然我写在这,是不是这样的? 这个你这打个perror算了可以吗?在这是不可以啊?这东西当然我再我给大家是不是说过这个errno是不是最好别在子线这里面用?对,在这不要用perror了,好,用 printf。
那么有时候你写着写着是有的,有时候这个行与不行是不是写着写着就发现了,是吧?啊,是这个是为此 error of client closed, 是不是这样的?然后这怎么写?你这是能这个写这个吗exit? ,是不是你不能这样写啊?是不是退出子线程就可以了?可以,这就可以了,是吧?当然这个是不是你?当然你在这是不是写个break也行,是不是?我可以把这个写在外面pthread_exit,是不是?然后这 close 关闭谁cfd这样写是不是比较合理?这是关闭谁关闭通信文件描述符吧,是吧?是吧?这个要好,就这样好,那么这个里面的话只要是不是写个 brit 就行了,是吧?这个 rate 是不是结束了?结束之后我们把这个打出来看看。
好,那么接下来是不是我们可以做一个大写的转换,然后发回去? 还有 buffer i 等于什么呀?
这个好,发出来是不是这个好,那么写完之后是不是要发回去啊?
write,是吧?发送数据,是不是这样的?好,那整个代码是不是结束了,是吧?兄弟们,咱们先这样给你测一测,然后咱把我给你把。咱们后面是不是给你提了几个问题,同学们,是不是咱们来测一测?知道什么意思吗?咱来测一测。
那其实讲到这的时候大家应该能够,这个应该能够回忆一下咱们前面讲这个多线程的时候,那么这线程是可以共享哪些东西的,还记得吗?同学,是不是除了栈之外,其他的都可以共享?对啊,当然这个栈的话,如果栈的变量的生存期比较长的话,是不是也能够被子线程去使用啊?还记得同学吗?我那个 i 是不是在man函数的站上?但是它是不是也可以被 子线路去使用?是可以的,你要看它的生存期,如果生存期比较短的话是不行的,那比如说你这个子线的 1 和显示 2 里面,它们里面都定义了什么呀?这个栈上定义的变量,这个时候它俩之间是不可以用的。
知道什么意思吗?好,那咱们就跟大家一块来测试一下,保存一下,这代码,再给大家捋一遍,再捋一遍。好在man 函数,首先创建socket给他,没问题,绑定监听,这前面讲过好几次了,那么接下来我们是不是要调用 accept 接受一个新的链接?接收新的连接,我们要创建一个子线程,让子线程去处理这个数据吧,是不是处理,我们是不是把这个文件描述符是不是传给他了?是吧同学们,好,同时我们设置子线程为什么呀?分离属性好,那么在这个子线程的回调函数里面,是不是首先我们要接收这个参数了?cfd,
那这个参数干什么呢?就是通讯的,就通讯的,接下来进入while循环,在这循环里我们要读数据,然后读到数据之后是不是要发回去,是吧?最后如果说对方关闭链接了,是不是直接break,然后 close cfd,最后退出子线程是不是就可以了?那么这个退出是不是不影响主线程?是不是也不影响其他线程这个函数是不是只是说使一个线程退出吧?嗯,是不是这个和exit 这个函数是完全不一样的?好,那么这个咱编译一下看看。 g c c 杠 o 这个server,
编成了,编成了,那启动起来看看。那先看看监听有没有起来。 net state 杠anp grep 8888 是不是起来了?这个进程已经启动起来了,那接下来咱们跳连一下,
还一个发一下试试?你好,没问题。那没问题,再来一个。你好,是不是也没问题? , n c 127. 1 8888,是不是也行啊?是不是三个都好使啊,是不是三个都好使啊,那么给他也给他发回去了吧,好,那么接下来咱们就开始来这个来琢磨琢磨咱们提出的这几个问题。
行,同学们,第一个,我在这个子线程里面能否关闭lfd,能不能关闭,是不是试一下就知道了。结束一下,在哪改看看,不定义全局遍历行吗?是吧?当然你这个是不是得定一个全变量?如果不定全局变量,这个参数传不进去,你或者是定一个是结构体,是不是把这个lfd 和cfd放在一个结构体里面,然后把这个参数传进来也可以呀?知道什么意思吗?这咱们来一个简单的吧?这个lfd是不是只有一个?我再来一个全局变量,这个 只是给你做实验,咱们完事之后把这个取消掉了,好让你去这个验证一下,是不是这样的? int g_lfd 可以吗?是不是在这第一个全局变量啊?那这全局变量是不是接收谁啊?同学们接收这个吧 51 lfd ?
是不是这个值啊?我在这跟他保存一下,是不是这样的?那么你看一下我这个东西,这个lg_lfd 这个关闭,这个是不是在子线程里面去做?子线程在哪?是不是直接在这写就可以了?可以吧,同学,我在这写一下可以吧?我就不写注释了,可以验证一下这,是不是?好,咱们先来一遍试一试。那么怎么验证它就不行了,你们再开一个客户端,看看能不能连上啊?
因为如果说你把这个监听文件描述给它关掉了,第二个子线程应该是连不上了,知道什么意思吗?我在这是不是在这编译的,启动起来,起来,貌似没反应了,他就没反应,是不是?这个没问题吧,因为这个是不是子线程在给他收发?
这个和主线上没有关系,这没问题,
是不是?然后再来一个,再来看能不能接收到新的链接,是不是没反应?这个是吧?没反应他是不是也干什么呀?Bad five descriptor什么意思? 为什么会 返回一个错误呢,因为你这个accpet参数是不是 l f d 这个监听文件描述已经被子线的给关掉了,那么这个相对是不是一个无效的文件描述符了?是不是这样的?所以第一个结论我们得到了怎么说的?你们来描述一下。
第一个结论,同学们关闭子线程,不能关闭监听文件描述符。lfd这个原因还用说吗?为什么呀?原因是,原因是子线程和谁和主线程。这个lfd是不是在主线程里面去监听的?什么呀?叫共享是不是只有一个啊?他们相当于,是不是只有一个?那么这个父子进程是不是相当于复制的啊?同学们,这个是不是共享的?叫共享文件描述符 ,而不是复制的能理解吗。那么前面是不是咱们给你写过一个啊?能共享的?这个文件描述符给你加个括号子进程。是子进程复制 父进程的什么呀?文件描述符吧?这是复制,那么下面的是不是共享啊?复制和共享是不是不是一样的?
那么也就是说我这个子线程和主线程是不是只有一份?这个什么呀?是不是只有一个文件描述符了?你关掉之后是不是已经进入变为0了?已经进入变成0了,所以你 close 之后就没有了,是不是看第二个,这第一个咱们验证出来的,我就给你删掉了。好,我给你删掉了,这咱就把它删掉,这个咱们已经验证出来了,有兴趣的你 自个去弄一弄。也行。
那么接着接下来咱们验证第二个怎么说。
那么主线程,能否关闭cfd,其实大家应该能想到了,在哪关闭?同学们,是不是你 accept 之后立刻关掉啊,是不是?在这?是不是一样吗?在后面是不?是在这?在下面是在这 close 哪个? cfd 是不是 close cfd 这个就不用定义全局变量吧,是不是?这里有吗?在这有,在试,这个报错了。嗯哪,还有一个,我看嗯哪来着,主线里面有一个吗?
同学们,是不是有他和没它都行?序行符,是如果你这一行大概太长,你可以分成两行写,就可以加个这 \。你没用过吗?没有,这没用过。这我看看不是不是讲 c 语言,前面应该说过,你们说什么?你看过这句话知道什么?我这样写知道什么意思吗?
我这句代码太长,是不是一行写不下啊?一行写不下我是不是可以写成两行啊?
看看行不行?我记得原先是超时的,怎么了?没问题,叫续行符。哪个续,继续的,继续的续,是不是这样的?这个我以为你们会这东西。你没事我讲的时候如果你不会就直接说。好,我可以告诉你的,不格外收费。 行,咱们编译。是不是编译过了,咱们试一下好使不好使叫,起来。
那么大家想一下我们应该怎么去验证呢?现在我这个主线程那边是不是把这个 cfd 关掉了?对,那我刚才说,按理来说是不是你在发的时候是不是他立刻报错呀? 你搞不好你 read 函数立刻就返回了,是不是?咱看是不是这样的, ,看没?同学们,看没?是不是 read 还是立个返回啊?
为什么?你这个,你本身,你这个 c f d 是不是已经是无效的了?你这还得 read 有用吗?是,是吧?同学们,这样的话,是不是这个 read 函数是不是返回0啊? 这个是不是返回- 1啊?
同学们 这你也不用记是不是?那你大不了你试一下就知道了,是吧?好,这个就结束了。
那么通过第二个测试,我们也得到了一个结论,什么结论?主线程不能什么呀?关闭 CFD 是不是?那么是不是原因和那个是一样的?也是一样的原因是这个主线程和子线这样,这个是这个 CFD 是不是这个 CFD 是不是这个主线程创建出来的?是不是主线程里面拿出来的值?原因是这个主线程和子线程还是不是?还是共享的意思?还是共享一个 CFD 是吧?
那么 close 之后是吧? CFD 是不是就会被真正关闭,就会被真正关闭?他俩是不是说的是一个意思?这俩是一个意思,那么这个是不是也是这个共享的,而不是复制的?共享一个cfd,而不是复制的,
能理解吗?同学们,就这个,这两个我给你试过了,再看,这回你弄回去,弄回来了,现在这个知道了,是不是又学一招了?应该说今天总结之后没白来,是不是看后面这个,
那么接下来咱们看第三个验证的,第三个我要给大家验证是哪个,看这个多个子线程是不是可以共享 CFD 是不是?当然我们现在是不是没有发现问题?有同学说了,
老师是不是好使?那么你是测试的方法不对,是吧同学们,测试的方法不对,我们应该怎样去测才会发生问题呢?看代码 那么你这个,你看咱们这个线程处理函数,线程直接函数里面这个c、f、 d 的值是不是从这个内存里面拿出来的?我们在这,你说你在哪?加sleep啊?是不是在上面?嗯,你在下面加他都拿到值了,还有用吗?没有。
是不是我在这上再把这个sleep加在上面,我把这个时间加长点, 20 秒的时候,我启动好几个子线程,那么这个内存这个指针指向那块内存的话,是不是这个它保留的值?是不是最后一个子线程的那个链接?对,能知道这个意思吗?同学们,知道,给你验证一下。是不是验证一下?怎么验证?我可以把这个cfd打出来吧
第一个 cfd是 3 吧的应该是,0123,第一个是4,应该是第2个是5啊,再有一个客户端连接是不是6啊,一直往后排,知道什么意思吗?因为你这个 cfd 是不是在这个主线程里面产生的?怎么了?又听不懂了?我把这个值打印 出来,看看是不是如咱们所说的,按照我的推理,按照我的分析,这个值最后应该是一个最新的值。
比如说我有三个子线程, 第一个子线程,这个 cfd 应该是几?0123,第 一个是四,第二个是5,第三个是6,应该是6,这个值打开应该是6,我只开三个子线程,知道什么意思?我只开 3 个,看是不是这样的,起来,来一个怎么连不上啊?怎么回事, 我写错了吗?他就连不上?这不至于,我看看你怎么连不上呢,他怎么会连不上呢?我看这个accept有问题吗?
噢,我说有一个没弄,吓我一跳是吧?比较诡异这个,好,再来一次,看没,报错了,报错了。,这样的话咱们先把那个问题解决一下。
好,我把这个东西给你解决一下,叫 address already use。是不是我们这个链接原来建好了?你这个服务端一关是不是就会报这个错误?第一天是不是跟你讲过我把这个东西给拿回来?嗯,在咱们这个我找找,是加一代码就可以了。
这个咱们现在用的上,第三天的时候有一个, 那加上一句话就可以了,叫 set sock opt,对吧? ,是不是加在这儿了?这个错误谁报的?同学们,错,谁报的?是bind函数报的?设置端口复用,但是你用这个时候你得定一个变量,叫什么呢? int opt =1;
这句话我先不给你解释了,咱们先用起来。好,先不解释了,明天咱会说 ?嗯,怎么了?用完了?用完了。好,再启动用头文件啥的。 ,起来是吧?现在是不是起来了?跑一个,再来一个,再来一个。是不是得过 20 秒啊?得过 20 秒,看 20 秒之后,那么咱们这个 c f d 等于那个应该是几?应该是几个?同学们。
这个加一个文件描述符应该是几 啊?并不意外,等于 6 并不意外。是不是都是6,要看是不是他是不是开始不停的打6,是不是打了 3 个6?是不是打 3 个6?打 3 个 6 正常吗?同学们, 为什么打 3 个6? 因为我们是不是开了三个子线程啊,这是正常的。
那么行,咱们就发数据看看,看看能不能把数据发过去。可以吗,你好,这个发不过去是不是?你好,后面他是不是也发过去?他能发过去吗?我先问一下第3个能不能发过去?它是可以的,应该是不是可以,但是一二个都不行。为什么呀?你这一二个,那个文件,那个链接是不是已经被第3个覆盖了,能看出什么意思来?同学们,已经被覆盖了,这个怎么解决?你先想一想。
好,这个咱们先说到这儿,是我刚才验证的,是不是我们这个问题发生了?
好,再花个 10 来分钟时间,我把这个修改思路给你说一下,你晚上自己写一写,这改动也不大,是吧?他们,那么如何让我这多个子线程不再共享这个一块内存呢?按照咱们前面讲的那个还进那个,怎么着?已经有人说出来了,是用数组,是不是?好在这里面,在这里面有一个问题。什么问题?我如何在这个子线程里面?子线程里面找到我用的那块地址?怎么找到?是不是你把那个地址传过去就可以了,可以吗?兄弟们,那怎么用? 是不是可以这样?我是不是定一个数组?我当你这个数组。
我给大家再说一个,这个里面我们这个代码里面有一个问题在哪?当然这个不重要,是不是我们在创建子下程的时候,这个也是公用的?看到没?这个是不是也应该定义一个,也应该定义成一个数组啊,可以吗?是不是我们能不能把这个threadID 和文件描述符定义成一个结构体?然后我再把这个结构体是不是定义成一个数组啊?这样的话这个 thread ID 和这个 cfd 是不是有一个对应关系?是的,意思明白了没有?大致这样的,我先给你写一下, 比如我定一个struck ,比如说我就随便写个INFO,那这个里面,比如说我们需要一个什么呀?这个就是 pthread_t threadID,可以吗?嗯,threadID 可以吗?这是一个,再一个int cfd 放上面去,是不是这儿?然后这是一个简单结构体,是不是你再定义一个结构体数组就可以了?比如说INFO这个,比如说你随便写一个 1024 行吧,是不是可以接受 1024 个子线程同处理?这个你可以定小一点,可以定大一点,好,是吧?你比如 100 个都行。
那么还有一个同学们,我如何在子线程里面把这个客户端的 ip 地址也获取到呢?也放进去,是不是也放进来?嗯,struct sockaddr_in client 这能理解什么意思吗?同学们,那么也就是说你定义一个数组,然后你在传的时候,是不是只要把这个这一块内存地址传到那个子线程里面去就可以了?那这样的话,我多个子线程还能共享吗? 你不能共享,同一块的, 你共享的是一个大数组,但是每一块是不共享的。对,这个思路能听懂吗?
好,我再提出一个问题,不再提出问题。那么这样的话相当于什么呢?我给大家画一个图,那么这样的话,我这个是不是一个大的数组?大体结构体数组,是吧?同学们?每一块内存里面是不是有这么几个成员? 好。开始的时候我是不是要找一个空闲位置来存这个呀?
能听懂吗?同学,我举个例子,一开始的时候这块占用了,是不是 3 个占用了?嗯,好。第二次的时候,比如说有一个线上退出了,这个是没有了,没有了这块内存是不是还可以被继续使用?怎么去解决这个问题?怎么解决?我的话听懂了吗?同学们,这前 3 个一开始被占用了,后来有一个子线程退出了,是不是相当于链接关闭了?链接关闭中间这一块,第二块内存是不是就被空闲出来了?空闲出来,如果下一次再有链接到来,是不是这块内存仍然可以被使用啊?怎么解决这个问题?返回怎么解决?是不是加标识?同学们,是不是加标识?
那么我每次我在这一块内存里面找的时候,是不会找一个空闲的?我怎么知道它是空闲的? ?是不是加一个 flag 呀?你不加 flag 加这个 cfd 用这个cfd 可以吗?这个cfd值是不是应该是都是大于 0 的数了?嗯,有没有小于 0 的数了?有没有这个文件描述符没有小于 0 的,只有都是大于 0 的,对,当然你那个 012 是不是都变那样了,
是不是往后都是大于 0 的?所以这个你可以初始化为-1,只要是你在遍历的时候,只要这个是-1,我们认为已经被释放出来了,空闲了,是不是仍然可以被继续使用?听懂了没有?怎么没听明白啊?同学们, for (i =0; i<100;i++)
if (info[i].cfd ==-1)等一个-1,是不是我们认为是空闲的? 干什么呀?是不是在这里面就什么呀?就赋值。
那么此块内存,这块内存干什么呀? 这块内存可以使用是吧?是不是可以使用?那么假如说后面到了外面是吧?同学们到了外面,如果 i 等一个 100 了,什么意思? i 等于 100 了,是不是循环了一圈?是不是没找到空闲位置?是不是我们认为这个空间已经全部被占用了?这个时候你认为链接是不是已经达到上限了?干什么呀?拒绝接收这个链接是不是 close 掉?拒绝就可以了,能理解吗? 拒绝链接,接受新的链接,是不是直接把这个 close 关闭掉就可以了?
知道什么意思吗?好,当然,你用这个之前是不是开初始化的时候应该初始化为几啊?同学们,最开始你这个- 1 怎么来的?先跟你说这个- 1 怎么来的?你是不是应该第一次初始化,应该把这个先全部初始化为- 1 啊?是吧同学们,这叫初始化?我先给你把这思路写清楚,你晚上自己写。初始化INFO数组, 怎么初始化?同学们,是不是还是for循环啊?给你拷一下。
嗯,是不是这块不要了?干什么呀?是不是直接这个?等于-1, 是不是就可以了?知道什么意思吗?一开始的时候,也就是说在这个 accrept的之前,是不是我先把这所有的数组的这个 cfd 设置- 1 设置-1以后,当我们 accrept的一个新的链接之后,我们是不是要找一个空闲的?找一个空闲位置怎么找?是不是循环找啊?
找到一个位置之后,是不是要把把这些什么cid cfd threadID client;
是不是要保存到这个位置来?然后的话我们再把这个地址是不是传给谁?同学们传给谁?是不是传给那个线程的回调函数参数了?
那么你这个线程回调函数参数,你得到这块内存之后,是不是你接下来接收一下,是不是就可以拿到这 3 个值了?接下来你是不是就知道你这个是哪个客户端连的了?是不是你也知道这个 cfd 是几了吧?你也知道他的什么呀? thread ID 是几了?是吧同学们,能理解吗?当然,你这个thread ID 不写也没事,为什么呀?因为你是不是可以在 子线程里面调用(pthread self ) 来获得,是不是也行?都行?我在这呢是给大家提供了这么一个思路,这个思路先说听懂了没有?明白听懂了没有?首先你第一个搞清楚为什么说多个子线程不能共享 c f d,不是,为什么不能使用同一个cfd。咱们知道了吧,通过测试,我们搞清楚了这个 cfd一定是最后一个链接的cfd吧? 是不是试过了。第二个问题如何解决?那么解决到我们定义了一个什么呀?结构体数组。
其实这个方法就和咱们前面讲那个循环创建 5 个子线程是不是类似的?嗯,是不是类似的?同学们,你只要让每一个子线程它读地址的时候读这块内存,它只要不是共享就可以了,是不是每一个子线程它读它自己的那一块啊?不同的子线程读的内存地址是不一样的,那么也就做到了多个子线程之间互轨干扰,是不是互不影响,这就可以了,是吧?思路我告诉你了,你晚上把这个加一下,我想的话也加不了几行代码,是吧?同学们,好,你有没听懂的过来找我。 然后剩下 20 分钟时间赶紧把它题做一下。好, 5 个题。