当前位置: 首页 > news >正文

个人网站成品下载wordpress导入数据库依然无法链接

个人网站成品下载,wordpress导入数据库依然无法链接,深圳龙岗是不是很落后,网站建设及报价高级IO 前言正式开始前面的IO函数简单过一遍什么叫做低效的IO钓鱼的例子同步IO和异步IO五种IO模型阻塞IO非阻塞IO信号驱动多路转接异步IO 小结 代码演示非阻塞IO多路转接select介绍简易select服务器timeout 为 nullptrtimeout 为 {0, 0}timeout 为 {5, 0}调用accept select编写… 高级IO 前言正式开始前面的IO函数简单过一遍什么叫做低效的IO钓鱼的例子同步IO和异步IO五种IO模型阻塞IO非阻塞IO信号驱动多路转接异步IO 小结 代码演示非阻塞IO多路转接select介绍简易select服务器timeout 为 nullptrtimeout 为 {0, 0}timeout 为 {5, 0}调用accept select编写代码的一般流程重写完整代码select优缺点 pollpoll的优缺点 前言 本篇主要讲解 五种IO模型的介绍重点讲解多路转接select服务器的编写poll服务器的编写 关于多路转接的epoll我会在下一篇详细讲解。 前面我一直在讲网络通信从创建套接字就可看到网络通信的就是IO发送方能发也能收接收方也是能发也能收站在网络角度来看就是机器把数据扔到了网络里面站在计算机体系结构角度来看就 是把数据把内存扔到网卡不管怎么理解都是IO。 正式开始 前面的IO函数简单过一遍 前面文件部分讲过的IO都是文件IO单机的打开文件将数据从磁盘读到os再从os将数据拷到用户缓冲区各设备离的都非常近在网络中两台主机相隔千里之外IO效率一定是要比单机来说低不少的。 IO问什么低效 read、recv、recvfrom、write、send、sendto这样的IO函数本质上都是一些拷贝函数都是在用户和内核之间拷贝数据不过毕竟是从内存中直接拷贝的效率还算OK。 以read为例当我们进行read/recv的时候如果底层没有数据read/recv会怎么办做有数据又会怎么做 没有数据read/recv进程就会阻塞也就是让进程等。 如果有数据就直接进行拷贝。 ⇒ 所以IO就是 等 数据拷贝。 等就是等IO类事件就绪。读就是底层有数据写就是底层有空间。 write也是一样的缓冲区满了就不让拷贝(等)没满就拷贝所以IO必须经历的两部就是等和数据拷贝。 看图 如果进程想要访问磁盘上的文件那就得先打开这个文件而文件 内容 属性所以打开文件后os要为文件创建相应的struct FILE结构体以维护文件的属性也就是在内存中维护而内存是惰性加载的不会说将文件中的所有数据全部加载完因为很多数据不一定能用上os可能会对文件预加载也就是先加载一部分当进程想要修改文件中的内容时就会先将需要的数据加载到内存里 此时就是进程先调用的IO类型的函数想要访问文件中的数据然后os才会做加载的这一步的也就是os加载之前进程就已经开始调用IO类函数了。 那么os在加载文件的内容时进程在干嘛 就是在等。 IO 等 拷贝。上面os在加载的时候就是等此刻进程是处于阻塞状态的。 那么拷贝呢 就是加载完毕之后。进程就会被os唤醒然后对os加载好的数据进行后续操作。 无论是网络还是单机只要是访问磁盘、键盘、网卡等等外设就一定是等 数据拷贝。 想一想scanf运行起来之后为什么会卡在命令行等你输入其实就是在等待标准输入。cin也是同理像这样的函数都是在等数据就绪后再将数据从外设搬到内存os的缓冲区中再从os搬到应用层这就是数据拷贝。 所以recv、read、send、write等函数看起来是在发送和接收其实都是在等IO类事件就绪然后再发起拷贝拷贝时无非就是从内核到用户或从用户到内核所以这些函数不是用户直接与硬件进行读写而是用户和内核之间的“交流”交流完毕后os再做后续的事情比如说将修改后的数据写回磁盘。 在os视角来看这些函数会让进程阻塞在IO视角来看就是让进程在等。 什么叫做低效的IO 网络里面谈IO是因为报文从A主机发送到B主机中间的发送时间会很长所以网络通信时调用read、recv等函数就要做IO这样就会花费大量的时间在等上如何提高IO的效率呢只要想办法在单位时间内让等的比重变得越低IO的效率就会越高。 单位时间内让等的比重变低如何做到呢 前面大佬们已经对于IO进行了深刻研究总结出来了五中IO模型这篇重点要讲的就是这五中IO模型。 先说说都是啥 阻塞IO非阻塞IO信号驱动多路转接多路复用异步IO 不过这里先不说这五种IO模型的细节我先通过一个生活中的例子来帮大家理解理解。 钓鱼的例子 钓鱼应该都见过吧。这里不说打窝这样的细节简单一点。 就直接说成等 鱼上钩的收杆后面直接说钓也就是等 钓。就像mc中的钓鱼一样。 什么场景下会说一个人钓鱼的效率非常高呢 一个人大半天都没有鱼咬钩一直在等。 另一个人一直是上钩不带停的。 很明显第二个人效率高所以只要单位时间内等的比重非常低这个人钓鱼的效率就非常高。 再来介绍个东西鱼漂钓鱼佬应该很熟悉但是没钓过鱼的同学可能很陌生看图 钓鱼的时候鱼漂能够反映出鱼咬钩的讯息。 假如说现在有五个人去钓鱼。 张三钓鱼的时候死死盯住鱼漂啥也不干非常专注鱼漂不动他不动。 李四耐不住性子看一会手机再看鱼漂有反应没没反应就接着看手机。 王五拿了个铃铛挂在鱼杆后面一直在玩手机铃铛一响就赶紧收杆。 赵六是个方圆五公里内的富二代一下子拿了100支鱼竿安置好后就来回检测哪只哪支鱼竿有鱼咬钩。 田七是个大老板但是最近想吃鱼了(不是高启强)但是他比较忙于是给了他手下小王一个桶让小王去钓等把桶钓满了再给他打电话田七再去取。 那么上面这五种情况就对应了五中IO模型。 张三就是阻塞式IO李四就是非阻塞式IO王五就是信号驱动赵六就是多路复用田七就是异步IO。 那么谁的钓鱼效率更高呢 赵六。 为啥呢 站在鱼的角度鱼脑袋上有104个诱饵这里认为鱼一定会咬钩不考虑打窝的情况诱饵都一样且在某个区域中均匀分布所以对于每个鱼竿来说上鱼的概率都是1/104但是赵六这个人的概率是100/104而其他人都是1/104所以单位时间内赵六等的比重是非常低的。 同步IO和异步IO 上面的人就对应的是进程或者线程进程或线程只要参与了IO就称为同步IO。 什么叫参与IO呢 就是要么参与了等要么参与了拷贝要么同时都参与。 只要参与了就叫做同步IO。 田七既没有等也没有钓拷贝所以田七是异步IO。 再来看看王五是同步IO吗 前面我讲信号的时候说过信号的产生是异步的但是王五是参与了IO的他在等也在等鱼上钩而且也是亲自钓的而不是像田七那样直接不在场。也就是说数据没有就绪就先忙着自己的事情但是一旦就绪了自己就将数据从内核拷贝到用户空间所以是参与了IO的。这里的信号驱动和单纯的信号产生有些不同就在于IO这里有后续的拷贝动作谈的不是信号的发送是异步的谈的是信号发送之后要参与IO还是同步的。 【注】这里信号驱动其实是有争议的有的人说是同步IO有的人说是异步IO但我这里按照同步来说。 张三和李四的阻塞IO和非阻塞IO有什么区别 都是同步IOIO 等 拷贝都要亲手钓这里没什么区别主要的区别是在等上张三是阻塞的等李四是非阻塞的等。 阻塞式等就是进程/线程检测某个文件描述符上是否有事件就绪没有事件就绪就阻塞也就是将进程的PCB放到等待队列中后面的工作就由os来做了并不是进程/线程在检测而是os在做检测当检测到对应文件描述符数据就绪了就把对应进程唤醒并将PCB放到运行队列中进程/线程阻塞期间什么也做不了状态为非R。 非阻塞等就是事件没有就绪时os不会将进程/线程的PCB放到等待队列中而是继续让它执行后续代码我们经常是写个循环然后其中调用IO函数如果数据没有就绪就循环回去执行IO前面的代码然后再次执行到IO函数然后再次检测是否就绪此即轮询。也就是非阻塞IO的非轮询检测。 前面多线程间的同步和这里的IO同步不是一个东西多线程的同步背景是线程是多线程执行流在协同工作而这里的IO同步背景是IO所以网上看计算机中的同步相关的资料时一定要确定是什么同步。 这里就带各位简单的了解了五中IO模型下面来细说说。 主要讲一下阻塞、非阻塞和多路转接。信号驱动用的最少异步IO在网络库或者IO库中是有的但是很多公司都不太想用因为可能会导致IO逻辑变的很混乱但也不是不用只是用的少。 五种IO模型 张三、李四这些人对应的就是一个进程或线程鱼竿对应的就是文件描述符鱼漂对应文件描述符是否有时间就绪鱼即数据鱼所在的水域就是缓冲区。 先简单过一遍然后再写代码。 阻塞IO 阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式。 阻塞IO是最常见的IO模型 左边对应用户空间右边对应os的内核空间。 上面用户调用recv这样的系统级别的IO函数就会进入阻塞状态后面的工作就是os在做了用户啥也做不了数据拷贝好后才能做后续工作。 非阻塞IO 非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码. 这里的EWOULDBLOCK错误码不写代码感受不出来等会写代码的时候就懂了。 非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用 这里会对数据是否准备好做轮询检测如果没有准备好就先干自己的事情干一会后再检查一下如果还没好就继续做自己的事情直到某一次检测数据准备好了就会对数据进行拷贝。 信号驱动 信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作。 来看看这个信号 流程 这里涉及到了信号的相关操作如果你不懂信号可以看看我这一篇信号详解 开始的时候对SIGIO信号自定义处理定义好信号的捕捉方法sigaction当接收到SIGIO信号的时候就去执行sigaction函数sigaction函数中一定是会调用recv这样的IO函数的。 这里就是由争议的地方信号。但是进程不是在等信号而是在等数据就绪但等数据的同时又能自由的做自己的事情SIGIO到来的时候就去处理SIGIO。不要深究这些东西没有太大意义。会用就行。 多路转接 先来看流程图 IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态。 支持多路转接的OS要提供独有的接口一个接口专门负责一个等的动作。 而select就是专门负责等多个文件描述符的不会进行拷贝这个接口可以向其中添加很多货文件描述符也就是一次可以等多个文件描述符上的数据准备就绪多个文件描述符随时有可能准备就绪如果有文件描述符准备就绪select就要把准备就绪的文件反应给进程让进程调用recv等函数进行读取。 所以这里等的时候能并行一块等读取的时候只能串行一个一个来读和赵六钓鱼一样的一下子把100个鱼竿安好并行等然后有杆钓上鱼了就去哪个杆串行。 select和IO函数各司其职select这种类似的多路转接的接口只负责等当数据就绪时就让上层的IO类接口只进行拷贝此时上层的IO函数就不会出现导致进程阻塞因为上层的select已经告诉了进程底层有数据了本次调用recv这样的IO函数绝对不会阻塞理想情况下只需要拷贝。 当然这里光说的话有点难懂后面用代码演示就好理解了。 异步IO 异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据). aio_read这样的函数一般都是要先给os一端用户级的缓冲区后续就不需要再等了不用调用recv之类的函数os自动帮你把数据拷到你给的缓冲区中拷贝完后就给你通知拷贝完了。 田七进程给小王os一个桶用户级缓冲区小王去钓os办事田七办自己的事桶钓满拷贝好了了通知田七。 注意这里的通知和前面的信号驱动不一样的前面的信号驱动是要进程自己调用recv拷贝数据的而这里是os直接帮进程把数据就拷贝好了。 小结 任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少. mmap也是一个高级的IO想了解的同学请自行查资料看看。 代码演示 非阻塞IO 前面我所有的博客都是阻塞式的IO想要变成非阻塞就需要在打开文件的时候就设置打开文件的选项O_NONBLOCK。 还有创建套接字也一样可以设置 设置了之后就文件就具有了非阻塞的属性。 所以想要让文件描述符在读写的时候能进行非阻塞读写就要进行属性设置打开文件时就设定。无论是创建套接字还是普通的文件。 但是这样有点麻烦我们可以用同一的方式来进行非阻塞的设置即fcntl函数 参数fd就是文件描述符cmd就是你要选择哪种功能后面的…表示这是可变参数。 传入的cmd的值不同, 后面追加的参数也不相同。 fcntl函数有5种功能: 复制一个现有的描述符cmdF_DUPFD.获得/设置文件描述符标记(cmdF_GETFD或F_SETFD).获得/设置文件状态标记(cmdF_GETFL或F_SETFL).获得/设置异步I/O所有权(cmdF_GETOWN或F_SETOWN).获得/设置记录锁(cmdF_GETLK,F_SETLK或F_SETLKW). 这里我们要改的是文件的状态也就是阻塞还是非阻塞所以等会用的就是第三行的F_GETFL和F_SETFL。F_GETFL是获取状态F_SETFL是设置状态。 这里无论是普通文件、管道文件还是套接字文件只要是文件描述符就行fcntl都可以将对应文件状态设置成非阻塞模式。 函数返回值 下面来写写代码。 先来看一个基本的阻塞IO 上面0就是标准输入这就不细讲了最开始给的那篇博客中有。 此时运行起来就会阻塞在这里 因为一直在等待键盘对应文件的资源就绪输入了之后才等于是资源就绪了 然后再来搞一下非阻塞简单封装一下fcntl 这里就是对F_GETFL和F_SETFL的使用先用F_GETFL获取原先文件描述符对应文件的状态然后再用F_SETFL来设置文件状态就是再添加一个非阻塞的标志位像位图一样用一个 | 就行在原始的f1标志位上新增一个非阻塞的标志位不影响其他标志位。 在前面对0设置非阻塞 运行 一直在打印err。 不过打印太快了加一个sleep控制一下 这样打印的慢一点 我输入后也可以读取 但是用起来有点怪因为打印的时候是往屏幕上打的输入的时候也是要在屏幕上显示。 所以非阻塞的时候是IO函数是以出错的形式返回告知上层数据没有就绪如果数据就绪了话正常读取就行。那么我们如何甄别是真的出错了还因为数据没有准备就绪呢 出错不仅仅通过read的返回值判断的出错了系统还会设置errno所以还可以通过errno来判断是什么问题。 运行 所以如果read失败的errno是11就代表其实read没出错不过是底层数据没有就绪所以 s 0的时候可以再判断一下errno是否等于11。不过可以不用数字刚刚再介绍非阻塞的时候说了一个EWOULDBLOCK字段这个字段的值其实就是11 很多地方判断errno是否是11都是这样用的 send、recv等IO函数非阻塞的时候也会返回这个EWOULDBLOCK但是我感觉这两个一个就够了如果有懂的老铁可以在评论给我解答一下吗谢谢了。 运行 还有一个很重要的字段EINTER就是interrupt被打断了用于在等的阶段被其他东西打断了比如说进程/线程可能收到某个信号此时os就会将进程/线程唤醒去处理信号可是处理信号了就不回来了此时errno就会被设置成EINTER表示中断了所以也可以再添加一个 相当于是IO没读完就被中断了需要重新读取。所以二者都是正常情况直接continue就行。但我这里整不出来相关的场景就不演示了。 多路转接 select用的稍微多一点但是工作中也不会直接从0开始写不过这里还是要写写这个了解一下过程方便理解。 select是Linux提供的多路转接方案中的一种根据前面所讲的赵六一次可以等多个文件描述符那么select功能就有两个 帮助用户进行一次等待多个文件fd当哪些文件fd就绪了select就要通知用户对应就绪的fd有哪些 然后用户再调用recv/read这样的函数进行数据读取记住多路转接是为我们提供一个更高效的等待方案一次可以等多个文件描述符。 认识一下select接口 select介绍 展开来看 select作用就是让os注意多个文件描述符如果有文件描述符就绪了就告诉用户哪个就绪了。 挑着讲 第一个参数nfds是你让os注意的最大文件描述符 1。 比如说最大文件描述符的值为5那么nfds就是60、1、2、3、4、5正好六个 返回值就是就绪的fd的个数有3个就绪了就是3有5个就绪了就是51个就绪了就是1至少有一个fd数据就绪/空间就绪了就可以返回了。 后四个参数都是输入输出型参数先来说最后一个timeout其类型为timeval的结构体 其中tv_sec单位是以秒tv_usec单位是微秒。 这个结构体可以配合着gettimeofday来用 这个函数可以获取当前系统的时间戳传一个timeval结构体来获取参数为tz区域的时间tz给空就是本地的时间。带着C语言中的time函数演示一下 打印出来前面秒级别的和C中的time一样.后面的是微秒级别的 再说回最后一个参数timeout 这个参数可以设置等待多个参数的策略有三种 阻塞式IOtimeout设置为空。非阻塞式IOtimeout设置为{0, 0}。timeout规定时间内阻塞时间一到立马返回比如说设置为{5, 0}就是5s。5s是输入性参数的含义还有输出型参数的含义若等待时间内有fd就绪timeout就表示剩余多少时间比如说设置5s2s时有文件就绪那么time此时就是{3, 0}也就是剩余三秒。 中间三个参数 三个参数分别对应有文件的读事件写事件和异常事件类型都是fd_set是一个系统提供的类型底层是位图每一个比特位表示一个文件描述符的状态等会细讲。作为输入的时候是用户告诉内核你要帮我关心哪个/哪些fd上的那种事件。作为输出时就是内核告诉用户我所关心的fd中哪些fd上的哪类时间已经就绪了。先来说说fd_set 系统是用一个定长的数组来表示的位图。结构体是由系统提供的用户不能直接对其进行按位与、按位或等操作而是用系统提供的方法 这四个函数作用分别是CLR清除一个文件描述符ISSET判断某个文件描述符在不在位图中SET设置一个文件描述符ZERO将文件描述符清空。 看一下系统中的fd_set最多能容纳多少个文件描述符 这里乘以8是因为sizeof求的是字节数而位图是看有多少比特位的一个字节8位 . . 再来看这三个参数 三个参数在用法上都是一样的我就挑readfds来说就是读文件描述符集。 a. 作为输入型参数时是用户通知内核我的比特位中比特位的位置就表示文件描述符的值比特位的内容表示是否关心比如说 0000 1010左边是高位右边是低位低位从0开始这里就是指0 ~ 7的文件描述符这里就表示0、2、4、5、6、7号文件描述符不关心读1、3关心读。 b. 输出的时候内核告诉用户用户你让我关心的多个fd有结果了比特位的位置依旧表示文件描述符的值比特位的内容表示是否就绪比如说刚刚让os关心1号和3号如果只有三号就绪返回的就是0000 1000表示用户可以直接读取3号而不会发送阻塞。 故用户和内核都修改同一个位图结构所以这个参数用一次之后一定需要进行重新设定剩下的三个一样如果既关心读又关心写就可以同时把文件描述符加到其中虽然这样的情况很少下面就来写写代码等会肯定是写一会就写不下去了因为还没说select的一般的编写代码的模式直接将模式的话不能理解得先见见select怎么用。 简易select服务器 关于怎么写服务器不再详谈我前面的博客中有不懂的同学请自行查看。 我这里就直接用我前面封装好的套接字接口来写了两个现成的文件 打印日志 #pragma once #include cstdio #include cstring #include ctime #include cstdarg#include unistd.h#include vector// 文件名 #define _F __FILE__ // 所在行 #define _L __LINE__enum level {DEBUG, // 0NORMAL, // 1WARING, // 2ERROR, // 3FATAL // 4 };std::vectorconst char* gLevelMap {DEBUG,NORMAL,WARING,ERROR,FATAL };#define FILE_NAME ./log.txtvoid LogMessage(int level, const char* file, int line, const char* format, ...) { #ifdef NO_DEBUGif(level DEBUG) return; #endif// 固定格式char FixBuffer[512];time_t tm time(nullptr);// 日志级别 时间 哪一个文件 哪一行snprintf(FixBuffer, sizeof(FixBuffer), \%s[file-%s] [line-%d] ----------------------------------- time:: %s, gLevelMap[level], file, line, ctime(tm));// 用户自定义格式char DefBuffer[512];va_list args; // 定义一个可变参数va_start(args, format); // 用format初始化可变参数vsnprintf(DefBuffer, sizeof DefBuffer, format, args); // 将可变参数格式化打印到DefBuffer中va_end(args); // 销毁可变参数// 往显示器打printf(%s\t\n\t %s\n\n\n, FixBuffer, DefBuffer);// 往文件中打// FILE* pf fopen(FILE_NAME, a);// fprintf(pf, %s\t %s\n\n\n, FixBuffer, DefBuffer);// fclose(pf); }套接字相关 #pragma once #include LogMessage.hpp#include iostream #include string #include memory#include netinet/in.h #include arpa/inet.h #include sys/socket.h #include sys/types.h#include unistd.h// 对套接字相关的接口进行封装 class Sock { private:static const int gBackLog 20;public:// 1. 创建套接字static int Socket(){/*先AF_INET确定网络通信*/ /*这里用的是TCP所以用SOCK_STREAM*/int listenSock socket(AF_INET, SOCK_STREAM, 0);// 创建失败返回-1if(listenSock -1){LogMessage(FATAL, _F, _L, server create socket fail);exit(2);}LogMessage(DEBUG, _F, _L, server create socket success, listen sock::%d, listenSock);// 创建成功return listenSock;}// 2. bind 绑定IP和portstatic void Bind(int listenSock, uint16_t port, const std::string ip 0.0.0.0){sockaddr_in local; // 各个字段填充memset(local, 0, sizeof(local));// 若为空字符串就绑定当前主机所有IPlocal.sin_addr.s_addr inet_addr(ip.c_str());local.sin_port htons(port);local.sin_family AF_INET;/*填充好了绑定*/if(bind(listenSock, reinterpret_castsockaddr*(local), sizeof(local)) 0){LogMessage(FATAL, _F, _L, server bind IPport fail :: %d:%s, errno, strerror(errno));exit(3);}LogMessage(DEBUG, _F, _L, server bind IPport success);}// 3. listen为套接字设置监听状态static void Listen(int listenSock){if(listen(listenSock, gBackLog/*后面再详谈listen第二个参数*/) 0){LogMessage(FATAL, _F, _L, srever listen fail);exit(4);}LogMessage(NORMAL, _F, _L, server init success);}// 4.accept接收连接 输出型参数返回客户端的IP portstatic int Accept(int listenSock, std::string clientIp, uint16_t clientPort){/*客户端相关字段*/sockaddr_in clientMessage;socklen_t clientLen sizeof(clientMessage);memset(clientMessage, 0, clientLen);// 接收连接int serverSock accept(listenSock, reinterpret_castsockaddr*(clientMessage), clientLen);// 对端的IP和port信息clientIp inet_ntoa(clientMessage.sin_addr);clientPort ntohs(clientMessage.sin_port);if(serverSock 0){// 这里没连接上不能说直接退出就像张三没有揽到某个客人餐馆就不干了所以日志等级为ERRORLogMessage(ERROR, _F, _L, server accept connection fail);return -1;}else{LogMessage(NORMAL, _F, _L, server accept connection success ::[%s:%d] server sock::%d, \clientIp.c_str(), clientPort,serverSock);}return serverSock;}};然后对服务器简单封装一下 这里还剩下一步accept就可以进行通信了但是有个问题这一篇要讲高级IO如果直接accept就会导致服务器阻塞在accept处等待连接。想要高级一点那就不要阻塞用select来进行多路转接此处我们是知道除了0、1、2这三个文件描述符就只有一个_listenSock了后面文件描述符会随着不断地accept而越来越多是一个动态增加的过程而且这里的动态增长完全是通过listenSock来实现的。 前面讲TCP的时候通信前要进行三次握手而三次握手本质上也是在通信握手报文的通信获取新的连接在IO角度来看就是input事件对于连接的input所以listenSock读事件就绪对应的就是能获取新连接了对应到普通文件的读事件就绪就是能进行读取。 如果没有连接到来accept就会阻塞和前面讲的read阻塞是一样的都是等这个listenSock文件描述符所以这里就不能直接调用accept了因为调了进程就会自己去等。 所以这里也要把listenSock当成一个普通的文件描述符加入到select中去让select帮进程等select只要告诉用户listenSock就绪了就直接调用accept这样accept就不会再阻塞了所以这里要先调用select。 本篇所讲的select相对于epoll来说没有那么重要所以只演示一下读文件描述符集等后面讲epoll了再将三个文件描述符集都演示一下。 timeout 为 nullptr 调用select 这里根据select的返回值来选择该干什么事情 这样运行起来的话会先阻塞 用telnet连接 会死循环打印listenSock的读已经准备好了。 因为连接上了以后一直没有取走连接底层中listenSock对应的资源一直是就绪的就是连接已经建立完成了accept一直没有取走底层对应连接的文件描述符所以select要一直通知你赶紧调用select。 timeout 为 {0, 0} 先不调用accept把timeout改成{0,0}看看 刚运行起来就一直打印time out 因为这里timeout设置成{0, 0}就是非阻塞等待和前面的非阻塞的read一样所以一般不这么用。 timeout 为 {5, 0} 我再来把timeout改成{5, 0} 刚运行没问题 但是5s后又开始疯狂打印了 因为timeout参数是输入输出型的第一次作为输出参数会被改成{0, 0}而我刚刚故意将tv的定义放在了while外面所以就会导致后续的tv都变成{0, 0}这样就会和上面的情况一样变成了非阻塞IO所以要把tv定义放在while中或者在while中更新tv中的值 这样就不会那么快 调用accept 再来说回timeout为nullptr的情况 因为接收连接后还会有后续动作所以再给一个函数把后续动作放到一起更方便观察这里我们是知道只有一个listenSock的所以写的简单点等后面有新场景了再做修改 运行起来 一切正常。 这里我故意把通信过程留下来了请问通信的时候能直接recv/read吗 很显然是不能的我前面写的TCP服务器至少都是创建进程/线程去专门负责读取更不用谈现在单进程的情况下想直接读了我们这里想实现一个单线程既能实现监听又能实现接受连接的但当前状态下单线程直接读如果用户不发消息进程直接就阻塞了没办法向后执行也就无法处理新的连接本质原因还是我们不清楚sock上面数据什么时候到来但是如果把sock也能放到select中select就清楚什么时候到来。 所以得到新的连接后此时我应该考虑的是将新的sock托管给select让select帮我们进行监测sock上是否有新数据有了新数据select就会通知我此时再进行读取就不会再阻塞但是如何把新的sock交给select呢以现在的写法无法实现。 前面说了写一半就写不下去了下面就得讲讲select编写代码的一般流程了。 select编写代码的一般流程 再看看这个接口 第一个参数nfds随着我们获取的sock越来越多需要添加到select中的sock也就会越来越多那么就注定了每一次调用select时nfds都可能要改变所以要对nfds动态计算。 readfds/writefds/exceptfds都是输入输出参数输入和输出不一定会一样比如说传入1111输出0010那再次输入的时候还要改成1111所以我们每一次都要对rfds重新添加。 timeout也是输入输出如果设置了时间每次都要重置。 对于1、2两点而言主要原因是文件描述符可能每次都在变想要完全掌握其变化就要自己将合法的文件描述符全部保存起来用来支持更新最大fd和更新位图结构。 所以select服务器编写的时候 需要一个第三方数组用来保存所有合法的fd数组就是select能同时监听的fd个数元素个数。我这里等会就直接用原生数组来实现了也可以用vector会更方便一点但至于为什么用原生数组等会写完了再说。 上面的流程大致如下 while(1) { 遍历数组更新最大的fd用于select中第一个参数遍历数组添加所有需要关心的fd到fd_set位图中用于select第二个参数调用select进行实践检测遍历数组找到就绪的事件根据就绪的事件完成对应的动作。 } 重写 这里直接将数组开完整select最大能监听的文件描述符的个数为1024个也就是fd_set位图的位数大小前面也讲过了。用这个数组来存放合法的sock合法就是指能用的。 构造函数里面初始化一下 那么代码就要改改了 每次都打印一下其中有效的文件描述符 每次都要对数组进行操作变化的就是红框中的 EventHandler也要改 想要将sock添加到select中其实只要将sock放到数组中就行EventHandler调用完毕后会循环回去遍历后就会放到位图中。 将新的连接加入select中 测试一下刚运行 连一个 连两个 很正常。 每次进行select的时候若有文件描述符就绪会有两种情况 就绪的是listenSock就绪的是sock 这两种文件描述符是不同的情况处理方式也是不同的。listenSock是用来获取连接的sock是用来通信时读取用户数据的。 那么EventHandler处理就绪的文件描述符时要先遍历一下_fdArray找到合法的文件描述符并判断文件描述符是否在os输出的rfds中用来判断有效的文件描述符是否就绪若在还要判断是listenSock还是普通通信的sock如果是listenSock就要接收连接如果是sock就要进行读取。分两种方式那么刚刚实现的EventHandler只是实现了接收连接读取还没有实现这两个方法完全可以再实现成两个函数一个reader用来实现读取一个accepter用来实现接收连接。 把这两个函数实现给出 其实接收连接就是刚刚写的代码。 获取数据 这样本次读取的时候就不会再阻塞。 然后EventHandler改成 测试一下刚运行(这里接收到连接后的listenSock is ready忘改了你懂我就行) 连接一个 连接两个 连接三个 第一个连接通信 第二个连接通信 第三个连接通信 挨个退出 成功。 其实上面的read是有bug的因为传输层TCP是面向字节流的不能保证每次读取到的是一个完整的报文就像我前面的网络版本计算器一样应用层需要自己手动定制协议不软会出现粘包问题这里就不改了等后面讲epoll的博客再解决这个问题。 上面的select服务器是一个单进程单线程的服务器但是依旧能并发的执行任务。 如果想要引入写呢也就是writefds参数。 简单说一下思路就是再定义一个_wrArray数组用来保存写的文件描述符后续的流程和_rdArray差不多。这里就不细说了等后面讲epoll了再说。 完整代码 服务器头文件 #include Sock.hpp #include assert.h#define NUM (sizeof(fd_set) * 8) // 数组元素个数 #define FD_NONE -1 // 数组初始化的值表明没有这个fdclass SelectServer { public:SelectServer(uint16_t port 8080):_port(port){// 创建套接字_listenSock Sock::Socket();// bind绑定Sock::Bind(_listenSock, _port);// 设置监听状态Sock::Listen(_listenSock);// 对_rdArray数组初始化for(int i 0; i NUM; i){_rdArray[i] FD_NONE; // 每一个都设置成FD_NONE表明某一位没有文件描述符}// 规定第一个位为_listenSock因为_listenSock一直存在_rdArray[0] _listenSock;}void Start(){while(1){showFds(); // 每次打印一下数组中有效的fdfd_set rfds; // 读文件描述符集FD_ZERO(rfds); // 初始化// 找出最大的文件描述符int maxfd _listenSock;for(int i 0; i NUM; i){if(_rdArray[i] FD_NONE) continue;// 找出最大的文件描述符if(maxfd _rdArray[i]) maxfd _rdArray[i];// 有效的文件描述符设置到select中FD_SET(_rdArray[i], rfds);}int n select(maxfd 1, rfds, nullptr, nullptr, nullptr);// select第一个参数为最大文件描述符 1这里最大的文件描述符就是maxfd// 中间只关心读文件描述符集所以只搞了一个后面两个都是空// 最后一个是timeout先演示一下nullptr为空阻塞等待// timeval tv;// tv.tv_sec 5;// tv.tv_usec 0;// int n select(_listenSock 1, rfds, nullptr, nullptr, tv);switch(n){case 0:LogMessage(DEBUG, _F, _L, time out);break;case -1:LogMessage(ERROR, _F, _L, select err, errno::%d, strerror::, errno, strerror(errno));break;default:LogMessage(NORMAL, _F, _L, fd is ready);EventHandler(rfds);break;}}}void EventHandler(fd_set rfds){for(int i 0; i NUM; i){// 是否有效if(_rdArray[i] FD_NONE) continue;// 是否就绪if(FD_ISSET(_rdArray[i], rfds)){if(i 0)// 是listenSock{Accepter();}else // 是通信的sock{Reader(i);}}}// if(FD_ISSET(_listenSock, rfds))// {// // 客户端IP 端口// std::string clientIP;// uint16_t clientPort;// int sock Sock::Accept(_listenSock, clientIP, clientPort);// assert(sock 0);// LogMessage(NORMAL, _F, _L, get link --client[%s:%d], clientIP.c_str(), clientPort);// // 通信过程...// int pos 1;// for(; pos NUM; pos)// {// 找FD_NONE// if(_rdArray[pos] FD_NONE) break;// }// if(pos NUM)// {// 没找到// std::cout 文件描述符集已满, 无法继续接收连接 std::endl;// close(sock);// return;// }// else// {// 找到了// std::cout new fd:: sock std::endl;// _rdArray[pos] sock;// }// }}void Accepter(){// 客户端IP 端口std::string clientIP;uint16_t clientPort;int sock Sock::Accept(_listenSock, clientIP, clientPort);assert(sock 0);LogMessage(NORMAL, _F, _L, get link --client[%s:%d], clientIP.c_str(), clientPort);// 通信过程...int pos 1;for(; pos NUM; pos){// 找FD_NONEif(_rdArray[pos] FD_NONE) break;}if(pos NUM){// 没找到std::cout 文件描述符集已满, 无法继续接收连接 std::endl;close(sock);return;}else{// 找到了std::cout new fd:: sock std::endl;_rdArray[pos] sock;}}void Reader(int pos){char buff[128] {0};ssize_t res read(_rdArray[pos], buff, sizeof(buff) - 1);if(res 0){// 读取到数据buff[res - 1] 0;printf(get client[%d] message # %s\n, _rdArray[pos], buff);}else if(res 0){// 对端关闭连接printf(client[%d] closed, me too\n, _rdArray[pos]);close(_rdArray[pos]);// 记得要把数组中对应位置置为FD_NONE_rdArray[pos] FD_NONE;}else{// read出错printf(read err, close client[%d]\n, _rdArray[pos]);std::cout read err :: errno strerror(errno) std::endl; close(_rdArray[pos]);// 记得要把数组中对应位置置为FD_NONE_rdArray[pos] FD_NONE;}}void showFds(){std::cout fds ::;for(auto e : _rdArray){if(e FD_NONE) continue;std::cout e ;}std::cout std::endl;}~SelectServer(){if(_listenSock 0){close(_listenSock);}}private:uint16_t _port;int _listenSock;int _rdArray[NUM]; };主函数 #include SelectServer.hpp #include memoryint main() {std::unique_ptrSelectServer pss(new SelectServer);pss-Start();return 0; }select优缺点 优点 效率高相比于前面多线程多进程的服务器select服务器比多进/线程服务器效率会更高。select()函数可以同时等待多个文件描述符而不需要建立多个线程、进程就可以实现一对多的通信。但是select放在整个多路转接中的效率还是一般的好的都在后面讲。应用场景有大量的连接但是只有少量是活跃的。前面的多进程/多线程服务器有一个连接就要维护一个进程/线程的空间对于资源的消耗会很大。但这里select不需要维护这些空间只有一个线程。 其实任何一个多路转接都具备上述两个优点。 缺点 为了维护第三方数组select服务器会充满大量的遍历os底层帮我们关心fd的时候也要遍历。每一次都要对select参数进行重新设定能够同时管理的fd的个数是有上限的一千多个有点少中小型应用还好用户量一大就扛不住。因为几乎每一个参数都是输入输出型select一定会频繁的进行用户到内核内核到用户的参数数据拷贝。编写代码比较复杂主要还是前面4个缺点导致的。 poll可以解决这里的部分缺点。下面就来说说poll。 poll poll也是多路转接的方案也是只负责IO中的等。 poll将输入输出参数做了分离不用再对参数重新设定了。而且解决了同时管理fd个数上限的问题。 三个参数。fds是看成数组nfds就是数组中元素的个数。等会细说pollfd结构体。 timeout是一个毫秒级别的时间单位比如说你传一个1000就是未就绪1s后超时如果传0就是非阻塞如果传-1就是阻塞。 poll返回值大于零是几就是几个文件描述符就绪了。 等于零超时。 小于零poll失败代码写错了比如根本不存在5号文件描述符但是你把文件描述符添加到了第一个参数数组中。 poll也是负责两个大问题 用户告诉内核你要帮我关心哪些fd的哪些事件内核告诉用户哪些事件已经就绪了。 第一个参数fds就能解决这两个问题。 这个数组中元素类型为pollfd 三个成员 fd就是文件描述符不管是用户到内核还是内核到用户都不会修改fd。 events就是你要让os关心的fd的什么事件是一个输入型参数。 revents算是一个输出型参数表明你要让os关心的fd中的事件是否就绪。 这样每次调用poll的时候就不会像select那样重新初始化了。 select中有读、写、异常这样的事件events如何表示这类事件呢 想一想文件操作open当我们想要打开文件的标记位就是用或运算比如O_CREATO_WRONLYO_RDONLY这样的标记位。同理poll用的也是这样的宏来表示某种特定事件 我已经把常用的标出来了。in、out就是读写err就是错误。剩下的都是一些属于异常范畴的因为event类型为short只有16个位所以最多只能有16中标记。上面这些每一个都是宏用或即可添加选项。 看看POLLPRI高优先级数据可读前面我讲TCP报头的时候其中有一个urg标志位还有一个紧急指针在这里就可配合POLLPRI来实现。 来一个示例 #include poll.h #include unistd.h #include stdio.h int main() {// 这里就监测一下标准输入就不搞那么多文件描述符了struct pollfd poll_fd;poll_fd.fd 0;poll_fd.events POLLIN; // 标准输入的读事件for (;;){// 每隔一秒poll一次int ret poll(poll_fd, 1, 1000);if (ret 0){ // poll错误perror(poll);continue;}if (ret 0){ // 超时printf(poll timeout\n);continue;}// 事件准备就绪if (poll_fd.revents POLLIN){// 判断一下是不是读事件就绪了char buf[1024] {0};read(0, buf, sizeof(buf) - 1);printf(stdin:%s, buf);}} }运行 下面来写写poll服务器其实和select还是有点像的写起来比select简单一点这里用一下select的大致框架 其中一些函数参数如果用到了再添加。 首先poll要有一个数组元素类型为pollfd 构造函数初始化 打印有效文件描述符 启动 EventHandler 接收连接 读取数据 测试连一个 连两个 连三个 发消息 挨个退 正常。 完整代码 服务器封装的头文件 #include Sock.hpp #include assert.h #include poll.h#define FD_NONE -1 // 每个fd的初始化的值 #define NFDS 100 // 数组元素个数class PollServer { public:PollServer(uint16_t port 8080): _port(port), _nfds(NFDS){// 创建套接字_listenSock Sock::Socket();// bind绑定Sock::Bind(_listenSock, _port);// 设置监听状态Sock::Listen(_listenSock);// 开辟空间_fds new pollfd[_nfds];for(int i 0; i _nfds; i){ // 初始化_fds[i].fd FD_NONE;_fds[i].events _fds[i].revents 0;}// 第零个位置给成listenSock_fds[0].fd _listenSock;_fds[0].events POLLIN; // 关系listenSock的读}void showFds(){std::cout fds:: ;for(int i 0; i _nfds; i){if(_fds[i].fd FD_NONE) continue;std::cout _fds[i].fd ;}std::cout std::endl;}void Start(){while(1){showFds();// 1s间隔int res poll(_fds, _nfds, -1);if(res 0){ // 有文件描述符就绪std::cout some fds ready std::endl;EventHandler();}else if(res 0){ // 超时std::cout time out std::endl;}else{ // poll出错printf(poll err, errno[%d], strerror::%s, errno, strerror(errno));}}}void EventHandler(){for(int i 0; i _nfds; i){// 第i位不是有效文件描述符if(_fds[i].fd FD_NONE) continue;// 读事件时候就绪if(_fds[i].revents POLLIN){if(i 0)Accepter();elseReader(i); }}}// 接收连接void Accepter(){// 获取连接std::string clientIP;uint16_t clientPort;int sock Sock::Accept(_listenSock, clientIP, clientPort);// 找空位置放sockint pos 1;for(; pos _nfds; pos){if(_fds[pos].fd FD_NONE) break;}if(pos _nfds){ // 没找到不过这里也可以选择对_fds进行扩容但是我懒得搞了你要是有兴趣可以自己搞一下std::cout _nfds is full std::endl;close(sock);}else{ // 找到了std::cout get a new link :: sock std::endl;_fds[pos].fd sock;_fds[pos].events POLLIN;}}// 读取数据void Reader(int pos){char buff[128];int res read(_fds[pos].fd, buff, sizeof(buff) - 1);if(res 0){ // 读取到数据buff[res] 0;std::cout client # buff std::endl;}else if(res 0){ // 对端关闭连接std::cout clinet closed std::endl;// 记得后续工作close(_fds[pos].fd);_fds[pos].fd FD_NONE;_fds[pos].events _fds[pos].events 0;}else{ // 读取出错printf(read err, errno[%d], strerror::%s, errno, strerror(errno));// 记得后续工作close(_fds[pos].fd);_fds[pos].fd FD_NONE;_fds[pos].events _fds[pos].events 0;}}~PollServer(){if(_listenSock 0) close(_listenSock);if(_fds ! nullptr) delete[] _fds;}private:uint16_t _port;int _listenSock;pollfd *_fds;int _nfds; };主函数 #include PollServer.hpp #include memoryint main() {std::unique_ptrPollServer pps(new PollServer);pps-Start();return 0; }poll的优缺点 优点 效率高更select一样 适用场景有大量的连接但是只有少量连接是活跃的节省资源 输入输出参数是分离的不需要进行大量的重置。 poll参数nfds可以自行设定没有上限除非内存不够。 缺点 poll依旧需要不少的遍历在用户层监测事件就绪与内核监测fd就绪都是一样的当只有几个就绪时就要将整个数组遍历一遍效率比较低连接越多越低 poll需要用户和内核进行拷贝更多的是需要内核到用户的拷贝少不了的。 poll代码比select容易但还是有点复杂 最需要关心的缺点就是第一点用户还是要维护数组。 为了解决上述问题epoll出现了强化版本的poll要比poll强得多关于epoll下一篇再详细说。 本篇就先讲到这里。下一篇详细讲解多路转接中最重要的epoll。 到此结束。。。
http://www.w-s-a.com/news/113760/

相关文章:

  • 建设通同类网站网站设计公司种类
  • 台州专业做网站网站可以个人做吗
  • 个人logo在线生成免费乐陵德州seo公司
  • 网站回答问题app怎么做专业定制网红柴火灶
  • 网站做的最好的公司行业网址大全
  • 内网怎么做网站服务器seo统计
  • 丽水市企业网站建设 微信营销 影视拍摄计算机专业吃香吗
  • 龙岗做网站公司哪家好找到做网站的公司
  • 网站图片alt属性wordpress 自定义栏目 调用
  • 怎样建网站最快广州网站建设工程
  • iis7 网站404错误信息12306网站很难做吗
  • 网站建设600元包公司设计图片大全
  • 网站建设费用怎么做分录做校园网站代码
  • 网站改版做重定向福州网站建设思企
  • 网站建设全流程企业形象网站开发业务范畴
  • wordpress无法查看站点西安优秀高端网站建设服务商
  • 固始网站制作熟悉免费的网络营销方式
  • 做网站到a5卖站赚钱搜索引擎优化代理
  • 沈阳网站建设包括win10优化
  • 做百度手机网站点击软网站seo优化徐州百度网络
  • 徐州专业网站制作标志设计作业
  • 自己可以做网站空间吗海天建设集团有限公司网站
  • 教学督导网站建设报告aspcms网站图片不显示
  • 网站开发公司成本是什么门户网站宣传方案
  • 上海 企业网站建设网站怎么开通微信支付
  • 饮料网站建设wordpress主题猫
  • 网站建设需要编码不有没有专门的网站做品牌授权的
  • 做爰在线网站免费空间列表
  • 网站外链建设工作总结郑州网站建设扌汉狮网络
  • 建设企业网站的需要多长时间网站使用说明书模板