Python进阶-TCP/UDP
- coding
- python
- advanced
TCP
socket
socket 表示网络连接,相当于建立了客户端和服务器的网络通路,打开一个 socket 需要目标计算机的 ip 地址和端口。
客户端
Python 中提供了 socket 模块来帮助编写网络编程代码。创建 socket 时,AF_INET
表示使用 IPv4 协议,SOCK_STREAM
表示建立 TCP 连接。
import socket
我们创建一个 socket 对象,创建后需要连接才能建立与服务端的网络通路,这里假设服务器为 localhost,端口为 8080:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server = ('127.0.0.1', 8080)
s.connect(server)
这样我们就建立起了与 localhost 的 TCP 连接,建立连接时,服务器可能会返回数据,来告知用户已连接成功:
s.recv(1024).decode('utf-8')
由于在网络中都是以 bytes 形式传输的,所以这里我们需要解码。
此时 TCP 已成功建立,我们可以使用 socket 与服务器通信,同样要注意数据编码与解码:
for item in [b'Genji', b'Hanzo', b'Mcree']:
s.send(item) # 发送
print(s.recv(1024).decode('utf-8')) #接收
s.send(b'exit') # 发送
s.close() # 单边关闭socket
服务端
服务器端也需要声明 socket,并监听 ip 和 端口:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8080))
s.listen(5) # 表示最大的等待数
这里可能会有点疑惑,一台服务器的 IP 不是固定的吗?其实还真不是。服务器可能会有多张网卡,也就有了多个 IP 地址,可以监听其中某个 IP 地址,也可使用 0.0.0.0
来监听所有网络地址,127.0.0.1
则表示只监听本地地址,外部客户端无法连接。
随后需要持续监听来自客户端的连接,每建立一个连接,就新建一个线程去处理该连接接下来的数据收发:
from threading import Thread
while True:
sock, addr = s.accept()
t = Thread(target=tcplink, args=(sock, addr))
t.start()
socket.accept
会一直等待连接,连接上了才会执行下一步操作。
线程的处理逻辑放在了 tcplink
方法中,将 sock
和 addr
作为参数传入:
def tcplink(sock, addr):
print('accept new connection from', addr)
sock.send(b'welcome')
while True:
data = sock.recv(1024)
if not data or data.decode('utf-8') == 'exit':
break
else:
sock.send(('Hello %s' % data.decode('utf-8')).encode('utf-8'))
sock.close()
可以看到,建立连接成功后,服务器端会发送 welcome
到客户端,然后持续去监听来自客户端的数据,并且发送数据给客户端,直到客户端发送 exit
才将当前连接关闭。
这就是 TCP 协议下服务端与客户端的交互方式。
UDP
UDP 协议不同于 TCP,后者建立的是可靠连接,一旦信息通道建立后双方均可以以流的方式发送数据,而且包发送失败会重发;前者则是不基于连接的,也就是不需要建立连接,只需要知道 IP 和端口号就可以直接发数据,但并不能保证可靠性,即不保证是否数据达到。
UDP 虽然传输数据不可靠,但是速度快,对于不要求可靠到达的数据,可以用 UDP 协议。
那位说话了,“md,什么叫不要求可靠达到的数据?都丢包了还能正常吗?”您别说,还真的有挺多情况是这样的,特别是一些高频并发的网络通信,比如游戏、直播等等。因为丢一两个包并没有关系,可能在游戏中就玩家瞬移了一小步,直播中丢了一帧画面而已,它们更关注的是通信的继续,总不能丢个包就把游戏给直接断了吧?总不可能丢了一帧画面就把直播给关了吧?是吧。
客户端
创建 socket 的方式与 TCP 类似,SOCK_DGRAM
表示建立 UDP 连接。
由于不需要建立连接,所以 UDP 方式代码比较简单:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server = ('127.0.0.1', 8080)
for item in [b'Genji', b'Hanzo', b'Mcree']:
s.sendto(item, server)
print(s.recv(1024).decode('utf-8'))
每次发送均需要制定服务器 IP 和 端口。
服务端
服务器端的逻辑也比较简单,因为不必像 TCP 一样每次建立连接后去持续监听来自客户端的数据:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 8080))
while True:
data, addr = s.recvfrom(1024)
t = threading.Thread(target=udplink, args=(data, addr))
t.start()
recvfrom
直接接收到客户端的地址和数据,然后新开线程在 udplink
中处理:
def udplink(data, addr):
s.sendto(('Hello,' + data.decode('utf-8')).encode('utf-8'), addr)
这就是 UDP 协议下服务端与客户端的交互方式。