Tags   Server TCP

Back

网络通信技术实践: (3)基本TCP

这是一个重在实践的计算机网络通信教程,旨在让读者快速写出相关功能的Python代码,因而没有详尽的概念与原理介绍。

1 TCP简介

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字节。

2 TCP编程实践

(1)TCP服务端代码示例

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() # 关闭服务端的套接字

(2)TCP客户端代码示例

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() # 关闭客户端的套接字

(3)封装TCP的服务端和客户端

为了方便使用,我们可以适当封装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())

目录

上一节

下一节