保持 TCP 连接
Keeping TCP connection alive
这可能是个愚蠢的问题,但听我说完。
在我的远程服务器上,我是 运行 具有以下代码的程序:
private const int BufferSize = 1024;
private static readonly byte[] BytesBeingReceived = new byte[BufferSize];
static void Main(string[] args)
{
TcpListener tcpListener = new TcpListener(localaddr: IPAddress.Any, port: 8080);
tcpListener.Start();
clientSocket = tcpListener.AcceptSocket();
clientSocket.Receive(BytesBeingReceived); // Receives header first
ReceiveAndUnzip(GetZippedFolderSizeInBytes());
// GetZippedFolderSizeInBytes() reads the header, which contains information about the folder size
Console.WriteLine("Press any key to exit the program.");
Console.ReadLine();
}
正如预期的那样,程序在连接到客户端套接字并从客户端接收所有数据后退出(到目前为止,客户端始终是我的本地计算机)。
但是,只要服务器可用(即不重新启动、休眠、关闭等),我想保留程序 运行。从客户端收到所有数据后,我想保持 TcpListener
处于活动状态并等待可能更多的数据。如果客户端断开连接,TcpListener
仍应保持活动状态并等待未来的挂起连接。
有什么建议吗?
更新:
下面是GetZippedFolderSizeInBytes()
的实现:
private static int GetFileSizeInBytes()
{
return Convert.ToInt32(Encoding.ASCII.GetString(BytesBeingReceived)
.Replace("[=11=]", Empty)
.Split(new[] { Environment.NewLine, ":" }, StringSplitOptions.RemoveEmptyEntries)[1]);
}
下面是ReceiveAndUnzip(int numBytesExpectedToReceive)
方法的实现:
private static void ReceiveAndUnzip(int numBytesExpectedToReceive)
{
int numBytesLeftToReceive = numBytesExpectedToReceive;
using (MemoryStream zippedFolderStream = new MemoryStream(new byte[numBytesExpectedToReceive]))
{
while (numBytesLeftToReceive > 0)
{
Array.Clear(BytesBeingReceived, 0, BufferSize);
int numBytesReceived = clientSocket.Receive(BytesBeingReceived, SocketFlags.Partial);
zippedFolderStream.Write(
BytesBeingReceived,
0,
numBytesLeftToReceive < BufferSize ? numBytesLeftToReceive : BufferSize);
numBytesLeftToReceive -= numBytesReceived;
}
zippedFolderStream.Unzip(afterReadingEachDocument: DoMoreStuff);
}
}
您必须更改代码以在循环中接受新客户。如果您希望客户端连接,您可能希望使用一些异步版本的 Receive 和 Accept 并生成一些任务来循环处理客户端 - 这并不是真正的最佳方式,但它应该有效。
网络编程是一个棘手的问题。这里有一些提示。
1。邮件碎片整理
您无法保证客户端中的单个 Send 调用会导致服务器上的单个 Receive。您的消息可以分成 片段 或者如果您快速发送几条消息,它们可以全部连接在一起并通过单个 Receive 调用接收。
该问题的一个解决方案是首先在消息前加上 header 作为前缀,例如4 字节整数。这个整数是您的消息 header 并且仅包含有关消息长度的信息(以字节为单位)。
你想要做的是接收直到你得到这个 4 字节整数 header 然后接收消息的其余部分 - 你现在知道消息有多长所以你接收直到你得到整个消息。对每条新消息重复此过程。
这称为碎片整理或碎片整理。您可以通过多种方式实现它:定界符、以 fixed-size header 为前缀的消息,以及两者的混合。
2。 Keep-alive / 心跳机制
在 tcp 中,half-open 和 half-closed 连接存在问题。简而言之,这只是意味着如果您不交换任何消息,您无法确定连接是否仍然 alive - peer/client 已连接。当 peer/client 断开连接时,您的 Receive 调用应该 return 0 - 这意味着连接已关闭,但 TCP 的工作方式并不总是case 你必须考虑到这一点以避免各种相关问题。
您可以实现的最简单的 Keep-Alive 机制只是一些超时逻辑,例如2 分钟内未收到任何消息表示 peer/client 已断开连接。或者添加额外的心跳消息,每 X 秒发送一次,如果在某个时间范围内未发送,则 peer/client 超时。
3。使用一些 well-tested 库
如果您没有实现任何自定义协议并且不想自己解决所有已经被一些聪明人解决的 TCP 问题,那么搜索一个库。
这可能是个愚蠢的问题,但听我说完。
在我的远程服务器上,我是 运行 具有以下代码的程序:
private const int BufferSize = 1024;
private static readonly byte[] BytesBeingReceived = new byte[BufferSize];
static void Main(string[] args)
{
TcpListener tcpListener = new TcpListener(localaddr: IPAddress.Any, port: 8080);
tcpListener.Start();
clientSocket = tcpListener.AcceptSocket();
clientSocket.Receive(BytesBeingReceived); // Receives header first
ReceiveAndUnzip(GetZippedFolderSizeInBytes());
// GetZippedFolderSizeInBytes() reads the header, which contains information about the folder size
Console.WriteLine("Press any key to exit the program.");
Console.ReadLine();
}
正如预期的那样,程序在连接到客户端套接字并从客户端接收所有数据后退出(到目前为止,客户端始终是我的本地计算机)。
但是,只要服务器可用(即不重新启动、休眠、关闭等),我想保留程序 运行。从客户端收到所有数据后,我想保持 TcpListener
处于活动状态并等待可能更多的数据。如果客户端断开连接,TcpListener
仍应保持活动状态并等待未来的挂起连接。
有什么建议吗?
更新:
下面是GetZippedFolderSizeInBytes()
的实现:
private static int GetFileSizeInBytes()
{
return Convert.ToInt32(Encoding.ASCII.GetString(BytesBeingReceived)
.Replace("[=11=]", Empty)
.Split(new[] { Environment.NewLine, ":" }, StringSplitOptions.RemoveEmptyEntries)[1]);
}
下面是ReceiveAndUnzip(int numBytesExpectedToReceive)
方法的实现:
private static void ReceiveAndUnzip(int numBytesExpectedToReceive)
{
int numBytesLeftToReceive = numBytesExpectedToReceive;
using (MemoryStream zippedFolderStream = new MemoryStream(new byte[numBytesExpectedToReceive]))
{
while (numBytesLeftToReceive > 0)
{
Array.Clear(BytesBeingReceived, 0, BufferSize);
int numBytesReceived = clientSocket.Receive(BytesBeingReceived, SocketFlags.Partial);
zippedFolderStream.Write(
BytesBeingReceived,
0,
numBytesLeftToReceive < BufferSize ? numBytesLeftToReceive : BufferSize);
numBytesLeftToReceive -= numBytesReceived;
}
zippedFolderStream.Unzip(afterReadingEachDocument: DoMoreStuff);
}
}
您必须更改代码以在循环中接受新客户。如果您希望客户端连接,您可能希望使用一些异步版本的 Receive 和 Accept 并生成一些任务来循环处理客户端 - 这并不是真正的最佳方式,但它应该有效。
网络编程是一个棘手的问题。这里有一些提示。
1。邮件碎片整理
您无法保证客户端中的单个 Send 调用会导致服务器上的单个 Receive。您的消息可以分成 片段 或者如果您快速发送几条消息,它们可以全部连接在一起并通过单个 Receive 调用接收。
该问题的一个解决方案是首先在消息前加上 header 作为前缀,例如4 字节整数。这个整数是您的消息 header 并且仅包含有关消息长度的信息(以字节为单位)。
你想要做的是接收直到你得到这个 4 字节整数 header 然后接收消息的其余部分 - 你现在知道消息有多长所以你接收直到你得到整个消息。对每条新消息重复此过程。
这称为碎片整理或碎片整理。您可以通过多种方式实现它:定界符、以 fixed-size header 为前缀的消息,以及两者的混合。
2。 Keep-alive / 心跳机制
在 tcp 中,half-open 和 half-closed 连接存在问题。简而言之,这只是意味着如果您不交换任何消息,您无法确定连接是否仍然 alive - peer/client 已连接。当 peer/client 断开连接时,您的 Receive 调用应该 return 0 - 这意味着连接已关闭,但 TCP 的工作方式并不总是case 你必须考虑到这一点以避免各种相关问题。
您可以实现的最简单的 Keep-Alive 机制只是一些超时逻辑,例如2 分钟内未收到任何消息表示 peer/client 已断开连接。或者添加额外的心跳消息,每 X 秒发送一次,如果在某个时间范围内未发送,则 peer/client 超时。
3。使用一些 well-tested 库
如果您没有实现任何自定义协议并且不想自己解决所有已经被一些聪明人解决的 TCP 问题,那么搜索一个库。