Phát hiện ngay lập tức ngắt kết nối máy khách khỏi ổ cắm máy chủ


82

Làm cách nào tôi có thể phát hiện thấy một ứng dụng khách đã ngắt kết nối khỏi máy chủ của tôi?

Tôi có mã sau trong AcceptCallBackphương pháp của mình

static Socket handler = null;
public static void AcceptCallback(IAsyncResult ar)
{
  //Accept incoming connection
  Socket listener = (Socket)ar.AsyncState;
  handler = listener.EndAccept(ar);
}

Tôi cần tìm cách phát hiện càng sớm càng tốt rằng máy khách đã ngắt kết nối khỏi handlerSocket.

Tôi đã thử:

  1. handler.Available;
  2. handler.Send(new byte[1], 0, SocketFlags.None);
  3. handler.Receive(new byte[1], 0, SocketFlags.None);

Các phương pháp trên hoạt động khi bạn đang kết nối với máy chủ và muốn phát hiện khi máy chủ ngắt kết nối nhưng chúng không hoạt động khi bạn là máy chủ và muốn phát hiện ngắt kết nối máy khách.

Bất kỳ trợ giúp sẽ được đánh giá cao.


10
@Samuel: Các giao thức TCP và các thẻ kết nối được rất nhiều liên quan đến bài đăng này trong đó TCP duy trì một kết nối (trong khi giao thức mạng khác như UDP không).
Noldorin

3
Thông tin thêm về giải pháp nhịp tim từ blog của tôi: Phát hiện kết nối nửa mở (bị bỏ rơi)
Stephen Cleary

Giải pháp được mô tả ở đây hoạt động tốt đối với tôi: stackoverflow.com/questions/1387459/…
Rawk

Câu trả lời:


110

Vì không có sự kiện nào có sẵn để báo hiệu khi ổ cắm bị ngắt kết nối, bạn sẽ phải thăm dò nó ở tần số có thể chấp nhận được đối với bạn.

Sử dụng phương pháp mở rộng này, bạn có thể có một phương pháp đáng tin cậy để phát hiện xem ổ cắm có bị ngắt kết nối hay không.

static class SocketExtensions
{
  public static bool IsConnected(this Socket socket)
  {
    try
    {
      return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
    }
    catch (SocketException) { return false; }
  }
}

1
Điều này đã hiệu quả. Cảm ơn. Tôi đã thay đổi phương thức để trả về! (Socket.Available == 0 && socket.Poll (1, SelectMode.SelectRead)); vì tôi nghi ngờ socket.Available nhanh hơn Socket.Poll ()

28
Phương pháp này không hoạt động trừ khi đầu kia của kết nối thực sự đóng / tắt ổ cắm. Mạng / cáp nguồn đã rút phích cắm sẽ không được nhận thấy trước khoảng thời gian chờ. Cách duy nhất để được thông báo về việc ngắt kết nối ngay lập tức là sử dụng chức năng nhịp tim để kiểm tra kết nối liên tục.
Kasper Holdum

7
@Smart Alec: thực ra, bạn nên sử dụng ví dụ như được hiển thị ở trên. Có một điều kiện chạy đua tiềm năng nếu bạn thay đổi thứ tự: nếu socket.Availabletrả về 0 và bạn nhận được một gói ngay trước khi socket.Pollđược gọi, Pollnó sẽ trả về true và phương thức sẽ trả về false, mặc dù socket thực sự vẫn khỏe mạnh.
Groo

4
Điều này hoạt động tốt 99% thời gian, đôi khi nó gây ra một ngắt kết nối sai.
Matthew Finlay

5
Giống như Matthew Finlay đã nhận thấy, điều này đôi khi sẽ báo cáo các ngắt kết nối sai vì vẫn có điều kiện chạy đua giữa kết quả của phương pháp Thăm dò ý kiến ​​và kiểm tra thuộc tính Có sẵn. Một gói có thể gần như đã sẵn sàng để đọc nhưng vẫn chưa, do đó Có sẵn là 0 - nhưng một phần nghìn giây sau, sẽ có dữ liệu được đọc. Một lựa chọn tốt hơn là cố gắng Nhận một byte và cờ SocketFlags.Peek. Hoặc thực hiện một số hình thức nhịp tim và giữ trạng thái kết nối ở mức cao hơn. Hoặc dựa vào việc xử lý lỗi tại các phương thức gửi / nhận của bạn (& gọi lại, nếu sử dụng các phiên bản không đồng bộ).
mbargiel

22

Ai đó đã đề cập đến khả năng keepAlive của TCP Socket. Ở đây nó được mô tả độc đáo:

http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html

Tôi đang sử dụng nó theo cách này: sau khi ổ cắm được kết nối, tôi đang gọi hàm này, chức năng này sẽ bật keepAlive. Các keepAliveTimetham số quy định cụ thể thời gian chờ, trong mili giây, không có hoạt động cho đến khi gói giữ-sống đầu tiên được gửi đi. Các keepAliveIntervaltham số xác định khoảng thời gian, trong mili giây, giữa khi Keep-alive liên tiếp được gửi nếu không nhận được nhận được.

    void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval)
    {
        int size = Marshal.SizeOf(new uint());

        var inOptionValues = new byte[size * 3];

        BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0);
        BitConverter.GetBytes((uint)keepAliveTime).CopyTo(inOptionValues, size);
        BitConverter.GetBytes((uint)keepAliveInterval).CopyTo(inOptionValues, size * 2);

        socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
    }

Tôi cũng đang sử dụng tính năng đọc không đồng bộ:

socket.BeginReceive(packet.dataBuffer, 0, 128,
                    SocketFlags.None, new AsyncCallback(OnDataReceived), packet);

Và trong cuộc gọi lại, ở đây là thời gian chờ bắt gặp SocketException, tăng lên khi ổ cắm không nhận được tín hiệu ACK sau khi gói tin còn tồn tại.

public void OnDataReceived(IAsyncResult asyn)
{
    try
    {
        SocketPacket theSockId = (SocketPacket)asyn.AsyncState;

        int iRx = socket.EndReceive(asyn);
    }
    catch (SocketException ex)
    {
        SocketExceptionCaught(ex);
    }
}

Bằng cách này, tôi có thể phát hiện ngắt kết nối giữa máy khách và máy chủ TCP một cách an toàn.


6
Có, hãy đặt khoảng thời gian keepalive + thành giá trị thấp, mọi cuộc thăm dò / gửi sẽ không thành công sau khoảng thời gian keepalive + 10 * mili giây. 10 lần thử lại dường như được mã hóa cứng kể từ Vista? Hoạt động ngay cả khi bạn rút cáp không giống như hầu hết các câu trả lời khác. Đây phải là câu trả lời được chấp nhận.
toster-cx

15

Điều này chỉ đơn giản là không thể. Không có kết nối vật lý nào giữa bạn và máy chủ (ngoại trừ trường hợp cực kỳ hiếm khi bạn kết nối giữa hai trình biên dịch bằng cáp loopback).

Khi kết nối được đóng một cách duyên dáng, phía bên kia sẽ được thông báo. Nhưng nếu kết nối bị ngắt kết nối theo một cách nào đó (giả sử kết nối người dùng bị ngắt) thì máy chủ sẽ không biết cho đến khi hết thời gian (hoặc cố gắng ghi vào kết nối và ack hết thời gian). Đó chỉ là cách thức hoạt động của TCP và bạn phải sống chung với nó.

Do đó, "tức thì" là không thực tế. Điều tốt nhất bạn có thể làm là trong khoảng thời gian chờ, điều này phụ thuộc vào nền tảng mà mã đang chạy.

CHỈNH SỬA: Nếu bạn chỉ đang tìm kiếm các kết nối duyên dáng, thì tại sao không gửi lệnh "DISCONNECT" đến máy chủ từ máy khách của bạn?


1
Cảm ơn nhưng ý tôi thực sự là ngắt kết nối phần mềm duyên dáng, không phải ngắt kết nối vật lý. Tôi đang chạy Windows

1
Nếu anh ấy chỉ tìm kiếm sự ngắt kết nối duyên dáng thì anh ấy không cần phải gửi bất cứ thứ gì. Bài đọc của anh ấy sẽ trở lại cuối luồng.
user207421 22/02/17

6

"Đó chỉ là cách thức hoạt động của TCP và bạn phải sống chung với nó."

Đúng, bạn nói đúng. Đó là một thực tế của cuộc sống mà tôi đã nhận ra. Bạn sẽ thấy hành vi tương tự được thể hiện ngay cả trong các ứng dụng chuyên nghiệp sử dụng giao thức này (và thậm chí cả những người khác). Tôi thậm chí đã thấy nó xảy ra trong các trò chơi trực tuyến; bạn thân của bạn nói "tạm biệt" và anh ấy có vẻ sẽ trực tuyến thêm 1-2 phút nữa cho đến khi máy chủ "dọn dẹp nhà cửa".

Bạn có thể sử dụng các phương pháp được đề xuất tại đây hoặc triển khai "nhịp tim", như được đề xuất. Tôi chọn cái trước. Nhưng nếu tôi chọn cái sau, tôi chỉ đơn giản là để máy chủ "ping" mỗi máy khách thường xuyên bằng một byte duy nhất và xem liệu chúng tôi có hết thời gian chờ hay không có phản hồi. Bạn thậm chí có thể sử dụng một chuỗi nền để đạt được điều này với thời gian chính xác. Thậm chí có thể một sự kết hợp có thể được thực hiện trong một số loại danh sách tùy chọn (cờ enum hoặc thứ gì đó) nếu bạn thực sự lo lắng về nó. Nhưng không có vấn đề gì quá lớn nếu có một chút chậm trễ trong việc cập nhật máy chủ, miễn là bạn NÊN cập nhật. Đó là internet, và không ai mong đợi nó là ma thuật! :)


3

Triển khai nhịp tim vào hệ thống của bạn có thể là một giải pháp. Điều này chỉ có thể thực hiện được nếu cả máy khách và máy chủ đều nằm trong tầm kiểm soát của bạn. Bạn có thể có một đối tượng DateTime theo dõi thời gian nhận được các byte cuối cùng từ ổ cắm. Và giả sử rằng ổ cắm không được phản hồi trong một khoảng thời gian nhất định sẽ bị mất. Điều này sẽ chỉ hoạt động nếu bạn đã triển khai nhịp tim / tùy chỉnh duy trì hoạt động.


2

Tôi thấy khá hữu ích, một giải pháp khác cho điều đó!

Nếu bạn sử dụng các phương thức không đồng bộ để đọc dữ liệu từ ổ cắm mạng (ý tôi là sử dụng BeginReceive- EndReceive các phương pháp), bất cứ khi nào kết nối bị ngắt; một trong những trường hợp sau sẽ xuất hiện: Một tin nhắn được gửi không có dữ liệu (bạn có thể thấy nó với Socket.Available- ngay cả khi BeginReceiveđược kích hoạt, giá trị của nó sẽ bằng 0) hoặc Socket.Connectedgiá trị trở thành sai trong lệnh gọi này (đừng cố sử dụng EndReceivekhi đó).

Tôi đang đăng chức năng tôi đã sử dụng, tôi nghĩ bạn có thể hiểu ý tôi từ nó tốt hơn:


private void OnRecieve(IAsyncResult parameter) 
{
    Socket sock = (Socket)parameter.AsyncState;
    if(!sock.Connected || sock.Available == 0)
    {
        // Connection is terminated, either by force or willingly
        return;
    }

    sock.EndReceive(parameter);
    sock.BeginReceive(..., ... , ... , ..., new AsyncCallback(OnRecieve), sock);

    // To handle further commands sent by client.
    // "..." zones might change in your code.
}

2

Điều này phù hợp với tôi, điều quan trọng là bạn cần một luồng riêng để phân tích trạng thái ổ cắm với tính năng thăm dò. làm điều đó trong cùng một chủ đề khi ổ cắm không phát hiện được.

//open or receive a server socket - TODO your code here
socket = new Socket(....);

//enable the keep alive so we can detect closure
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);

//create a thread that checks every 5 seconds if the socket is still connected. TODO add your thread starting code
void MonitorSocketsForClosureWorker() {
    DateTime nextCheckTime = DateTime.Now.AddSeconds(5);

    while (!exitSystem) {
        if (nextCheckTime < DateTime.Now) {
            try {
                if (socket!=null) {
                    if(socket.Poll(5000, SelectMode.SelectRead) && socket.Available == 0) {
                        //socket not connected, close it if it's still running
                        socket.Close();
                        socket = null;    
                    } else {
                        //socket still connected
                    }    
               }
           } catch {
               socket.Close();
            } finally {
                nextCheckTime = DateTime.Now.AddSeconds(5);
            }
        }
        Thread.Sleep(1000);
    }
}

1

Mã ví dụ ở đây http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx cho thấy cách xác định xem Socket vẫn được kết nối mà không gửi bất kỳ dữ liệu nào.

Nếu bạn đã gọi Socket.BeginReceive () trên chương trình máy chủ và sau đó máy khách đóng kết nối một cách "duyên dáng", lệnh gọi lại nhận của bạn sẽ được gọi và EndReceive () sẽ trả về 0 byte. 0 byte này có nghĩa là máy khách "có thể" đã ngắt kết nối. Sau đó, bạn có thể sử dụng kỹ thuật được hiển thị trong mã ví dụ MSDN để xác định xem kết nối có bị đóng hay không.


1

Mở rộng về nhận xét của mbargielmycelo về câu trả lời được chấp nhận, phần sau có thể được sử dụng với ổ cắm không chặn ở đầu máy chủ để thông báo liệu máy khách đã tắt hay chưa.

Cách tiếp cận này không bị điều kiện chủng tộc ảnh hưởng đến phương pháp Thăm dò ý kiến ​​trong câu trả lời được chấp nhận.

// Determines whether the remote end has called Shutdown
public bool HasRemoteEndShutDown
{
    get
    {
        try
        {
            int bytesRead = socket.Receive(new byte[1], SocketFlags.Peek);

            if (bytesRead == 0)
                return true;
        }
        catch
        {
            // For a non-blocking socket, a SocketException with 
            // code 10035 (WSAEWOULDBLOCK) indicates no data available.
        }

        return false;
    }
}

Phương pháp này dựa trên thực tế là Socket.Receivephương thức trả về 0 ngay lập tức sau khi đầu cuối từ xa tắt ổ cắm của nó và chúng tôi đã đọc tất cả dữ liệu từ nó. Từ tài liệu Socket.Receive :

Nếu máy chủ từ xa tắt kết nối Socket bằng phương pháp Tắt máy và tất cả dữ liệu có sẵn đã được nhận, phương thức Nhận sẽ hoàn tất ngay lập tức và trả về 0 byte.

Nếu bạn đang ở chế độ không chặn và không có sẵn dữ liệu trong bộ đệm ngăn xếp giao thức, phương thức Nhận sẽ hoàn tất ngay lập tức và đưa ra một SocketException.

Điểm thứ hai giải thích sự cần thiết của việc thử bắt.

Việc sử dụng SocketFlags.Peekcờ sẽ không ảnh hưởng đến mọi dữ liệu đã nhận cho một cơ chế nhận riêng biệt để đọc.

Cách trên cũng sẽ hoạt động với ổ cắm chặn , nhưng hãy lưu ý rằng mã sẽ chặn trên cuộc gọi Nhận (cho đến khi nhận được dữ liệu hoặc hết thời gian chờ nhận, một lần nữa dẫn đến a SocketException).


0

Bạn không thể chỉ sử dụng Chọn?

Sử dụng tính năng chọn trên ổ cắm được kết nối. Nếu lựa chọn trả về với ổ cắm của bạn là Sẵn sàng nhưng Nhận tiếp theo trả về 0 byte có nghĩa là máy khách đã ngắt kết nối. AFAIK, đó là cách nhanh nhất để xác định xem máy khách có ngắt kết nối hay không.

Tôi không biết C # nên chỉ cần bỏ qua nếu giải pháp của tôi không phù hợp với C # (C # cung cấp lựa chọn mặc dù) hoặc nếu tôi đã hiểu sai ngữ cảnh.


0

Sử dụng phương thức SetSocketOption, bạn sẽ có thể đặt KeepAlive sẽ cho bạn biết bất cứ khi nào Socket bị ngắt kết nối

Socket _connectedSocket = this._sSocketEscucha.EndAccept(asyn);
                _connectedSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);

http://msdn.microsoft.com/en-us/library/1011kecd(v=VS.90).aspx

Hy vọng nó giúp! Ramiro Rinaldi


2
Nó sẽ không 'cho bạn biết bất cứ khi nào ổ cắm bị ngắt kết nối'. Cuối cùng, nó sẽ phát hiện ra nó nếu bộ đếm thời gian duy trì hoạt động hết hạn. Theo mặc định, nó được đặt thành hai giờ. Bạn không thể mô tả điều đó là 'bất cứ khi nào'. Bạn cũng không thể mô tả nó là 'cho [ting] bạn biết: bạn vẫn phải đọc hoặc ghi để phát hiện lỗi. -1
dùng207421

0

tôi đã gặp vấn đề tương tự, hãy thử cái này:

void client_handler(Socket client) // set 'KeepAlive' true
{
    while (true)
    {
        try
        {
            if (client.Connected)
            {

            }
            else
            { // client disconnected
                break;
            }
        }
        catch (Exception)
        {
            client.Poll(4000, SelectMode.SelectRead);// try to get state
        }
    }
}

0

Điều này là trong VB, nhưng nó có vẻ hoạt động tốt đối với tôi. Nó tìm kiếm trả về 0 byte như bài trước.

Private Sub RecData(ByVal AR As IAsyncResult)
    Dim Socket As Socket = AR.AsyncState

    If Socket.Connected = False And Socket.Available = False Then
        Debug.Print("Detected Disconnected Socket - " + Socket.RemoteEndPoint.ToString)
        Exit Sub
    End If
    Dim BytesRead As Int32 = Socket.EndReceive(AR)
    If BytesRead = 0 Then
        Debug.Print("Detected Disconnected Socket - Bytes Read = 0 - " + Socket.RemoteEndPoint.ToString)
        UpdateText("Client " + Socket.RemoteEndPoint.ToString + " has disconnected from Server.")
        Socket.Close()
        Exit Sub
    End If
    Dim msg As String = System.Text.ASCIIEncoding.ASCII.GetString(ByteData)
    Erase ByteData
    ReDim ByteData(1024)
    ClientSocket.BeginReceive(ByteData, 0, ByteData.Length, SocketFlags.None, New AsyncCallback(AddressOf RecData), ClientSocket)
    UpdateText(msg)
End Sub

-1

Bạn cũng có thể kiểm tra thuộc tính .IsConnected của ổ cắm nếu bạn muốn thăm dò ý kiến.


2
Điều này sẽ không hoạt động đối với bất kỳ tình huống nào (máy chủ / máy khách) mà tôi đã trình bày ở trên

1
Ngoài ra, thuộc tính được gọi là .Connected.
Qwertie

Điều đó không phát hiện các ngắt kết nối tùy ý. Bạn vẫn phải thực hiện một số I / O.
user207421

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.