如何检测客户端何时关闭连接?

 

想象一下以下场景:您正在编写一个服务器应用程序。客户端向服务器发送他们的查询,对于每个新的客户端连接,服务器启动一个新进程,负责回答从客户端收到的所有查询。对于收到的每个查询,该过程会工作一段时间,最后将结果发送到查询返回给客户端。

 

到现在为止还挺好。但是,如果在进程处理刚刚从客户端收到的查询时客户端关闭了与服务器的连接,会发生什么情况?在这种情况下,没有人会查看查询结果,因此一旦客户端关闭连接,立即终止进程是很有意义的。问题是:我们如何判断套接字连接是否已被客户端关闭?

我们需要一个单独的进程,它只检查客户端是否关闭了一个连接(当然,这个进程可能会同时监视多个连接)。实现这一点的最明显的方法是在连接的套接字上读取该进程调用,并检查 read 是否返回 0(即从套接字读取零字节),在这种情况下,我们知道连接已关闭。问题是我们实际上不想调用 read 因为这会从流中删除通常由负责该连接的进程读取的数据。在这种情况下,我们如何将数据从监控进程获取到查询处理器?所以,显然,调用 read 不是一个好主意。

幸运的是,我们有 select 和 poll 函数,可用于检测套接字是否已被远程主机关闭。不幸的是,两者的问题在于它们只告诉我们“数据可用”,即使连接已关闭。这是因为“可用数据”实际上意味着完全不同的东西,即“可以在不阻塞的情况下调用此套接字上的读取”。为了确定连接是否实际上已关闭(或者实际上是否有新数据可供读取),我们需要再次调用 read。坏的。

该问题的解决方案称为 recv,它做得很好。诀窍是 recv 支持两个非常好的标志: MSG_PEEK 告诉 recv 不要从流中删除任何内容,而 MSG_DONTWAIT 使函数在没有数据可供读取时立即返回。下面是一个代码片段,它接受一个新的客户端连接并生成一个新进程,该进程响应通过该连接接收到的查询。父进程一直等到套接字关闭。这可能是因为子进程已完成执行(并关闭套接字)或因为客户端在子进程完成其任务之前关闭了套接字。

 

fd = accept(listenSocket);
pid_t childProcess = fork();
if (childProcess == (pid_t)-1) {
	perror("Unable to create new process for client connection");
	exit(1);
}
else if (childProcess == 0) {
	// read from socket, process queries, etc.
}
else {
	// use the poll system call to be notified about socket status changes
	struct pollfd pfd;
	pfd.fd = fd;
	pfd.events = POLLIN | POLLHUP | POLLRDNORM;
	pfd.revents = 0;
	while (pfd.revents == 0) {
		// call poll with a timeout of 100 ms
		if (poll(&pfd, 1, 100) > 0) {
			// if result > 0, this means that there is either data available on the
			// socket, or the socket has been closed
			char buffer[32];
			if (recv(fd, buffer, sizeof(buffer), MSG_PEEK | MSG_DONTWAIT) == 0) {
				// if recv returns zero, that means the connection has been closed:
				// kill the child process
				kill(childProcess, SIGKILL);
				waitpid(childProcess, &status, WNOHANG);
				close(fd);
				// do something else, e.g. go on vacation
			}
		}
	}
}

 

 

 

Python Module:

from ctypes import (
    CDLL, c_int, POINTER, Structure, c_void_p, c_size_t,
    c_short, c_ssize_t, c_char, ARRAY
)


__all__ = 'is_remote_alive',


class pollfd(Structure):
    _fields_ = (
        ('fd', c_int),
        ('events', c_short),
        ('revents', c_short),
    )


MSG_DONTWAIT = 0x40
MSG_PEEK = 0x02

EPOLLIN = 0x001
EPOLLPRI = 0x002
EPOLLRDNORM = 0x040

libc = CDLL(None)

recv = libc.recv
recv.restype = c_ssize_t
recv.argtypes = c_int, c_void_p, c_size_t, c_int

poll = libc.poll
poll.restype = c_int
poll.argtypes = POINTER(pollfd), c_int, c_int


class IsRemoteAlive:  # not needed, only for debugging
    def __init__(self, alive, msg):
        self.alive = alive
        self.msg = msg

    def __str__(self):
        return self.msg

    def __repr__(self):
        return 'IsRemoteClosed(%r,%r)' % (self.alive, self.msg)

    def __bool__(self):
        return self.alive


def is_remote_alive(fd):
    fileno = getattr(fd, 'fileno', None)
    if fileno is not None:
        if hasattr(fileno, '__call__'):
            fd = fileno()
        else:
            fd = fileno

    p = pollfd(fd=fd, events=EPOLLIN|EPOLLPRI|EPOLLRDNORM, revents=0)
    result = poll(p, 1, 0)
    if not result:
        return IsRemoteAlive(True, 'empty')

    buf = ARRAY(c_char, 1)()
    result = recv(fd, buf, len(buf), MSG_DONTWAIT|MSG_PEEK)
    if result > 0:
        return IsRemoteAlive(True, 'readable')
    elif result == 0:
        return IsRemoteAlive(False, 'closed')
    else:
        return IsRemoteAlive(False, 'errored')
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值