网络编程(五)

前言

在上一篇文章中我们学习了 基于 TCP 套接字的服务端和客户端通信,并解决了粘包的问题,解决粘包问题的思路很简单,就是确保接受方能够把数据收取干净,发多少,就收多少.

出现粘包的问题:原因有两个,一个是接收数据量少于发送数据量;一个是因为为了优化 TCP 的传输效率,使用了 Nagle算法,当客户端连续发送时间间隔很短的两个数据包时,在未确认数据发送的时候让发送器把数据送到系统缓存里.任何数据随后继续直到得到明显的数据确认或者直到攒到了一定数量的数据了再发包.

这是不可避免的,为了优化传输效率,尽量发送大块数据,避免网络中充斥的许多小数据块.知道了解决方法,那么避免粘包就很简单了,只需要在每次发送数据之前先发送真实数据的长度,然后接收方可以根据收到的真实长度收取数据了.

SOCKET使用

SOCKET AF_INET套接字主要有两种协议,现在来看 UDP 协议是如何进行网络通信的.

基于 UDP 连接的套接字

tcp 是无连接,不稳点的套接字,但是传输效率较高,所以在某些应用上如实时直播,游戏, DNS 服务器,DHCP 服务器等等.

服务端

from socket import *

serverSock = socket(AF_INET, SOCK_DGRAM)
serverSock.bind(('', 8080))

while True:
    data, client_addr = serverSock.recvfrom(1024)
    print(data)
    serverSock.sendto(data.upper, client_addr)
serverSock.close()

客户端

from socket import *

clientSock = socket(AF_INET, SOCK_DGRAM)

while True:
    msg = input('>>>').strip()
    clientSock.sendto(msg.encode('utf-8'), ('', 8080))
    data, server_addr = clientSock.recvfrom(1024)
    print(data)

当我们连续开多个客户端和服务端通信的时候,没有出现阻塞的情况,发出去的消息都可以收回来,这是因为 udp 是无连接的套接字,不用关注一个连接,只要你给我发消息拿到了发送方的 ip 和端口,那么就可以直接和你通信,而且不像 TCP 那样必须先启动服务端才可以, udp 在发送数据的时候,服务端没有启动也可以发送过去,只不过是发到了对方的系统缓存中.

那么 udp 可以实现多个客户端同时和客户端通信吗?

之前几个客户端可以同时和服务端通信是因为服务端的处理能力很大,看起来是同时通信一样,但是如果把客户端加到 1w, 甚至更多就会感觉到明显的时间差了.

那么如何可以实现真正的并发呢?关键点就在一个通信循环和连接循环互相不干扰,不用因为 i/o 堵塞而耽搁另一个循环要做的事.

Socketserver 模块实现并发

在这里 python 有一个模块为 socketserver 可以实现真正的并发.

基于 TCP 协议的并发

服务端

import sockerserver

class MyTCPhandler(socketserver.BaseRequestHandler):
    def handler(self):
        while True:
            try:
                data = self.request.recv(1024)
                if len(data) == 0:
                    break
                print('-->收到客户端的消息:', data)
                self.request.send(data.upper())
            except ConnectResetError:
                break
        self.request.close()
        
if __name__ == '__main__':
    serverSock = socketserver.ThreadingTCPServer(('127.0.0.1', 8081), MyTCPhandler)
    serverSock.serve_forever() # 和客户端进行连接

客户端

from socket import *

clientSock = socket(AF_INET, SOCK_STREAM)
clientSock.connect(('127.0.0.1', 8081))

# 通信循环
while True:
    clientSock.send(b'hello')
    data = clientSock.recv(1024)
    print(data)
    
clientSock.close()

这样就解决了 TCP 不能实现并发的问题了.

基于 UDP 协议的并发

服务端

import socketserver

class MyUDPhandler(socketserver.BaseRequestHandler):
    def handler(self):
        data, serverSock = self.request
        serverSock.sendto(data.upper(), self.client_address)
        
if __name__ == '__main__':
    server = socketserver.ThreadingUDPServer(('127.0.0.1', 8081), MyUDPhandler)
    server.serve_forever()

客户端

from socket import *

clientSock = socket(AF_INET, SOCK_DGRAM)

while True:
    clientSock.sendto(b'hello', ('127.0.0.1', 8081))
    data, server_addr = client.recvfrom(1024)
    print(data)

这就是 UDP 实现并发的模板.

总结

到此,网络编程终于告一段落了,其实我写的是网络编程里面极少的也是较为重要的一部分,还有很多底层协议没了解,像 ping 服务器时发送的 ICMP 包,还有很多,不过我觉得了解了这些其实也就了解了互联网工作的基本方式,其余的待有时间了再来学习.

如有不对的地方,欢迎指正.