昨天一个同事让帮忙写个服务,用于接收并返回他那边提交过来的数据,以便其查看提交的数据及格式是否正确。python
开始想用django写个接口,但写接口接口名称就得是定死的,他那边只能向这接口提交数据;接收一下就返回这种事情不如直接写个socket监听而后返回去。django
之前也没怎么写正经的socket编程,基本是能收发点数据应差很少了,这次收发的数据一多就出了问题。编程
一是没接收完客户端要发送的数据就给客户端回RST,二是没发送完要给客户端发送的数据就又直接给客户端发送RST。服务器
使用s.recv()接收数据时,s.recv()只管从操做系统缓冲区中读取数据,阻塞模式下只要读到数据、非阻塞模式下无论读到读不到或者读到多少,函数都算执行完了程序会继续日后执行。所以接收大量数据时咱们须要不断使用s.recv()进行读取而后设定一个终止标志。网络
使用s.send()发送数据时,s.send()只管通知操做系统发送数据,操做系统每次只是尽力发送数据而后把本次发送的数据做为返回值并不保证数据发送完成。所以在发送大量数据时咱们须要不断使用s.send()发送而后设定一个终止标志。(不过若是单纯说python那可使用snedall()函数来实现一次发送完,sendall()的实现方法和咱们这里说的意思同样)socket
另外注意若是测试本身使用requests等做为客户端时,服务端的返回要加上http的响应头部,否则数据原样返回requests等进行解析会因不是一个正确的http响应而出错。tcp
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)}") # 错误2、当发来的数据很长时tcp不会等接收完成再执行下一条语句,这里没处理这个问题 result = client_socket.recv(1024 * 1024) # 问题1、decode默认使用utf-8编码,但当发来的数据有utf-8不可解码内容时会报异常,这里没捕获异常 print(f"{result.decode()}") # 错误3、发送时tcp不会等发送完再执行下一条语句,这里没处理这个问题 client_socket.send(result) # 注意4、若是客户端中的接收代码是和上边错误二同样的,那么没发完也会被客户端reset client_socket.close()
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) print("服务器程序已启动") while True: # 创建客户端链接 client_socket, addr = server_socket.accept() # 阻塞模式,设置接收超时时长为1秒,1秒内没有新数据视为数据已传送完成 client_socket.settimeout(1) # client_socket.setblocking(0) print(f"链接地址: {str(addr)}") # 这里result不能赋值为None,不然下边的result += tmp_result会因类型不一致报错 # 这里result不能赋值为字符串"",不然下边的result += tmp_result会因类型不一致报错 result = b"" tmp_result = "" # 错误二修正:此处while循环用于确保接收完全部数据再执行后续指令 while True: # 每次最多读取2048字节 try: tmp_result = client_socket.recv(2048) # 1秒内无数据,触发超时异常,此时咱们断定为数据已接收完成break退出 # 不能使用获取数据为空做为退出标志,由于阻塞模式除非是已创建的网络链接被拆除否则读不到数据是不会返回的 except socket.timeout as e: print(f"{e}") 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 # 错误三修正:确保数据发送完才执行后续代码 # python其实可使用sendall()来完成,但sendall自己也是相似如下形式,为了通用性咱们这里暂时不用 while True: # 每次从已发送数据位置发送 # 每次返回的是本次发送数据长度 tmp_flag = client_socket.send(result[flag:]) flag += tmp_flag # 若是已发送完则退出 if flag == total_lenght: break # 至于问题四,客户端未完成接收即返回reset,那就只能由客户端去处理了 client_socket.close()
参考:函数