双向通信管理实现

网络编程中,客户端和服务器之间的双向通信是一个常见的需求。本文将介绍如何使用C#实现这种通信方式。通常情况下,如果双方被防火墙隔开,直接连接到对方端点的方法可能行不通,因为网络管理员通常不愿意允许出站连接。因此,维护并重用已建立的连接进行双向通信是唯一的选择。

基本概念

要实现这一点,必须区分传入的请求和传入的响应。在一种情况下,期望对方发送给一个请求,只需返回一个响应;在另一种情况下,是发送一个请求并等待对方发送给一个响应。将使用HTTP协议来传递的带外信息来区分这两种情况。

将使用HTTP消息进行点对点通信。一旦监听套接字接受连接,将假设并完全接收一个HTTP消息,然后可以很容易地将其识别并处理为请求或响应。以下是一些代码来说明基本思想:

TcpListener listener = new TcpListener(7070); listener.Start(); Socket socket = listener.AcceptSocket(); HttpMessage msg = new HttpMessage(); msg.Receive(socket); if (msg.IsResponse) { ProcessResponse(msg); } else { ProcessRequest(msg); }

HttpMessage对象简单地从套接字流读取HTTP协议的基本部分,第一行,可选的头列表和消息体。通过检查第一行的第一个标记来区分请求和响应。在响应的情况下,第一行必须以"HTTP"开头。这就是msg.IsResponse确定的。

处理响应

最关心的是如何将响应与之前发出的请求相关联。让检查发送请求的方法。

void SendRequest(Socket socket, HttpMessage msg) { msg.CorrelationID = Guid.NewGuid().ToString(); msg.Send(socket); _requests[msg.CorrelationID] = msg; }

核心思想是创建并附加一个唯一的标识符到传出的HTTP消息。期望这个标识符也出现在返回的HTTP响应消息中。以下是基本的HTTP协议交换的示例。

// 传出请求 GET / HTTP/1.1 Correlation-ID: 0B83745D-2AAB-4bce-8AC9-B8A590F07768 // 传入响应 HTTP/1.1 200 Ok Correlation-ID: 0B83745D-2AAB-4bce-8AC9-B8A590F07768

双向通信管理器

双向通信的核心思想现在已经描述了。让继续开发一个可以实际部署的模块。想要的是一个类,它可以管理双向通信的细节,可以像这样部署:

TcpListener listener = new TcpListener(7070); listener.Start(); while (true) { Socket socket = listener.AcceptSocket(); Connection conn = new Connection(socket); new Thread(new ThreadStart(conn.ThreadProc)).Start(); }

Connection类负责管理双向通信。将接受的套接字传递给它,并依赖连接对象使用同一个套接字发送和接收HTTP消息。为了等待并接收额外的连接,生成一个工作线程来管理已建立的连接。以下是连接的线程过程:

void ThreadProc() { while (Continue()) { HttpMessage msg = new HttpMessage(); msg.Receive(_socket); if (msg.IsResponse) { ProcessResponse(msg); } else { ProcessRequest(msg); } } }

线程过程中的代码应该是熟悉的。让重新检查发送请求的方法。希望发送一个请求并同步等待响应,如下所示:

HttpMessage request = new HttpMessage(); request.Verb = "GET"; request.RequestUri = "/"; request.Version = "HTTP/1.1"; HttpMessage response = conn.SendMessage(request);

这意味着SendMessage(request)方法必须等到响应收到。需要一种方法来信号响应的到来。解决这个问题的最佳方法是实现互补的异步方法BeginSendMessage和EndSendMessage。

public IAsyncResult BeginSendMessage(HttpMessage request) { request.CorrelationID = Guid.NewGuid().ToString(); request.Send(_socket); IAsyncResult async = new HttpAsyncResult(); _requests[request.CorrelationID] = async; return async; } public HttpMessage EndSendMessage(IAsyncResult async) { if (!async.IsCompleted) async.AsyncWaitHandle.WaitOne(); HttpMessage response = (HttpMessage)_requests[async]; _requests.Remove(async); return response; }

在讨论这段代码之前,让展示同步版本的实现。它非常简单。

public HttpResponse SendRequest(HttpRequest request) { IAsyncResult async = BeginRequest(request); return EndRequest(async); }

让讨论异步版本。将传出消息的相关ID映射到一个可等待的对象,该对象实现了IAsyncResult接口。显然,可等待的对象需要设置,当传出消息的响应到达时。这必须发生在ProcessResponse方法中。以下是它的实现:

void ProcessResponse(HttpMessage response) { HttpAsyncResult async = (HttpAsyncResult)_requests[response.CorrelationID]; _requests.Remove(response.CorrelationID); _requests[async] = response; async.Set(); }

需要仔细比较EndSendMessage和ProcessResponse方法。一旦发送了一个请求,必须等待响应的到来。

处理传入请求

现在,让将注意力转向需要处理传入请求的情况,如ProcessRequest。Connection首先是关于管理双向通信的。因此,将HTTP请求的处理委托给某个外部代理是有意义的。可以通过定义适当的委托来最好地实现它:

public delegate HttpMessage ProcessRequestDelegate(HttpMessage request);

以下是PrecessRequest方法的简单实现。

delegate member public ProcessRequestDelegate DelegateRequest; private method void ProcessRequest(HttpMessage request) { HttpMessage response = DelegateRequest(request); response.CorrelationID = request.CorrelationID; response.Send(_socket); } temporary queue for storing the request Queue _queue = Queue.Synchronized(new Queue()); void ProcessRequest(HttpMessage request) { _queue.Enqueue(request); new Thread(new ThreadStart(this.ProcessRequestThreadProc)).Start(); } delegate member public ProcessRequestDelegate DelegateRequest; private method void ProcessRequestThreadProc() { HttpMessage request = (HttpMessage)_queue.Dequeue(); HttpMessage response = DelegateRequest(request); response.CorrelationID = request.CorrelationID; response.Send(_socket); }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485