1 套接字(socker):通信端点
服务器创建一个通信端点使服务器监听请求,客户端创建一个通信端点建立一个到服务器的连接
1.1 套接字
有两种类型的套接字:基于文件的和基于网络的
1.1.1 基于文件的套接字
基于文件的套接字:两个进程运行在同一台计算机上
AF_UNIX(又名AF_LOCAL):代表地址家族(address family):UNIX
其他比较旧的系统可能会将地址家族表示成域(domain)或协议家族(protocol family),缩写PF
1.1.2 基于网络的套接字
1) AF_INET:地址家族:因特网(最广泛)
AF_INET6:第6版因特网协议(IPV6)寻址
1.1.3 Python中引入对特殊类型的Linux套接字
1) AF_NETLINK:无连接,允许使用标准的BSD(加利福尼亚大学的伯克利版本)套接字接口进行用户级别和内核级别代码之间的IPC(通信进程:Inter Process Communication)
2) AF_TIPC:支持透明的进程间通信协议,允许计算机集群之中的机器互相通信,而无需使用基于IP的寻址方式
1.2 套接字地址:主机-端口对
一个网络地址由主机名和端口号对组成,有效的端口号范围为0~65535(小于1024的端口号预留给了系统)
1.3 面向连接的套接字与无连接的套接字
不管采用的是哪种地址家族,都有两种不同风格的套接字连接(面向连接的套接字和无连接的套接字)
1.3.1 面向连接的套接字
在进行通信之前必须先建立一个连接,实现这种连接类型的主要协议是传输控制协议(TCP),每条消息可以拆分成多个片段,并且每一条消息片段都确保能够到达目的地。
创建TCP 套接字,必须使用SOCK_STREAM 作为套接字类型(AF_INET套接字与TCP)
1.3.2 无连接的套接字
在通信开始之前并不需要建立连接,实现这种连接类型的主要协议是用户数据报协议(UDP),消息是以整体发送的。
创建UDP 套接字,必须使用SOCK_DGRAM 作为套接字类型(AF_INET套接字与UDP)
2 Python中的网络编程socket模块
主要模块是socket 模块,在这个模块中可以找到socket()函数,该函数用于创建套接字对象
2.1 socket()模块函数
创建套接字,必须使用socket.socket()函数,它一般的语法如下:
socket(socket_family, socket_type, protocol=0)
其中,socket_family是AF_UNIX或AF_INET,socket_type是SOCK_STREAM或SOCK_DGRAM。protocol通常省略,默认为0。
例:
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
使用from socket import *,则:
tcpSock = socket(AF_INET, SOCK_STREAM)
2.2 套接字对象(内置)方法
名称 | 描述 |
服务器套接字方法 | |
s.bind() | 将地址(主机名、端口号对)绑定到套接字上 |
s.listen() | 设置并启动TCP 监听器 |
s.accept() | 被动接受TCP 客户端连接,一直等待直到连接到达(阻塞) |
客户端套接字方法 | |
s.connect() | 主动发起TCP 服务器连接 |
s.connect_ex() | connect()的扩展版本,此时会以错误码的形式返回问题,而不是抛出一个异常 |
普通的套接字方法 | |
s.recv() | 接收TCP 消息 |
s.recv_into() | 接收TCP 消息到指定的缓冲区 |
s.send() | 发送TCP 消息 |
s.sendall() | 完整地发送TCP 消息 |
s.recvfrom() | 接收UDP 消息 |
s.recvfrom_into() | 接收UDP 消息到指定的缓冲区 |
s.sendto() | 发送UDP 消息 |
s.getpeername() | 连接到套接字(TCP)的远程地址 |
s.getsockname() | 当前套接字的地址 |
s.getsockopt() | 返回给定套接字选项的值 |
s.setsockopt() | 设置给定套接字选项的值 |
s.shutdown() | 关闭连接 |
s.close() | 关闭套接字 |
s.detach() | 在未关闭文件描述符的情况下关闭套接字,返回文件描述符 |
s.ioctl() | 控制套接字的模式(仅支持Windows) |
面向阻塞的套接字方法 | |
s.setblocking() | 设置套接字的阻塞或非阻塞模式 |
s.settimeout() | 设置阻塞套接字操作的超时时间 |
s.gettimeout() | 获取阻塞套接字操作的超时时间 |
面向文件的套接字方法 | |
s.fileno() | 套接字的文件描述符 |
s.makefile() | 创建与套接字关联的文件对象 |
数据属性 | |
s.family | 套接字家族 |
s.type | 套接字类型 |
s.proto | 套接字协议 |
2.3 创建TCP服务器
1) 通用TCP服务器的一般伪代码
ss = socket() # 创建服务器套接字
s.bind() # 套接字与地址绑定
ss.listen() # 监听连接
inf_loop: # 服务器无限循环
cs = ss.accept() # 接受客户端连接
comm_loop: # 通信循环
cs.recv()/cs.send() # 对话(接收/发送)
cs.close() # 关闭客户端套接字
ss .close() # 关闭服务器套接字#(可选)
ss.bind()服务器需要绑定到一个本地地址和占用一个端口。ss.listen()监听(传入)的连接。
ss.accept()开启一个简单的(单线程)服务器等待客户端的连接,是阻塞的,意味着执行将被暂停,直到一个连接到达,一旦服务器接收了一个连接,就会返回(利用accept())一个独立的客户端套接字,用来与即将到来的消息进行交换(当一个传入的请求到达时,服务器会创建一个新的通信端口来直接与客户端进行通信,再次空出主要的端口,以使其能够接受新的客户端连接);
2) 创建TCP服务器代码
1 ''' 2 创建TCP服务器 3 ''' 4 5 from socket import * 6 from time import ctime 7 8 HOST = '' #空白表示可以使用任何可用的地址,是对bind()方法的标识 9 PORT = 21567 #端口 10 BUFSIZ = 1024 #缓冲区大小设置为1KB,可根据网络性能和程序需要改变这个容量 11 ADDR = (HOST,PORT) 12 13 tcpSerSock = socket(AF_INET,SOCK_STREAM) #创建套接字 14 tcpSerSock.bind(ADDR) #将套接字绑定到服务器地址和端口 15 tcpSerSock.listen(1) #开启TCP监听,参数是传入连接在排队的请求的最大数,设置为0和1相同 16 17 while True: 18 print('waiting for connection...') 19 tcpCliSock,addr = tcpSerSock.accept() #生成一个包含socket对象(tcpClisock)和连接的IP地址、端口(addr)的元组 20 print('...connected from:',addr) 21 22 while True: 23 data = (tcpCliSock.recv(BUFSIZ)).decode('utf-8') #接收客户端发来的TCP消息 24 if not data: 25 break 26 27 #发送给客户端的TCP消息,使用utf-8编码转成二进制数据发送,只能转成二进制数据才能发送 28 tcpCliSock.send(bytes(('[%s] %s' % (ctime(),data)),'utf-8')) 29 30 tcpCliSock.close() #关闭当前客户端连接,然后等待另一个客户端连接 31 tcpSerSock.close() #这一行在这个程序里永远不会执行,只是提醒可以使用这个来考虑一个更优雅的退出方式
3)创建TCP客户端伪代码
cs = socket() # 创建客户端套接字
cs.connect() # 尝试连接服务器
comm_loop: # 通信循环
cs.send()/cs.recv() # 对话(发送/接收)
cs .close() # 关闭客户端套接字
4) 创建TCP客户端代码
1 ''' 2 创建TCP客户端 3 ''' 4 5 from socket import * 6 7 HOST = 'localhost' 8 PORT = 21567 #端口号应与服务器设置的完成相同 9 BUFSIZ = 1024 10 ADDR = (HOST,PORT) 11 12 tcpCliSOCK = socket(AF_INET,SOCK_STREAM) 13 tcpCliSOCK.connect(ADDR) #主动调用并连接服务器 14 15 while True: 16 data = input('>') 17 if not data: 18 break 19 20 #发送给服务器的TCP消息,使用utf-8编码转成二进制数据发送,只能转成二进制数据才能发送 21 tcpCliSOCK.send(bytes(data,'utf-8')) 22 data = tcpCliSOCK.recv(BUFSIZ) #接收服务器发来的TCP消息 23 if not data: 24 break 25 print(data.decode('utf-8')) 26 tcpCliSOCK.close()
5) 执行TCP服务器和客户端
执行TCP服务器
执行TCP客户端
2.4 创建UDP服务器
UDP因为数据报套接字是无连接的,所以就没有为了成功通信而使一个客户端连接到一个独立的套接字“转换”的操作。这些服务器仅仅接受消息并有可能回复数据。
1) 创建UDP服务器的伪代码
ss = socket() # 创建服务器套接字
ss.bind() # 绑定服务器套接字
inf_loop: # 服务器无限循环
cs = ss.recvfrom()/ss.sendto() # 关闭(接收/发送)
ss .close() # 关闭服务器套接字
2) 创建UDP服务器代码
1 ''' 2 创建UDP服务器 3 ''' 4 5 from socket import * 6 from time import ctime 7 8 HOST = '' #空白表示可以使用任何可用的地址,是对bind()方法的标识 9 PORT = 21567 #端口 10 BUFSIZ = 1024 #缓冲区大小设置为1KB,可根据网络性能和程序需要改变这个容量 11 ADDR = (HOST,PORT) 12 13 udpSerSock = socket(AF_INET,SOCK_DGRAM) #创建套接字 14 udpSerSock.bind(ADDR) #将套接字绑定到服务器地址和端口 15 16 while True: 17 print('waiting for message...') 18 data,addr = udpSerSock.recvfrom(BUFSIZ) #接收客户端发来的UDP消息,此消息包含客户端发来数据和客户端地址、端口号的元组 19 20 #发送给客户端的UDP消息,使用utf-8编码转成二进制数据发送,只能转成二进制数据才能发送 21 udpSerSock.sendto(('[%s] %s' % (ctime(),data.decode('utf-8'))).encode('utf-8'),addr) 22 print('...received from and returned to:',addr) 23 udpSerSock.close() #这一行在这个程序里永远不会执行,只是提醒可以使用这个来考虑一个更优雅的退出方式
3) 创建UDP客户端伪代码
cs = socket() # 创建客户端套接字
comm_loop: # 通信循环
cs.sendto()/cs.recvfrom() # 对话(发送/接收)
cs .close() # 关闭客户端套接字
4) 创建UDP客户端代码
1 ''' 2 创建UDP客户端 3 ''' 4 5 from socket import * 6 7 HOST = b'localhost' 8 PORT = 21567 #端口号应与服务器设置的完成相同 9 BUFSIZ = 1024 10 ADDR = (HOST,PORT) 11 12 udpCliSOCK = socket(AF_INET,SOCK_DGRAM) 13 14 while True: 15 data = input('>') 16 if not data: 17 break 18 19 #发送给服务器的UDP消息,使用utf-8编码转成二进制数据发送,只能转成二进制数据才能发送 20 udpCliSOCK.sendto(data.encode('utf-8'),ADDR) 21 data,ADDR = udpCliSOCK.recvfrom(BUFSIZ) #接收服务器发来的UDP消息 22 if not data: 23 break 24 print(data.decode('utf-8')) 25 udpCliSOCK.close()
5) 执行TCP服务器和客户端
执行UDP服务器
执行UDP客户端
3 Python中的网络编程socketserver模块
socketserver是标准库中的一个高级模块,目标是简化很多样板代码
3.1 socketserver模块类
类 | 描述 |
TCPServer/UDPServer | 基础的网络同步TCP/UDP 服务器 |
UnixStreamServer/UnixDatagramServer | 基于文件的基础同步TCP/UDP 服务器 |
ForkingTCPServer/ForkingUDPServer | ForkingMixIn 和TCPServer/UDPServer 的组合 |
ThreadingTCPServer/ThreadingUDPServer | ThreadingMixIn 和TCPServer/UDPServer 的组合 |
BaseRequestHandler | 包含处理服务请求的核心功能;仅仅用于推导,这样无法创建这个类的实例; 可以使用StreamRequestHandler 或DatagramRequestHandler 创建类的实例 |
3.2 创建socketserver TCP服务器
1 ''' 2 创建TCP服务器 3 ''' 4 5 from socketserver import (TCPServer as TCP,StreamRequestHandler as SRH) 6 from time import ctime 7 8 HOST = '' #空白表示可以使用任何可用的地址 9 PORT = 21567 #端口 10 ADDR = (HOST,PORT) 11 12 class MyRequestHandler(SRH): #MyRequestHandler作为StreamRequestHandler的一个子类 13 #当接收到一个来自客户端的消息时,它就会调用handle()方法 14 def handle(self): 15 print('...connected from:',self.client_address) 16 #StreamRequestHandler类将输入和输出套接字看作类似文件的对象, 17 #将使用readline()来获取客户端消息,并利用write()将字符串发送回客户端 18 #因此,在客户端和服务器代码中,需要额外的回车和换行符 19 self.wfile.write(('[%s] %s' % (ctime(),self.rfile.readline().decode('utf-8'))).encode('utf-8')) 20 21 tcpServ = TCP(ADDR,MyRequestHandler) #创建TCP 服务器 22 print('waiting for connection...') 23 tcpServ.serve_forever() #无限循环地等待并服务于客户端请求
3.3 创建socketserver TCP客户端
1 ''' 2 创建TCP客户端 3 ''' 4 5 from socket import * 6 7 HOST = 'localhost' 8 PORT = 21567 #端口号应与服务器设置的完成相同 9 BUFSIZ = 1024 10 ADDR = (HOST,PORT) 11 12 while True: 13 tcpCliSOCK = socket(AF_INET,SOCK_STREAM) 14 tcpCliSOCK.connect(ADDR) 15 data = input('>') 16 if not data: 17 break 18 19 #发送给服务器的TCP消息,使用utf-8编码转成二进制数据发送,只能转成二进制数据才能发送 20 #服务端将输入和输出套接字看作类似文件的对象,需要额外的回车和换行符 21 tcpCliSOCK.send(('%s\r\n' % data).encode('utf-8')) 22 data = tcpCliSOCK.recv(BUFSIZ) #接收服务器发来的TCP消息 23 if not data: 24 break 25 print(data.decode('utf-8').strip()) 26 tcpCliSOCK.close()
3.4 执行TCP服务器和客户端
TCP服务器输出
TCP客户端输出
4 Python中的网络编程twisted框架
Twisted 是一个完整的事件驱动的网络框架,利用它既能使用也能开发完整的异步网络应用程序和协议,不是Python 标准库的一部分
4.1 下载与安装twisted
python3.6版本pip install twisted会报错,在Python扩展包的非官方Windows二进制文件里https://www.lfd.uci.edu/~gohlke/pythonlibs/ 下载Twisted-18.7.0-cp36-cp36m-win_amd64.whl,
pip install Twisted-18.7.0-cp36-cp36m-win_amd64.whl进行安装
4.2 创建Twisted Reactor TCP服务器代码
1 ''' 2 创建TCP服务器 3 ''' 4 5 from twisted.internet import protocol,reactor 6 import re 7 from time import ctime 8 9 PORT = 21567 #端口 10 11 class TSServProtocol(protocol.Protocol): 12 #重写connectionMade()方法,当一个客户端连接到服务器时就会执行connectionMade()方法 13 def connectionMade(self): 14 clnt = self.clnt = self.transport.getPeer().host #获取主机信息 15 print('...connected from:',clnt) 16 #重写dataReceived()方法,当服务器接收到客户端通过网络发送的一些数据时就会调用dataReceived()方法 17 def dataReceived(self,data): 18 self.transport.write(('[%s] %s' % (ctime(),data.decode('utf-8'))).encode('utf-8')) #将数据返回给客户 19 20 factory = protocol.Factory() #创建了一个协议工厂 21 factory.protocol = TSServProtocol #每次得到一个接入连接时,都能“制造”协议的一个实例 22 print('waiting for connection...') 23 #TCP监听器,当接收到一个请求时,就会创建一个TSServProtocol实例来处理那个客户端的事务。 24 reactor.listenTCP(PORT,factory) 25 reactor.run()
4.3 创建Twisted Reactor TCP客户端代码
''' 创建TCP客户端 ''' from twisted.internet import protocol,reactor HOST = 'localhost' PORT = 21567 #端口号应与服务器设置的完成相同 class TSClntProtocol(protocol.Protocol): def sendData(self): data = input('>') if data: print('...sending %s...' % data) self.transport.write(data.encode('utf-8')) ##将数据发送给服务器 else: self.transport.loseConnection() #不输入任何内容来关闭连接 #重写connectionMade()方法,当一个客户端连接到服务器时就会执行connectionMade()方法 def connectionMade(self): self.sendData() #重写dataReceived()方法,当客户端接收到服务器通过网络发送的一些数据时就会调用dataReceived()方法 def dataReceived(self,data): print(data.decode('utf-8')) self.sendData() class TSClntFactory(protocol.ClientFactory): protocol = TSClntProtocol #某些其他的原因而导致系统调用了clientConnectionFailed(),那么也会停止reactor clientConnectionLost = clientConnectionLostFailed = lambda self,connector,reason:reactor.stop() #创建了一个客户端工厂,一个到服务器的连接并运行reactor #实例化了客户端工厂TSClntFactory(),因为是一个客户端,所以创建单个连接到服务器的协议对象 reactor.connectTCP(HOST,PORT,TSClntFactory()) reactor.run()
4.4 执行TCP服务器和客户端
执行TCP服务器
执行TCP客户端
5 Python中的网络编程相关模块
5.1 网络/套接字编程相关模块
模块 | 描述 |
socket | 是低级网络编程接口 |
asyncore/asynchat | 提供创建网络应用程序的基础设施,并异步地处理客户端 |
select | 在一个单线程的网络服务器应用中管理多个套接字连接 |
SocketServer | 高级模块,提供网络应用程序的服务器类,包括forking 或threading 簇 |
1) select模块和socket 模块
当开发低级套接字程序时,经常配合使用select 模块和socket 模块。select 模块提供了select()函数,该函数管理套接字对象集合。它所做的最有用的一个事情就是接收一套套接字,并监听它们活动的连接。select()函数将会阻塞。
2) async*和SocketServer 模块
async*和SocketServer 模块都提供更高级的功能。它们以socket 和/或select 模块为基础编写,能够使客户端/服务器系统开发更加迅速,因为它们已经自动处理了所有的底层代码。你需要做的所有工作就是以自己的方式创建或继承适当的基类。
3) Concurrence现代化的网络框架
Concurrence 是一个搭配了libevent 的高性能I/O 系统,libevent 是一个低级事件回调调度系统。Concurrence 是一个异步模型,它使用轻量级线程(执行回调)以事件驱动的方式进行线程间通信和消息传递工作。
现代网络框架遵循众多异步模型(greenlet、generator 等)之一来提供高性能异步服务器