Socket编程(异步通讯)(Tcp,Udp)
上一章主要展示了Socket的TcpUdp两种协议下的基本通讯方式,属于同步通讯。至于一个服务器对应多个客户端,或者对应多个请求,我们采用的是多线程的方式来解决此问题。然而本章节我们将有更好的方式去实现它:Socket在TcpUdp两种协议下的异步通讯方式。
基于Tcp协议异步:
BeginAccept方法和EndAccept方法
包含在System.Net.Sockets命名空间下。异步Tcp使用BeginAccept方法开始接受新的客户端连接请求,该方法中系统自动利用线程池创建需要的线程,并在操作完成时利用异步回调机制调用提供给它的方法,同时返回相应的状态参数,然后方可利用EndAccept方法结束该连接请求.
BeginRecive方法和EndRecive方法
异步Tcp使用BeginRecive方法和开始接受客户端发送的的消息,该方法如上同理,接受完毕后调用回调函数传递相应的状态参数。利用EndRecive方法接受接受消息。
至于BeginSend方法和EndSend方法、BeginConnect方法和EndConnect方法与上类似。
下面我们来看看如何在Tcp协议下进行客户端与服务器端之间的通讯:
服务器端:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 #region 命名空间 5 using System.Net; 6 using System.Net.Sockets; 7 using System.Threading; 8 #endregion 9 10 namespace AsynServerConsole 11 { 12 /// <summary> 13 /// Tcp协议异步通讯类(服务器端) 14 /// </summary> 15 public class AsynTcpServer 16 { 17 #region Tcp协议异步监听 18 /// <summary> 19 /// Tcp协议异步监听 20 /// </summary> 21 public void StartListening() 22 { 23 //主机IP 24 IPEndPoint serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8686); 25 Socket tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 26 tcpServer.Bind(serverIp); 27 tcpServer.Listen(100); 28 Console.WriteLine("异步开启监听..."); 29 AsynAccept(tcpServer); 30 } 31 #endregion 32 33 #region 异步连接客户端 34 /// <summary> 35 /// 异步连接客户端 36 /// </summary> 37 /// <param name="tcpServer"></param> 38 public void AsynAccept(Socket tcpServer) 39 { 40 tcpServer.BeginAccept(asyncResult => 41 { 42 Socket tcpClient = tcpServer.EndAccept(asyncResult); 43 Console.WriteLine("server<--<--{0}", tcpClient.RemoteEndPoint.ToString()); 44 AsynSend(tcpClient, "收到连接...");//发送消息 45 AsynAccept(tcpServer); 46 AsynRecive(tcpClient); 47 }, null); 48 } 49 #endregion 50 51 #region 异步接受客户端消息 52 /// <summary> 53 /// 异步接受客户端消息 54 /// </summary> 55 /// <param name="tcpClient"></param> 56 public void AsynRecive(Socket tcpClient) 57 { 58 byte[] data = new byte[1024]; 59 try 60 { 61 tcpClient.BeginReceive(data, 0, data.Length, SocketFlags.None, 62 asyncResult => 63 { 64 int length = tcpClient.EndReceive(asyncResult); 65 Console.WriteLine("server<--<--client:{0}", Encoding.UTF8.GetString(data)); 66 AsynSend(tcpClient, "收到消息..."); 67 AsynRecive(tcpClient); 68 }, null); 69 } 70 catch (Exception ex) 71 { 72 Console.WriteLine("异常信息:", ex.Message); 73 } 74 } 75 #endregion 76 77 #region 异步发送消息 78 /// <summary> 79 /// 异步发送消息 80 /// </summary> 81 /// <param name="tcpClient">客户端套接字</param> 82 /// <param name="message">发送消息</param> 83 public void AsynSend(Socket tcpClient, string message) 84 { 85 byte[] data = Encoding.UTF8.GetBytes(message); 86 try 87 { 88 tcpClient.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult => 89 { 90 //完成发送消息 91 int length = tcpClient.EndSend(asyncResult); 92 Console.WriteLine("server-->-->client:{0}", message); 93 }, null); 94 } 95 catch (Exception ex) 96 { 97 Console.WriteLine("异常信息:{0}", ex.Message); 98 } 99 } 100 #endregion 101 } 102 }
客户端:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 #region 命名空间 5 using System.Net; 6 using System.Net.Sockets; 7 using System.Threading; 8 #endregion 9 10 namespace AsynClientConsole 11 { 12 /// <summary> 13 /// Tcp协议异步通讯类(客户端) 14 /// </summary> 15 public class AsynTcpClient 16 { 17 #region 异步连接 18 /// <summary> 19 /// Tcp协议异步连接服务器 20 /// </summary> 21 public void AsynConnect() 22 { 23 //主机IP 24 IPEndPoint serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8686); 25 Socket tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 26 tcpClient.BeginConnect(serverIp, asyncResult => 27 { 28 tcpClient.EndConnect(asyncResult); 29 Console.WriteLine("client-->-->{0}", serverIp.ToString()); 30 AsynSend(tcpClient, "我上线了..."); 31 AsynSend(tcpClient, "第一次发送消息..."); 32 AsynSend(tcpClient, "第二次发送消息..."); 33 AsynRecive(tcpClient); 34 }, null); 35 } 36 #endregion 37 38 #region 异步接受消息 39 /// <summary> 40 /// 异步连接客户端回调函数 41 /// </summary> 42 /// <param name="tcpClient"></param> 43 public void AsynRecive(Socket tcpClient) 44 { 45 byte[] data = new byte[1024]; 46 tcpClient.BeginReceive(data, 0, data.Length, SocketFlags.None, asyncResult => 47 { 48 int length = tcpClient.EndReceive(asyncResult); 49 Console.WriteLine("client<--<--server:{0}", Encoding.UTF8.GetString(data)); 50 AsynRecive(tcpClient); 51 }, null); 52 } 53 #endregion 54 55 #region 异步发送消息 56 /// <summary> 57 /// 异步发送消息 58 /// </summary> 59 /// <param name="tcpClient">客户端套接字</param> 60 /// <param name="message">发送消息</param> 61 public void AsynSend(Socket tcpClient, string message) 62 { 63 byte[] data = Encoding.UTF8.GetBytes(message); 64 tcpClient.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult => 65 { 66 //完成发送消息 67 int length = tcpClient.EndSend(asyncResult); 68 Console.WriteLine("client-->-->server:{0}", message); 69 }, null); 70 } 71 #endregion 72 } 73 }
通讯效果如下图:
服务器:
客户端:
上面我们完成了基于Tcp协议下的Socket通讯,那么Udp协议下的通讯我们将以什么样的形式来通讯呢?毕竟Udp协议下是无连接模式。
基于Udp协议的异步通讯:
其实与Tcp协议具有的方法类似,但由于Udp协议是无连接模式,我们所用到方法就无BeginConnect和EndConnect方法。我们所要做的就是收发消息的处理。
在Udp协议的异步通讯中,我们需要注意一下几个编程点:
1.在EndRecive方法中,由于无状态返回模式,不能返回发送端的Remote,所以我们需要在该方法中获取活动端的Remote,然后利用EndRecive方法结束接受该消息接受。
2.客户端由于无需Connect到服务器端,但是需要先向服务器端发送一个请求如Send一些消息。让服务器端确定自己Remote,然后可利用Recive方法接收其他终端发送过来的消息。
下面将演示Udp协议下异步通讯:
服务器端:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 #region 命名空间 5 using System.Net; 6 using System.Net.Sockets; 7 using System.Threading; 8 #endregion 9 10 namespace AsynServerConsole 11 { 12 /// <summary> 13 /// Udp协议异步通讯类(服务器端) 14 /// </summary> 15 public class AsynUdpServer 16 { 17 #region 容器对象 18 /// <summary> 19 /// 容器对象 20 /// </summary> 21 public class StateObject 22 { 23 //服务器端 24 public Socket udpServer = null; 25 //接受数据缓冲区 26 public byte[] buffer = new byte[1024]; 27 //远程终端 28 public EndPoint remoteEP; 29 } 30 31 public StateObject state; 32 #endregion 33 34 #region 服务器绑定终端节点 35 public void ServerBind() 36 { 37 //主机IP 38 IPEndPoint serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8686); 39 Socket udpServer = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 40 udpServer.Bind(serverIp); 41 Console.WriteLine("server ready..."); 42 IPEndPoint clientIp = new IPEndPoint(IPAddress.Any, 0); 43 state = new StateObject(); 44 state.udpServer = udpServer; 45 state.remoteEP = (EndPoint)clientIp; 46 AsynRecive(); 47 } 48 #endregion 49 50 #region 异步接受消息 51 public void AsynRecive() 52 { 53 state.udpServer.BeginReceiveFrom(state.buffer, 0, state.buffer.Length, SocketFlags.None, ref state.remoteEP, 54 new AsyncCallback(ReciveCallback), null); 55 } 56 #endregion 57 58 #region 异步接受消息回调函数 59 public void ReciveCallback(IAsyncResult asyncResult) 60 { 61 if (asyncResult.IsCompleted) 62 { 63 //获取发送端的终节点 64 IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 0); 65 EndPoint remoteEP = (EndPoint)ipep; 66 state.udpServer.EndReceiveFrom(asyncResult, ref remoteEP); 67 Console.WriteLine("server<--<--client:{0}", Encoding.UTF8.GetString(state.buffer)); 68 //向发送端通知:收到消息 69 state.remoteEP = remoteEP; 70 AsynSend("收到消息"); 71 //继续接受消息 72 AsynRecive(); 73 } 74 } 75 #endregion 76 77 #region 异步发送消息 78 public void AsynSend(string message) 79 { 80 Console.WriteLine("server-->-->client:{0}", message); 81 byte[] buffer = Encoding.UTF8.GetBytes(message); 82 state.udpServer.BeginSendTo(buffer, 0, buffer.Length, SocketFlags.None, state.remoteEP, 83 new AsyncCallback(SendCallback), null); 84 } 85 #endregion 86 87 #region 异步发送消息回调函数 88 public void SendCallback(IAsyncResult asyncResult) 89 { 90 //消息发送完毕 91 if (asyncResult.IsCompleted) 92 { 93 state.udpServer.EndSendTo(asyncResult); 94 } 95 } 96 #endregion 97 } 98 }
客户端:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 #region 命名空间 5 using System.Net; 6 using System.Net.Sockets; 7 using System.Threading; 8 #endregion 9 10 namespace AsynClientConsole 11 { 12 /// <summary> 13 /// Udp协议异步通讯类(客户端) 14 /// </summary> 15 public class AsynUdpClient 16 { 17 #region 容器对象 18 /// <summary> 19 /// 容器对象 20 /// </summary> 21 public class StateObject 22 { 23 //客户端套接字 24 public Socket udpClient = null; 25 //接收信息缓冲区 26 public byte[] buffer = new byte[1024]; 27 //服务器端终节点 28 public IPEndPoint serverIp; 29 //远程终端节点 30 public EndPoint remoteEP; 31 } 32 33 public StateObject state; 34 #endregion 35 36 #region 客户端初始化 37 public void InitClient() 38 { 39 state = new StateObject(); 40 state.udpClient = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 41 state.serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8686); 42 state.remoteEP = (EndPoint)(new IPEndPoint(IPAddress.Any, 0)); 43 //此处注意: 44 // 由于当前是客户端,所以没有绑定终节点 45 // 不可直接接收消息,必须先向其他终端发送信息告知本机终节点 46 AsynSend("第1次发送消息"); 47 AsynSend("第2次发送消息"); 48 AsynRecive(); 49 } 50 #endregion 51 52 #region 异步接收来自其他终端发送的消息 53 public void AsynRecive() 54 { 55 state.udpClient.BeginReceiveFrom(state.buffer, 0, state.buffer.Length, SocketFlags.None, ref state.remoteEP, 56 new AsyncCallback(ReciveCallback), null); 57 } 58 #endregion 59 60 #region 异步接收来自其他终端发送的消息回调函数 61 public void ReciveCallback(IAsyncResult asyncResult) 62 { 63 //信息接收完成 64 if (asyncResult.IsCompleted) 65 { 66 state.udpClient.EndReceiveFrom(asyncResult, ref state.remoteEP); 67 Console.WriteLine("client<--<--{0}:{1}", state.remoteEP.ToString(), Encoding.UTF8.GetString(state.buffer)); 68 AsynRecive(); 69 } 70 } 71 #endregion 72 73 #region 异步发送消息 74 public void AsynSend(string message) 75 { 76 Console.WriteLine("client-->-->{0}:{1}", state.serverIp.ToString(), message); 77 byte[] buffer = Encoding.UTF8.GetBytes(message); 78 state.udpClient.BeginSendTo(buffer, 0, buffer.Length, SocketFlags.None, state.serverIp, 79 new AsyncCallback(SendCallback), null); 80 } 81 #endregion 82 83 #region 异步发送消息回调函数 84 public void SendCallback(IAsyncResult asyncResult) 85 { 86 //消息发送完成 87 if (asyncResult.IsCompleted) 88 { 89 state.udpClient.EndSendTo(asyncResult); 90 } 91 } 92 #endregion 93 } 94 }
通讯效果如下图:
服务器:
客户端:
总结:基于异步模式的通讯无须采用多线程来服务多个客户端以及多个请求,这样的通讯模式效率更高。
同步上面Tcp效果展示图,我们发现客户端分几次连续发送的消息被服务器端一次接收了,读成了一条数据,而这就是Socket通讯基于Tcp协议下发生的粘包问题,下面一种我们将着重对Tcp协议的通讯信息封包,拆包以解决上面问题。
同样Udp协议通讯下属于无连接模式通讯,客户端只管将消息发送出去,或者由于网络原因,而造成的丢包问题,下一章也将采用一定的方式解决。
作者:曾庆雷
出处:http://www.cnblogs.com/zengqinglei
本页版权归作者和博客园所有,欢迎转载,但未经作者同意必须保留此段声明, 且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利