socket通信的套路,无论是同步的方式或者异步的实现方式,套路总是不变的,那就是服务端开启一个线程监听客户端socket,客户端创建一个socket去连接服务端的监听端口,服务端接收这个客户端socket,在开辟一个线程负责与客户端线程通信(send receive 数据),这里有个误区,并不是监听socket与客户端socket进行通信,监听socket只是负责接收客户端连接请求,最后接收到的socket与客户端socket进行通信。
关于同步和异步的主要区别在哪里?
Accept Receive是阻塞的方法,通常线程在执行到这里的时候就阻塞了。那么他们之后的代码是无法执行的,Accept Receive的异步方法允许你执行他们之后的处理。举个简单的例子,假如客户端正在Receive一段数据,数据比较长,需要10秒,那么Receive之后的代码我们想立刻执行,不需要等待接收完毕才执行,这时候异步Receive就起到作用了,但是这里面依旧有个误区,异步会不会起到增大并发处理效果?并不会,异步只是起到不阻塞线程的作用(异步本身也是开辟一个新的线程去等待接收数据完毕进行处理),并不会起到增大并发接收数据的能力。并发处理归根结底还是由线程来控制的,就算是同步的处理方式,我们开辟多个线程针对对同一个socket进行Receive操作,依旧可以起到并发的效果。
private void ConnectToServer() {//创建一个套接字 IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6001); clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //将套接字与远程服务器地址相连 try { //客户端连接 clientSocket.Connect(ipep); } catch (SocketException ex) { MessageBox.Show("connect error: " + ex.Message); return; } for (int i = 0; i <= 3;i++ ) { Thread th = new Thread(new ThreadStart(Receive)); th.IsBackground = true; th.Start(); } } public void Receive() { byte[] data = new byte[1024]; while (true) { //接收服务器信息 int bufLen = 0; try { if (clientSocket.Connected) { bufLen = clientSocket.Receive(data); if (bufLen == 0) {break; } } } catch (Exception ex) { MessageBox.Show("Receive Error:" + ex.Message); return; } string clientcommand = System.Text.Encoding.UTF8.GetString(data, 0, bufLen);//.Substring(0, bufLen); SetTextValue(clientcommand+Thread.CurrentThread.ManagedThreadId.ToString()); } }
以上代码是客户端接收数据的处理过程,开辟了4个线程针对同一个socket做并发接收数据的能力。
接下来看看异步接收数据的实现方式
private void ConnectToServer() { byte[] data = new byte[1024]; //创建一个套接字 IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6001); clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //将套接字与远程服务器地址相连 try { //客户端连接 clientSocket.Connect(ipep); } catch (SocketException ex) { MessageBox.Show("connect error: " + ex.Message); return; } if (clientSocket.Connected) { clientSocket.BeginReceive(buffer, 0, data.Length, SocketFlags.None, Receive, clientSocket); } } static byte[] buffer = new byte[1024]; public void Receive(IAsyncResult ar) { try { var socket = ar.AsyncState as Socket; var bufLen = socket.EndReceive(ar); if (bufLen > 0) { string clientcommand = System.Text.Encoding.UTF8.GetString(buffer, 0, bufLen);//.Substring(0, bufLen); SetTextValue(clientcommand + Thread.CurrentThread.ManagedThreadId.ToString()); socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Receive), socket); } if (bufLen == 0) { SetTextValue("socket shutdown"); } } catch (Exception ex) { MessageBox.Show("Receive Error:" + ex.Message); } }
经过测试,异步接收数据的线程始终是同一个线程,所以异步本身没有起到并发处理的能力,除非开辟多个线程进行异步接收数据。
如何实现这样的一种场景?
我并不想机械的开辟多个线程对一个socket进行receive,而是根据需要灵活动态地开辟线程,既保证并发能力,也不浪费线程。c#中的socket机制根据什么来做到这一点?值得思考的问题。
最后总结一下同步和异步的方式
如果我们站在主线程的角度,如果我们进行的socket操作,send也好、receive也好,我们不想阻塞主线程造成不好的体验,那么异步和同步差异很大。但是如果我们在主线程的基础上开辟了新的线程进行这些同步操作,个人认为异步和同步的差异不是很大,也许只是代码code的方式不同而已,因为异步本身没有解决上面提出的并发处理问题。(这是我个人的观点)
如何做好线程终结和异常处理?
如果我们关闭一个socket
最好的方式是先shutdown两边的socket,通知远程正在通信的socket,这时候远程socket会空接一次,如果处在循环中,可以break终结线程,如果是异步的,可以避免再次异步调用,也可以避免捕获不必要的异常,但是还是需要加上异常处理机制。因为如果我们直接close一个socket,远程socket的receive会捕获异常
if (clientSocket.Connected) { clientSocket.Shutdown(SocketShutdown.Both); clientSocket.Close(); }
服务端可以缓存客户端socket?
如果实现复杂的通信需求,多个客户端,客户端通过服务端转发,所以服务端如果不缓存这些客户端socket是无法做到转发功能的。因此客户端连接的时候可以带上自己的标志信息,服务端缓存这些socket,根据标志取出socket进行转发,这是通信类软件的实现思路。但是同时要做好对这些客户端socket的管理操作,中断、异常、关闭等及时清理。