侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

基于并发服务器几种实现方法(总结)

技术  /  管理员 发布于 7年前   286

今天主题是实现并发服务器,实现方法有多种版本,先从简单的单进程代码实现到多进程,多线程的实现,最终引入一些高级模块来实现并发TCP服务器。

说到TCP,想起吐槽大会有个段子提到三次握手,也只有程序猿(媛)能get。

UDP服务器数据传输不可靠,这里就忽略了。

>>:

简单的单进程TCP服务器

假代码:

#创建tcp服务器套接字

#绑定端口

#设置正常情况退出的服务器下,端口可以重用

#设置监听,变为主动监听

# 等待客户端的链接,返回新的socket和地址

#关闭tcp服务器套接字

from socket import socket, AF_INET,SOCK_STREAM,SOL_SOCKET,SO_REUSEADDR#创建tcp服务器套接字server_socket = socket(AF_INET,SOCK_STREAM)#绑定端口server_socket.bind(("",9999))#设置正常情况退出的服务器下,端口可以重用server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)#设置监听,变为主动监听server_socket.listen(5)while True: # 等待客户端的链接,返回新的socket和地址 new_socket,new_address = server_socket.accept() #接收数据,并且发送数据 try: while True: recv_data = new_socket.recv(1024) #当有客户端关闭后,recv解除阻塞,并且返回长度为0 if len(recv_data) > 0: recv_content = recv_data.decode("gb2312") print("收到:%s的信息是:%s" % (str(new_address),recv_content)) new_socket.send("thank you!".encode("gb2312")) else: print("客户端%s已经关闭" % (str(new_address))) break finally: new_socket.close() print("关闭%s客户端" % (str(new_address)))#关闭tcp服务器套接字server_socket.close()

多进程TCP服务器

from socket import socket, AF_INET,SOCK_STREAM,SOL_SOCKET,SO_REUSEADDRfrom multiprocessing import Process#在子进程中接收消息def recv_data(new_socket,new_address): while True:  recv_data = new_socket.recv(1024)  # 当有客户端关闭后,recv解除阻塞,并且返回长度为0  if len(recv_data) > 0:   recv_content = recv_data.decode("gb2312")   print("收到:%s的信息是:%s" % (str(new_address), recv_content))   new_socket.send("thank you!".encode("gb2312"))  else:   print("客户端%s已经关闭" % (str(new_address)))   break #关闭与客户端的连接 print("关闭与客户端的连接") new_socket.close()def main(): #创建tcp服务器套接字 server_socket = socket(AF_INET,SOCK_STREAM) #绑定端口 server_socket.bind(("",8888)) #设置正常情况退出的服务器下,端口可以重用 server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #设置监听,变为被动连接 server_socket.listen(3) try:  while True:   # 等待客户端的链接,返回新的socket和地址   new_socket,new_address = server_socket.accept()   #接收数据,并且发送数据   Process(target=recv_data,args=(new_socket,new_address)).start()   #因为主进程和子进程不共享数据   #如果我们直接关闭new_socket,只是关闭主进程的new_socket,而子进程的不受影响   new_socket.close() finally:  #关闭tcp服务器套接字  server_socket.close()if __name__ == "__main__": main()

多进程TCP服务器

from socket import socket, AF_INET,SOCK_STREAM,SOL_SOCKET,SO_REUSEADDRfrom multiprocessing import Process#在子进程中接收消息def recv_data(new_socket,new_address): while True:  recv_data = new_socket.recv(1024)  # 当有客户端关闭后,recv解除阻塞,并且返回长度为0  if len(recv_data) > 0:   recv_content = recv_data.decode("gb2312")   print("收到:%s的信息是:%s" % (str(new_address), recv_content))   new_socket.send("thank you!".encode("gb2312"))  else:   print("客户端%s已经关闭" % (str(new_address)))   break #关闭与客户端的连接 print("关闭与客户端的连接") new_socket.close()def main(): #创建tcp服务器套接字 server_socket = socket(AF_INET,SOCK_STREAM) #绑定端口 server_socket.bind(("",8888)) #设置正常情况退出的服务器下,端口可以重用 server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #设置监听,变为被动连接 server_socket.listen(3) try:  while True:   # 等待客户端的链接,返回新的socket和地址   new_socket,new_address = server_socket.accept()   #接收数据,并且发送数据   Process(target=recv_data,args=(new_socket,new_address)).start()   #因为主进程和子进程不共享数据   #如果我们直接关闭new_socket,只是关闭主进程的new_socket,而子进程的不受影响   new_socket.close() finally:  #关闭tcp服务器套接字  server_socket.close()if __name__ == "__main__": main()

多线程TCP服务器

from socket import socket, AF_INET,SOCK_STREAM,SOL_SOCKET,SO_REUSEADDRfrom threading import Thread#接收消息def recv_data(new_socket,new_address): while True:  recv_data = new_socket.recv(1024)  # 当有客户端关闭后,recv解除阻塞,并且返回长度为0  if len(recv_data) > 0:   recv_content = recv_data.decode("gb2312")   print("收到:%s的信息是:%s" % (str(new_address), recv_content))   new_socket.send("thank you!".encode("gb2312"))  else:   print("客户端%s已经关闭" % (str(new_address)))   breakdef main(): #创建tcp服务器套接字 server_socket = socket(AF_INET,SOCK_STREAM) #绑定端口 server_socket.bind(("",9999)) #设置正常情况退出的服务器下,端口可以重用 server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #设置监听,变为被动连接 server_socket.listen(3) try:  while True:   # 等待客户端的链接,返回新的socket和地址   new_socket,new_address = server_socket.accept()   #接收数据,并且发送数据   Thread(target=recv_data,args=(new_socket,new_address)).start() finally:  #关闭tcp服务器套接字  server_socket.close()if __name__ == "__main__": main()

多任务协程实现 ――

greenlet和gevent

#coding=utf-8from greenlet import greenletimport timedef test1(): while True:  print "---A--"  gr2.switch()  time.sleep(0.5)def test2(): while True:  print "---B--"  gr1.switch()  time.sleep(0.5)gr1 = greenlet(test1)gr2 = greenlet(test2)#切换到gr1中运行gr1.switch()
import gevent#函数def f(n): for i in range(n):  print("%s:%s" % (gevent.getcurrent(),i))f1 = gevent.spawn(f,5)f2 = gevent.spawn(f,5)f3 = gevent.spawn(f,5)#让主线程等待三个协程执行完毕,否则没有机会执行f1.join()f2.join()f3.join()#可以看到,3个greenlet是依次运行而不是交替运行。要让greenlet交替运行,可以通过gevent.sleep()交出控制权。
#coding=utf-8import geventdef f(n): for i in range(n):  print gevent.getcurrent(), i  #用来模拟一个耗时操作,注意不是time模块中的sleep  gevent.sleep(1)g1 = gevent.spawn(f, 5)g2 = gevent.spawn(f, 5)g3 = gevent.spawn(f, 5)#下面三行代码意思:主线程等待各个协成支持完,否则协成没有机会执行g1.join()g2.join()g3.join()

单进程TCP服务器 ――

非堵塞式

from socket import AF_INET,socket,SO_REUSEADDR,SOCK_STREAM,SOL_SOCKETdef main(): #创建tcp的socket套接字 server_socket = socket(AF_INET,SOCK_STREAM) server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #绑定端口 server_socket.bind(("",9999)) #设置非阻塞,也就是说accept方法不阻塞了, # 但是在没有客户端链接且被执行的时候会报错 #有客户端链接的时候正常执行 server_socket.setblocking(False) #设置监听 server_socket.listen(5) #客户端列表 client_lists = [] try:  #不断调用accept  while True:   try:   # print("accept--111")   new_socket,new_address = server_socket.accept()   print("accept--2222")   except Exception as result:   # print(result)   pass   else:   print("新的客户%s链接上" % str(new_address))   #新链接的new_sokect默认也是阻塞,也设置为非阻塞后,recv为非阻塞   new_socket.setblocking(False)   client_lists.append((new_socket,new_address))   # print(111)   for client_sokect,client_address in client_lists:   #接收数据   try:    recv_data = client_sokect.recv(1024)   except Exception as result:    # print(result)    pass   else:    # print("正常数据:%s" %recv_data)    if len(recv_data) > 0 :     print("收到%s:%s" % (str(client_address),recv_data))     client_sokect.send("thank you!".encode("gb2312"))    else:     #客户端已经端口,要把该客户端从列表中异常     client_lists.remove((client_sokect,new_address))     client_sokect.close()     print("%s已经断开" % str(new_address)) finally:  #关闭套接字  server_socket.close()if __name__ == "__main__": main()

单进程TCP服务器 ――

select版

select 原理

其他语言(c或者c++)也有使用select实现多任务服务器。

select 能够完成一些套接字的检查,从头到尾检查一遍后,标记哪些套接字是否可以收数据,返回的时候,就返回能接收数据的套接字,返回的是列表。select是由操作系统提供的,效率要高些,非常快的方式检测哪些套接字可以接收数据。select是跨平台的,在window也可以用。

io多路复用:没有使用多进程和多线程的情况下完成多个套接字的使用。

from socket import AF_INET,socket,SO_REUSEADDR,SOCK_STREAM,SOL_SOCKETfrom select import selectimport sysdef main(): #创建tcp的socket套接字 server_socket = socket(AF_INET,SOCK_STREAM) server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #绑定端口 server_socket.bind(("",9999)) #设置监听 server_socket.listen(5) #客户端列表 socket_lists = [server_socket,sys.stdin] wirte_list = [] #是否退出 is_run = False try:  while True:   #检测列表client_lists那些socket可以接收数据,   #检测列表[]那些套接字(socket)可否发送数据   #检测列表[]那些套接字(socket)是否产生了异常   print("select--111")   #这个select函数默认是堵塞,当有客户端链接的时候解除阻塞,   # 当有数据可以接收的时候解除阻塞,当客户端断开的时候解除阻塞   readable, wirteable,excep = select(socket_lists,wirte_list,[])   # print("select--2222")   # print(111)   for sock in wirteable:   #这个会一直发送,因为他是处于已经发的状态   sock.send("thank you!".encode("gb2312"))   for sock in readable:   #接收数据   if sock == server_socket:    print("sock == server_socket")    #有新的客户端链接进来    new_socket,new_address = sock.accept()    #新的socket添加到列表中,便于下次socket的时候能检查到    socket_lists.append(new_socket)   elif sock == sys.stdin:    cmd = sys.stdin.readline()    print(cmd)    is_run = cmd   else:    # print("sock.recv(1024)....")    #此时的套接字sock是直接可以取数据的    recv_data = sock.recv(1024)    if len(recv_data) > 0:     print("从[%s]:%s" % (str(new_address),recv_data))     sock.send(recv_data)     #把链接上有消息接收的socket添加到监听写的列表中     wirte_list.append(sock)    else:     print("客户端已经断开")     #客户端已经断开,要移除     sock.close()     socket_lists.remove(sock)   #是否退出程序   if is_run:   break finally:  #关闭套接字  server_socket.close()if __name__ == "__main__": main()

单进程TCP服务器 ――

epoll版

from socket import *import selectdef main(): #创建tcp服务器套接字 server_socket = socket(AF_INET,SOCK_STREAM) #设置端口可以重用 server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #绑定端口 server_socket.bind(("",9999)) #设置监听 server_socket.listen(5) #用epoll设置监听收数据 epoll = select.epoll() #把server_socket注册到epoll的事件监听中,如果已经注册过会发生异常 epoll.register(server_socket.fileno(),select.EPOLLIN|select.EPOLLET) #装socket列表 socket_lists = {} #装socket对应的地址 socket_address = {} while True:  #返回套接字列表[(socket的文件描述符,select.EPOLLIN)],  # 如果有新的链接,有数据发过来,断开链接等都会解除阻塞  print("epoll.poll--111")  epoll_list = epoll.poll()  print("epoll.poll--222")  print(epoll_list)  for fd,event in epoll_list:   #有新的链接   if fd == server_socket.fileno():   print("新的客户fd==%s" % fd)   new_sokect,new_address = server_socket.accept()   #往字典添加数据   socket_lists[new_sokect.fileno()] = new_sokect   socket_address[new_sokect.fileno()] = new_address   #注册新的socket也注册到epoll的事件监听中   epoll.register(new_sokect.fileno(), select.EPOLLIN | select.EPOLLET)   elif event ==select.EPOLLIN:   print("收到数据了")   #根据文件操作符取出对应socket   new_sokect = socket_lists[fd]   address = socket_address[fd]   recv_data = new_sokect.recv(1024)   if len(recv_data) > 0:    print("已经收到[%s]:%s" % (str(address),recv_data.decode("gb2312")))   else:    #客户端端口,取消监听    epoll.unregister(fd)    #关闭链接    new_sokect.close()    print("[%s]已经下线" % str(address)) #关闭套接字链接 server_socket.close()if __name__ == "__main__": main()

单进程TCP服务器 ――

gevent版

gevent原理

greenlet已经实现了协程,但是这个还得人工切换,是不是觉得太麻烦了,莫要捉急,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent

原理------当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO.

import sysimport timeimport geventfrom gevent import socket,monkeymonkey.patch_all()def handle_request(conn): while True:  data = conn.recv(1024)  if not data:   conn.close()   break  print("recv:", data)  conn.send(data)def server(port): s = socket.socket() s.bind(('', port)) s.listen(5) while True:  newSocket, addr = s.accept()  gevent.spawn(handle_request, newSocket)if __name__ == '__main__': server(7788)

首先基于以上代码模块,撒点概念问题:

1.什么是协程?

协程:存在线程中,是比线程更小的执行单元,又称微线程,纤程。自带cpu上下文,操作协程由程序员决定,它可以将一个线程分解为多个微线程,每个协程间共享全局空间的变量,每秒钟切换频率高达百万次。

2. 什么是计算密集型和IO密集型

计算密集型:要进行大量的计算,消耗cpu资源。如复杂计算,对视频进行高清解码等,全靠cpu的运算能力。而计算密集型任务完成多任务切换任务比较耗时,cpu执行任务效率就越低。在python中,多进程适合计算密集型任务。

IO密集型:涉及到网络、磁盘io的任务都是io密集型。cpu消耗少,计算量小,如请求网页,读写文件等。在python中,使用sleep达到IO密集型任务的目的,多线程适合IO密集型任务。

各大实现版本对比:

select:

1)支持跨平台,最大缺陷是单个进程打开的FD是有限的,由FD_SETSIZE设置,默认是1024;

2)对socket扫描时是线性扫描,及采用轮询方式,效率低;

3)需要维护一个存放大量FD的数据结构,使得用户空间和内核空间在传递该数据结构时复制开销大。

poll:

1)poll与select本质上没有区别,但poll没有最大连接数的限制;

2)大量的fd数组被整体复制于用户态和内核地址空间之间,不管这样的复制是不是有意义;

3)‘水平触发',如果报告了fd后,没有被处理,下次poll时还会再次报告该fd。

epoll:

1)是之前poll和select的增强版,epoll更灵活,没有描述符限制,能打开的fd远大于1024(1G的内存上能监听约10万个端口);

2)‘边缘出发',事件通知机制,效率提升,最大的特点在于它只管你活跃的连接,而跟连接总数无关。而epoll对文件描述符的操作模式之一ET是一种高效的工作方式,很大程度减少事件反复触发的次数,内核不会发送更多的通知(only once)。

以上这篇基于并发服务器几种实现方法(总结)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。


  • 上一条:
    matplotlib简介,安装和简单实例代码
    下一条:
    matplotlib绘制动画代码示例
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 2024.07.09日OpenAI将终止对中国等国家和地区API服务(0个评论)
    • 2024/6/9最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(1个评论)
    • 国外服务器实现api.openai.com反代nginx配置(0个评论)
    • 2024/4/28最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(1个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2017-09
    • 2018-01
    • 2018-07
    • 2018-08
    • 2018-09
    • 2018-12
    • 2019-01
    • 2019-02
    • 2019-03
    • 2019-04
    • 2019-05
    • 2019-06
    • 2019-07
    • 2019-08
    • 2019-09
    • 2019-10
    • 2019-11
    • 2019-12
    • 2020-01
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-12
    • 2024-02
    • 2024-04
    • 2024-05
    • 2024-06
    • 2025-02
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客