一、背景说明

昨天一个同事让帮忙写个服务,用于接收并返回他那边提交过来的数据,以便其查看提交的数据及格式是否正确。

开始想用django写个接口,但写接口接口名称就得是定死的,他那边只能向这接口提交数据;接收一下就返回这种事情不如直接写个socket监听然后返回去。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

以前也没怎么写正经的socket编程,基本是能收发点数据应差不多了,此次收发的数据一多就出了问题。

一是数据没接收完就给客户端发送RST,二是数据没发送完就给客户端发送RST。

 

二、问题处理

2.1 tcp收发处理

使用s.recv()接收数据时,s.recv()只管从操作系统缓冲区中读取数据,不管读到读不到或者读到多少函数都算执行完了,程序会继续往后执行。因此接收大量数据时我们需要不断使用s.recv()进行读取然后设定一个终止标志。

使用s.send()发送数据时,s.send()只管通知操作系统发送数据,操作系统每次只是尽力发送数据然后把本次发送的数据作为返回值并不保证数据发送完成。因此在发送大量数据时我们需要不断使用s.send()发送然后设定一个终止标志。(不过如果单纯说python那可以使用snedall()函数来实现一次发送完,sendall()的实现方法和我们这里说的意思一样)

 

2.2 有问题程序

import socket

# 获取本地主机名
host = socket.gethostname()
port = 9999

# 创建socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定端口号
server_socket.bind((host, port))
# 设置最大连接数,超过后排队
server_socket.listen(5)

# 这里实现的是接收客户端发来的数据、打印、然后再原样返回给客户端
while True:
    client_socket, addr = server_socket.accept()
    print(f"连接地址: {str(addr)}")

    # 错误二、当发来的数据很长时tcp不会等接收完成再执行下一条语句,这里没处理这个问题
    result = client_socket.recv(1024 * 1024)
    # 问题一、decode默认使用utf-8编码,但当发来的数据有utf-8不可解码内容时会报异常,这里没捕获异常
    print(f"{result.decode()}")

    # 错误三、发送时tcp不会等发送完再执行下一条语句,这里没处理这个问题
    client_socket.send(result)
    # 注意四、如果客户端中的接收代码是和上边错误二一样的,那么没发完也会被客户端reset
    client_socket.close()

 

2.3 修正后程序

import socket
import time

# 获取本地主机名
host = socket.gethostname()
port = 9999

# 创建socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定端口号
server_socket.bind((host, port))
# 设置最大连接数,超过后排队
server_socket.listen(5)

while True:
    # 建立客户端连接
    client_socket, addr = server_socket.accept()
    print(f"连接地址: {str(addr)}")
    # 这里result不能赋值为None,否则下边的result += tmp_result会因类型不一致报错
    # 这里result不能赋值为字符串"",否则下边的result += tmp_result会因类型不一致报错
    result = b""
    # 错误二修正:此处while循环用于确保接收完所有数据再执行后续指令
    while True:
        # 每次最多读取2048字节
        tmp_result = client_socket.recv(2048)
        # 如果最后读取不到数据,则跳出循环
        if tmp_result == b"":
            break
        # 将本次读取到的内容拼接到result中
        result += tmp_result

    # 问题一修正:对解码异常进行捕获,直接以byte形式输出
    try:
        print(f"{result.decode()}")
    except:
        print(f"{result}")

    total_lenght = result.__len__()
    print(f"\r\ntotal_length :{total_lenght}")
    flag = 0
    # 错误三修正:确保数据发送完才执行后续代码
    while True:
        # 每次从已发送数据位置发送
        # 每次返回的是本次发送数据长度
        tmp_flag = client_socket.send(result[flag:])
        flag += tmp_flag
        # 如果已发送完则退出
        if flag == total_lenght:
            break

    # 至于问题四,客户端未完成接收即返回reset,那就只能由客户端去处理了
    client_socket.close()

 

参考:

https://stackoverflow.com/questions/34252273/what-is-the-difference-between-socket-send-and-socket-sendall

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄