当前位置: 首页 > 后端技术 > Python

用Python写网络程序(二)

时间:2023-03-26 18:02:52 Python

回顾上一篇文章的第一篇,只是开始数据结构和一些传输实例。第二部分会讲网络编程传输协议在Python场景下是如何使用的。按照上面的回复,先按顺序讲Tcp,然后进入正题。复习作业:用Python写网络程序(一)深度优化的传输协议——TcpTcp标准来自1980年代,经过30多年的不断改进和优化。但是这些优化都不是在应用层,所以避免长篇大论,有兴趣的可以自己去了解一下。后面会用Python验证这里的一些要点。然后学习网络协议,第一部分是协议使用的数据结构。接下来就是学习Tcp和常规http的区别了。首先,梳理结构。结构是:传输形式(包括传输的各种设置)链接地址传输形式Http在应用层被划分为多个域,总体来说比TCP更复杂,更复杂,但是Http比Tcp使用的范围更广,所以实践使完美和更多的理解。Http和Tcp底层都是socket。这个http在web层做了很多限制和修改,所以域比较多。Http这里就不介绍了,进入正题。tcp也是一个数据流,可以得到byte数组bytes的长度。传输模式层由sockets做成,Python层做了大量的容忍,所以不需要进行复杂的socket设置。socketnative函数需要通过nodename,servname,ai_flags,ai_family,ai_socktype,ai_protocol设置ai_family:AF_INETIpv4,目前大部分都是Ipv4。AF_INET6是IPv6。ai_socktype:SOCK_STREAM为数据流,即TCP的流设置。SOCK_DGRAM是数据包,UDP的数据包设置。deftcp_client_options():"""TCP客户端(用户)设置的IPv4和数据流:return:"""sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#channelsettingreturnsockdeftcp_server_options():"""tcpserversettings(manager):return:"""sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)"""level=socket.SOL_SOCKET哪个协议层socket.SO_REUSEADDR访问选项名这是int的一个参数type"""#SO_REUSEADDRportcanbeusedimmediatelyafterrelease1表示打开后可以使用sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)print(sock.getsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR))相对于client,server是manager,管理client的连接,所以多了一个setsockopt来设置socket规则或者配置。而client就是用户,所以只设置了channel(访问目标频道的方式)。这里有一个前提知识就是Tcp握手和wave,这也是一道重要的面试题,这里就不赘述了。socket.SOL_SOCKET代表你默认选择的协议层,也就是所有的socket都是这样。socket.SO_REUSEADDR是一个设置,TCPwave之后仍然可以使用,只能设置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这个重要的概念。前面的例子加上:deftcp_server_options():"""tcpserversetting: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。当然默认大小是可以修改的。通常建议修改为100,000,根据当地机器情况。socket.SO_SNDBUF是16*1024,不设置会怎样。socket层双关语很多,默认也是65536,也可以设置通信阻塞。这种阻塞是为了服务器处理客户端套接字请求。阻塞将在处理后接收来自同一客户端的下一个请求。但是在高并发场景下,多工作线程的Tcp服务器一般都是非阻塞的,这里的设置是全局设置,会在整个服务器当前启动时生效。sock.setblocking(False)#设置非阻塞。也可以设置超时时间,但是不要加鱼肉和红烧肉。添加超时时间会和上面的阻塞设置有冲突,会影响缓冲区大小的正常设置,所以不推荐。这与测试工具使用的请求超时不同。一般设置已经讨论过了。下面说一下服务端是如何让客户端向他传递数据的。2、链接地址基本没有区别。1端到另外1端的网络传输,需要一个地址接收器iP+口进行传输。客户端需要知道服务器的链接地址,以便将消息传递到正确的地方。服务端有接收者IP,会分配一个地址,也就是端口,然后绑定端口监听链接到这个地址来管理客户端。服务器还将具有最大支持连接数。客户端没有这个设置。在下面的例子中,max=5000,所以客户端和服务器是多对一的关系,最多支持5000个客户端连接到同一台服务器。看到上面,标注关键关键字bind和listen,并举例区分client和server代码:defserver_listener(addr:tuple,max:int):"""serverlistener最大支持5000个连接:parammax:最大支持链接数:paramaddr:tuple:return:"""sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#设置wave后可以使用sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)sock.bind(addr)#支持的最大链接数sock.listen(max)returnsockif__name__=='__main__':server_listener(("0.0.0.0",12580),5000)因为httpserver封装的很好,没有这个设置,server就是监听client的fd,轮询管理这些fd的。客户端和服务器使用同一组套接字选项,以便服务器可以接收来自客户端的消息,fd=sock.fileno()。获取方法和客户端链接代码,如下例所示:defcreate_client():"""Createclient:return:"""#客户端和服务端设置通道的socket必须相同,否则连接不上工作sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)sockfd=sock.fileno()print(f"clientfd={sockfd}")sock.connect(("127.0.0.1",12580))returnsockif__name__=='__main__':client=create_client()print(client)客户端链接代码中地址不能为0.0.0.1,本地测试只能为127.0.0.1。如果connect_ex()不为0,则返回错误码,后面的章节会结合协议号和错误协议号进行描述。服务端监听客户端fd的代码如下,核心函数是accept():defaccept_client_connect(listener_sock):"""服务端处理确认客户端连接的核心方法是accept():paramlistener_sock:serversockobject:return:"""new_conn=listener_sock.accept()ifnew_conn==None:returnsock,addr=new_conn#服务器监听可以拿到那边sock的fdprint(f"---accept_client_connection,sockfd={sock.fileno()},addr={addr}")returnsockif__name__=='__main__':sock=server_listener(("0.0.0.0",12580),5000)sock_=accept_client_connect(sock)打印(sock_)最终打印---accept_client_connection,sockfd=512,addr=('127.0.0.1',14024)addr端口是服务器临时分配给客户端的端口,服务器通过字典、sockfd和addr[1]进行管理,f以下描述可以被其他语言理解。ClientInfo={"client_fd":sockfd,"fd_port":addr[1]}ClientGroup=[]ClientInfo{"gateway_threads":ClientGroup}工作线程属于不同的函数。服务器中的网关服务绑定clientGroup数组。这些细节在后面的章节中会有具体的例子。Preview&Summary预览socket合约交付流程step1:socketoptions(Part2)step2:disconnectsocket,struct+pack(datastructure)+pack(datastructurepart1,part3isdisconnectsocketandstruct)step3:Variousformsofcompression(Part4)step4:Variousformsofencryption(Part5)小结需要大量的实践和对套接字设置的更深入的理解。http非Python其实还有很多设置,建议也可以深入理解。写法会支持客户端,有服务端的例子。没有提到的socket.shutdown和struct库可以预览。>