我正在编写一个简单的客户端/服务器套接字程序,其中客户端与服务器连接并进行通信,然后将退出消息发送到服务器,然后服务器关闭连接。代码如下所示。
的 server.py 的
import socket
import sys
from threading import Thread
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# This is to prevent the socket going into TIME_WAIT status and OSError
# "Address already in use"
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except socket.error as e:
print('Error occured while creating the socket {}'.format(e))
server_address = ('localhost', 50000)
sock.bind(server_address)
print('**** Server started on {}:{} ****'.format(*server_address))
sock.listen(5)
def client_thread(conn_sock, client_add):
while True:
client_msg = conn_sock.recv(1024).decode()
if client_msg.lower() != 'exit':
print('[{0}:{1}] {2}'.format(*client_add, client_msg))
serv_reply = 'Okay ' + client_msg.upper()
conn_sock.send(bytes(serv_reply, 'utf-8'))
else:
conn_sock.close()
print('{} exitted !!'.format(client_add[0]))
sys.exit()
try:
# Keep the server until there are incominmg connections
while True:
# Wait for the connctions to accept
conn_sock, client_add = sock.accept()
print('Recieved connection from {}:{}'.format(*client_add))
conn_sock.send(
bytes('***** Welcome to {} *****'.format(server_address[0]), 'utf-8'))
Thread(target=client_thread, args=(
conn_sock, client_add), daemon=True).start()
except Exception as e:
print('Some error occured \n {}'.format(e))
except KeyboardInterrupt as e:
print('Program execution cancelled by user')
conn_sock.send(b'exit')
sys.exit(0)
finally:
sock.close()
的 client.py 的
import socket
import sys
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('localhost', 50000)
print('Connecting to {} on {}'.format(*server_address))
sock.connect(server_address)
def exiting(host=''):
print('{} exitted !!'.format(host))
sys.exit()
while True:
serv_msg = sock.recv(1024).decode()
if serv_msg.lower() != 'exit':
print('{1}: {0}'.format(serv_msg, server_address[0]))
client_reply = input('You: ')
sock.send(bytes(client_reply, 'utf-8'))
if client_reply.lower() == 'exit':
exiting()
else:
exiting('Server')
我想要的是万一服务器通过ctrl-c或任何其他方式退出我想要关闭所有客户端套接字并将msg发送给客户端,他们也应该关闭他们的套接字。
我在下面的部分除外,但由于某种原因,服务器发送的消息未被客户端接收。
except KeyboardInterrupt as e:
print('Program execution cancelled by user')
conn_sock.send(b'exit')
sys.exit(0)
令人惊讶的是,如果我将来自client_thread的'exit'msg作为srvr_reply发送,客户端接受msg并在其结束时退出客户端套接字就好了。所以我很难知道为什么服务器无法发送相同的消息,除了上面提到的代码部分。
任何帮助都可以!!
答案 0 :(得分:0)
我很遗憾地说,除非您尝试通过连接发送数据,否则无法检测到TCP / IP连接的异常终止。
这被称为"半开放"套接字,它也在Python documentation中提及。
通常,当服务器进程崩溃时,操作系统将关闭TCP / IP套接字,向客户端发出有关关闭的信号。
当客户端收到信号时,可以在轮询时检测到服务器的终止。轮询机制(即poll
/ epoll
/ kqueue
)将测试HUP
(挂起)事件。
这就是为什么"半开放"除非问题被强制,否则套接字不会在开发中发生。当客户端和服务器都在同一台机器上运行时,操作系统将发送关于关闭的信号。
但如果服务器计算机崩溃或连接丢失(即移动设备),则不会发送此类信号,客户端也不会知道。
检测异常终止的唯一方法是失败write
尝试read
将无法检测到问题(它将表现为没有收到任何数据)。
这就是他们发明ping
概念的原因,这就是为什么HTTP / 1.1服务器和客户端(不支持ping)使用超时来假设终止。
有a good blog post about Half Open sockets here。
编辑(由于评论而澄清)
如何处理这种情况:
我会推荐以下内容:
向协议添加显式Ping消息(或空/空消息)(客户端和服务器都理解的消息)。
通过记录每个send
或recv
操作来监控套接字是否处于不活动状态。
为代码添加超时监控。这意味着您需要实施轮询,例如select
(或poll
或特定于操作系统的epoll
/ kqueue
),而不是recv
上的阻止
达到连接超时时,发送Ping /空消息。
要获得简单的解决方案,请在发送Ping后重置超时。
下次轮询套接字时,轮询机制应提醒您连接失败。或者,第二次尝试ping服务器/客户端时,您将收到错误消息。
请注意,即使连接丢失,第一个send
操作也可能成功。
这是因为TCP / IP层发送了消息,但send
功能并没有等待TCP / IP的ACK
确认。
但是,第二次进入ping时,TCP / IP层可能已经意识到没有ACK
即将到来,并在套接字中注册了错误(这需要时间)。
为什么send
在退出服务器之前失败
我留下的关于这个问题的评论是错误的(部分)。
conn_sock.send(b'exit')
失败的主要原因是conn_sock
是客户端线程中的局部变量,并且无法从SIGINT
(CTRL + C)的全局状态访问)被提出。
这是有道理的,如果服务器有多个客户端会发生什么?
但是,socket.send
只有计划要发送的数据,所以假设数据实际发送不正确。
另请注意socket.send
might not send the whole message if there isn't enough room in the kernel's buffer。