用|用 Python 写网络编程(二)

回溯前文
第一篇只是开篇了数据结构和一些传输的例子。第二篇会讲网络编程传输协议在 Python 场景下如何使用。
根据上文的回复,顺序上会先讲 Tcp,下面进入正题。
复习功课:用 Python 写网络编程(一)
深度优化过的传输协议-Tcp
Tcp 标准来自上个世纪 80 年代,也是历经了 30 多年的改进和优化。但是这些优化不是应用层的,避免长篇大论,有兴趣可以自己去了解下。
这里面一些观点后面也会用 Python 去验证。
那么学习网络协议,在第一篇里面是协议用的数据结构。下一步就是学习 Tcp 和常规的 http 镞有什么不同,先从结构上进行梳理,结构分别有:
传输形态(包含传输的各种设置)
链接地址
传输形态
Http 在应用层分为多个域,整体比较复杂,比 TCP 更复杂,只是 Http 使用场景比 Tcp 广域,所以熟能生巧和更多理解。
Http 镞和 Tcp 底层都是 socket,这个 http 在 web 那层做了大量限制和改造,所以有了更多域。
Http 镞不打算这里介绍,进入正题。Tcp 也是数据流,可以获得字节数组 bytes 长度的。
传输形态这层是由 socket 做的,Python 那层做了大量的包容,不用设置很复杂的 socket 设置。
socket 原生函数需要通过 nodename、servname、ai_flags、ai_family、ai_socktype、ai_protocol 来设置
ai_family:AF_INET Ipv4,目前大部分都是 Ipv4。 AF_INET6,就是 Ipv6。
ai_socktype:SOCK_STREAM 是数据流,也就是 TCP 的流设置。SOCK_DGRAM 是数据包,UDP 的包设置。

def tcp_client_options(): """ tcp客户端设置的IPv4和数据流 (使用者) :return: """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #通道设置 return sockdef tcp_server_options(): """ tcp服务器设置(管理者) :return: """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) """ level=socket.SOL_SOCKET 所在哪个协议层 socket.SO_REUSEADDR 访问选项名 这个是一个参数int类型 """ # SO_REUSEADDR端口释放后立即就可以被再次使用 1代表开启就可以使用 sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) print(sock.getsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR))

服务器对比客户端来说是管理者身份,管理客户端链接,所以会多一个 setsockopt,设置 socket 规则或者叫配置。而客户端是使用者,所以只有设置通道(访问目标通道方式的)。
这个有个前置知识是 Tcp 握手和挥手,也是重要面试题,这里不做描述。
socket.SOL_SOCKET 是默认的代表你选择的协议层,也就是 socket 都是这个。
socket.SO_REUSEADDR 是设置,TCP 挥手后还能被使用,只能设置 1 和 0。
sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

这里修改为 2 或者-1 下面打印还是 1,这个也是 Python 做了大量的封装包容的体现。
socket.SO_REUSEADDR 代表的是 optname 的控制方式,也就是挥手断开连接。
Http 是单次请求交互模式,也就是说你发一个数据流请求,会立即被投递到服务器,进行验证合法,合法后应答后
Tcp 不是,Tcp 是发一组(多段数据流请求后),但是服务器不是立即接受,是会拥堵在网络缓存区内,到达一定数量在发给服务器。
所以这个也是初学者经常会遇到的,假设服务器完全接收到客户端信息,客户端发了 10 次大小共 5000 个字节,服务器那边往往只会收到 3-4 次,大小总量也是 5000 个字节。这个可以在看完本文后,自己试试写个例子。
可以设置缓存区大小,缓存区大小会和 Tcp 重要概念有关,前面例子 plus:
def tcp_server_options(): """ tcp服务器设置 :return: """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) print(sock.getsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR)) #设置发送缓存区容纳单位 sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 16 * 1024) print(f'Send -> {sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)}') print(f'Revice -> {sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)}')

socket.SO_RCVBUF 是由协议层决定的,协议层这里常量是通过协议层默认大小来表示(双关),65535+1 的 65536,当然这个默认大小可以修改的,通常建议修改成 10 万,根据机器因地制宜。
socket.SO_SNDBUF 就是 16 * 1024 的,那么如果不设置会是多少呢。在 socket 层有很多双关,默认也是 65536。
还可以设置通信阻塞,这个阻塞是服务器处理客户端 socket 请求的,阻塞是会处理完才会接收同一个客户端的下一条请求,但是高并发场景下,多个 work 线程的 Tcp 服务器一般都是非阻塞的,这里的设置是全局设置的,会对整个服务器当前启动生效。
sock.setblocking(False) #设置非阻塞的。
还可以设置超时时间,但是不要画鱼加红烧肉这种做法,添加了超时时间会和上面设置阻塞有一些冲突,会影响正常设置了缓存区大小等,所以不推荐使用。
这个和平时测试工具使用的 requests 里面 timeout 不一样。
常规设置都已经讲了,下面讲服务器如何去让客户端投递数据给他。
2.链接地址
根本上没差别,网络传输从 1 端到另外 1 端,要传输都需要有地址 接收者 iP+ 端口。
客户端需要知道服务器的链接地址才能把消息投递到正确的地方,服务器拥有接收者 iP 会分配一个地址也就是端口,然后 bind 端口,监听链接到这个地址去管理客户端。
服务器还会有一个最大支持链接数,客户端是没有这个设置的,下面例子里面 max=5000,所以客户端和服务器关系是多对一的,最多支持 5000 个客户端链接到同个服务器。
看到上面,划重点关键字 bind 和 listen,给个例子,区分客户端和服务器代码:
def server_listener(addr: tuple, max: int): """ 服务器监听 最大支持5000个链接数 :param max:最大支持链接数 :param addr:元组 :return: """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置挥手后可以使用 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(addr) # 最大支持max个链接数 sock.listen(max) return sockif __name__ == '__main__': server_listener(("0.0.0.0", 12580),5000)

http 的服务器因为包装的很好,并不会有这种设置,服务器都是一个监听客户端 fd 和轮询管理这些 fd 的。
客户端&服务器用同一套 socket options,这样服务器才能接收链接客户端的消息,fd=sock.fileno()。
获取方式以及客户端链接代码,如下例子:
def create_client(): """ 创建客户端 :return: """ # 客户端和服务器设置通道的socket肯定是要一样的,否则链接不过来 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sockfd = sock.fileno() print(f"客户端fd={sockfd}") sock.connect(("127.0.0.1", 12580)) return sock if __name__ == '__main__': client = create_client() print(client)

客户端链接代码里面,地址不能是 0.0.0.1,本机测试也只能是 127.0.0.1。
connect_ex() 不为 0,会返回 error 的码,会在和协议号和错误协议号串联一起的后面章节来讲。
服务器监听客户端 fd 的代码如下,核心函数是 accept():
def accept_client_connect(listener_sock): """ 服务器处理 确认客户端链接 核心方法是accept() :param listener_sock:服务器sock对象 :return: """ new_conn = listener_sock.accept() if new_conn == None: return sock, addr = new_conn # 服务器监听可以拿到那边拿sock的fd print(f"---accept_client_connection, sockfd={sock.fileno()}, addr={addr}") return sockif __name__ == '__main__': sock = server_listener(("0.0.0.0", 12580), 5000) sock_ = accept_client_connect(sock) print(sock_)

最终打印
---accept_client_connection, sockfd=512, addr=('127.0.0.1', 14024)

addr 端口就是服务器给客户端临时分配的端口,服务器就是通过字典管理,sockfd 和 addr[1],下面这段描述可以让其他语言也可以理解。
ClientInfo = {"client_fd":sockfd,"fd_port":addr[1]} ClientGroup = []ClientInfo {"gateway_threads":ClientGroup}

Workthread 分属不同作用,服务器里面的 gateway 服务 bind clientGroup 数组,这些细节后面章节会有具体的例子。
预告&总结
预告 socket 发包部分流程
step1: socket options(第二篇)
step2: 断开 socket,struct+pack 包 (数据结构)+ 压包 (数据结构在第一篇,第三篇是断开 socket 和 struct)
step3: 压缩的多种形式 (第四篇)
step4: 加密的多种形式 (第五篇)
总结
需要对 socket 设置做大量练习和理解更深入的。其实 http 非 Python 得也有很多设置,建议也可以深入理解。
写法上都会支持客户端,服务器例子都有。
没有讲的 socket.shutdown 和 struct 这个库可以预习下。
> 本文首发于TesterHome社区,作者是资深游戏测试开发工程师陈子昂。用 Python 写网络编程共三篇,会陆续分享给大家。原文链接:https://testerhome.com/topics...
用|用 Python 写网络编程(二)
文章图片

以上是今天的分享,你学废了吗~
想学习更多干货知识和前沿技术?
想结识测试行业大咖和业界精英?
欢迎关注2022 MTSC大会(第十届中国互联网测试开发大会)
业界对MTSC大会的评价:落地、务实、有深度、重分享
用|用 Python 写网络编程(二)
文章图片

中国互联网测试开发大会 Testing Summit China,是由TesterHome发起的软件测试开发行业技术会议。还有个别名:Make Testing Super Cool!
MTSC大会以软件质量保障体系和测试研发技术交流为主要目的,始于 2015 年,已成功举办了九届。共有 1000+ 家企业,10000+ 测试工程师、测试经理、CTO 参会,受到了全行业的广泛关注,是中国互联网质量保证行业的顶级会议。
为了保证议题质量,每年都会提前半年进行议题征集,再经过历时数月的审核,最终确定在大会分享的议题。MTSC 2022的议题还在征集中,诚邀各位资深测试技术专家、质量管理经理和测试新秀们投递议题!
提交议题方式
直接点击 https://www.wjx.top/vj/wZwCju... 进入投递入口,按照格式进行填写并提交即可。
议题截止时间
为便于评审组有更充分时间进行评审及与讲师进行沟通优化,为广大参会者呈现更好的议题,议题投稿(可无 ppt)投递截止时间提前为:2022年3月30日
议题征集评选流程
总体流程:案例提交> 初审核定 > PPT提交 > 确认议题 > 会议演讲
用|用 Python 写网络编程(二)
文章图片

成为分享讲师的收益
  1. 锻炼演讲能力,增强个人品牌
  2. 获得和业内专家面对面交流的机会,博采众长
  3. 提升公司品牌,给自己的团队增加吸引力
  4. 获取免费的大会门票和资料:
    每位讲师都会获赠 1 张大会门票,大会后续的PPT和视频都会第一时间给到讲师
【用|用 Python 写网络编程(二)】MTSC 2022早鸟票已悄悄开售,点击了解详情。

    推荐阅读