β

Python3萌新入门笔记(51)

Python自动化运维 88 阅读

这一篇教程,我们通过例子对网络编程相关的一些模块进行简单的了解。

一、socket模块

通过socket模块,我们能够创建服务器和客户端。

这里我们需要使用socket模块中的socket()函数,这个函数能够使用给定的地址族、套接字类型和协议号创建一个新的套接字。

创建一个普通的套接字,可以省略所有参数。

当创建了一个套接字,就需要使用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通道

如上方代码所示,一个简单的服务器,我们只需要以下几个步骤:

在此基础上,就能够进行更多的操作。例如,示例代码中通过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()。

这样,当服务器接收到一个客户端连接发来的请求时,就实例化一个对请求进行处理的类,并调用实例中的请求处理方法进行处理。

另外,当服务器与客户端进行通信时,我们可以使用以下方法:

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模块来实现多连接并发的处理。

我们来看一个例子,这个例子实现下述功能:

首先,我们先编写客户端的代码。

示例代码:(客户端)

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])函数。

这个函数的三个必填参数均为序列,序列中为“等待的对象”,可选参数为数字:

函数的返回值是一个元组,包含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)

作者:Python自动化运维
写python,然后喝茶,指挥千军万马~
原文地址:Python3萌新入门笔记(51), 感谢原作者分享。

发表评论