(1)第一个版本就是解决了六个模块的问题 1、socket网络编程模块:就是为了让服务器可以与客户端进行通信。也就是包含:socket、setsockopt、bind、listen、accept。 2、epoll模块:作用就是为了监听的以及将就绪的文件描述符取出来。就是包含了三个函数:epoll_create、epoll_ctl、epoll_wait; 3、队列模块:让其存放文件描述符。就会包含:入队列的函数、出队列的函数、获取队列的大小; 4、子线程模块:worker模块。从队列中取出文件描述符,然后处理业务逻辑,将处理好之后的数据发给客户端; 5、发送模块:工作线程(子线程,worker线程)将发送数据的过程封装成函数,也就是一个模块。send_file 6、线程池模块:负责开辟多少个线程,每个线程的id是什么,互斥锁、条件变量、队列的管理 这个版本解决了从0到1的设计,六个模块的设计思想是比较重要的,然后可以达到服务器给客户端发送hello这样的字符串 证明服务器与客户端是可以进行正常通信的 (2)第二个版本将发送大文件 问题一:传统的read/recv、write/send的效率比较低,会有多次在用户态与内核态之间的拷贝; 之前也学过尽量减少在内核态与用户态之间的拷贝函数mmap; 现在有一种更高效的方式sendfile,可以在内核之间传输数据,从磁盘读到内核缓冲区、然后直接发送给网卡,不经过socket缓冲区,这是新的sendfile 函数的功能,对于大文件而言,这种方式的性能更好; 问题二:TCP是流式协议,而流式协议是没有边界的,所以数据有可能会发生粘包或者半包,也就是发送的数据不知道一次接收多少;所以需要自定义私有协议 思路是:定义一个固定大小,填充要发送的内容长度,然后后面跟要发送的内容,这样对端在接收的时候,可以根据固定大小读到其中的值,然后就知道发送的 内容了,比如:int是4字节,在其中存放4字节的大小数据,那么对端就读int大小,获取其中的值,就知道要读多少字节的数据了; 对于上述两个问题,每次发送数据的时候,小数据使用普通的send发送,大文件采用sendfile发送; 对于客户端而言,每次接收的大小可以精准的控制,但是每次一定要等到该大小才行,所以需要将recv的最后一个标志设置为,MSG_WAITALL; (3)目前的情况是服务器发送数据的时候,采用ctrl + C的方式断开的,但是有个问题,如何知道数据已经传输完毕了呢?如果没有传输完毕, 是不是有可能导致传输中断呢? 第三个版本采用的是被动断开的方式 现在在终端输入Ctrl + C产生的信号会发送给前台进程组,也就是我们的程序代码,会导致整体进程的结束,现在我们不想让我们的工作线程直接 受到信号的影响,那么就可以让其不在当前进程组中,而每个进程组至少会有一个进程,所以至少需要两个进程,可以使用fork函数创建子进程, 但是fork创建的父子进程在同一个进程组中,所以需要让子进程脱离本进程组,所以可以使用setpgid函数(如何传参上课已经讲过),然后由于 一个会话中最多只有一个前台进程组和多个多个后台进程组,那么我们脱离前台进程组的子进程自动就变为后台进程组了;下面是几个重要概念: (a)进程组是进程的集合,也就是一个进程组会包含多个进程; (b)会话是进程组的集合,也就是一个进程组会包含多个进程组;一个会话会包含最多一个前台进程组和多个后台进程组。 (c)从终端输入的信号,会将信号发送到前台进程组所有进程; (d)通过fork创建的父子进程会处于同一个进程组,可以使用setpgid让进程组中的进程脱离原来的进程组; 这样在终端输入的信号就只会对前台进程组产生影响,不会对后台进程组产生影响,我们通过ctrl + c以及ps命令可以查看,父进程结束了 但是子进程还是存活在,然后由1号进程收养(孤儿进程)。 接下来,如果我们在终端输入ctrl + C,说明可以将所有的线程退出来,那么就需要在前台进程组收到信号后通知子进程,所以涉及到父子进程 的通信以及更改2号信号的信号处理函数,而父子进程通信可以使用匿名管道(可以思考父子进程通信的其他方式),更改信号的信号处理函数 可以使用signal函数。这样父进程在收到信号后,可以使用write写管道告知子进程,可以让子进程中的main线程让工作线程退出来,这里有几种 方式,主动退出以及被动退出,咱们版本三采用的被动退出,版本四采用主动退出,这里将被动退出,也就是使用pthread_cancel函数;如果子进 程中的工作线程在做完任务后会在pthread_cond_wait上睡眠,而这个函数正好是具备取消点的函数,正好可以让子线程退出,但是线程退出的时候 是带着互斥锁退出的,所以需要清理锁资源,所以这里使用了线程清理栈函数pthread_cleanup_push以及pthread_cleanup_pop函数(注意这两个 函数的特点与使用哈),将锁释放掉即可 (4)第四个版本采用的是主动退出的方式 父进程收到信号后,通过管道告知子进程中的main线程,让main线程将标志位设置为1,然后唤醒所有可能在条件变量上睡眠的线程,这样工作线程 在做完任务后,如果发现标志位为1,那么就释放锁,然后主动退出,这种方式更合理。 这就是线程池的四个版本,使用了之前所学的所有知识点,所以是比较麻烦的
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。