服务器退出后关闭客户端套接字

时间:2018-01-08 13:00:29

标签: python sockets

我正在编写一个简单的客户端/服务器套接字程序,其中客户端与服务器连接并进行通信,然后将退出消息发送到服务器,然后服务器关闭连接。代码如下所示。

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并在其结束时退出客户端套接字就好了。所以我很难知道为什么服务器无法发送相同的消息,除了上面提到的代码部分。

任何帮助都可以!!

1 个答案:

答案 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消息(或空/空消息)(客户端和服务器都理解的消息)。

  • 通过记录每个sendrecv操作来监控套接字是否处于不活动状态。

  • 为代码添加超时监控。这意味着您需要实施轮询,例如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