这是一个重在实践的计算机网络通信教程,旨在让读者快速写出相关功能的Python代码,因而没有详尽的概念与原理介绍。
TCP是一种面向连接的、可靠的、 基于IP的传输层协议。它区分服务端和客户端,在发送数据前需要先通过3次握手创建连接,保证数据能够正确送达。数据送达后,需要通过4次握手关闭连接。
上节提到,IPv4数据报的最大长度为65535字节,减去20字节的IP首部和20字节的TCP首部,TCP协议一次性能传输最多65495字节的数据。但由于MTU的存在,发送数据长度不应当达到这个值。因此,TCP协议有一个选项,称为最大报文段长度(Maximum Segment Size, MSS)。该选项用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度(不包括文段头)。MSS应当设置为MTU减40字节(去20字节的IP首部和20字节的TCP首部)。默认的以太网MTU是1500字节。那么,通过以太网接口发出的IPv4数据包,其TCP段的MSS就应该是1460。因此,TCP每次收发数据不应当超过1460字节。
import socket # 导入socket
sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建数据流型socket
# 绑定服务端地址和端口
server_address=('',8080)
sock.bind(server_address)
sock.listen(5) # 使套接字变为可被动连接,且最大连接数为5
client_sock,client_address=sock.accept() # 接收来自客户端的连接请求,这个操作是阻塞的
# 连接后,可以发送或者接收数据
client_sock.sendall('hello'.encode()) # 向客户端发送数据,这个操作是阻塞的
data=client_sock.recv(1024) # 接收来自客户端最长1024字节的数据,这个操作是阻塞的
print(data.decode())
# 收发完毕后,断开连接
client_sock.close()
# 此时可以关闭服务端的套接字,也可以接受新的连接
sock.close() # 关闭服务端的套接字
import socket # 导入socket
sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建数据流型socket
# 绑定客户端地址和端口
client_address=('',8088)
sock.bind(client_address)
# 这一步可以省略。在省略的情况下,会用随机端口发送数据。
server_address=('127.0.0.1',8088)
sock.connect(server_address) # 连接服务端,连接失败会引发异常,这个操作是阻塞的
sock.send('hello'.encode()) # 向服务发送数据,这个操作是阻塞的
data=sock.recv(1024) # 从服务器接受数据,这个操作是阻塞的
print(data.decode())
sock.close() # 关闭客户端的套接字
为了方便使用,我们可以适当封装Socket,使之支持非阻塞模式。
# tcp.py
import socket
class tcpserver: #TCP服务端类
# 以本机IP和指定端口初始化,指定是否阻塞,超时时间(单位:秒,可以是小数)和最大连接数
def __init__(self,port:int,blocking:bool=True,timeout:float=-1,backlog:int=5):
self.sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
if blocking: # 阻塞时设置超时
if timeout>0: self.sock.settimeout(timeout)
else: # 不阻塞则超时无效
self.sock.setblocking(blocking)
self.sock.bind(('',port))
self.sock.listen(backlog) # 使套接字变为可被动连接,且最大连接数为backlog
# 接受客户端连接,返回客户端socket和地址(ip,port)
def accept(self):
try:
return tcpsubclient(self.sock.accept())
except:
return None
# 关闭TCP套接字
def close(self):
self.sock.close()
class tcpclientbase: #TCP客户端基类,不要自行创建它的实例
sock:socket.socket
# 发送已经编码的数据
def send(self,encoded_data):
try:
return self.sock.send(encoded_data)
except:
return -1
# 接收数据,需要自行解码
def recv(self,buf_size:int=1024):
try:
data,from_address=self.sock.recvfrom(buf_size)
except BlockingIOError: # 非阻塞未收到信号处理
data,from_address=None,None
except socket.timeout: # 超时处理
data,from_address=None,None
return data,from_address
# 关闭TCP套接字
def close(self):
self.sock.close()
class tcpsubclient(tcpclientbase): #服务端接受连接后派生的TCP客户端类,不要自行创建它的实例
def __init__(self,sock_addr):
self.sock,self.addr=sock_addr
class tcpclient(tcpclientbase): #TCP客户端类
def __init__(self,port:int,blocking:bool=True,timeout:float=-1,backlog:int=5):
self.sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
if blocking: # 阻塞时设置超时
if timeout>0: self.sock.settimeout(timeout)
else: # 不阻塞则超时无效
self.sock.setblocking(blocking)
self.sock.bind(('',port))
# 连接到指定服务端,返回是否连接成功
def connect(self,addr):
try:
self.sock.connect(addr)
return True
except:
return False
再分别创建服务器和客户端的使用例。我们以经典的“回声”程序为例。所谓“回声”程序,就是服务端接受到客户端的消息后再原样发回的程序。
#server.py
from tcp import *
server=tcpserver(8080)
while True:
clnt=server.accept()
if clnt==None: continue
data,addr=clnt.recv()
print("Received: "+data.decode())
clnt.send(data)
clnt.close()
#client.py
from tcp import *
clnt=tcpclient(8088)
if clnt.connect(('127.0.0.1',8080)):
clnt.send("meow".encode())
print("Send a message to the server.")
dat,addr = clnt.recv()
if dat != None:
print("Message received: "+dat.decode())