一、引言
最近在工作中使用 Python 写了一个测试用的小工具,大概作用是这样的:
这个小工具作为一个服务端,对客户端发起的请求信息,做一些处理,然后将返回的报文同步或者异步的返回回去。
对于这个小工具来说,其主要作为被请求服务的服务端进行工作。而在这个小工具的开发过程中,我经常会遇到诸如 Python Socket 编程的 [WinError XXXXX]
之类的错误。
遇到的多了,我发现,与其遇到一个 WinError 错误然后去搜索原因,不如我们自己写一个简单的基于 TCP 的服务端客户端通信程序,来自己把这些 WinError 错误的情况给模拟出来,这样,我们也会对这些 WinError 错误有更加深刻的理解。
说做就做,先上一份最简单的使用 Python Socket 编程实现的的服务端客户端通信程序。然后,我们在这份代码基础上,进行各种操作,去模拟实现一些常见的 WinError 错误。
二、C&S 代码
这里,直接上代码吧:
1. 代码
服务端程序
"""
Client side
Author: Wang Ying
Last modified: July 30 2019
"""
from socket import socket, AF_INET, SOCK_STREAM
# Create socket
tcpSerSocket = socket(AF_INET, SOCK_STREAM)
# Bind ip & port
address = ('127.0.0.1', 9999)
tcpSerSocket.bind(address)
# Begin listen
tcpSerSocket.listen(5)
while True:
# Receive client's connect, newSocket is served for client.
# tcpSerSocket wait for another client's connect.
newSocket, clientAddr = tcpSerSocket.accept()
while True:
# Receive client data
recvData = newSocket.recv(1024)
# if data length is 0, just break
if len(recvData) > 0:
print('recv:', recvData)
else:
break
# Send some data to client
sendData = input("send:")
newSocket.send(bytes(sendData.encode('utf-8')))
# Close the client socket
newSocket.close()
# Stop listen
tcpSerSocket.close()
客户端程序
"""
Client side
Author: Wang Ying
Last modified: July 30 2019
"""
from socket import socket, AF_INET, SOCK_STREAM
# Create socket
tcpClientSocket = socket(AF_INET, SOCK_STREAM)
# Connect to server
serAddr = ('127.0.0.1', 9999)
tcpClientSocket.connect(serAddr)
while True:
# Prompt input
sendData = input("send:")
if sendData == 'bye':
break
if len(sendData) > 0:
tcpClientSocket.send(bytes(sendData.encode('utf-8')))
else:
break
# Receive server data
recvData = tcpClientSocket.recv(1024)
print('recv:', recvData)
# Close socket
tcpClientSocket.close()
2. 分析
对于 Socket 编程来说,什么语言都是一样的,Python 也不例外。在这个例子中:
服务端程序绑定了一个 9999 端口,然后监听客户端 socket 的连接。这里的两层 while 循环,外层是用来遍历多个客户端的连接请求的,内层是用来实现与客户端交互接收发送数据的。
客户端程序连接服务端端口,然后进入一个循环,用于给服务端发送数据。
这里,首先运行服务端程序,然后再运行客户端程序,客户端先发送消息,服务端收到,然后服务端再发送消息给客户端,客户端收到,客户端又可以继续给服务端发送消息…直到,客户端输入 bye,然后跳出循环,关闭客户端的 socket 连接,这一过程也就结束了。
这是一个非常简单的服务端客户端通信程序,我们可以根据此程序来模拟各种 WinError 错误。接下来就开始吧:)
三、实验
[WinError 10061] 由于目标计算机积极拒绝,无法连接
我们刚才说到,运行这套程序,需要先运行服务端程序,然后再运行客户端程序,这是因为服务端监听在先,然后客户端再去连接。
那么,如果服务端程序并没有先启动,此时客户端就运行了,会出现什么错误呢?
总结下:
一旦客户端启动的时候,服务端并没有在客户端想要连接的 IP 和端口号上进行监听,那么就会出现
[WinError 10061] 由于目标计算机积极拒绝,无法连接
的错误。
[WinError 10054] 远程主机强迫关闭了一个现有的连接
我们先运行服务端程序,然后运行客户端程序,再然后紧接着强制性将客户端程序杀掉(不让其正常的关闭 socket 连接),此时会出现什么错误呢?
这个错误很好理解,连接上的 socket 连接被对方强制性中断(非正常 close 掉的),就会出现这个报错。
可能会有网友觉得,这里是客户端强制杀掉,那么我杀掉服务端又会报什么错呢?哈哈,大家可以自行尝试下,答案是不会报错:)个中原因,可以自行思考,这里只提示一句,一个客户端只连接一个服务端,但是一个服务端,是要连接零个一个或者多个客户端的。
总结下:
当客户端连接上服务端后,客户端程序强制性杀掉,此时服务端就会报错
[WinError 10054] 远程主机强迫关闭了一个现有的连接
[WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次
我们这回稍微修改一下客户端程序的代码,尝试下让客户端不去 connect 服务端的 socket 连接,而是 bind(就像服务端一样),修改如下:client.py
tcpClientSocket.connect(serAddr)
# 修改为
tcpClientSocket.bind(serAddr)
会出现什么问题呢?
其实这个也很好理解,毕竟同一个端口被两个程序所监听,肯定会出现端口占用问题。
总结下:
一个端口已经被占用了,然后另一个程序想要去使用它,就会出现错误
[WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次
[WinError 10038] 在一个非套接字上尝试了一个操作
我们这回接着魔改代码,这回我们修改服务端程序的代码:server.py
while True:
...
# 2. 这里调用 close,导致下一次循环 recv 的
# 时候会使用一个已经关闭了的 socket 连接
# 进行数据接收
newSocket.close()
# 1. 原本应该是跳出循环后才关闭 socket,我们现在
# 在接收到客户端的返回之后,就立即关闭。模拟关
# 闭 socket 之后再接收 socket 数据的场景。
# newSocket.close()
我们修改的地方就是 server.py,将原来的 close 函数放到了循环内部,导致 close 之后调用一次 recv。让我们先运行服务端程序,然后再运行客户端程序来看看会发生什么:
我们发现,在服务端程序运行到 recv 的时候会出现 [WinError 10038] 在一个非套接字上尝试了一个操作
的错误,其实这个很好理解,因为毕竟你已经把这个 socket 连接关闭了,然后你又想使用它来接收数据。
总结一下:
当你关闭了一个 socket 连接(close),然后又使用那个 socket 对象接收数据(recv),就会出现报错
[WinError 10038] 在一个非套接字上尝试了一个操作
。。。未完待续,等待持续更新
如果在后续工作中,遇到了其他的有意思的错误信息,我也还会过来持续更新:)
四、总结
学习 Python 本就是一个很快乐的事情,比它更快乐的,那就是使用 Python 的过程。