个人建设视频网站兰州网站建设哪家专业

web/2025/10/7 19:26:23/文章来源:
个人建设视频网站,兰州网站建设哪家专业,网站开发工具书,下载建设网站软件人其实很难抵制诱惑#xff0c;人只能远离诱惑#xff0c;所以千万不要高看自己的定力。 文章目录 一、LT和ET模式1.理解LT和ET的工作原理2.通过代码来观察LT和ET工作模式的不同3.ET模式高效的原因#xff08;fd必须是非阻塞的#xff09;4.LT和ET模式使用时的读取方式 二…人其实很难抵制诱惑人只能远离诱惑所以千万不要高看自己的定力。 文章目录 一、LT和ET模式1.理解LT和ET的工作原理2.通过代码来观察LT和ET工作模式的不同3.ET模式高效的原因fd必须是非阻塞的4.LT和ET模式使用时的读取方式 二、Reactor1.tcpServer.hpp1.1 连接结构体1.2 初始化服务器1.3 事件派发器1.4 回调函数1.5 epoller.hpp 2.protocol.hpp2.1 解析出一个完整的报文2.2 应用层协议定制2.3 序列化和反序列化 3.main.cc3.1 业务逻辑处理3.2 Reactor服务器运行结果 4.总结Reactor模式 一、LT和ET模式 1.理解LT和ET的工作原理 1. 多路转接接口select poll epoll所做的工作其实都是事件通知只向上层通知事件到来处理就绪事件的工作并不由这些API来完成这些接口在进行事件通知时有没有自己的策略呢 其实是有的在网络编程中select poll 只支持LT工作模式而epoll除了LT工作模式外还支持ET工作模式不同的工作模式对应着不同的就绪事件通知策略LT模式是这些IO接口的默认工作模式ET模式是epoll的高效工作模式。 2. 下面来举一个例子帮助大家理解ET和LT模式的区别送快递的例子 新上任的快递员小李要给学24宿舍楼的张三送快递张三买了很多的快递估摸着有6-7个快递小李到了学24的楼底然后就给楼上的张三打电话通知张三下来拿快递但是张三正在和他的狐朋狗友开黑打游戏呢于是张三就嘴上答应着我马上下去但始终就不下去老实人小李见张三迟迟不下来拿快递又给张三打电话让张三下来拿快递但张三嘴上又说我马上下去拿快递真的马上但过了一会儿张三依旧还是不下来小李又只能给张三打电话张三啊你的快递到了你赶快下来取快递吧终于张三和自己的狐朋狗友推完对面的水晶了下楼来取快递了但是张三一个人只拿走了3个快递还剩下三个快递张三也没办法了张三一个人一次只能拿这么多快递啊于是张三就拿着他的三个快递上楼了继续和他的舍友开黑打游戏。结果没一会儿小李又给张三打电话说张三啊你的快递没拿完呢你买了6样东西你只拿了3样还剩3个包裹你没拿呢张三又嘴上说好的好的我马上下去拿但其实又重复着前面的动作好一会儿才下楼拿走了剩余的3个包裹当包裹全部被拿走之后小李才不会给张三打电话了。 老油条快递员小王恰巧也要给学24宿舍楼的张三送快递恰巧的是张三这次又买了6个快递所以小王也碰巧要给张三送6个包裹。小王到了张三楼底下给张三打了一个电话说 张三啊我只给你打一次电话你现在要是不下来取快递我后面是不会给你打电话的除非你又买了新的快递我手上你的快递数量变多的时候我才会稍微好心的再给你打一个电话否则其他情况下我只会打一次你要是不下来取快递那我就不管你了我给其他客户送快递去了。张三一听这不行啊我要是现在不下来取快递这个快递员以后就不给我打电话了那我下楼找不到快递员拿不到我的快递怎么办所以张三就立马下楼取快递去了。张三一次拿不了这么多快递啊但张三又不能漏下一些快递因为小王下一次不会再给张三打电话了所以张三刚到楼上放下手中的三个快递又立马返回楼下取走剩余的三个快递了。 3. 在上面的这两个例子中其实小李的工作模式就是水平触发Level Triggered模式简称LT模式小王的工作模式就是边缘触发Edge Triggered模式简称ET模式也是多路转接接口高效的模式。 LT对应epoll的工作方式就是当epoll检测到sock上有就绪的事件时epoll_wait会立马返回通知程序员事件就绪了程序员可以选择只读取sock缓冲区的部分数据剩下的数据暂时不读了等下次调用recv的时候再读取sock缓冲区中的剩余数据下次怎么调用recv呢当然也是通过epoll_wait通知然后再进行调用啦所以只要sock中的数据程序员没有一次性拿走那么后续再调用epoll_wait时epoll_wait依旧会进行就绪事件的通知告诉程序员来读取sock中的剩余数据而这样的方式就是LT模式即只要底层有数据没读完后续epoll_wait返回时就会一直通知用户读取数据。 而ET对应的工作方式是如果底层有数据没读完后续epoll_wait不会通知程序员事件就绪了只有当底层数据增多的时候epoll_wait才会再通知一次程序员否则epoll_wait只会通知一次。 2.通过代码来观察LT和ET工作模式的不同 1. 在前一篇文章中我们写过epoll_server当然epoll_server的默认工作模式也是LT模式在下面的代码中我将处理就绪事件的接口HandlerEvent( )屏蔽掉了当客户端连接到来时服务器的epoll_wait一定会检测到listensock上的读事件就绪了所以epoll_wait会返回告知程序员要处理数据了但如果程序员一直不处理数据的话那epoll_wait每次都会告知程序员要处理数据了所以从显示器的输出结果来看epoll_wait返回后根据返回值n一定是进入到了default分支中并且每次epoll_wait都会告知程序员事件就绪所以显示器会一直疯狂打印have events ready因为只要底层有事件就绪对于listensock来说只要内核监听队列有就绪的连接那就是就绪epoll_wait就会一直通知程序员事件就绪了赶快处理吧。就像小李一样只要张三不拿走快递小李就会一直给张三打电话 2. 在添加listensock到epoll底层的红黑树中时不仅仅关心listensock的读事件同时还让listensock的工作模式是ET只要将EPOLLIN和EPOLLET按位或即可。 所以当连接到来时可以看到服务器只会打印一次have event ready只要没有新连接到来那么epoll_wait只会通知程序员一次事件就绪除非到来了新连接那就说明内核监听队列中就绪的连接变多了换言之就是listensock底层的数据变多了此时epoll_wait才会再好心提醒一次程序员事件就绪了你赶快处理吧。反过来就是只要后续listensock底层的数据没有增多那么epoll_wait就不会在通知程序员了。 而由于我们设置的timeout是阻塞式等待所以你可以看到只要没有新连接到来服务器就会阻塞住epoll_wait调用不会再返回也就不会再通知程序员。而反观LT模式虽然每次epoll_wait都是阻塞式等待但epoll_wait每次都会返回每次都会告知程序员这就是两者的不同。边缘触发只会触发一次水平触发会一直触发。 3.ET模式高效的原因fd必须是非阻塞的 1. 为什么ET模式是高效的呢这是非常重要的一个面试题许多的面试官在问到网络环节时都会让我们讲一下select poll epoll各自的用法epoll的底层原理三个接口的优缺点还有就是epoll的两种工作模式以及ET模式高效的原因ET模式高效的原因也是一个高频的问题。 2. ET模式下只有底层数据从无到有从有到多的时候才会通知上层一次通知的机制就是rbtreeready_queuecb所以ET这种通知机制就会倒逼程序员一次将底层的数据全部读走如果不一次读走就可能造成数据丢失你无法保证对方一定会继续给你发数据啊如果无法保证这点那就无法保证epoll_wait还会通知你下一次如果无法保证这一点那就有可能你只读取了sock的部分数据但后续epoll_wait可能不会再通知你了从而导致后续的数据你永远都读不上来了所以你必须一次将底层的数据全部读走。 如何保证一次将底层的数据全部读走呢那就只能循环读取了如果只调用recv一次是无法保证一次将底层的数据全部读走的。所以我们可以打个while循环一直读sock接收缓冲区中的数据直到读取不上来数据但这里其实就又有一个问题了如果sock是阻塞的循环读读到最后一定会没数据而此时由于sock是阻塞的那么服务器就会阻塞在最后一次的recv系统调用处直到有数据到来而此时服务器就会被挂起服务器一旦被挂起那就完蛋了~ 服务器被挂起那就无法运行了无法给客户提供服务了这就很有可能造成很多公司盈利上的损失所以服务器一定不能停下来更不能被挂起需要一直运行以便给客户提供服务。而如果使用非阻塞文件描述符当recv读取不到数据时recv会返回-1同时错误码被设置为EAGAIN和EWOULDBLOCK这俩错误码的值是一样的此时就可以判断出我们一次把底层的数据全部都读走了。 所以在工程实践上epoll以ET模式工作时文件描述符必须设置为非阻塞防止服务器由于等待某种资源就绪从而被挂起。 3. 解释完ET模式下fd必须是非阻塞的原因后那为什么ET模式是高效的呢可能有人会说因为ET模式只会通知一次倒逼程序员将数据一次全部读走所以ET模式就是高效的如果这个问题满分100分的话你这样的回答只能得到20分因为你的回答其实仅仅只是答案的引线真正最重要的部分你还是没说出来。 倒逼程序员一次将数据全部读走那不就是让上层尽快取走数据吗尽快取走数据后就可以给对方发送一个更大的16位窗口大小让对方更新出更大的滑动窗口大小提高底层数据发送的效率更好的使用TCP延迟应答滑动窗口等策略这才是ET模式高效的最本质的原因 因为ET模式可以更好的利用TCP提高数据发送效率的种种策略例如延迟应答滑动窗口等。 之前在讲TCP的时候TCP报头有个字段叫做PSH其实这个字段如果被设置的话epoll_wait就会将此字段转换为通知机制再通知一次上层让其尽快读走数据。 4.LT和ET模式使用时的读取方式 二、Reactor 1.tcpServer.hpp 1.1 连接结构体 1. 我们知道socket套接字在通信的时候每个sock在内核都会创建接收缓冲区和发送缓冲区这样的缓冲区常常开辟在堆上不会像临时变量char buffer[1024]随着栈帧空间的销毁而销毁这能更好的存储网络中收到的数据 和 即将要发送到网络中的数据如果用栈上的空间来存储网络收发的数据则数据极有可能被销毁掉因为只要变量所在栈帧销毁则变量中的数据在下次变量重新开辟时就会由原来存储的网络数据变为未初始化过的随机数据了。 所以为了让每个sock都有自己的收发缓冲区我们不再使用原来编写服务器时用一个char buffer[1024]来存储sock上的网络数据而是改用一个Connection结构体来代表一个通信的sock这个结构体内部包含通信的套接字描述符_sock以及sock所对应的_inbuffer和_outbuffer。 除此之外该结构体还包括了三个回调方法_recver_sender_excepter分别表示sock对应的读方法写方法异常方法func_t是一个包装器类型包装内容为函数指针返回值是void参数是Connection指针类型这三个参数其实就是Reactor反应堆模式的神来之笔所在后面总结Reactor时就知道为什么要这么设计Connection了同时也知道为什么Reactor叫反应堆模式了。实现Reactor网络库这个Connection是关键所在。 该结构体还包括了一个额外的服务器类型的指针在某些场景下比如Connection结构体和TcpServer服务器两个类是分文件的此时如果在Connection的回调方法中想要调用一下TcpServer类中的方法时这个回指指针会帮我们拿到TcpServer中的方法今天我们是不需要的因为今天两个类都放到了tcpServer.hpp中 Connection还实现了两个函数一个是注册函数一个是关闭sock的函数注册函数用于将外部实现的sock对应的读方法写方法异常方法注册到sock所在的结构体Connection中。 1.2 初始化服务器 1. initServer接口还是先将listensock创建出来将服务器的ip地址和port端口号都bind绑定好然后设置服务器为监听状态既然是Reactor网络库则使用的多路转接接口一定是epoll所以还需要调用epoll_create创建epoll模型与sock相同的是今天的epoll所对应的接口我们也做了封装将其单独实现到epoller.hpp中作为一个组件来使用。 当服务器开始运行时一定会有大量的Connection结构体对象需要被new出来那么这些结构体对象需不需要被管理呢当然是需要的所以在服务器类里面定义了一个哈希表_connections用sock来作为哈希表的键值sock对应的结构体connection作为键值所对应的value值也就是哈希桶存储的值今天是不会出现哈希冲突的所以每个键值下面的哈希桶只会挂一个value值即一个Connection结构体. 初始化服务器时第一个需要被添加到哈希表中的sock一定是listensock所以在initServer方法中先把listensock添加到哈希表里面添加的同时还要传该listensock所对应的关心事件的方法对于listensock来说只需要关注读方法即可其他两个方法设为nullptr即可。 2. 在AddConnection中要判断events是否有EPOLLET如果有则文件描述符必须是非阻塞所以要将sock设置为非阻塞设置的方式也简单只要通过fcntl来实现即可同样的我们把fcntl也封装成了一个SetNonBlock()方法来使用。Reactor中epoll的工作模式是ET这也是Reactor网络库高效的原因。 接下来就是new一个连接结构体然后将结构体的字段填充好比如设置好回调方法的值结构体中的文件描述符值等等。连接结构体创建好后我们还需要调用封装好的AddEvent接口将sock及其关心的事件交给epoll来监视最后别忘了把new好的结构体交给哈希表来管理。 2. 在代码实现上给AddConnection传参时用到了一个C11的知识就是bind绑定的使用一般情况下如果你将包装器包装的函数指针类型传参给包装器类型时是没有任何问题的因为包装器本质就是一个仿函数内部调用了被包装的对象的方法所以传参是没有任何问题的。 但如果你要是在类内传参那就有问题了会出现类型不匹配的问题这个问题真的很恶心而且这个问题一报错就劈里啪啦的报一大堆错因为function是模板C报错最恶心的就是模板报错一报错人都要炸了。话说回来为什么是类型不匹配呢因为在类内调用类内方法时其实是通过this指针来调用的如果你直接将Accepter方法传给AddConnection两者类型是不匹配的因为Accepter的第一个参数是this指针正确的做法是利用包装器的适配器bind来进行传参bind将Accepter进行绑定前两个参数为绑定的对象类型 和 给绑定的对象所传的参数因为Accepter第一个参数是this指针所以第一个参数就可以固定传this后面的一个参数不应该是现在传而应该是调用Accepter方法的时候再传只有这样才能在类内将类成员函数指针传给包装器类型。 不过吧还有一种不常用的方法就是利用lambda表达式来进行传参lambda可以捕捉上下文的this指针然后再把lambda类型传给包装器类型这种方式不常用用起来也怪别扭的function和bind是适配模式两者搭配在一起用还是更顺眼一些lambda这种方式了解一下就好。 1.3 事件派发器 1. 事件派发器是真正服务器要开始运行了服务器会将就绪的每个连接都进行处理首先如果连接不在哈希表中那就说明这个连接中的sock还没有被添加到epoll模型中的红黑树不能直接进行处理需要先添加到红黑树中然后让epoll_wait来拿取就绪的连接再告知程序员这个时候再进行处理这样才不会等待而是直接进行数据拷贝。 Loop中处理就绪的事件的方法非常非常的简单如果该就绪的fd关心的是读事件那就直接调用该sock所在连接结构体内部的读方法即可如果是写事件那就调用写方法即可。有人说那如果fd关心异常事件呢其实异常事件大部分也都是读事件不过也有写事件所以处理异常的逻辑我们直接放到读方法和写方法里面即可当有异常事件到来时直接去对应的读方法或写方法里面执行对应的逻辑即可。 假设某个异常事件发生了那么这个异常事件会自动被内核设置到epoll_wait返回的事件集中这个异常事件一定会和一个sock关联比如客户端和服务器用sock通信着突然客户端关闭连接那么服务器的sock上原本关心着读事件此时内核会自动将异常事件设置到该sock关心的事件集合里在处理sock关心的读事件时读方法会捎带处理掉这个异常事件处理方式为服务器关闭通信的sock因为客户端已经把连接断开了服务器没必要维护和这个客户端的连接了服务器也断开就好这样的逻辑在读方法里面就可以实现。 2. 像下面这样的事件派发器就是典型的Reactor反应堆模式当连接到来时直接调用对应的sock所在Connection中的回调方法来进行处理即可这就像是化学反应一样当连接请求或通信的网络数据到来时代码就像产生化学反应一样自动调用连接对应的listensock 或 通信对应的sock所在的方法进行处理即可就像是一个化学反应堆这也是为什么这样的网络库叫Reactor的原因因为每个sock都有自己对应的读 写 异常方法。 listensock对应的_recver方法就是Accepter函数通信sock对应的_recver方法就是Recver函数通信sock对应的_sender方法就是Sender函数。 1.4 回调函数 1. 当listensock底层有连接到来时epoll_wait告知程序员有事件到来后则应该调用listensock对应的_recver回调方法这个回调方法在将listensock添加到连接结构体时我们就已经将Accepter绑定给listensock的_recver回调方法了。 进入Accepter之后就开始读取listensock的底层连接了但你能保证一次就把listensock底层的数据全部读取上来吗你accept系统调用一次最多就只能拿取一个连接万一listensock底层有很多连接呢今天epoll是ET模式如果你只读取一次的话且恰好后面没有新连接到来呢那没有被拿取上来的连接所对应的客户端就无法和服务器通信了这个问题就是你服务器产生的我客户端和你好好的通信着结果你服务器不受理我的连接请求那就说明你服务器代码有bug。 所以在Accepter中必须循环读取listensock底层的数据确保一次将listensock底层的数据全部读走所以Accepter中必须得打死循环进行读取循环读我们也不怕服务器被挂起因为ET模式下所有的文件描述符都被我们设置成了非阻塞当accept拿上来通信的连接后下一步要做的就是将这个连接添加到_connections哈希表中在AddConnection中会构建sock对应的connection结构体然后将结构体中的字段填充好将回调方法设置到结构体的成员变量里面另外AddConnection中还会将sock和其关心的事件设置到epoll模型的红黑树当中让epoll帮忙监视程序员所关心的fd的就绪情况。 对于listensock来讲只关心读事件所以在给AddConnection传参的时候后两个方法就不传了但对于通信的sock来讲后两个方法将来也是要调用的所以也要传这里在传参的时候由于参数是成员函数所以也要使用bind固定参数的方式来进行传参。 当accept系统调用返回值小于0同时错误码被设置为EAGAIN或EWOULDBLOCK时则说明accept已经将本轮listensock下就绪的数据全部读完了此时就可以break跳出死循环了。如果错误码被设置为EINTR则说明进程可能由于执行某种到来信号对应的handler方法导致这里的accept系统调用被中断则此时应该继续循环读取listensock底层的数据所以直接continue即可还有另一种可能就是accept系统调用真的出错了此时的做法也break跳出循环即可。 2. Recver这里还是和之前一样的问题也是前面在写三个多路转接接口服务器时一直没有处理的问题你怎么保证你一次就把所有数据全部都读上来了呢如果不能保证那就和Accepter一样必须打死循环来进行读取当recv返回值大于0那我们就把读取到的数据先放入缓冲区缓冲区在哪里呢其实就在conn参数所指向的结构体里面结构体里会有sock所对应的收发缓冲区。然后就调用外部传入的回调函数_service对服务器收到的数据进行应用层的业务逻辑处理。 当recv读到0时说明客户端把连接关了那这就算异常事件直接回调sock对应的异常处理方法即可。 当recv的返回值小于0同时错误码被设置为EAGAIN或EWOULDBLOCK时则说明recv已经把sock底层的数据全部读走了则此时直接break跳出循环即可也有可能是被信号给中断了则此时应该继续执行循环另外一种情况就是recv系统调用真的出错了则此时也调用sock的异常方法进行处理即可。 业务逻辑处理方法应该在本次循环读取到所有的数据之后再进行处理。 3. 之前写服务器时我们从来没处理过写事件写事件和读事件不太一样关心读事件是要常设置的但写事件一般都是就绪的因为内核发送缓冲区大概率都是有空间的如果每次都要让epoll帮我们关心读事件这其实是一种资源的浪费因为大部分情况下你send数据都是会直接将应用层数据拷贝到内核缓冲区的不会出现等待的情况而recv就不太一样recv在读取的时候有可能数据还在网络里面所以recv要等待的概率是比较高的所以对于读事件来说常常都要将其设置到sock所关心的事件集合中。 但写事件并不是这样的写事件应该是偶尔设置到关心集合中比如你这次没把数据一次性发完但你又没设置该sock关心写事件当下次写事件就绪了也就是内核发送缓冲区有空间了epoll_wait也不会通知你那你还怎么发送剩余数据啊所以这个时候你就应该设置写事件关心了让epoll_wait帮你监视sock上的写事件以便于下次epoll_wait通知你时你还能够继续发送上次没发完的数据。 这个时候可能有人会问ET模式不是只会通知一次吗如果我这次设置了写关心但下次发送数据的时候还是没发送完毕因为内核发送缓冲区可能没有剩余空间了那后面ET模式是不是就不会通知我了呀那我还怎么继续发送剩余的数据呢ET模式在底层就绪的事件状态发生变化时还会再通知上层一次的对于读事件来说当数据从无到有从有到多状态发生变化时ET就还会通知上层一次对于写事件来说当内核发送缓冲区剩余空间从无到有从有到多状态发生变化时ET也还会通知上层一次所以不用担心数据发送不完的问题产生因为ET是会通知我们的。 在循环外我们只需要通过判断outbuffer是否为空的情况来决定是否要设置写事件关心当数据发送完了那我们就取消对于写事件的关心不占用epoll的资源如果数据没发送完那就设置对于写事件的关心因为我们要保证下次写事件就绪时epoll_wait能够通知我们对写事件进行处理。 4. 下面是异常事件的处理方法我们统一对所有异常事件都先将其从epoll模型中移除然后关闭文件描述符最后将conn从哈希表_connecions中移除。 值得注意的是conn指针指向的连接结构体空间必须由我们自己释放有人说为什么啊你哈希表不是都已经erase了么为什么还要程序员自己再delete连接结构体空间呢 这里要给大家说明一点的是所有的容器在erase的时候都只释放容器自己所new出来的空间像哈希表这样的容器它会new一个节点节点里面存储着conn指针和指向下一个节点的指针当调用哈希表的erase时哈希表只会释放它自己new出来的节点空间至于这个节点空间里面存储了一个Connection类型的指针并且这个指针变量指向一个结构体空间这些事情哈希表才不会管你呢容器只会释放他自己开辟的空间这个空间里面放着一个指针变量还有可能有其他变量那容器就只会释放这些变量所在结构体的空间这个结构体一定是容器开辟出来用于存放使用者希望存储的一些东西比如今天的Connection指针当然也有可能存储其他变量对于哈希表来说还可能存储链表节点类型的指针因为哈希表是vector挂单链表的方式来实现的。 所以我们要自己手动释放conn指向的空间如果你不想自己手动释放conn指向的堆空间资源则可以存储智能指针对象这样在哈希表erase时其实就是delete存储Connection类型指针的结构体这样就会调用该结构体的析构函数像这样的结构体内部的析构函数我们是不会自己写的直接使用编译器默认生成的即可编译器对内置类型不处理对自定义类型会调用该类的析构函数也就是调用智能指针对象的析构函数在析构函数内部就会释放conn所指向的连接结构体的动态内存了。 这样搞起来其实还是很麻烦的所以我们就自己手动释放就好了如果不手动释放那就会造成内存泄露。 5. 下面这篇文章的第五部分的第二个标题讲述了编译器默认生成的析构函数对于对象的成员变量的处理策略对于内置类型不处理对于自定义类型会调用该类的析构函数。 值得注意的是就算是自定义类型的指针他其实也是被编译器当作内置类型了并不会调用指针类型的析构函数。 当析构函数被调用完毕之后delete的目标堆空间就会被释放归还给操作系统。 【C】类和对象核心总结 下面是当指针为自定义类型时编译器默认生成的析构函数不会调用对应的析构函数和内置类型处理策略一样的证明过程。 1.5 epoller.hpp 下面是封装的epoll的各个接口没什么难度我这里也不会赘述因为之前我们已经写过简单版的LT模式epoll服务器了对于epoll接口的使用肯定没什么难度了所以屏幕前的老铁们可以简单看一下代码实现今天的重点是前面Reactor的实现不是这些小组件是怎么实现的。 2.protocol.hpp 2.1 解析出一个完整的报文 1. 其实在tcpServer.hpp讲解完毕之后Reactor网络库的重点就已经实现完毕了也就是网络IO层面上的处理连接到来处理网络数据传输的工作已经大功告成了。 下面的protocol.hpp只是在Reactor网络库的基础上接入了服务器的应用层比如如何处理黏包问题应用层如何定制协议添加或去掉应用层协议报头对报文的序列化和反序列化等等工作全部都属于应用层的事情。 但其实早在以前我们讲协议定制和序列化反序列化的时候也就是实现网络版本计算器的时候我们就已经实现过这些工作了所以protocol.hpp就是从当时的代码直接拷贝过来的仅仅只是对解析报文这个代码作了修改。 所以如果有老铁忘记了当时怎么定制的协议那就可以回头再重新看看当时的文章。 协议定制序列化和反序列化 2. 下面的接口是用来解析sock在应用层的_inbuffer数据的由于TCP是面向字节流的所以如何解析出一个完整报文的问题就必须由应用层来做。 我们当时定过协议协议报头和有效载荷之间有LINE_SEP也就是\r\n有效载荷的尾部也\r\n协议报头表示有效载荷的字节大小所以在字节流的_inbuffer中解析出一个完整报文的逻辑就可以是这样的 为了安全起见先把输出型参数text设置为空串然后在inbuffer中找LINE_SEP的迭代器位置找到之后将报头部分substr截取出来再将其stoi转换为整数这样就得到了有效载荷的大小然后再将截取出来的报头调用其类内函数size()得到报头的字节大小最后再加上两个LINE_SEP的大小这些字节大小作和之后就可以得到一个完整报文的字节大小了。 最后一步就是直接从0开始截取total_len大小个字节将截取到的字串放到输出型参数text里面即可。然后再将0到total_len字节的数据从inbuffer中删除即可其实就是覆盖数据。这样我们就从大批的字节流数据中截取出了一个完整的报文。 2.2 应用层协议定制 其实关于应用层协议的这些代码在之前网络版本计算器都已经讲过了这里我就言简意赅的说一下如果有懵逼的老铁请移步我原来写的那篇文章。 移步协议定制序列化和反序列化 1. 下面是应用层报头的添加与去除报头添加时其实只要在报头和有效载荷间以及有效载荷尾部都加上LINE_SEP报头的内容就是有效载荷的长度。 报头去除时先以第一个LINE_SEP作为分隔符截取头部字符串这样就得到了有效载荷的长度大小然后从第一个LINE_SEP位置开始截取子串截取长度为有效载荷的长度即可这样就得到了完整的有效载荷。 2.3 序列化和反序列化 1. 下面是序列化反序列化的工作主要用到的就是我们自己写的方案和json的方案企业内部自己一般会使用protobuf对外使用json。json我也不会只能简单的使用一下没有系统的学过所以下面我只能说说我们自己的序列化和反序列化方案不过值得注意的是实际在公司使用中对于序列化和反序列化是有现成的解决方案的程序员绝对不会自己去写但今天我们作为学习者自己写肯定更能理解序列化和反序列化究竟是作的一个什么样的工作对学习者肯定是大有好处的。 2. 对于请求报文的序列化其实就是将结构体Request中的_x _op _y等字段都拼接成一个字符串这样就完成了序列化的工作但不是仅仅序列化就完了能序列化就一定得能反序列化所以在拼接字符串时_x和_op_op和_y之间都得有一个SEP作为分隔符方便对到来的请求报文进行反序列化。结构化数据 → 字节流数据 反序列化其实主要就是字符串操作将字符串中的_x _y _op截取出来分别转换为int int char类型赋值给结构体Request的三个成员变量里面这样就完成了反序列化的工作。字节流数据 → 结构化数据 3. 对于响应报文的序列化只要将int类型的退出码和计算结果转换为string类型中间在拼接一个SEP字段这样就从结构化转为了序列化的数据。 反序列化的工作也很简单只要将字符串中的退出码和结果部分截取子串出来然后再将其转为int类型这样就从序列化转为了结构化的数据。 3.main.cc 3.1 业务逻辑处理 1. 下面是整个Reactor服务器的调用逻辑先初始化服务器然后执行事件派发接口Dispatcher。 服务器的应用层提供的服务是计算服务所以在构建服务器对象时要将上层的处理逻辑函数calculate也传到服务器对象内部。 在服务器执行Recver方法时收到数据后会调用回调函数执行流就会执行calculate方法进行读到的数据的业务处理。 2. calculate就是业务逻辑处理方法在方法内部打一个while循环只要能够解析出一个完整的报文那就可以进入循环对拿到的报文作应用层的逻辑处理当_inbuffer中的数据被拿的导致剩余数据无法构成一个完整报文时也就是ParseOnePackage出错时我们此时选择退出循环将已经处理好的请求报文也就是构建出了响应报文全部发送到客户端有人说怎么把全部的响应报文发送给客户端呢其实很简单在ParseOnePackage内部每次处理好一个请求报文后相对应的响应报文会被放到conn内部的发送缓冲区_outbuffer中所以当跳出循环时_outbuffer中已经存放了很多就绪的响应报文了此时只要在调用conn内部的sender方法进行发送即可。所以这么来看的话这个conn是不是很有用呢整个贯穿了Reactor代码实现的所有模块。 在ParseOnePackage内部也很简单因为我们在protocol.hpp内部已经将请求/响应报文的序列化反序列化应用层报文的报头和有效载荷分离添加报头等工作全部做好了所以在ParseOnePackage内部只需要调用对应protocol.hpp内部实现的方法即可。比如先去掉报头然后调用反序列化接口得到一个结构化的请求将结构化的请求和一个未初始化的结构化响应进行cal处理在cal处理内部其实就是作相应的计算工作计算工作完成后将结果填充到结构化的响应报文中即可然后对响应报文作序列化添加报头等工作最后只要将完整的响应报文放到outbuffer中即可等到循环结束时统一将所有的响应报文发送给对方。 3.2 Reactor服务器运行结果 1. 客户端我们自己也就不写了之前讲协议定制的时候我们自己已经实现过calclient和calserver了所以这里直接拿calclient作为客户端来使用。 从运行结果可以看出正常的数据计算请求服务器是能够给我们返回对应的计算结果的并且当客户端发生异常时比如ctrlc断开TCP连接服务器也能够对异常事件做出相对应的处理比如服务器也关闭对应的tcp连接同时释放sock对应的所有资源例如sock对应的connection结构体将sock移除epoll模型移除哈希表关闭sock文件描述符等处理。 4.总结Reactor模式 1. 我个人对于Reactor的理解Reactor主要围绕事件派发和自动反应展开的就比如连接请求到来epoll_wait提醒程序员就绪的事件到来应该尽快处理则与就绪事件相关联的sock会对应着一个connection结构体这个结构体我觉得就是反应堆模式的精华所在无论是什么样就绪的事件每个sock都会有对应的回调方法所以处理就绪的事件很容易直接回调connection内的对应方法即可是读事件就调用读方法是写事件就调用写方法是异常事件则在读方法或写方法中处理IO的同时顺便处理掉异常事件。 所以我感觉Reactor就像一个化学反应堆你向这个反应堆里面扔一些连接请求或者网络数据则这个反应堆会自动匹配相对应的处理机制来处理到来的事件很方便同时由于ET模式和EPOLL这就让Reactor在处理高并发连接时展现出了不俗的实力。 2. 我们今天所实现的服务器是半同步半异步的半同步是说Reactor既保证了就绪事件的通知同时又负责了IO半异步指的是今天的服务器还实现了业务处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/88645.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

宜昌网站设计公司wordpress 站点图标

先说结论:需求还是很大,但是没有什么初级程序员能干的岗位。 游戏引擎,存储,推荐引擎,infra,各种各样的性能敏感场景。 在开始前我分享下我的经历,我刚入行时遇到一个好公司和师父,…

手机网站开发模拟器三网合一企业网站

来源:悟空智能科央行发布工作论文《区块链能做什么、不能做什么?》,论文称,不要夸大或迷信区块链的功能。区块链应用要立足实际情况。目前区块链投融资领域泡沫明显。论文从经济学角度研究了区块链的功能。首先,在给出…

网站盈利模式有哪几种网络维护费计入什么科目

文章目录JWT工具模块测试JWT工具模块 如果要想在项目之中去使用JWT技术,那么就必须结合到已有的模块之中,最佳的做法就是将JWT的相关的处理 操作做为一个自动的starter组件进行接入 1、【microcloud项目】既然要开发一个starter组件,最佳的做法就是开发…

有百度推广的网站建设网站都需要准备什么材料

目录 一、实时嵌入式操作系统 1.1 概述 1.2 什么“实时” 1.3 什么是硬实时和软实时 1.4 什么是嵌入式 1.5 什么操作系统 二、常见重量级操作系统 三、常见轻量级嵌入式操作系统 3.1 概述 3.2 FreeRTOS 3.3 uC/OS-II 3.4 RT-Thread 3.5 RT-Thread、uC/OS-II、Free…

做商务网站服务网站建立的具体步骤

学习目标: 项目 实验 学习时间: 2023.11.24-2023.12.1 学习产出: 项目 由于小程序要上线了,这周前几天都在和前端联调改bug,并且多拆分出来两张表,工作量比较大,花的时间很多。 实验 整…

湛江网站制作计划聊天网站开发

1.Axure是什么??? Axure是一款功能强大的原型设计工具,它可以让用户快速地创建交互式原型,并针对原型进行测试和改进。Axure的主要特点包括可定制的界面元素库、交互动画效果、条件逻辑、团队协作等功能,适…

小白建设论坛网站科技部网站建设合同

namespace hunan\changsha class Person{ static $namewu_han; } namespace hunan\changsha 声明命名空间,它的作用包括方法,类名,常量,这三者都统称为元素 当在程序里使用元素的时候,默认在当前的命名空间里找该元素…

青岛建设网站设计公司合肥网站排名优化公司哪家好

关键条目:ERROR 1045(28000): Access deniedforuserrootlocalhost(using password: YES)这个错误1045(28000)的本质其实就是访问被拒绝,问题原因也很简单,就是用户密码不适用,也可以理解为用户或密码错误。Access deniedforuserro…

网站关键词优化seo关键词之间最好用逗号淄博手机网站建设公司

本接线大家介绍一下Google SVN托管和Google SVN使用问题,本人用过一段时间Google SVN,有一些自己的心得和大家分享一下,希望对你有所启示。 Google SVN的使用http://tortoisesvn.net/downloads这里可以下svn,再发给创建者你的Goog…

哪家做网站的公司比较好网站导航条内容

哈哈转载于:https://www.cnblogs.com/zzzzw/p/5182224.html

徐州网站建设哪家好薇管理网络的应用软件

在前面分享的几篇中咱已经玩耍了Popup、ListBox多选、Grid动态分、RadioButton模板、控件的拖放效果、控件的置顶和置底、控件的锁定、自定义Window样式、动画效果、Expander控件、ListBox折叠列表、聊天窗口、ListBox图片消息、窗口抖动、语音发送、语音播放、语音播放问题、玩…

怎么查询网站的建站时间定制规划设计公司

要知道C/CLI是什么,首先知道什么是CLI。 一、CLI简介 CLI:(Common Language Infrastructure,通用语言框架)提供了一套可执行代码和它所运行需要的虚拟执行环境的规范。更通俗的我们可以说它是一个虚拟平台,是操作系统和应用程序间的一层抽象…

西安高科鱼化建设有限公司网站网站建设平台卜先明

基础篇 基础篇要点:算法、数据结构、基础设计模式 1. 二分查找 要求 能够用自己语言描述二分查找算法能够手写二分查找代码能够解答一些变化后的考法 算法描述 前提:有已排序数组 A(假设已经做好) 定义左边界 L、右边界 R&…

工信部网站备案查询验证码错误揭阳市php网站开发找工作

旅游管理复试很难?! 别怕!经验超丰富的老学姐来给你们出谋划策啦! 最近是不是被旅游管理考研复试折磨得够呛?莫慌!我这有着丰富复试指导经验的老学姐来帮你们排雷,助力大家顺利上岸&#xff01…

免费ppypp网站唐山做企业网站

题目说明: 语法定义涉及数字、括号和运算符和-的表达式的语法。起始符号为Expression。一个表达式应该对应于以下之一:一个数字标记Expression ExpressionExpression - Expression- Expression( Expression ) level help 通过语法来描述高级语言的…

手机网站做落地页开源网站模板cms

一、程序调试的debug宏 1、程序调试的常见方案 单步调试、裸机LED调试、打印信息、log文件 利用调试器进行单步调试(譬如IDE中,Jlink)适用于新手,最大的好处就是直观,能够帮助找到问题。缺点是限制性大、速度慢。裸机…

没有网站可以做备案吗网站推广网络

注意点 private File image;//对应的就是表单中文件上传的那个输入域的名称,Struts2框架会封装成File类型的private String imageFileName;// 上传输入域FileName 文件名private String imageContentType;// 上传文件的MIME类型 单个文件 1 package cn.itcast.ac…

网站为什么会出现死链互联网保险案例

啥是MCU,MCU科普 附赠自动驾驶学习资料和量产经验:链接 MCU是Microcontroller Unit 的简称,中文叫微控制器,俗称单片机,是把CPU的频率与规格做适当缩减,并将内存、计数器、USB、A/D转换、UART、PLC、DMA等…

医疗室内设计网站推荐网络营销策划书模板

本篇文章主要介绍了详解如何在 vue 项目里正确地引用 jquery 和 jquery-ui的插件,具有一定的参考价值,有兴趣的可以了解一下使用vue-cli构建的vue项目,webpack的配置文件是分散在很多地方的,而我们需要修改的是build/webpack.base…

led外贸网站合肥做网站yuanmus

缺省参数 调用函数时,缺省参数的值如果没有传入,则被认为是默认值。 下例会打印默认的age,如果age没有被传入. def printinfo( name,age 35 ): # 打印任何传入的字符串 print "Name: ", name print "Age ", age #调…