您现在的位置是:首页 >学无止境 >【Hello Network】网络编程套接字(三)网站首页学无止境
【Hello Network】网络编程套接字(三)
我们在前面的网络编程套接字(二)中写出了一个单执行流的服务器
我们再来回顾下它的运行
我们首先启动服务器 之后启动客户端1 最后启动客户端2
我们发现启动客户端1之后向服务器发送数据服务器很快的就回显了一个数据并且打印了得到一个新连接
可是在客户端2连接的时候却没有发生任何情况
当我们的客户端1退出的时候 服务器接受到了客户端2的连接并且回显了数据
单执行流服务器
这是因为我们的服务器是单执行流的 所以在同一时间只能有一个客户端接受服务
当服务端调用accept函数获取到连接后就给该客户端提供服务 但在服务端提供服务期间可能会有其他客户端发起连接请求 但由于当前服务器是单执行流的 只能服务完当前客户端后才能继续服务下一个客户端
客户端为什么会显示连接成功
服务器是处于监听状态的 在我们的客户端2发送连接请求的时候实际上已经被监听到了 只不过服务端没有调用accept函数将该连接获取上来
实际在底层会为我们维护一个连接队列 服务端没有accept的新连接就会放到这个连接队列当中 而这个连接队列的最大长度就是通过listen函数的第二个参数来指定的 因此服务端虽然没有获取第二个客户端发来的连接请求 但是在第二个客户端那里显示是连接成功的
如何解决?
单执行流的服务器一次只能给一个客户端提供服务 此时服务器的资源并没有得到充分利用 因此服务器一般是不会写成单执行流的 要解决这个问题就需要将服务器改为多执行流的 此时就要引入多进程或多线程
多进程版的TCP网络程序
我们将之前的单执行流服务器改为多进程服务器
当服务端调用accept函数获取到新连接后不是由当前执行流为该连接提供服务 而是当前执行流调用fork函数创建子进程 然后让子进程为父进程获取到的连接提供服务
由于父子进程是两个不同的执行流 当父进程调用fork创建出子进程后 父进程就可以继续从监听套接字当中获取新连接 而不用关心获取上来的连接是否服务完毕
子进程继承父进程的文件描述符表
需要注意的是 文件描述符表是隶属于一个进程的 子进程创建后会继承父进程的文件描述符表
比如父进程打开了一个文件 该文件对应的文件描述符是3 此时父进程创建的子进程的3号文件描述符也会指向这个打开的文件 而如果子进程再创建一个子进程 那么子进程创建的子进程的3号文件描述符也同样会指向这个打开的文件
但是当父进程创建出子进程之后 父子进程就会保持独立性了 此时父进程文件描述符表的变化不会影响子进程的文件描述符表
在我们之前学习的匿名管道通信时 我们就是使用的这个原理
父进程首先使用pipe函数得到两个文件描述符 一个是文件读端一个是文件的写端 此时父进程创建的子进程会继承这两个文件描述符
之后父子进程一个关闭管道的读端 另一个关闭管道的写端 这时父子进程文件描述符表的变化是不会相互影响的 此后父子进程就可以通过这个管道进行单向通信了
对于套接字文件也是一样的 父进程创建的子进程也会继承父进程的套接字文件 此时子进程就能够对特定的套接字文件进行读写操作 进而完成对对应客户端的服务
等待子进程问题
当父进程创建出子进程后 父进程是需要等待子进程退出的 否则子进程会变成僵尸进程 进而造成内存泄漏
因此服务端创建子进程后需要调用wait或waitpid函数对子进程进行等待
此时我们就有两种等待方式 阻塞式等待和非阻塞式等待:
- 如果服务端采用阻塞的方式等待子进程 那么服务端还是需要等待服务完当前客户端 才能继续获取下一个连接请求此时服务端仍然是以一种串行的方式为客户端提供服务
- 如果服务端采用非阻塞的方式等待子进程 虽然在子进程为客户端提供服务期间服务端可以继续获取新连接 但此时服务端就需要将所有子进程的PID保存下来 并且需要不断花费时间检测子进程是否退出
总之 服务端要等待子进程退出 无论采用阻塞式等待还是非阻塞式等待 都不尽人意 此时我们可以考虑让服务端不等待子进程退出
不等待子进程退出的方式
让父进程不等待子进程退出 常见的方式有两种:
- 捕捉SIGCHLD信号 将其处理动作设置为忽略
- 让父进程创建子进程 子进程再创建孙子进程 最后让孙子进程为客户端提供服务
捕捉SIGCHLD信号
实际当子进程退出时会给父进程发送SIGCHLD信号 如果父进程将SIGCHLD信号进行捕捉 并将该信号的处理动作设置为忽略 此时父进程就只需专心处理自己的工作 不必关心子进程了
下面是我们的处理代码 其中比较核心的代码是这一行
signal(SIGCHLD, SIG_IGN);
class TcpServer
{
public:
void Start()
{
signal(SIGCHLD, SIG_IGN); //忽略SIGCHLD信号
for (;;){
//获取连接
struct sockaddr_in peer;
memset(&peer, '