上一篇中的Client端和Sever端的代码,只有在Client端发送 exit
才能安全结束两个python进程,如果直接退会导致while
循环后的 s.close()
没有被执行,那么被占用的端口就不能被释放,只用重启才能接触端口占用。Client端还好,可以手动输入exit来结束,Sever端则需要一直等接收到才能结束,这显然是不合理的。
解决这个问题只需要在结束的时候执行 s.close()
即可,可以用 try: ;except KeyboardInterrupt:;
语句来捕获结束的信号,在命令行按下Ctrl-C触发执行except KeyboardInterrupt:
中的内容即可。Client端的代码如下
Client端:
import socket
# UDP客户端持续发送消息代码
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建UDP类型的套接字
#s.bind(("127.0.0.1", 46552)) #UDPclient端可以不绑定端口,client端不绑定时是随机端口发送的
addr = ("127.0.0.1", 46551)
try:
while True:
data = input("请输入:")
s.sendto(data.encode("gbk"), addr)
if data == "exit":
print("结束聊天!")
break
s.close()
except KeyboardInterrupt: #Ctrl-C
s.close()
print('结束聊天!')
Sever端用同样的方法并不能直接退出python进程,经分析,发现是因为 s.recvfrom
默认是阻塞状态,所以调用不会立即返回,而是进入睡眠等待操作完成,只需要将将套接字设置为非阻塞模式, recvfrom
函数在非阻塞模式下进行调用,如果没有数据包可用,则会抛出 socket.error
异常,该异常可以通过 try/except
语句进行捕获,从而使程序可以继续执行其他操作。Sever端代码如下
Sever端:
import socket
# 持续通信
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建UDP类型的套接字
s.bind(("127.0.0.1", 46551)) # 绑定端口,ip可以不写
s.setblocking(False) # 将套接字设置为非阻塞模式(默认阻塞模式,执行到recvfrom函数会阻塞)
#recvfrom函数在非阻塞模式下进行调用,如果没有数据包可用,则会抛出socket.error异常,该异常可以通过try/except语句进行捕获,从而使程序可以继续执行其他操作
print("等待接收数据!")
try:
while True:
try:
recv_data, recv_addr = s.recvfrom(1024) # 1024表示本次接收的最大字节数,recvfrom一直监听知道由数据被接受
print(recv_data, recv_addr)
data = recv_data.decode('gbk')
print(f"收到远程信息:{data},from {recv_addr}")
if data == "exit":
print("结束聊天!")
break
except socket.error:
pass
s.close()
except KeyboardInterrupt: #Ctrl-C
s.close()
print('结束聊天!')
阻塞就是让当前调用线程一直处于停止等待当中,挂起的状态,线程函数会被卡住。
非阻塞则是不管运行结果如何,都会继续往下执行(往往都要处理很多返回结果),线程函数里一般都是一个循环,不停的轮询。
再理一下发送接收函数。