我一直在寻找有关如何处理TCP消息框架的示例。我看到许多示例,其中NetworkStreams被传递到StreamReader或StreamWriter对象,然后使用ReadLine或WriteLine方法来处理'\ n'分隔的消息。我的应用程序协议包含以'\ n'结尾的消息,因此NetworkStream似乎是最佳选择。但是,我找不到任何关于正确处理所有这些与异步套接字相结合的方法的具体示例。当下面调用ReceiveCallback()时,如何实现NetworkStream和StreamReader类来处理消息框架?根据我所读到的内容,我可以在一次接收中获取一条消息的一部分,并在下一次接收时获取剩余的消息(包括'\ n')。这是否意味着我可以得到一条消息的结尾和下一条消息的一部分?当然,必须有一种更简单的方法来解决这个问题。
我有以下代码:
private void StartRead(Socket socket)
{
try
{
StateObject state = new StateObject();
state.AsyncSocket = socket;
socket.BeginReceive(state.Buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
catch (SocketException)
{
m_Socket.Shutdown(SocketShutdown.Both);
Disconnect();
}
}
private void ReceiveCallback(IAsyncResult ar)
{
try
{
StateObject state = (StateObject)ar.AsyncState;
int bytes_read = state.AsyncSocket.EndReceive(ar);
char[] chars = new char[bytes_read + 1];
System.Text.Decoder decoder = System.Text.Encoding.UTF8.GetDecoder();
int charLength = decoder.GetChars(state.Buffer, 0, bytes_read, chars, 0);
String data = new String(chars);
ParseMessage(data);
StartRead(state.AsyncSocket);
}
catch (SocketException)
{
m_Socket.Shutdown(SocketShutdown.Both);
Disconnect();
}
}
答案 0 :(得分:3)
使用长度预先添加块比使用分隔符更好。您不必处理任何类型的转义,以便以这种方式使用换行符发送数据。
这个答案现在可能与您无关,因为它使用AsyncCTP中的功能,这些功能只会出现在.net的下一个版本中。但是,它确实使事情更加简洁。基本上,您准确编写了您为同步情况所做的代码,但在有异步调用的地方插入'await'语句。
public static async Task<Byte[]> ReadChunkAsync(this Stream me) {
var size = BitConverter.ToUInt32(await me.ReadExactAsync(4), 0);
checked {
return await me.ReadExactAsync((int)size);
}
}
public static async Task<Byte[]> ReadExactAsync(this Stream me, int count) {
var buf = new byte[count];
var t = 0;
while (t < count) {
var n = await me.ReadAsync(buf, t, count - t);
if (n <= 0) {
if (t > 0) throw new IOException("End of stream (fragmented)");
throw new IOException("End of stream");
}
t += n;
}
return buf;
}
public static void WriteChunk(this Stream me, byte[] buffer, int offset, int count) {
me.Write(BitConverter.GetBytes(count), 0, 4);
me.Write(buffer, offset, count);
}
答案 1 :(得分:1)
基本上,您创建一个缓冲区,每次接收数据时,都会将该数据添加到缓冲区,并确定您是否已收到一条或多条完整邮件。
在ReceiveCallback
和StartRead
之间,您将不会收到任何异步消息(传入的数据将自动在套接字级别缓冲),因此它是检查完整消息并从中删除它们的理想位置。缓冲液中。
所有变体都是可能的,包括接收消息1的结尾,加上消息2,加上消息3的开头,所有变化都在一个块中。
我不建议对块进行UTF8解码,因为一个UTF8字符可能包含两个字节,如果它们在块之间分割,则数据可能会被破坏。在这种情况下,您可以保留byte [] - buffer(MemoryStream
?)并在0x0A字节上拆分消息。
答案 2 :(得分:0)
好的,这就是我最终做的事情。我创建了一个读者线程,它根据网络流创建NetworkStream和StreamReader。然后我使用StreamReader.ReadLine以这种方式读取行。这是一个同步调用,但它在自己的线程中。它似乎工作得更好。我必须实现这个,因为这是我们的应用程序协议(换行符分隔的消息)。我知道其他人会像我在这里一样四处寻找答案,就像我在客户端类中的相关读取代码一样:
public class Client
{
Socket m_Socket;
EventWaitHandle m_WaitHandle;
readonly object m_Locker;
Queue<IEvent> m_Tasks;
Thread m_Thread;
Thread m_ReadThread;
public Client()
{
m_WaitHandle = new AutoResetEvent(false);
m_Locker = new object();
m_Tasks = new Queue<IEvent>();
m_Thread = new Thread(Run);
m_Thread.IsBackground = true;
m_Thread.Start();
}
public void EnqueueTask(IEvent task)
{
lock (m_Locker)
{
m_Tasks.Enqueue(task);
}
m_WaitHandle.Set();
}
private void Run()
{
while (true)
{
IEvent task = null;
lock (m_Locker)
{
if (m_Tasks.Count > 0)
{
task = m_Tasks.Dequeue();
if (task == null)
{
return;
}
}
}
if (task != null)
{
task.DoTask(this);
}
else
{
m_WaitHandle.WaitOne();
}
}
}
public void Connect(string hostname, int port)
{
try
{
m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress[] IPs = Dns.GetHostAddresses(hostname);
m_Socket.BeginConnect(IPs, port, new AsyncCallback(ConnectCallback), m_Socket);
}
catch (SocketException)
{
m_Socket.Close();
OnConnect(false, "Unable to connect to server.");
}
}
private void ConnectCallback(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState;
socket.EndConnect(ar);
OnConnect(true, "Successfully connected to server.");
m_ReadThread = new Thread(new ThreadStart(this.ReadThread));
m_ReadThread.Name = "Read Thread";
m_ReadThread.IsBackground = true;
m_ReadThread.Start();
}
catch (SocketException)
{
m_Socket.Close();
OnConnect(false, "Unable to connect to server.");
}
}
void ReadThread()
{
NetworkStream networkStream = new NetworkStream(m_Socket);
StreamReader reader = new StreamReader(networkStream);
while (true)
{
try
{
String message = reader.ReadLine();
// To keep the code thread-safe, enqueue a task in the CLient class thread to parse the message received.
EnqueueTask(new ServerMessageEvent(message));
}
catch (IOException)
{
// The code will reach here if the server disconnects from the client. Make sure to cleanly shutdown...
Disconnect();
break;
}
}
}
... Code for sending/parsing the message in the Client class thread.
}