这一篇教程,我们通过例子对网络编程相关的一些模块进行简单的了解。
一、socket模块
通过socket模块,我们能够创建服务器和客户端。
这里我们需要使用socket模块中的socket()函数,这个函数能够使用给定的地址族、套接字类型和协议号创建一个新的套接字。
- 地址族:默认为socket.AF_INET,指定使用IPv4 网络协议;如果填入socket.AF_INET6,则能够使用IPv6 网络协议。
- 套接字类型:默认为socket.SOCK_STREAM,指定使用TCP协议的套接字流类型;如果填入socket.SOCK_DGRAM,则是使用UDP的套接字数据报类型。
- 协议号:默认为0。
创建一个普通的套接字,可以省略所有参数。
当创建了一个套接字,就需要使用bind()方法为其绑定主机地址和端口号,以便能够进行连接。
并且,我们还需要通过listen()方法,指定套接字最大等待连接数。也就是客户端与服务器连接时,允许有多少个客户端同时等待连接。当等待连接的数量超出最大限制数量时,后发起连接的客户端会被拒绝连接。
服务器与客户端的连接通过accept()方法完成,这个方法能够返回一个SSL(Secure Sockets Layer 安全套接层)通道和客户端的IP地址。
通过SSL通道,我们可以实现服务器和客户端的通信。
import socket skt = socket.socket() # 创建套接字对象 host = '127.0.0.1' # 指定主机IP地址 port = 6666 # 指定连接端口号 skt.bind((host, port)) # 为套接字对象绑定主机名称与端口号 skt.listen(3) # 设置套接字最大等待连接数 while True: print('等待连接...') ssl, addr = skt.accept() # 获取服务器端的SSL通道和远程客户端的地址 print('连接到:', addr) cname = ssl.recv(1024).decode() # 获取客户端发来的信息 ssl.send('欢迎来自{0}的{1}'.format(addr, cname).encode()) # 通过SSL通道发送信息 print('关闭连接...') ssl.close() # 关闭SSL通道
如上方代码所示,一个简单的服务器,我们只需要以下几个步骤:
- 创建套接字对象:socke()
- 绑定主机与端口:bind()
- 设置等待连接数:listen()
- 接收客户端连接:accept()
在此基础上,就能够进行更多的操作。例如,示例代码中通过send()方法向客户端发送信息、通过recv()方法接收服务器发送的信息以及通过close()方法关闭与客户端的连接。
在使用send()方法和recv()方法时,注意进行编解码的操作。
send()方法参数为发送的数据。
recv()方法的参数是一次读取的字节数量,如果没有特别的要求或无法确定,不妨就写1024。
但是,只有服务器的话,我们无法验证这个服务器能否正常的工作。
我们再来创建一个客户端。
相对于服务器,客户端的创建非常简单,只需要创建一个套接字,然后通过connect()方法向服务器请求连接。
当连接到服务器之后,即可与服务器进行通信。
示例代码:(客户端)
import socket skt = socket.socket() # 创建套接字对象 host = '127.0.0.1' # 指定要连接到的主机IP地址 port = 6666 # 指定连接端口号 skt.connect((host, port)) # 发起连接 print('接收信息:', skt.recv(1024).decode()) # 接收来自服务器端的信息
以上两段代码,创建了简单的服务器和客户端。
我们可以运行服务器,然后运行多个客户端进行测试。
在PyCharm中,可以在客户端的编辑区多次按下快捷键<Ctrl>+<Shift>+<F10>启动多个客户端。
当服务器启动之后,会显示“等待连接…”。
当第一个客户端启动之后,会显示类似“连接到: (‘127.0.0.1’, 57409)”的信息。
当完成数据的发送之后,会显示“关闭连接…”。
而能够与服务器正常连接的客户端,会显示类似“接收信息: 欢迎你,(‘127.0.0.1’, 57337)”的信息(注意进行解码)。
不能够与服务器进行连接的客户端,会抛出连接拒绝异常“ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。”。
二、socketserver模块
除了使用socket模块能够创建服务器,通过socketserver模块也能够创建服务器。
socketserver模块的TCPServer类和UDPServer类分别针对TCP套接字流和UDP套接字数据报。
仍然以TCP服务器的创建举例。
在这个socketserver模块中有这样对请求进行处理的类。
如果使用socketserver模块创建一个服务器,我们需要创建一个对请求进行处理的类,继承StreamRequestHandler类,并重写请求处理方法handle()。
这样,当服务器接收到一个客户端连接发来的请求时,就实例化一个对请求进行处理的类,并调用实例中的请求处理方法进行处理。
另外,当服务器与客户端进行通信时,我们可以使用以下方法:
- self.request.recv():接收客户端连接请求中发来的信息
- self.rfile.readline():读取客户端发来的信息
- self.request.send():向客户端连接请求发送信息(可能多次发送,直至完成)
- self.request.sendall():向客户端连接请求发送信息(一次全部发送)
- self.wfile.write():向客户端写入并发送信息
rfile和wfile是StreamRequestHandler类的两个属性,通过这两个属性能够进行写入与读取,所以通过这两个类文件对象(file-like Object)就能够实现服务器和客户端的通信。
提示:Python中的类文件对象(file-like object)是指实现了read()方法或write()的对象。根据创建的方式,一个文件对象可以是一个真正的磁盘文件,也可以是对存储或通信设备的访问(例如标准输入/输出、内存缓冲区、套接字、管道等)。所以,文件对象也可称为类文件对象或流。
示例代码:(服务端)
from socketserver import TCPServer, StreamRequestHandler class Handler(StreamRequestHandler): # 定义类继承对数据流请求处理的类 def handle(self): # 定义处理方法 addr = self.client_address # 获取客户端地址 print('处理来自%s的连接...' % (addr,)) cname = self.request.recv(1024).decode() # 获取客户端发来的信息 # cname=self.rfile.readline().decode() # 获取客户端发来的信息 self.request.sendall('欢迎来自{0}的{1}'.format(addr, cname).encode()) # 向客户端发送信息 # self.request.send('欢迎来自{0}的{1}'.format(addr, cname).encode()) # 向客户端发送信息 # self.wfile.write('欢迎来自{0}的{1}'.format(addr, cname).encode()) # 向客户端发送信息 server = TCPServer(('', 6666), Handler) # 实例化TCP服务器对象 server.serve_forever() # 持久运行服务器
不过,前两个服务端示例代码我们通过测试能够知道,多个客户端与服务器的连接是按顺序进行的。
在一个客户端的连接未执行结束之前,无法进行下一个客户端的连接。
这样的服务器端编程称为阻塞或同步网络编程。
那么,如何实现非阻塞的网络编程,或者说异步网络编程呢?
对于通过socketserver模块创建的服务器,我们可以使用多线程实现多连接的处理。
通过创建一个新的服务器类,继承ThreadingMixIn类和 TCPServer类,就能够完成多连接并发的处理。
示例代码:
from socketserver import TCPServer, StreamRequestHandler, ThreadingMixIn # 导入ThreadingMixIn类 import threading class Server(ThreadingMixIn, TCPServer): #创建服务器类(注意继承顺序) pass # 无需添加代码 class Handler(StreamRequestHandler): def handle(self): addr = self.client_address print('线程%s处理来自%s的连接...' % (threading.current_thread().name, addr)) cname = self.request.recv(1024).decode() self.request.sendall('欢迎来自{0}的{1}'.format(addr, cname).encode()) server = Server(('', 6666), Handler) # 实例化服务器对象 server.serve_forever()
三、select模块
通过socket模块创建的服务器,我们可以使用select模块来实现多连接并发的处理。
我们来看一个例子,这个例子实现下述功能:
- 客户端向服务器发送需要翻译的汉字,服务器端将相应的翻译结果(英文单词)发送回客户端。
- 如果没有相应的翻译结果,服务器端发回信息:未找到对应的英文单词!
- 如果客户端未发送的信息为空值或“exit”,能够关闭服务器。
首先,我们先编写客户端的代码。
示例代码:(客户端)
import socket s = socket.socket() # 创建套接字 host = socket.gethostname() port = 6666 s.connect((host, port)) # 向服务器端请求连接 while True: char = input('输入中文:') # 获取用户输入 s.send(char.encode()) # 编码后发送到服务器端 if char in ['exit', '']: # 如果发送的内容为"exit"或空值 break # 跳出循环,结束程序 print('英文单词:', s.recv(1024).decode()) # 显示输出来自服务器端的信息
接下来,编写服务器端的代码。
先定义一个翻译功能的函数。
示例代码:(服务器端)
import socket, select def translate(char): # 定义翻译功能的函数 my_dict = {'你': 'you', '我': 'me', '他': 'he'} # 定义可翻译的内容 if char not in my_dict: # 如果字典中不包含客户端发来的汉字 return '未找到对应的英文单词!' # 返回提示信息 else: # 否则 return my_dict[char] # 返回翻译结果
再创建套接字,绑定主机地址与端口,并设置最大等待连接数。
示例代码:
s = socket.socket() # 创建套接字 host = socket.gethostname() # 获取主机名 port = 6666 # 指定端口号 s.bind((host, port)) # 套接字绑定主机名与端口号 s.listen(5) # 指定最大等待连接数
然后,通过select模块实现异步I/O。
这里,我们使用select.select(rlist, wlist, xlist[, timeout])函数。
这个函数的三个必填参数均为序列,序列中为“等待的对象”,可选参数为数字:
- rlist:一个列表,包含等待直到能够读取的对象;可以为空列表。
- wlist:一个列表,包含等待直到能够写入的对象;可以为空列表。
- xlist:一个列表,包含等待出现一个“异常状态”的对象;可以为空列表。
- timeout:连接超时的时长,值为秒的浮点数;省略此参数时,函数将阻塞,直到至少一个“等待的对象”准备好;如果参数为0,则从不阻塞。
函数的返回值是一个元组,包含3个已经准备好的对象的列表,当没有准备好的对象或连接超时,则返回3个空列表。
示例代码:
inputs = [s] # 创建等待能够读取对象的列表,默认包含等待连接的套接字。 while True: print('等待连接...') rs, ws, xs = select.select(inputs, [], []) # 获取已经准备好的对象列表 for r in rs: # 遍历已经准备好的可读取对象列表 if r is s: # 如果列表元素是套接字对象 ssl, addr = r.accept() # 等待客户端连接 print('连接到:', addr) print(type(ssl)) inputs.append(ssl) # 连接成功后,将SSL通道对象添加到等待可读对象列表 else: # 否则(对象是SSL通道) try: char = r.recv(1024).decode() # 通过SSL通道接收来自客户端的信息 if char in ['', 'exit']: # 判断信息内容是否空值或“exit” print(addr, '主动关闭连接!') inputs.remove(r) # 从等待可读取对象的列表中移除SSL通道对象 else: # 否则 r.send(translate(char).encode()) # 通过SSL通道向客户端发送翻译结果 print('完成任务...') except: # 捕获连接出现的异常 print(addr, '异常断开连接!') inputs.remove(r) # 从等待可读取对象的列表中移除SSL通道对象
当我们启动上方的服务器和多个客户端,就能够看到任何一个客户端都能够即时与服务器进行连接,而无需等待前一个发起连接的客户端连接结束。
本节知识点:
1、使用socket模块实现服务器和客户端的连接;
2、使用select模块进行异步网络编程。
本节英文单词与中文释义:
1、AF(address family的简写):地址族
2、stream:流
3、DGRAM(datagram的简写):数据报
4、iNET(integrated Network Enhanced Telemetry的简写:遥测网络体系结构和标准,代指一切支持IP协议的网络。
5、translate:翻译
6、recv(receive的简写):收到
7、connect:连接
8、refuse:拒绝
9、host:主机
10、timeout:超时
11、char(character的简写):字符
12、secure:安全
13、layer:层
14、bind:绑定
15、port:端口
16、request:请求
17、handle:处理
18、mix in:混合
转载请注明:魔力Python » Python3萌新入门笔记(51)