摘录 python核心编程
使用socket()模块函数创建套接字——通信端点
>>> from socket import * >>> tcpSock = socket(AF_INET,SOCK_STREAM) >>> udpSock = socket(AF_INET,SOCK_DGRAM)
其中,AF_INET表示使用的是IPv4协议,SOCK_STREAM表示的面向连接的TCP协议,SOCK_DGRAM表示面向无连接的UDP协议。
在创建TCP和UDP客户端与服务器前,先看看socket模块的属性以及套接字对象的方法
socket模块的属性:
属性名 | 描述 |
数据属性 | |
AF_UNIX、AF_INET、AF_INET6、AF_NETLINK、AF_TIPC | python支持的套接字地址家族 |
SO_STREAM、SO_DGRAM | 套接字类型(TCP=流,UDP=数据报) |
has_ipv6 | 表示是否支持IPv6的布尔标志 |
异常 | |
error | 套接字相关错误 |
herror | 主机和地址相关错误 |
gaierror | 地址相关错误 |
timeout | 超时时间 |
函数 | |
socket() | 以给定的地址家族、套接字类型和协议类型(可选)创建一个套接字对象 |
socketpair() | 以给定的地址家族、套接字类型和协议类型(可选)创建一对套接字对象 |
create_connection() | 常规函数,它接收一个地址(主机、端口号)对,返回套接字对象 |
fromfd() | 以一个打开的文件描述符创建一个套接字对象 |
ssl() | 通过套接字启动一个安全套接字连接,不执行证书验证 |
getaddrinfo() | 获取一个五元组序列形式的地址信息 |
getnameinfo() | 给定一个套接字地址,返回二元组(主机名、端口号) |
getfqdn() | 返回完整的域名 |
gethostname() | 返回当前主机名 |
gethostbyname() | 将一个主机名映射到它的IP地址 |
gethostbyname_ex() | gethostname()的扩展,返回主机名、别名主机集合和IP地址列表 |
gethostbyaddr() | 将一个IP地址映射到DNS信息;返回和gethostbyname_ex()相同的三元组 |
getprotobyname() | 将一个协议名映射到一个数字 |
getservbyname()/getservbyport() | 将一个服务名映射到一个端口号,或者反过来;任何一个函数,协议名都是可选的 |
ntohl()/ntohs() | 将来自网络的整数转换为主机字节顺序 |
htonl()/htons() | 将来自主机的整数转换为网络字节顺序 |
inet_aton()/inet_ntoa() | 将IP地址八进制字符串转换为32位包格式,或者反过来(仅用于IPv4地址) |
inet_pton()/inet_ntop() | 将IP地址字符串转换为打包的二进制格式,或者反过来(适用于IPv4和IPv6) |
getdefaulttimeout()/setdefaulttimeout() | 以秒为单位,获得/设置默认套接字超时时间 |
套接字对象方法和属性:
名称 | 描述 |
服务器套接字特有方法 | |
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 | 套接字协议 |
创建TCP服务器
首先先来说说服务器设计的一般思路(伪代码):
ss = socket() #1、创建服务器套接字
ss.bind() #2、套接字于地址绑定
ss.listen() #3、监听连接
inf_loop: #4、服务器无限循环:
cs = ss.accept() #1)接收客户端连接
comm_loop: #2)通信循环
cs.recv()/cs.send() #①对话(接收/发送)
cs.close() #3)关闭客户端套接字
ss.close() #5、关闭服务器套接字(如有必要)
值得关心的是accept()的调用。该步骤默认会以阻塞的形式开启一个单线程服务器,用于等待客户端的连接,如果连接成功,则会返回一个独立的客户端套接字,用于和即将到来的消息进行交换,直到连接终止(终止方式一般是一方关闭连接或者向另外发送一个空字符串)。
下面的tsTserv.py脚本描述的是一个TCP服务器,接收来自客户端的消息,然后将消息加上时间戳前缀并发送给客户端:
#导入了time.time()和socket模块的所有属性 from socket import * from time import ctime HOST = '' #空白的变量,表示可以使用任何可用的地址 PORT = 21567 #端口号 BUFSIZ = 1024 #缓冲区大小,单位是bite ADDR = (HOST,PORT) tcpSerSock = socket(AF_INET,SOCK_STREAM) #创建一个TCP套接字 tcpSerSock.bind(ADDR) #绑定地址 tcpSerSock.listen(5) #启动TCP监听,其中5表示在连接被转接或拒绝之前,传入连接请求的最大数 while True: #服务器无限循环 print('服务器等待连接……') tcpCliSock,addr = tcpSerSock.accept() #被动等待客户端的连接,当连接请求出现的时候,会返回一个新的套接字和客户端的地址组成的元组,于该客户端的通信是在这个新的套接字上进行数据的接受和发送s print('……来自于:',addr) while True: #上述新套接字中进行通信循环 data = tcpCliSock.recv(BUFSIZ).decode('utf-8') #接受客户端发送过来的消息,从网络传输过来的是bytes类型的,需要解码 print(data) if not data: #如果客户端发送的内容为空,认为客户端已经关闭,此时应该退出通信循环 break tcpCliSock.send(('[%s] %s' % (ctime(),data)).encode('utf-8')) #服务器将处理的内容发送给客户端,需要将字符串类型数据转化为bytes数据。 tcpCliSock.close() #关闭当前客户端连接,下一步是等待另外一个客户端 tcpSerSock.close()
创建TCP客户端
下面给出创建客户端的伪代码:
cs = socket() #创建客户端套接字
cs.connect() #尝试连接服务器
comm_loop: #通信循环
cs.send()/cs.recv() #对话(发送/接收)
cs.close() #关闭客户端套接字
下面的tsTclnt.py脚本是和上面创建的服务器相关的客户端代码-连接服务器,并以逐行数据的形式提示用户,并展示从服务器返回的数据:
#导入socket模块的所有属性 from socket import * HOST = '192.168.1.125' #服务器的主机名,这里是在本地一台计算机上测试,所以这里是本地计算地址 PORT = 21567 #服务器的端口号,必须和服务端的端口号设置一样 BUFSIZ = 1024 ADDR = (HOST,PORT) tcpCliSock = socket(AF_INET,SOCK_STREAM) #创建客户端套接字 tcpCliSock.connect(ADDR) #主动调用并尝试连接服务器 while True: #无限循环 data = input('>') #等待客户端的录入 if not data: #如果用户没有输入任何的东西,则退出无限循环 break tcpCliSock.send(data.encode('utf-8')) #将客户端的数据发送到服务器,并将字符串编码为bytes类型 data = tcpCliSock.recv(BUFSIZ).decode('utf-8') #接收服务器返回的数据 if not data: #如果服务器终止了或者上一步的recv()方法调用失败的话,也会退出无限循环 break print(data) #正常情况下,从服务器返回的数据会被打印出来 tcpCliSock.close() #关闭客户端套接字
运行结果
首先,启动服务器
然后,在另外一台计算机(或者本机,记得更改对应的IP地址呦)上执行客户端脚本。然后就可以进行两台计算机的通信表演了。
这里要特别强调的是,我测试的版本是3.6x,通信端点发送接收内容的一定要进行编码和解码!因为,区别于2.x版本,在python3.x版本中,字符串和bytes是两种不同的数据类型了。
创建UDP服务器
和TCP服务器相比,UDP不需要那么多设置(因为他不是面向连接的),下面是伪代码:
ss = socket() #创建服务器套接字
ss.bind() #绑定服务器套接字
inf_loop: #服务器无限循环
cs = ss.recvfrom()/ss.sendto() #关闭(接收/发送)
ss.close() #关闭服务器套接字
上脚本:
#导入需要的模块 from socket import * from time import ctime HOST = '' PORT = 21567 BUFSIZ = 1024 ADDR = (HOST,PORT) udpSerSock = socket(AF_INET,SOCK_DGRAM) udpSerSock.bind(ADDR) #这里明显和TCP不同,没有所谓的‘监听传入的连接’的动作 while True: print('等待接收消息……') data,addr = udpSerSock.recvfrom(BUFSIZ) #接收数据报 udpSerSock.sendto(('时间:%s 地址:%s 内容:%s' % (ctime(),addr,data)).encode('utf-8'),addr) #给客户端返回数据 print('……接收并返回数据于:',addr) #服务器上打印记录信息 udpSerSock.close() #一般来说用不到,更优雅的方法是将无限循环放入一个try catch模块中,在捕捉异常或者finally中实现关闭套接字
创建UDP客户端
from socket import * HOST = 'localhost' PORT = 21567 BUFSIZ = 1024 ADDR = (HOST,PORT) udpCliSOcket = socket(AF_INET,SOCK_DGRAM) while True: data = input('请输入:') if not data: break udpCliSOcket.sendto(data.encode('utf-8'),ADDR) data,ADDR = udpCliSOcket.recvfrom(BUFSIZ) if not data: break print(data.decode('utf-8'),ADDR) udpCliSOcket.close()
看到:UDP和TCP客户端循环的方式基本一样,唯一的区别在于,事先不需要建立与UDP服务器的连接,只是简单的发送一条消息并等待服务器的回复。