gpt4 book ai didi

c# - 为什么 Socket.Receive 在超时设置为无限时在半关闭连接上超时?

转载 作者:太空狗 更新时间:2023-10-29 23:24:37 24 4
gpt4 key购买 nike

下面是演示问题的 C# 程序。

服务器开始监听套接字。客户端连接到服务器,发送一条消息,使用 Shutdown(SocketShutdown.Send) 关闭其发送的一半连接,让服务器知道消息的结尾在哪里,并等待来自服务器的响应。服务器读取消息,进行一些冗长的计算(此处模拟 sleep 调用),向客户端发送消息,然后关闭连接。

在 Windows 上,客户端的 Receive 调用总是在整整 2 分钟后失败,并显示“连接尝试失败,因为连接方在一段时间后没有正确响应,或者建立的连接失败,因为连接的主机未能响应”,即使超时设置为无限。

如果我在 Linux 中使用 Mono 运行该程序,即使我将“长时间操作”设置为 10 分钟也不会发生超时,但在 Windows 中无论我使用 Mono 还是 .NET 运行它都会发生。如果我将超时设置为 1 秒,它会在 1 秒后超时。换句话说,它在我设置的超时时间或 2 分钟内超时,以较小者为准。

一个类似的示例程序,其中服务器向客户端发送消息,没有从客户端到服务器的消息,也没有半关闭,按预期工作,没有超时。

我可以通过修改我的协议(protocol)以使用一些其他方法在消息完成时向服务器指示(可能在消息前加上消息长度)来解决这个问题。但我想知道这里发生了什么。当超时设置为无限时,为什么 Socket.Receive 在连接半关闭时超时?

据我了解,仅发送半关闭的连接应该能够无限期地继续接收数据。 Windows 的这样一个基本部分似乎不太可能存在错误。我做错了什么吗?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Start server thread
Thread serverThread = new Thread(ServerStart);
serverThread.IsBackground = true;
serverThread.Start();

// Give the server some time to start listening
Thread.Sleep(2000);

ClientStart();
}

static int PortNumber = 8181;

static void ServerStart()
{
TcpListener listener = new TcpListener(new IPEndPoint(IPAddress.Any, PortNumber));
listener.Start();
while (true)
{
TcpClient client = listener.AcceptTcpClient();
Task connectionHandlerTask = new Task(ConnectionEntryPoint, client);
connectionHandlerTask.Start();
}
listener.Stop();
}

static void ConnectionEntryPoint(object clientObj)
{
using (TcpClient client = (TcpClient)clientObj)
using (NetworkStream stream = client.GetStream())
{
// Read from client until client closes its send half.
byte[] requestBytes = new byte[65536];
int bufferPos = 0;
int lastReadSize = -1;
while (lastReadSize != 0)
{
lastReadSize = stream.Read(requestBytes, bufferPos, 65536 - bufferPos);
bufferPos += lastReadSize;
}
client.Client.Shutdown(SocketShutdown.Receive);
string message = Encoding.UTF8.GetString(requestBytes, 0, bufferPos);

// Sleep for 2 minutes, 30 seconds to simulate a long-running calculation, then echo the client's message back
byte[] responseBytes = Encoding.UTF8.GetBytes(message);
Console.WriteLine("Waiting 2 minutes 30 seconds.");
Thread.Sleep(150000);

try
{
stream.Write(responseBytes, 0, responseBytes.Length);
}
catch (SocketException ex)
{
Console.WriteLine("Socket exception in server: {0}", ex.Message);
}
}
}

static void ClientStart()
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
// Set receive timeout to infinite.
socket.ReceiveTimeout = -1;

// Connect to server
socket.Connect(IPAddress.Loopback, PortNumber);

// Send a message to the server, then close the send half of the client's connection
// to let the server know it has the entire message.
string requestMessage = "Hello";
byte[] requestBytes = Encoding.UTF8.GetBytes(requestMessage);
socket.Send(requestBytes);
socket.Shutdown(SocketShutdown.Send);

// Read the server's response. The response is done when the server closes the connection.
byte[] responseBytes = new byte[65536];
int bufferPos = 0;
int lastReadSize = -1;

Stopwatch timer = Stopwatch.StartNew();
try
{
while (lastReadSize != 0)
{
lastReadSize = socket.Receive(responseBytes, bufferPos, 65536 - bufferPos, SocketFlags.None);
bufferPos += lastReadSize;
}

string responseMessage = Encoding.UTF8.GetString(responseBytes, 0, bufferPos);
Console.WriteLine(responseMessage);
}
catch (SocketException ex)
{
// Timeout always occurs after 2 minutes. Why?
timer.Stop();
Console.WriteLine("Socket exception in client after {0}: {1}", timer.Elapsed, ex.Message);
}
}
}
}
}

以下程序使用 4 字节消息长度作为消息前缀,而不是使用 socket.Shutdown(SocketShutdown.Send) 来表示消息结束。此程序中不会发生超时。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Threading;

namespace WithoutShutdown
{
class Program
{
static void Main(string[] args)
{
// Start server thread
Thread serverThread = new Thread(ServerStart);
serverThread.IsBackground = true;
serverThread.Start();

// Give the server some time to start listening
Thread.Sleep(2000);

ClientStart();
}

static int PortNumber = 8181;

static void ServerStart()
{
TcpListener listener = new TcpListener(new IPEndPoint(IPAddress.Any, PortNumber));
listener.Start();
while (true)
{
TcpClient client = listener.AcceptTcpClient();
Task connectionHandlerTask = new Task(ConnectionEntryPoint, client);
connectionHandlerTask.Start();
}
listener.Stop();
}

static void SendMessage(Socket socket, byte[] message)
{
// Send a 4-byte message length followed by the message itself
int messageLength = message.Length;
byte[] messageLengthBytes = BitConverter.GetBytes(messageLength);
socket.Send(messageLengthBytes);
socket.Send(message);
}

static byte[] ReceiveMessage(Socket socket)
{
// Read 4-byte message length from the client
byte[] messageLengthBytes = new byte[4];
int bufferPos = 0;
int lastReadSize = -1;
while (bufferPos < 4)
{
lastReadSize = socket.Receive(messageLengthBytes, bufferPos, 4 - bufferPos, SocketFlags.None);
bufferPos += lastReadSize;
}
int messageLength = BitConverter.ToInt32(messageLengthBytes, 0);

// Read the message
byte[] messageBytes = new byte[messageLength];
bufferPos = 0;
lastReadSize = -1;
while (bufferPos < messageLength)
{
lastReadSize = socket.Receive(messageBytes, bufferPos, messageLength - bufferPos, SocketFlags.None);
bufferPos += lastReadSize;
}

return messageBytes;
}

static void ConnectionEntryPoint(object clientObj)
{
using (TcpClient client = (TcpClient)clientObj)
{
byte[] requestBytes = ReceiveMessage(client.Client);
string message = Encoding.UTF8.GetString(requestBytes);

// Sleep for 2 minutes, 30 seconds to simulate a long-running calculation, then echo the client's message back
byte[] responseBytes = Encoding.UTF8.GetBytes(message);
Console.WriteLine("Waiting 2 minutes 30 seconds.");
Thread.Sleep(150000);

try
{
SendMessage(client.Client, responseBytes);
}
catch (SocketException ex)
{
Console.WriteLine("Socket exception in server: {0}", ex.Message);
}
}
}

static void ClientStart()
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
// Set receive timeout to infinite.
socket.ReceiveTimeout = -1;

// Connect to server
socket.Connect(IPAddress.Loopback, PortNumber);

// Send a message to the server
string requestMessage = "Hello";
byte[] requestBytes = Encoding.UTF8.GetBytes(requestMessage);
SendMessage(socket, requestBytes);

// Read the server's response.
Stopwatch timer = Stopwatch.StartNew();
try
{
byte[] responseBytes = ReceiveMessage(socket);
string responseMessage = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseMessage);
}
catch (SocketException ex)
{
// Timeout does not occur in this program because it does not call socket.Shutdown(SocketShutdown.Send)
timer.Stop();
Console.WriteLine("Socket exception in client after {0}: {1}", timer.Elapsed, ex.Message);
}
}
}
}
}

最佳答案

此行为是设计使然。当客户端关闭连接的一半并且服务器确认关闭时,客户端处于 FIN_WAIT_2 状态,等待服务器关闭连接。 http://support.microsoft.com/kb/923200声明 FIN_WAIT_2 超时为 2 分钟。如果连接处于 FIN_WAIT_2 状态时 2 分钟内没有收到数据,则客户端强制关闭连接(使用 RST)。

By default in Windows Server 2003, TCP connections must close after the TCP connection state has been set to FIN_WAIT_2 for two minutes.

This old Apache article建议超时的原因:恶意或行为不当的应用程序可以通过从不关闭连接的一端而无限期地将连接的另一端保持在 FIN_WAIT_2 中,从而占用操作系统资源。

Linux apparently has a timeout as well您可以使用

检查值

$ cat/proc/sys/net/ipv4/tcp_fin_timeout

我不确定为什么我在 Linux 上没有发生超时。也许是因为它是环回连接,因此 DoS 攻击不是问题,或者环回连接使用不使用 tcp_fin_timeout 设置的不同代码?

底线:操作系统有充分的理由使连接超时。避免将 Shutdown 用作应用层信号机制,而是使用实际的应用层方法。

关于c# - 为什么 Socket.Receive 在超时设置为无限时在半关闭连接上超时?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13466639/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com