线程池版本的 TCP 服务器

线程池版本的 TCP 服务器_第1张图片

文章目录

  • 1. 引入线程池
  • 2. 构建任务
  • 3. 创建其它任务
  • 4. 守护进程
    • 4.1 实现守护进程
    • 4.2 改进日志
    • 4.3 nohup

1. 引入线程池

前面我们完成了简易的TCP服务器,但是创建进程还是创建线程都会花费一定的时间,我们可不可以在服务前就把线程给创建好呢?那么我们就需要引用线程池了。

如果不懂线程池的看一下这个文章:线程池
我们把之前写的线程池模板给拿过来。
线程池版本的 TCP 服务器_第2张图片
我们把单例模式的线程池和创建任务和RAII型的智能锁添加进TCP这个项目里。
线程池版本的 TCP 服务器_第3张图片
那么在构造的时候也需要修改。
线程池版本的 TCP 服务器_第4张图片
那么我们在服务端获取连接前就把线程池加载到内存中。
线程池版本的 TCP 服务器_第5张图片
然后在线程池添加一个获取线程个数的函数:
线程池版本的 TCP 服务器_第6张图片
然后我们启动线程池:
线程池版本的 TCP 服务器_第7张图片
那么线程池启动之后,我们就可以去构建任务,加载到线程池里了。

2. 构建任务

那么我们该如何去构建任务呢?
线程池版本的 TCP 服务器_第8张图片
这里的意思是:当前如果有任务了,就可以用参数来构建任务,然后紧跟着去执行对应的回调方法。
线程池版本的 TCP 服务器_第9张图片
这是构造和析构函数。
线程池版本的 TCP 服务器_第10张图片
这里使用仿函数去调用。
线程池版本的 TCP 服务器_第11张图片
在线程池中的线程再取出任务直接以仿函数的方式去执行。
线程池版本的 TCP 服务器_第12张图片
这里构建任务时的第4个参数用了C++11里面的bind函数。不懂的可以看一下这篇文章:C++11新特性

我们将transService进行绑定后传给Task。

下面我们再次进行测试:
线程池版本的 TCP 服务器_第13张图片

3. 创建其它任务

线程池版本的 TCP 服务器_第14张图片
下面我们需要用到这个函数:
线程池版本的 TCP 服务器_第15张图片
线程池版本的 TCP 服务器_第16张图片
也就是说,这个函数内部会帮我们创建命名管道,然后fork,让子进程调用exec把这个command命令解析并执行,然后通过管道传给父进程。父进程再通过文件描述符让我们读到
线程池版本的 TCP 服务器_第17张图片
运行结果如下:
线程池版本的 TCP 服务器_第18张图片
这样服务器会帮我们去执行指令。

4. 守护进程

线程池版本的 TCP 服务器_第19张图片
像以D结尾的这样命名的叫做服务器。ppid为1,所以是孤儿进程。COMMAND:叫做启动这个进程的命令。TIME:进程启动的时长。UID:是谁启动的。TPGID:进程组和终端的关系,-1代表无关,有数字的代表有关。TTY:代表的是哪个终端。PGID:代表的是哪个进程组。SID:叫做会话ID。
线程池版本的 TCP 服务器_第20张图片
可以看到PGID都是一样的,属于同一进程组,而通常第一个是进程组组长。

那么什么是会话ID呢
线程池版本的 TCP 服务器_第21张图片
当客户端连接服务器时,会创建一个会话,会话里有一个前台进程(在任何时刻,有且只有一个前台进程),可以有0个或多个后台进程。前台进程的作用是:给我们加载bash,然后bash构建一个会话,自己是会话前台进程组组长,后面如果我们自己在新启进程或者启动进程组就属于bash自己的会话。
线程池版本的 TCP 服务器_第22张图片
我们可以看到,我们把它设置前台进程了,但是不能使用命令了。原因是:前台进程只能有一个,bash就自己成后台进程了。

以前,我们说在命令行中启动一个进程,现在是在会话中启动一个进程组(可以一个),来完成某种任务。

所有会话内的进程fork创建子进程,一般而言依旧属于当前会话。如果我们不想让它属于当前会话,就让它自成进程组,自成新会话(守护进程或精灵进程)

一般以服务器的方式工作,对外提供服务的服务器,都是以守护进程(精灵进程)的方式在服务器中工作,一旦启动之后,除非用户主动关闭,否则会一直运行

像我们前面自己写的服务端是可以被Ctrl+c的,所以我们需要把它改成守护进程。

4.1 实现守护进程

要改成守护进程必须要调用一个函数:setsid(),将调用进程设置成为独立的会话
线程池版本的 TCP 服务器_第23张图片
但是已经是进程组的组长,不能调用setsid函数。

如何不成为进程组组长呢
我们可以成为进程组内的第二个进程。常规做法:fork()子进程,让父进程退出,让子进程调用setsid()

守护进程的其它选做:
1.在管道中,写端一直写,读端关闭,写端会被终止,原因是被信号SIGPIPE终止。那么在服务端写给客户端,如果客户端关闭,服务器也会收到信号SIGPIPE。
2.用chdir来更改进程的工作路径。
3.因为服务器是写入网络,从网络读。不需要键盘和显示器,所以标准输入和标准输出和我们没有关系,那么我们可以关闭它。但是很少有人这么去做。一般是打开/dev/null,并且对0,1,2进行重定向到/dev/null

在这里插入图片描述
在Linux中,有一个垃圾回收桶,往里面去读写,都会被丢弃。
在这里插入图片描述
下面我们可以写一个守护进程的hpp:
线程池版本的 TCP 服务器_第24张图片
然后我们在服务器中启动起来:
线程池版本的 TCP 服务器_第25张图片
线程池版本的 TCP 服务器_第26张图片
一般我们在这里加上一个d来代表它是守护进程。
在这里插入图片描述
此时就是守护进程了,如果我们想退出它,只能发送信号。
线程池版本的 TCP 服务器_第27张图片

4.2 改进日志

线程池版本的 TCP 服务器_第28张图片
我们定义一个Log的类,我们调用它的话会创建一个文件,然后把标准输出和标准错误都重定向到这个文件中。
线程池版本的 TCP 服务器_第29张图片
我们要把数据刷新到OS,不然可能看不到结果。
线程池版本的 TCP 服务器_第30张图片
并且,如果我们想让服务器安全退出,我们可以这样做:
线程池版本的 TCP 服务器_第31张图片
线程池版本的 TCP 服务器_第32张图片
将quit_先设置成false。
线程池版本的 TCP 服务器_第33张图片
线程池版本的 TCP 服务器_第34张图片
调用这个函数就把quit_设置成true。
线程池版本的 TCP 服务器_第35张图片
当服务器收到3号信号时,就把quit_设置成true。

测试结果如下
线程池版本的 TCP 服务器_第36张图片
可以看到日志信息都打印到了文件中。
线程池版本的 TCP 服务器_第37张图片
也可以进行安全退出服务器。

4.3 nohup

线程池版本的 TCP 服务器_第38张图片
这里我们简单的写了一个循环打印hello。编译一下:
线程池版本的 TCP 服务器_第39张图片
如果我们想让这个a.out变成守护进程,我们可以使用nohup命令:
在这里插入图片描述
意思是不要挂起。
线程池版本的 TCP 服务器_第40张图片
并且会自动默认形成日志,nohup.out。
在这里插入图片描述
它的会话ID是17730,依然在本会话内部。但是和守护进程是一样的性质了。它不会收到登录和注销的影响了。我们可以重新登录一下:
在这里插入图片描述
可以看到已经是独立会话了。

你可能感兴趣的:(Linux,tcp/ip,服务器,linux)