(二)通过fork编写一个简单的并发服务器
概述
那么最简单的服务端并发处理客户端请求就是,父进程用监听套接字监听,当有连接过来时那么监听套接字就变成了已连接套接字(源和目的的IP和端口都包含了),这时候就可以和客户端通信,但此时其他客户端无法连接进来,因为这个套接字被占用,所以就会产生一个子进程来处理和客户端的通信,也就是这个连接套接字由子进程处理,而父进程继续用监听套接字的形式来等待下一个连接请求。
代码段
服务器程序
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: rex.cheny
- # E-mail: rex.cheny@outlook.com
- import socket
- import os, time, sys
- def echoStr(connFd):
- print("新连接:", connFd.getpeername())
- while True:
- bytesData = connFd.recv(1024)
- data = bytesData.decode(encoding="utf-8")
- print("收到客户端消息:", data)
- if data == "Bye":
- return
- else:
- time.sleep(1)
- connFd.send(data.encode(encoding="utf-8"))
- def main():
- sockFd = socket.socket()
- sockFd.bind(("", 5555))
- sockFd.listen(5)
- print("等待客户端连接......")
- while True:
- connFd, remAddr = sockFd.accept()
- try:
- pid = os.fork()
- if pid == 0:
- # 说明当前运行在子进程中
- sockFd.close() # 关闭监听套接字
- echoStr(connFd) # 执行回显函数
- # connFd.close() # 关闭连接套接字,这里是否要显示的关闭和客户端的连接套接字与个人编程风格有关,因为客户端发送完数据后就主动调用了close()
- exit(0) # 子进程退出
- else:
- """
- 关闭连接套接字,这时候并不会关闭服务器与客户端的TCP连接,因为connFd这个套接字的会被子进程所使用,所以该套接字的引用
- 计数器为1,只有为0时才会被关闭。
- """
- connFd.close()
- except Exception as err:
- print(err)
- if __name__ == '__main__':
- main()
客户端程序
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: rex.cheny
- # E-mail: rex.cheny@outlook.com
- import socket
- def echoStr(sockFd, data):
- sockFd.send(data)
- bytesData = sockFd.recv(1024)
- data = bytesData.decode(encoding="utf-8")
- print(data)
- def main():
- sockFd = socket.socket()
- sockFd.connect(("127.0.0.1", 5555))
- for i in range(1, 11):
- data = "第:" + str(i) + " 条消息。"
- echoStr(sockFd, data.encode(encoding="utf-8"))
- echoStr(sockFd, "Bye".encode(encoding="utf-8"))
- sockFd.close()
- if __name__ == '__main__':
- main()
结果演示
这时候就实现了多个客户端并发连接服务器端。这里只是演示了一种最简单也是最原始的一种方式,因为fork一个进程系统开销很大所以虽然是并发但是不适用大规模并发的情况下。无论是多进程或者是进程池或者是多线程并发其实效率都不高当然这是相对来讲,你想一想1000个请求难道要启动1000个线程或者进程吗?显然不现实。在面对大量并发请求的时候就要用到多路复用或者是异步,这个后面章节会讲。不过需要先明白多路复用和并发处理其实是两个概念,多路复用主要解决不阻塞问题而并发是同时处理问题,我上面这种FORK的形式虽然是并发的,但是单一进程内其实还是阻塞的,无论是对服务器进程还是客户端进程它们内部都是阻塞的。
我们这次通过后台运行查看一下网络和进程状态。
- # 这样来运行服务器程序
- python3 ./server.py >> ./log.txt &
当有客户端连接进来是这样的,子进程PID是65686而它的PID也就是父进程ID是65623;网络监控显示有一个TCP连接成功建立
当客户端执行完毕之后是这样的,服务器显示的连接套接字时TIME_WAIT状态,而之前fork的子进程变成了僵尸进程。TIME_WAIT过一会儿就消失掉,但是这个僵尸进程会一直存在,直到父进程退出。
僵尸进程
上面最后一个图中的 “Z” 就表示僵尸进程。
接下来我们改进一下服务器程序来解决一下僵尸进程问题,客户端不变。
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: rex.cheny
- # E-mail: rex.cheny@outlook.com
- import socket
- import os, time, sys
- import signal
- def echoStr(connFd):
- print("新连接:", connFd.getpeername())
- while True:
- bytesData = connFd.recv(1024)
- data = bytesData.decode(encoding="utf-8")
- print("收到客户端消息:", data)
- if data == "Bye":
- return
- else:
- time.sleep(1)
- connFd.send(data.encode(encoding="utf-8"))
- def sigChld(signum, frame):
- """
- 僵尸进程处理函数这两个参数是必须的
- :param signum: 发生的信号
- :param frame: 发生信号的时候的函数调用栈
- :return:
- """
- """
- waitpid() 用于清理僵尸进程,返回值为已终止的子进程ID和进程终止状态
- pid 进程号,如果是-1表示等待第一个终止的子进程
- os.WNOHANG 参数,默认为0也就是os.WNOHANG,表示在内核没有通知有已终止的子进程时不阻塞
- """
- pid, status = os.waitpid(-1, os.WNOHANG)
- while pid > 0:
- print("child ", pid, " is terminated")
- return
- def main():
- sockFd = socket.socket()
- sockFd.bind(("", 5555))
- sockFd.listen(5)
- """
- 调用signal函数,第一个参数是信号名,第二个是信号处理函数。这个signal函数就是对系统调用signal函数的简单封装。
- 这个函数必须在fork第一个子进程之前做且只能做一次。
- """
- signal.signal(signal.SIGCHLD, sigChld)
- print("等待客户端连接......")
- while True:
- connFd, remAddr = sockFd.accept()
- try:
- pid = os.fork()
- if pid == 0:
- # 说明当前运行在子进程中
- sockFd.close() # 关闭监听套接字
- echoStr(connFd) # 执行回显函数
- # connFd.close() # 关闭连接套接字,这里是否要显示的关闭和客户端的连接套接字与个人编程风格有关,因为客户端发送完数据后就主动调用了close()
- exit(0) # 子进程退出
- else:
- """
- 关闭连接套接字,这时候并不会关闭服务器与客户端的TCP连接,因为connFd这个套接字的会被子进程所使用,所以该套接字的引用
- 计数器为1,只有为0时才会被关闭。
- """
- connFd.close()
- except Exception as err:
- print(err)
- if __name__ == '__main__':
- main()
SIGCHLD信号的含义是一个进程终止或者停止,将该信号发送给父进程,父进程的wait或者waitpid可以捕捉这个信号。
这时候可以看到通信完毕后已经不存在僵尸进程了。
僵尸进程会被init或者systemd进程接管,而他们会使用wait来清理这些僵尸进程,所以当我们使用fork子进程的时候都要wait他们防止他们变成僵尸进程。wait和waitpid都可以清理僵尸进程,但是略有不同:
- 调用wait的时候如果没有已经终止的子进程那么调用wait的进程将会被阻塞在这里直到出现一个被终止的子进程
- 调用waitpid我们可以通过参数指定pid号也可以指定如果没有已经终止的子进程是否要阻塞,这样体验就会好很多。
如果你这里调用wait将会发生什么呢?已上面的程序为例你同时启动5个客户端,在服务器就会fork5个子进程处理,当通信完毕需要清理僵尸进程的时候有很大可能会清理小于5个。因为5个中断信号几乎同时到达,第一个到达的时候阻塞在wait,而后面相继到达,Unix的信号是不排队的,也就是被阻塞期间产生一次或多次最终只提交一次。
Python自带的一个叫做socketserver模块里面有一个ForkingMixIn的其核心实现就是fork加信号处理。
需要知道的事情
服务器进程崩溃会怎么样
与服务器之间的网络中断会怎么样
(二)通过fork编写一个简单的并发服务器的更多相关文章
- 【实验 1-1】编写一个简单的 TCP 服务器和 TCP 客户端程序。程序均为控制台程序窗口。
在新建的 C++源文件中编写如下代码. 1.TCP 服务器端#include<winsock2.h> //包含头文件#include<stdio.h>#include<w ...
- 编写一个简单的java服务器程序
import java.net.*;import java.io.*; public class server{ ); //监听在80端口 Socket sock = server.accept(); ...
- 【实验 1-2】编写一个简单的 UDP 服务器和 UDPP 客户端程序。程序均为控制台程序窗口。
1.服务器 #include<winsock2.h> //包含头文件#include<stdio.h>#include<windows.h>#pragma comm ...
- c#编写一个简单的http服务器
先来张我的帅照哈哈哈 好了不臭美了 上代码 世间万物 只有想不到 没有做不到 哈哈哈 仔细阅读代码 我要凑够 150个字 哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈 ...
- 如何编写一个简单的Linux驱动(二)——完善设备驱动
前期知识 1.如何编写一个简单的Linux驱动(一)——驱动的基本框架 2.如何编写一个简单的Linux驱动(二)——设备操作集file_operations 前言 在上一篇文章中,我们编写设备驱动遇 ...
- 使用CEF(二)— 基于VS2019编写一个简单CEF样例
使用CEF(二)- 基于VS2019编写一个简单CEF样例 在这一节中,本人将会在Windows下使用VS2019创建一个空白的C++Windows Desktop Application项目,逐步进 ...
- 如何编写一个简单的Linux驱动(二)——设备操作集file_operations
前期知识 如何编写一个简单的Linux驱动(一)--驱动的基本框架 前言 在上一篇文章中,我们学习了驱动的基本框架.这一章,我们会在上一章代码的基础上,继续对驱动的框架进行完善.要下载上一篇文章的全部 ...
- 编写一个简单的C++程序
编写一个简单的C++程序 每个C++程序都包含一个或多个函数(function),其中一个必须命名为main.操作系统通过调用main来运行C++程序.下面是一个非常简单的main函数,它什么也不干, ...
- 使用Java编写一个简单的Web的监控系统cpu利用率,cpu温度,总内存大小
原文:http://www.jb51.net/article/75002.htm 这篇文章主要介绍了使用Java编写一个简单的Web的监控系统的例子,并且将重要信息转为XML通过网页前端显示,非常之实 ...
随机推荐
- SpringCloud使用Sofa-lookout监控(基于Eureka)
本文介绍SpringCloud使用Sofa-lookout,基于Eureka服务发现. 1.前景 本文属于是前几篇文章的后续,其实一开始感觉这个没有什么必要写的,但是最近一个朋友问我关于这个的问题,所 ...
- 封装PDO函数
funPDO.php <?php /** * @title: 封装PDO函数 * * @Features: * 1. 封装 SELECT ,INSERT,DELETE,UPDATE 操作 @do ...
- 单个 js 文件禁用 ESLint 语法校验
在代码顶部添加一行注释 /* eslint-disable */ ESLint 在校验的时候就会跳过后面的代码 还可以在注释后加入详细规则,这样就能避开指定的校验规则了 /* eslint-disab ...
- weex 学习: 添加图标(使用阿里吧吧-icon)
添加图标(使用阿里吧吧-icon) <text slot="left" class="header-left"></text> i ...
- django+javascrpt+python实现私有云盘
代码稍后上,先整理下私有云盘的相关功能介绍. 1.登陆界面 2.首页展示,有个人目录.部门目录以及公司目录,针对不用的目录设置不同的权限控制. 3.个人信息展示 4.账号管理.账号信息展示 5.账号添 ...
- 20175324 《Java程序设计》第七周学习总结
教材学习内容总结 常用实用类 String类 - 程序可以直接使用String类,但不能进行扩展,即String类不可以有子类 - 常用构造方法 - String(char a[])用一个字符数组a创 ...
- 20175324 2018-2019-2 《Java程序设计》第5周学习总结
20175324 2018-2019-2 <Java程序设计>第5周学习总结 教材学习内容总结 抽象类和具体类的区别在于抽象类中有抽象方法而具体类中没有.且抽象类不能实例化. 接口:如果一 ...
- Flutter 文本样式继承
使用inherit来设置是否继承样式 DefaultTextStyle( style: TextStyle(color: Colors.red, fontSize: 22), child: Colum ...
- extremecomponents
具体教程: http://www.cnblogs.com/QQParadise/articles/1488920.html 教程中涉及到springmvc的相关知识 下载地址:http://sourc ...
- Java spring boot 2.0连接mysql异常:The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone
解决办法:application.yml提示信息表明数据库驱动com.mysql.jdbc.Driver'已经被弃用了.应当使用新的驱动com.mysql.cj.jdbc.Driver' com.my ...