把昨天的 第二天的内容说一下,复习一下,第二天 讲的东西不算多,但是有两个作业题来写一写,
大致浏览一下,三次握手 四次挥手的过程,大家有没有画一下? 能画出来吗?同学们,大家注意 这个写代码的时候其实我们用不着它,就是让大家面试的时候能够顺利一些,这个你需要了解清除,让大家画图,每一个图里面 它的编号 序号怎么来的,
这个数字怎么来的 你要搞清楚,这个你们都动手画了画是吗?其中 三次握手是干什么的? 是不是建立连接的 三次握手的过程啊? 还有四次挥手 是不是断开连接的? 那么这两个过程是你做的吗?内核帮我完成的,那么我再提一个问题,那么为什么我们这个tcp 它是面向连接 安全可靠的啊? 你从哪几个方面去说这个问题呀? 首先 第一个 那么为什么 说它是面向连接的,为什么这么说?因为 它是不是有一个 三次握手 建立连接的过程啊?这肯定是面向连接的? 第二个 为什么它是安全可靠的呢?其实主要是 这两个方面,
是不是序号 和确认序号啊? 是不是主要体现在这啊? 那么如果说这个 丢了包呢?是不是会重传啊?那么对方收到之后,它是不是会给你来一个确认序号啊?那么你收到确认序号,它是不是会给你往下发呀?是不是?所以说 它是面向连接 安全可靠的数据流传输, 或者叫字节流传输也行,
滑动窗口的主要作用是什么呀?进行流量控制,举个例子,如果发送的过快,接收的过慢 是不是造成数据的阻塞啊?有可能会造成数据的丢失,那么 接收方的接收缓冲区 ,满以后那么再发的话 那么有可能会丢失,是不是这样的? 其实也就是这个滑动窗口双方是相当于决定了规则啊?我发多少你收多少? 你那边有多少空间你告诉我,
这个图里面每一个标识的含义你要搞清楚,mss 是什么意思?谁告诉谁?箭头左边的 告诉 箭头右边的,那么它里面 也就是说箭头左边,告诉箭头右边的,那么它这边一次性 最多接多少个数据吧?大家注意,这个是字节, 按字节来说的,8个位是不是一个字节啊?
那么这里面还有一个 win 什么意思?是不是告诉对方他这边的缓存区还有多少可用空间吧?还有多少可用空间?这里面的每一个数字怎么来的?自己会算了吗现在?咱们还是重点说的,而且我这个图上是不是也有同学们,我记得那个图上有,是不是这个自己看一看就不再说了,这个封装的思想这个也不是死的,这个真正我们使用的时候,
你根据你的实际情况来是不是?咱们说的时候是不是?这上面咱们讲义上大纲,说让你这个在写函数的时候,这个名字是不是第一个用大写的?你不用行不行?,是不是也可以?这个不是死的,这里边给你红色标记的这个你得知道,还记得吗?同学们,这个阻塞函数是不是能够被信号打断?那被信号打断之后呢?它的返回,它的这个errno=EINTR是不是设置为这个 EINTR,是吧?这你知道这个自己看一看,然后这个粘包的概念以及解决方案也知道。
那么粘包的意思是不是这样的?就是说有可能两次或多次发送数据,在对方接收的时候,他是不是一次整个把一次的数据接收完啊,它可能放在两次了,是不是?那这种情况下就会产生粘包,那么解决粘包的这个办法最常见的是不是这个方法啊?是不是前面加一个包头了?包头加数据,那么这个包头最常见的就是什么呀?用一个长度,比如 4 个字节,两个字节都行,是不是把前面固定的这个长度,把这个数据的长度填在这个位置,是不是这样的?那么你注意有一点你得知道,就是你在用的时候,比如说我们这个数据长度是0010,是吧?同学们,如果说你发送的数据就是整型,你要进行转换,什么转换啊?是不是大写字段转换的?要主机字节序转换为网络的字节序?如果你发的是字符串的话,还用转吗?不用了,比如说0010,它这个字数串就不用转了。如果你发的10,比如 10 是个整型,你要转,这个要知道。
其实咱们在写代码的时候,是不是多次用到这个 IP 和端口进行转换的?是不这样的?好,那么方案 2 方案 3 这个你看一看。那最常见的是我给大家红色标记,这个是我们用的比较多的,是不是?那么这个高并发服务器呢?咱们就直接看作业了,讲两个,一个是多进程版本的,一个是多线程版本的,这两个要求大家能够把这里边的每一句话搞明白,能够独立写出来,清楚了吗?这是给大家要求。好,那咱们把那个作业看一看,其中咱们多进程版本的时候呢,咱们是这么给大家,是不是说要怎样进行完善?完善什么功能啊,是不是父进程使用信号完成对子进程的回收?好,那么咱们一块把这个程序再大体捋一捋,那么咱们这个创建这个服务端的这个过程,是不是咱们都讲过好几次了,是吧?第一步先创建socket ?第二步? 这个是设置端口复用,今天咱会说这个设置端口复用叫 setsockopt 的这个函数。
好。第三个是bind绑定吧,将文件描述和端口 IP 进行绑定,是不是后续我们是不是都在操作这个文件描述了?其实操作这个文件描述就是一共这个文件描述,在这个服务端有几类啊?两类吧,一类是什么监听文件描述符,一类是通信文件描述符。好,那么接下来调用 linsten函数进行监听,那这个时候我们的程序是不是就已经处于监听的状态了?大写的 linsten是不是还记得吧?嗯,好,那么接下来呢?这个先不说,先把整体流程说一下,那么接下来我们进入一个while语句 循环,然后是不是调用 accept 接收一个新的链接啊,大家应该知道在accept 之前,这个链接是不是已经可能已经建立了?是不是这个大家搞这个搞清楚,也就是说这个accept 函数,accept 的这个函数本身它不是说新建一个链接,而是说拿出一个可用链接来。
那么接下来接受一个新链接之后,他是不是调用 fork函数创建一个子进程啊,是吧?在父进程里面,是吧同学们,是不是只是用来接受新的链接啊,所以他不通信,所以这个要关掉是不这样的,这个应该知道为什么要关掉了吧?那么父进程fork出一个子进程来,父子进程是不是共享?那么应该说不叫共享,这两个是不是父亲有一个文件描述符?是不是这个啊?子进程是不是也有啊?是不是?那这个时候你说这个文件描述的引用计数是几啊?引用计数 ,引用计数,那么这个通讯文件描述符引用计数是不是 2 了?是不是这样的?那父进程关掉以后,是不是不影响另一个进程?这个知道好,那么这个是父进程,那么父进程好,那么做的事情就这些。
然后子进程是不是他要关闭监听文件描述符啊?对吧?那么关闭以后,因为他子进程咱们都知道他是不是不用监听啊,是不是父进程监听,然后子进程通讯,是这么一个规则,那接下来它进入while循环是不是收发数据啊?这是咱们这个第二天给你讲的时候,那个整体的流程吧?要求你加的逻辑是在父进程里面完成对子进程的回收,是吧?同学们,好,咱们看怎么加的?首先咱们看一下,前面为什么一开始我要阻塞这个SIGCHLD的信号?防止,防止什么?
防止什么防止?是不是你这个信号注册的过程还没完成,然后子进程的全部结束了?有没有可能?这是有可能的,所以我们在前面先阻塞这信号是不是这样的?好?那么接下来在这个父进程里面是不是要注册一个回调函数了?是不是?给谁给内核注册一个信号处理函数?这哪个信号了?是不是 SIGCHLD信号了?是不是这个信号?调用的是sigaction吧?是吧同学们?大家记得 signal 和 sigaction 的区别吗?什么区别?是不是一个可以移植啊?一个,另外一个 signal 是不是一直可能会出现问题?是不是?你用的时候尽可能用这个sigaction ,用尽可能用这个?那么接下来是不是调用 sigaction 注册了一个信号处理函数,那么注册完了以后是不是在解除对这个信号的阻塞啊?用的是sigprocmask这个函数的用法,要求你掌握。好吧?要求你掌握,这个,你们考试被考到了吗?这个好,那么这个时候就已经完成注册了,并且解除了对这个信号的阻塞了。
好,那么如果说这个时候子进程通信结束了,子进程要怎么样?要退出?是不是?那么那么想一下,这个子进程在什么情况下会退出?对方关闭链接,他是不是需要退出?是不是这样的?然后读失败是不是也有可能退出?那这个时候他退出之后,是不是这个内核会给他的附近发送一个SIGCHLD 的信号了?那么这个父进程收到这信号之后干什么呀?回收,是不是回收?所以这个函数出来了,
是不是调用 wait PID wait 这个函数完成?当然这个函数是不是完成对子进程的回收吧?这个函数这几个参数还记得吗?第一个参数是-1,什么意思?表示回收任意一个子进程,那第二个是NULL,那在这是不是这个子进程退出状态啊?对啊,你不关心就设置为NULL就可以了。
后面这个 WNOHANG什么意思?表示不阻塞,那么这个在这,我们在这个里面加了 while循环,其目的是什么还记得吗?是不是这个目的是为了防止有多个子进程,是不是同时退出的情况?是不是好,那么如果是有一个的话也没有关系,是不是大不了多循环一次,是不是这样的?那么这两个是他退出的一个条件,是不是?如果说子进程还活着 25,或者是子进程已经全部没有了 25?这个时候是break;。
好,这个里面我问大家一下,你说这个函数这个信号处理函数,一开始在调的时候,是不是肯定会能够回收一个呀?为什么呀?因为你这个信号不是产生了之后才去回收。所以他肯定会回收一个,那么也有可能说会循环回收多个,是不是这样的?对,是不是?当然这里面肯定有一个过程,是肯定会多循环一次,是不是这样的?也就是有一次无用的循环,是不是?不过也没有关系,我们用这个无用的循环是不是可以退出进,退出这个跳出循环的,是不是?好,这个还这个代码要求你能够完完全全掌握,因为这一块的话,只要是涉及到多进程开发的话,这一步这些东西是免不了的,清楚了吗?是免不了的。
好,那么下面看另外一个那么多线程版本了,那么这个流程我就不再说了,咱们从这开始说,这个是多线程版本的,那么在这个主线程里面接受一个新的链接之后,它是不是要调用 pthread_create的函数创建一个子线程?是不是?那么在这个子线程里面是不是完成对什么呀?完成对这个客户端数据的收发啊?是不是这样的?好,那么这个里面咱们要求你改进的方向是什么呀?还记得吗?改进的方向。
是不是让你定义一个结构数组啊,然后这个结构体,这个数组里面的每一个元素是不是存放这个线程相关的信息啊?这个咱们就给你定义了一个结构体,这个结构体看一下这结构体是吧?其中第一个是cfd,这个是不是通信的文件描述符了,这个值从哪来的?谁给的?
是不是这个主线程调用accept返回的那个值吧?是不是这样?第二个index,这个你不用看,这个是这个是索引,我们这个数组是不是都有下标? 01234 往后排,那这个就是那下标,待会告诉你这个干什么用的?好,第三个是线程id吧?第四个是不是客户端的地址吧? 是不是这样的?好,那么看一下这个函数怎么用?首先这里面我们进行了初始化,那么这个初始化是不是在一开始就初始化了?我们 把这个 cid 全部设置为- 1 了,那么意思是这是不是都是可用的?是不是这样的?因为一开始没有这个客户端的时候,是不是我们这个空间都是可用的啊?这个意思,
然后这个函数是干什么呢?你猜一下,findindex什么意思?是不是找一个空闲位置啊?他怎么找的?是不是在,是不是先循环?是不是整个循环?如果等于- 1 它是不是就 break 了,它返回的什么呀?同学们看返回的什么呀?数字下标,这个结构体数字下标,那么这儿来了,这玩意儿什么意思?如果找了一圈没找到,那意思是不是没有可用空间了?是不是这样的?好,这个好,咱们看一下这个在哪用?
监听完以后我们就在这儿进行了初始化,没问题吧,当然这个初始化是不是也放上面了?都行,当然你在他使用之前给他初始化了就可以了。
接下来咱们看一下,那么这个主线程调用accept 接收了一个新的链接以后,接下来是不是又开始找位置了,是吧?同学们 叫findindex 找位置,返回一个什么呀?数组下标,能知道什么意思吗?返回一个数下标,当然他这做了一个判断,如果是没找到是不是就 close ,close 他之后是不是相当于拒绝这个服务了?知道什么意思吗?因为这个服务端的这个链接处是不是已满了?满了之后你可以给它关掉,这个是不是相当于拒绝服务?是吧?当然你这个你也可以在这什么呀?你也可以在这加个sleep,是不是?也可以?是不是等待有这个客户端退出吧?是不是这样的?关闭链接也行,好在这他找到一个空闲的位置以后,他是不是把这个c、f、 d 填在这个结构体的数组里面去了,能明白吗?我这个没打开,我看看,这是那个结构体吧,同学们是不是?这是那结构体数组,那么结构体数组里面是不是都有一个下标啊? 012 是吧?是吧同学们 0123 等等这个函数的意思是不是返回一个可用的结构体元素的下标?嗯,那么一开始是不是返回0,一开始的话,那么这个i、d、 x 是不是等于就等于 0 啊?是不是?那么接下来是把这些值是不是填在这个里面去?能理解吗?就干这个事用的,那么你看一下我的这个 i d s 什么意思?看到没?是不是记录的就是这一块地址的那个什么呀?所以是不是下标位置啊?能理解吗?同学们,接着看。
好,这个是这块,这三个是不是相当于赋值啊?接下来是不是调用这个函数?调用pthread_create函数是不是创建一个子线程?嗯,这儿他是不是又把这个东西也赋过去了?看到没? thread info i d s 点 thread 是不是也填到这个位置来了 index 0?能看懂吗?好,这是创建完成了,那么这个最后的话,他是不是要把什么呀?把这个结构体是不是传到那个线程执行函数里面去啊?嗯,那么接下来你就看一下这个线程的执行函数,那么这个执行函数很简单,是不是就是收发数据啊,就是收发数据?当然这个限制这个执行函数,是不是首先他要接收这个参数啊?是不是他是不是用了指针啊?同学们,大家想一下,我们这个使用这个结构体数组来保存这个链接的信息的话,意图是什么呀?还记得吗?
本质上是这样的,本质上是不是有多个线程?如果你不这样干,是不是多个线程会共用同一块内存啊,还记得吗?咱们循环创建 5 个子线程,所以打印出去是几啊?5吧,我们后来怎么做的?是不是分开了?一个线程是不是只等于一块内存啊?我们这个意思是不是跟那意思一样啊?是不是要搞清楚这个?好,那接下来我把这个索引值打出来了,我打印索引值的目的是给你验证一下,我这个在执行的时候,我看一下这个找的这的位置是不是每次都不一样?知道什么意思吗?这是一个情况。
第二个情况是我如果说一个空间,我这一块内存空间腾出来了,下次再分配的时候应该是分配上了,能理解吗?这意思看一下这个里面是不是我们的这个解,我们的每一个元素。同学们,那么这个一开始用了,这都用了,那假如说后来这个对应的客户代链接给关掉了,下次你再分配空间的时候,是不是从这开始分配的啊?我给你打印的值呢?打印这个值的意思是不是这个?清楚什么意思吗?干这用的,别搞混了,好好接着看。
那接下来这是不是打印这个客户单地址?是不是?其实你只要得到那块内存是不是你想打印什么都可以,是不是?
改 改 改
好,接下来然后是不是就抒发出去了?这一块是不是就一模一样了?当然对方关闭链接的话,对方关闭链接或你读一场的话,你还要记得要关闭这个什么呀?关闭这个通信的文件描述什么?同时是不是要把这个设置为-1?如果你不设置为- 1 会怎么样,对吧?嗯,那个是不是会浪费很多内存?是不是因为这个内,这块内存你用完了是不是记得释放出来?是不是要让后续的链接去使用它?这个思想一定有,最后这个紫阳退出是不是这样的?那这个我给你验证一下,这个验证一下,代码能看懂吗?同学们,代码我已经放到共享目录里面去了,你可以拿一下,你可你的比较一下到底是哪没写?对啊,这个跑起来,咱们看看这个监听起来没?竖起来了,连一下 n c 127. 18888 回事,再来一个 n c 127. 18888