在Socket之前,首先得了解TCP/IP协议,该协议用来定义主机如何连入因特网和数据在它们之间传输的标准。
OSI七层网络模型如下图所示:
其中,TCP和UDP在传输层,完成了程序到程序,端口到端口的通信。UDP协议简单高效,只管发送,不管是否收到,TCP协议可以理解为带确认模式的UDP协议(三次握手)。
Socket,翻译为套接字,是TCPIP的使用接口,基于Socket我们才能调用TCP协议。
Socket通信基本流程图入下图所示:
言归正传,以下通过代码实例进行简单演示,客户端我们通过SocketTool.exe进行模拟,主要演示服务器端代码
(1)Socket服务端连接
1 int port = 60001; 2 string host = "127.0.0.1";//服务器端ip地址 3 4 IPAddress ip = IPAddress.Parse(host); 5 IPEndPoint ipe = new IPEndPoint(ip, port); 6 7 Socket sSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 8 sSocket.Bind(ipe); 9 sSocket.Listen(0); 10 11 Socket serverSocket = sSocket.Accept(); 12 Console.WriteLine("连接已建立...."); 13 try 14 { 15 while (true) 16 { 17 18 //receive message 19 string recStr = ""; 20 byte[] recBytes = new byte[4096]; 21 int bytes = serverSocket.Receive(recBytes, recBytes.Length, 0); 22 23 recStr += Encoding.ASCII.GetString(recBytes, 0, bytes); 24 string RetMsg = $"客户端:{serverSocket.RemoteEndPoint.ToString()},消息:{recStr},时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}"; 25 Console.WriteLine($"获得客户端消息:{RetMsg}"); 26 27 //回发消息 28 if (!string.IsNullOrEmpty(recStr)) 29 { 30 byte[] sendByte = Encoding.ASCII.GetBytes("success!"); 31 serverSocket.Send(sendByte, sendByte.Length, 0); 32 } 33 } 34 } 35 catch (Exception ex) 36 { 37 Console.WriteLine($"Socket Error:" + ex.Message); 38 } 39 finally 40 { 41 sSocket.Close(); 42 }
其中,分别定义用于监听的Socket和连接的Socket,通过方法Receive监听客户端传来的消息。
问题:当前模式无法支持连接多个客户端,Socket等待连接的过程会造成阻塞。解决:通过多线程进行优化
(2)多线程下的Socket服务端
int port = 60001; string host = "127.0.0.1";//服务器端ip地址 IPAddress ip = IPAddress.Parse(host); IPEndPoint ipe = new IPEndPoint(ip, port); serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(ipe); serverSocket.Listen(10); Console.WriteLine("等待客户端连接..."); try { Thread socketThread = new Thread(ListenClientSocket); socketThread.Start(); } catch (Exception ex) { Console.WriteLine($"Socket Error:" + ex.Message); } finally { serverSocket.Close(); }
1 static void ListenClientSocket() 2 { 3 while (true) 4 { 5 Socket clientSocket = serverSocket.Accept(); 6 Console.WriteLine("连接已建立...."); 7 #region 消息回发 8 byte[] sendByte = Encoding.ASCII.GetBytes("success!"); 9 clientSocket.Send(sendByte, sendByte.Length, 0); 10 #endregion 11 12 #region 连接客户端,解析数据,多线程 13 Thread receivethread = new Thread(ReceiveSocket); //委托方法 14 receivethread.Start(clientSocket); 15 #endregion 16 17 } 18 }
1 static void ReceiveSocket(object clientsocket) 2 { 3 Socket myclientSocket= (Socket)clientsocket; 4 while (true) 5 { 6 string recStr = ""; 7 byte[] recBytes = new byte[4096]; 8 int bytes = myclientSocket.Receive(recBytes, recBytes.Length, 0); 9 10 recStr += Encoding.ASCII.GetString(recBytes, 0, bytes); 11 string RetMsg = $"客户端:{myclientSocket.RemoteEndPoint.ToString()},消息:{recStr},时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}"; 12 Console.WriteLine($"获得客户端消息:{RetMsg}"); 13 } 14 }
监听和连接的Socket我们通过创建线程Thread解决等待时候的阻塞。
问题:多线程再客户端数量较多的情况下,每次创建新线程都会消耗一定的内存。解决:通过线程池进行优化
(3)线程池下的Socket
线程池中的线程执行完指定的方法后并不会自动消除,而是以挂起状态返回线程池,如果应用程序再次想线程池发出请求,以挂起状态的线程就会被激活并执行任务,而不会创建新线程,从而节约开销
1 ThreadPool.QueueUserWorkItem(new WaitCallback(方法名)); 2 ThreadPool.QueueUserWorkItem(new WaitCallback(方法名), 参数);
通过创建1000个线程和从线程池中取1000个线程各自消耗的时间进行对比:
不难发现,线程池的方案性能更优异。但也不是没有缺点,线程池取的线程都是后台线程,级别都是Normal。
更新后的代码如下:
主体方法中:
ThreadPool.QueueUserWorkItem(state=> ListenClientSocket()); //Thread socketThread = new Thread(ListenClientSocket); //socketThread.Start();
监听方法中:
1 #region 连接客户端,解析数据,多线程 2 ThreadPool.QueueUserWorkItem(state => ReceiveSocket(clientSocket)); 3 //Thread receivethread = new Thread(ReceiveSocket); //委托方法 4 //receivethread.Start(clientSocket); 5 #endregion
上述基本介绍了Socket服务器端的一些操作,更多的优化例如超时断开的处理,数据包中黏包的处理等会在后续篇幅中再介绍
以上,仅用于学习和总结!