Python 提供了两个级别拜访的网络服务。:
- 低级别的网络服务反对根本的 socket,,能够拜访底层操作系统Socket接口的办法。
- 高级别的网络服务模块 socketserver, 能够简化网络服务器的开发。
socket
查看socket类的帮忙如下
import socket # 导入socket模块 >>> help(socket.socket)
重点关注初始化函数:
__init__(self, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, proto=0, fileno=None)
- family:网络协议簇,默认值为AF_INET
- type:套接字的类型,依据是面向连贯的还是非连贯分为
SOCK_STREAM
或SOCK_DGRAM
- proto:套接字协定,个别默认为0,示意
- fileno:套接字的int型的文件描述符
上面实现一个TCP聊天室和一个UDP聊天室
<!–more–>
TCP聊天室
概要设计
获取多个连贯的解决
开启accept线程,执行accept操作开始阻塞,有客户端连贯时,再开启一个线程recv进行数据接管的解决。而后accept线程持续阻塞,期待后续客户端的连贯。
阻塞的解决
服务端解决客户端的连贯时,有两处存在阻塞,别离是:
- 获取连贯时,socket.accept()会阻塞
- 每一个建设胜利的连贯在获取数据时,socket.recv(1024)
因而这两处都须要开启线程独自解决,否则会阻塞主线程。
客户端被动断开的解决
客户端被动断开时,如果不告诉服务端,那么服务端上保留的客户端连贯不会被清理,这是不合理的。因而客户端被动断开时,咱们在应用层约定,客户端推出前须要发送/quit
指令到服务端上,而后有服务端敞开socket。
TCP聊天室-server
聊天室的server端次要是监听端口,解决来自client端的连贯,并且散发数据到所有的client端
代码
import socket import threading class TcpChatServer: def __init__(self, ip='192.168.110.13', port=9001): self.ip = ip self.port = port self.clients = {} self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) self.event = threading.Event() def recv(self, so, ip ,port): while not self.event.is_set(): data = so.recv(1024).decode() # 将承受到的字节数据bytes转化为utf-8格局的字符串 if data.strip() == '/quit': # 客户端被动断开时的解决 so.close() self.clients.pop((ip, port)) return for s in self.clients.values(): # 播送发送 s.send('{}:{}\n{}'.format(ip, port, data).encode()) def accept(self): while not self.event.is_set(): so, (ip, port) = self.sock.accept() self.clients[(ip, port)] = so # 因为so.recv会产生阻塞,因而独自开一个线程解决数据的承受局部。这样accept能够持续承受来自其余客户端的链接 threading.Thread(target=self.recv, args=(so, ip, port), name='client-{}:{}'.format(ip, port)).start() def start(self): self.sock.bind((self.ip, self.port)) self.sock.listen() t = threading.Thread(target=self.accept, daemon=True) # 为了不阻塞主线程,独自开启一个线程解决accept(accept会阻塞线程) try: t.start() t.join() # 阻塞直到获取到KeyboardInterrupt except KeyboardInterrupt: self.stop() def stop(self): for s in self.clients.values(): s.close() self.sock.close() self.event.set() # 进行所有的循环 if __name__ == '__main__': tcp_chat_server = TcpChatServer() tcp_chat_server.start()
TCP聊天室-client
聊天室的client端次要是发动连贯,连贯到server端,并且要承受来自服务端播送散发的音讯。
代码
import socket import threading class TcpChatClient: def __init__(self): self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) self.event = threading.Event() def recv(self): # 客户端须要始终接管服务端播送散发的音讯 while not self.event.is_set(): data = self.sock.recv(1024).decode() data = data.strip() print(data) def send(self): # 输出音讯就发送 while not self.event.is_set(): data = input() self.sock.send(data.encode()) if data.strip() == '/quit': # 发送/quit的时候本身敞开 self.stop() def start(self, ip, port): self.sock.connect((ip, port)) s = threading.Thread(target=self.send, daemon=False) r = threading.Thread(target=self.recv, daemon=False) s.start() r.start() def stop(self): self.sock.close() self.event.set() if __name__ == '__main__': tcp_chat_client = TcpChatClient() tcp_chat_client.start('192.168.110.13', 9001)
UDP聊天室
概要设计
阻塞的解决
在UDP服务端接管客户端的音讯时,采纳socket.recvfrom(1024)
这个办法以便保留客户端的地址信息,这个办法会阻塞以后线程,因而须要开启线程独自解决。
客户端被动断开的解决
UDP客户端被动敞开之后,服务端是无奈检测到客户端曾经敞开的。咱们能够采纳以下两种办法:
- 如果相似于TCP采纳约定退出指令的办法,那么客户端发送退出指令后就调用close办法,而后服务端依据失去的指令剔除客户端字典中对应的客户端。
- 还能够通过客户端定时发送心跳给服务端,服务端通过心跳来判断客户端过程是否存活。
UDP聊天室-server
UDP服务端程序开启线程期待接管客户端的数据,而后播送给其余的客户端,并且查看所有连贯的心跳是否超时。
代码
import socket import datetime import threading class UdpChatServer: def __init__(self, ip='192.168.110.13', port=9001): self.addr = (ip, port) self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) self.clients = {} self.event = threading.Event() def recv(self): while not self.event.is_set(): data, addr = self.sock.recvfrom(1024) data = data.decode().strip() now = datetime.datetime.now() if data == '#ping#': # 判断是否收到心跳 self.clients[addr] = now # 收到心跳则保留客户端地址,并且更新工夫戳 continue disconnected = set() # 没收到一次数据就判断所有的生效链接 for addr, timestamp in self.clients.items(): if (now - timestamp).total_seconds() > 10: # 生效条件:2次(即10s)没收到心跳就判断客户端敞开 disconnected.add(addr) else: self.sock.sendto('{}:{}\n{}'.format(addr[0], addr[1], data).encode(), addr) for addr in disconnected: self.clients.pop(addr) def start(self): self.sock.bind(self.addr) # 绑定端口之后就开启线程始终承受客户端的数据 t = threading.Thread(target=self.recv(), daemon=True) try: t.start() t.join() except KeyboardInterrupt: self.stop() def stop(self): self.event.set() self.sock.close() if __name__ == '__main__': udp_chat_server = UdpChatServer() udp_chat_server.start()
UDP聊天室-client
UDP的客户端的主线程始终在期待用户输出数据而后将数据发送到服务端,同时开启了一个心跳过程和一个承受服务端播送数据的线程。
代码
import socket import threading import time class UdpChatClient: def __init__(self, ip, port): self.addr = (ip, port) self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) self.event = threading.Event() def heartbeat(self): # 心跳线程函数:每5s发一次心跳 while not self.event.wait(5): self.sock.sendto(b'#ping#', self.addr) def recv(self): # 始终期待承受udp服务器播送的数据 while not self.event.is_set(): data = self.sock.recv(1024) print(data.decode()) def start(self): threading.Thread(target=self.heartbeat, name='heartbeat', daemon=True).start() threading.Thread(target=self.recv, name='recv', daemon=True).start() print('请在5s后发言') time.sleep(5) # 因为服务端必须收到一个心跳之后才会保留次客户端,因而须要期待5s print('请开始发言') while not self.event.is_set(): data = input('') data = data.strip() if data == '/quit': self.event.set() self.sock.close() return self.sock.sendto(data.encode(), self.addr) if __name__ == '__main__': udp_chat_client = UdpChatClient('192.168.110.13', 9001) udp_chat_client.start()
SocketServer
TODO(Flowsnow):改写聊天室程序的TcpChatServer和UdpChatServer
附一:TCP和UDP的本质区别
- udp:所有的客户端发来的数据报都沉积在队列上,而后服务端一个一个的解决
- tcp:每一个客户端和服务端都有一个连贯通道,只解决对应客户端的数据流
附二:参考资料
- socketserver — A framework for network servers
记得帮我点赞哦!
精心整顿了计算机各个方向的从入门、进阶、实战的视频课程和电子书,依照目录正当分类,总能找到你须要的学习材料,还在等什么?快去关注下载吧!!!
朝思暮想,必有回响,小伙伴们帮我点个赞吧,非常感谢。
我是职场亮哥,YY高级软件工程师、四年工作教训,回绝咸鱼争当龙头的斜杠程序员。
听我说,提高多,程序人生一把梭
如果有幸能帮到你,请帮我点个【赞】,给个关注,如果能顺带评论给个激励,将不胜感激。
职场亮哥文章列表:更多文章
自己所有文章、答复都与版权保护平台有单干,著作权归职场亮哥所有,未经受权,转载必究!