检测TCP客户端断开连接


77

假设我正在运行一个简单的服务器,并accept()从客户端建立了连接。

告诉客户端何时断开连接的最佳方法是什么?通常,客户端应该发送关闭命令,但是如果客户端手动断开连接或完全失去网络连接怎么办?服务器如何检测或处理?


在这里查看(最坏的情况):tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html(检查已死的同级对象)
Blauohr,

4
因为有很多错误和误导性的答案,所以这里是正确的答案:请遵循您要在TCP之上实现的协议的规范。它应该指定是通过超时,写入失败还是其他某种机制来完成此操作。如果要设计协议,请确保设计某种方法来检测客户端断开连接(如果需要)。
David Schwartz

Answers:


-2

select(设置了读取掩码)将返回带有句柄的信号,但是当您使用ioctl *检查待读取的字节数时,它将为零。这表明套接字已断开连接。

这是有关检查客户端已断开连接的各种方法的精彩讨论:Stephen Cleary,检测到半开(断开)连接

*对于Windows,请使用ioctlsocket。


78
这绝对不是一个“插座已断开的信号”。这表明套接字接收缓冲区中没有数据。期。远非一国之遥。您援引支持答案的文章甚至没有提到此技术。
罗恩侯爵

3
@MarkKCowan很难相信。直到通过校验和验证,数据才应该进入套接字接收缓冲区。您的主张是否有来源或可重复的实验?
2014年

2
@MarkKCowan仅记录在您引用的错误中。没有在IOCTL规范中进行记录。任何时候都可以读取零字节,这通常是因为对等方未发送任何内容。这不是正确的技术。
罗恩侯爵

2
@EJP没有读取0字节表示EOF(即,对等方已关闭连接)吗?如果套接字上没有任何内容,并且尝试读取它,则会产生EWOULDBLOCK / EAGAIN错误,而不是读取0字节。
ustust

1
@Matthieu:你能指出我一个吗?我认为您永远无法在应用程序级别从TCP中读取0字节(是的,您可能会为ACK等获得该字节,但是不会传播给套接字的用户),这并不意味着EOF。
ustust

122

在TCP中,只有一种方法可以检测到有序的断开连接,即从read()/recv()/recvXXX()读取时获取零作为返回值。

也只有一种可靠的方法来检测断开的连接:通过对其进行写入。在对断开的连接进行足够的写入之后,TCP将进行足够的重试和超时以知道它已断开,并最终导致write()/send()/sendXXX()返回errno/WSAGetLastError()值-1ECONNRESET,或在某些情况下“连接超时”的-1 。请注意,后者不同于“连接超时”,后者可能发生在连接阶段。

您还应该设置一个合理的读取超时,并丢弃失败的连接。

这里的答案是ioctl()FIONREAD胡说八道。所要做的就是告诉您套接字接收缓冲区中当前有多少字节,可以无阻塞地读取。如果客户五分钟内未向您发送任何东西,这并不构成断开连接,但确实会导致断开连接FIONREAD。不一样的东西:甚至都不接近。


2
@Jay问题是关于如何检测TCP断开连接,而不是有关导致连接重置的原因。导致“连接重置”的原因很多,我不同意其中任何一个构成“正常操作”。顾名思义,这是一种异常情况。
罗恩侯爵

2
@ user1055568除非非常大,否则通常只通过网络异步缓存和发送单个写入。您需要发出足够的写操作,以便原始写操作上的所有内部计时器和重试都已用尽,才能检测到错误。
洛恩侯爵

2
如果应用程序不继续发出写操作,则无法保证在连接断开后它将发出任何写操作。虽然在连接失败后发出一次写入就足够了,但是连接可以随时失败,并且,如果您无限期地停止写入,则即使在连接失败后也无法知道发出了一次写入。
David Schwartz 2015年

3
@EJP我已经多次说过,如果应用程序正在等待select / epoll / kevent上的读取准备就绪,那么它将被提醒进行读取以读取错误。您对此一直提出异议,一再坚持要求它必须执行更多写入操作。您没有说过任何有关读取的信息,并且使用epoll实际上不需要读取或写入,因为epoll可以直接发出超时信号。可能也是kevent。
user1055568

2
@ user1055568如果您只读取数据,那么您就不会对网络做任何事情,因此除非对等方有足够的义务进行重置,否则您不会遇到任何错误情况。如果编写,则说明您正在对网络进行处理,因此,如果有错误,则可以保证最终会遇到错误。
罗恩侯爵,

13

要进一步扩展:

如果您正在运行服务器,则需要使用TCP_KEEPALIVE来监视客户端连接,或者自己进行类似的操作,或者具有有关通过连接运行的数据/协议的知识。

基本上,如果连接被终止(即未正确关闭),则服务器将不会注意到它,直到尝试向客户端写入内容,这就是keepalive为您实现的。或者,如果您更好地了解协议,则无论如何都可以在不活动超时时断开连接。


服务器还应设置合理的读取超时,并丢弃失败的连接。
罗恩侯爵

删除失败的连接吗?如果超时按照建议的默认默认值200毫秒怎么办?它不应该退缩到某个合理的超时时间吗?也许那会给您带来太多的上下文切换?当这样的Timeout
Jay

在Winsock2上,keepalive每5秒轮询一次,并且我有一些阻塞的send或recv调用,那么keepalive是否可以正常工作?另外,保持活动超时和间隔的最小限制是多少?
Anurag Daware 2015年

1
@EJP,那是什么操作系统?大多数操作系统的默认读取超时是我上次检查时的0.5-5秒... tcp的rfc特别表示tcp的默认
Jay

@杰伊,我不知道你在说什么。在所有操作系统上,SO_RCVTIMEO的默认值为无穷大。否则,每个人都会一直读取超时。您关于200毫秒的建议如此荒谬。
罗恩侯爵,

2

如果您使用带有完成例程或完成端口的重叠(即异步)I / O,则当客户端关闭连接时,将立即通知您(假设您的读操作很出色)。


不完全的。阅读完流后,将立即通知您。如果在关闭之前有大量的客户数据在飞行,则可能要花费有限的时间。
2014年


0

TCP在协议中具有“打开”和“关闭”过程。一旦“打开”,将保持连接直到“关闭”。但是有很多事情可以异常阻止数据流。就是说,确定是否可能使用链接的技术高度依赖于协议和应用程序之间的软件层。上面提到的那些重点是试图以非侵入性方式(读或写0字节)使用套接字的程序员,这可能是最常见的。库中的某些层将为程序员提供“轮询”。例如,Win32异步(延迟)调用可以启动读取操作,该读取操作将返回无错误且0字节的信号,以指示无法再读取的套接字(大概是TCP FIN过程)。其他环境可能使用“事件” 如其包装层中所定义。这个问题没有单一答案。检测何时无法使用套接字并应将其关闭的机制取决于库中提供的包装器。还要注意的是,套接字本身可以被应用程序库下的各层重用,因此明智的做法是弄清楚您的环境如何与Berkley Sockets接口打交道。


-1
"""
tcp_disconnect.py
Echo network data test program in python. This easily translates to C & Java.

A server program might want to confirm that a tcp client is still connected 
before it sends a data. That is, detect if its connected without reading from socket.
This will demonstrate how to detect a TCP client disconnect without reading data.

The method to do this:
1) select on socket as poll (no wait)
2) if no recv data waiting, then client still connected
3) if recv data waiting, the read one char using PEEK flag 
4) if PEEK data len=0, then client has disconnected, otherwise its connected.
Note, the peek flag will read data without removing it from tcp queue.

To see it in action: 0) run this program on one computer 1) from another computer, 
connect via telnet port 12345, 2) type a line of data 3) wait to see it echo, 
4) type another line, 5) disconnect quickly, 6) watch the program will detect the 
disconnect and exit.

John Masinter, 17-Dec-2008
"""

import socket
import time
import select

HOST = ''       # all local interfaces
PORT = 12345    # port to listen

# listen for new TCP connections
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(1)
# accept new conneciton
conn, addr = s.accept()
print 'Connected by', addr
# loop reading/echoing, until client disconnects
try:
    conn.send("Send me data, and I will echo it back after a short delay.\n")
    while 1:
        data = conn.recv(1024)                          # recv all data queued
        if not data: break                              # client disconnected
        time.sleep(3)                                   # simulate time consuming work
        # below will detect if client disconnects during sleep
        r, w, e = select.select([conn], [], [], 0)      # more data waiting?
        print "select: r=%s w=%s e=%s" % (r,w,e)        # debug output to command line
        if r:                                           # yes, data avail to read.
            t = conn.recv(1024, socket.MSG_PEEK)        # read without remove from queue
            print "peek: len=%d, data=%s" % (len(t),t)  # debug output
            if len(t)==0:                               # length of data peeked 0?
                print "Client disconnected."            # client disconnected
                break                                   # quit program
        conn.send("-->"+data)                           # echo only if still connected
finally:
    conn.close()

检查套接字是否已准备就绪,但没有数据对于我的项目而言效果很好。这是一个简单的解决方案
luc

3
@luc根本不起作用。这是一个简单,错误,无效的解决方案。这是对可以无阻塞读取的数据量的测试,而不是断开连接的测试。您必须阅读以进行测试。如果客户五分钟内未向您发送任何内容,FIONREAD将为零,但他可能仍处于连接状态。
罗恩侯爵

1
这是Python,但标记显示为C ++
Greg Schmit

-1

在python中,您可以执行以下try-except语句:

try:
  conn.send("{you can send anything to check connection}")
except BrokenPipeError:
  print("Client has Disconnected")

之所以可行,是因为当客户端/服务器关闭程序时,python会根据断开连接的人将破碎的pip错误返回给服务器或客户端。


-3

这真的很容易做到:可靠而不凌乱:

        Try
            Clients.Client.Send(BufferByte)
        Catch verror As Exception
            BufferString = verror.ToString
        End Try
        If BufferString <> "" Then
            EventLog.Text &= "User disconnected: " + vbNewLine
            Clients.Close()
        End If

不可靠 由于套接字发送缓冲区,它无法区分有序关闭和无序关闭,并且直到至少发生两次发送后,它才起作用。
洛恩侯爵,

-3

我玩弄了一些解决方案,但该解决方案似乎最适合检测Windows中的主机和/或客户端断开连接。它用于非阻塞套接字,并且是从IBM的示例派生

char buf;
int length=recv(socket, &buf, 0, 0);
int nError=WSAGetLastError();
if(nError!=WSAEWOULDBLOCK&&nError!=0){
    return 0;
}   
if (nError==0){
    if (length==0) return 0;
}

recv()对电线没有任何作用,因此它无法触发对电缆拉动等的任何检测。只有send()可以这样做。
洛恩侯爵,

-3

如果连接丢失,receive的返回值将为-1,否则将为缓冲区的大小。

void ReceiveStream(void *threadid)
{
    while(true)
    {
        while(ch==0)
        {
            char buffer[1024];
            int newData;
            newData = recv(thisSocket, buffer, sizeof(buffer), 0);
            if(newData>=0)
            {
                std::cout << buffer << std::endl;
            }
            else
            {
                std::cout << "Client disconnected" << std::endl;
                if (thisSocket)
                {
                    #ifdef WIN32
                        closesocket(thisSocket);
                        WSACleanup();
                    #endif
                    #ifdef LINUX
                        close(thisSocket);
                    #endif
                }
                break;
            }
        }
        ch = 1;
        StartSocket();
    }
}

2
仅在发生错误时才返回-1,而在断开连接时则不返回-1。我已经在Windows和Linux上进行了验证,当对等点不正常地断开连接时,recv将仅返回一个充满零的缓冲区。
TekuConcept

@TekuConcept错误。它将返回-1 errno == ECONNRESET,并且对缓冲区完全不做任何事情。
罗恩侯爵

根据手册页,您说得对!我想我忽略了“可能会从底层协议模块生成并返回其他错误”
TekuConcept,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.