我最近写了一个简单粗糙的C#原型代理服务器,作为努力使Java Web应用程序与其他服务器上的遗留VB6应用程序通信的一部分。它非常简单:
代理服务器和客户端都使用相同的消息格式;在代码中,我使用一个ProxyMessage类来表示客户端的请求和服务器生成的响应:
这些信息非常简单:正文的长度和消息正文本身。
我有一个独立的ProxyClient类,表示与客户端的连接。它处理代理和单个客户端之间的所有交互。
我想知道是否有设计模式或最佳实践可以简化异步套接字编程中与样板代码相关的问题?例如,您需要特别注意管理读取缓冲区,以避免意外丢失字节,并且需要跟踪当前消息处理状态的进度。在我的当前代码中,我在
下面是我传递给
到目前为止,我唯一的真正想法是将与缓冲区相关的内容提取到单独的
代理服务器和客户端都使用相同的消息格式;在代码中,我使用一个ProxyMessage类来表示客户端的请求和服务器生成的响应:
public class ProxyMessage
{
int Length; // message length (not including the length bytes themselves)
string Body; // an XML string containing a request/response
// writes this message instance in the proper network format to stream
// (helper for response messages)
WriteToStream(Stream stream) { ... }
}
这些信息非常简单:正文的长度和消息正文本身。
我有一个独立的ProxyClient类,表示与客户端的连接。它处理代理和单个客户端之间的所有交互。
我想知道是否有设计模式或最佳实践可以简化异步套接字编程中与样板代码相关的问题?例如,您需要特别注意管理读取缓冲区,以避免意外丢失字节,并且需要跟踪当前消息处理状态的进度。在我的当前代码中,我在
TcpClient.BeginRead
的回调函数中完成所有这些工作,并使用一些实例变量来管理缓冲区的状态和当前消息处理状态。下面是我传递给
BeginRead
的回调函数的代码,以及相关的实例变量用于上下文。代码似乎“按原样”运行良好,但我想知道是否可以重构一下使其更清晰一些(或者可能已经很清晰了?)。private enum BufferStates
{
GetMessageLength,
GetMessageBody
}
// The read buffer. Initially 4 bytes because we are initially
// waiting to receive the message length (a 32-bit int) from the client
// on first connecting. By constraining the buffer length to exactly 4 bytes,
// we make the buffer management a bit simpler, because
// we don't have to worry about cases where the buffer might contain
// the message length plus a few bytes of the message body.
// Additional bytes will simply be buffered by the OS until we request them.
byte[] _buffer = new byte[4];
// A count of how many bytes read so far in a particular BufferState.
int _totalBytesRead = 0;
// The state of the our buffer processing. Initially, we want
// to read in the message length, as it's the first thing
// a client will send
BufferStates _bufferState = BufferStates.GetMessageLength;
// ...ADDITIONAL CODE OMITTED FOR BREVITY...
// This is called every time we receive data from
// the client.
private void ReadCallback(IAsyncResult ar)
{
try
{
int bytesRead = _tcpClient.GetStream().EndRead(ar);
if (bytesRead == 0)
{
// No more data/socket was closed.
this.Dispose();
return;
}
// The state passed to BeginRead is used to hold a ProxyMessage
// instance that we use to build to up the message
// as it arrives.
ProxyMessage message = (ProxyMessage)ar.AsyncState;
if(message == null)
message = new ProxyMessage();
switch (_bufferState)
{
case BufferStates.GetMessageLength:
_totalBytesRead += bytesRead;
// if we have the message length (a 32-bit int)
// read it in from the buffer, grow the buffer
// to fit the incoming message, and change
// state so that the next read will start appending
// bytes to the message body
if (_totalBytesRead == 4)
{
int length = BitConverter.ToInt32(_buffer, 0);
message.Length = length;
_totalBytesRead = 0;
_buffer = new byte[message.Length];
_bufferState = BufferStates.GetMessageBody;
}
break;
case BufferStates.GetMessageBody:
string bodySegment = Encoding.ASCII.GetString(_buffer, _totalBytesRead, bytesRead);
_totalBytesRead += bytesRead;
message.Body += bodySegment;
if (_totalBytesRead >= message.Length)
{
// Got a complete message.
// Notify anyone interested.
// Pass a response ProxyMessage object to
// with the event so that receivers of OnReceiveMessage
// can send a response back to the client after processing
// the request.
ProxyMessage response = new ProxyMessage();
OnReceiveMessage(this, new ProxyMessageEventArgs(message, response));
// Send the response to the client
response.WriteToStream(_tcpClient.GetStream());
// Re-initialize our state so that we're
// ready to receive additional requests...
message = new ProxyMessage();
_totalBytesRead = 0;
_buffer = new byte[4]; //message length is 32-bit int (4 bytes)
_bufferState = BufferStates.GetMessageLength;
}
break;
}
// Wait for more data...
_tcpClient.GetStream().BeginRead(_buffer, 0, _buffer.Length, this.ReadCallback, message);
}
catch
{
// do nothing
}
}
到目前为止,我唯一的真正想法是将与缓冲区相关的内容提取到单独的
MessageBuffer
类中,然后只需在读取回调函数中按顺序附加新字节即可。 MessageBuffer
将会担心当前的BufferState
等问题,并在接收完整消息时触发事件,ProxyClient
随后可以将其传播到主代理服务器代码中,以便请求进行处理。