Socket 一对多通信
这篇文章橙色的文字都是废话,不耐烦的园友可以跳过那些文字。包括这句话。
最初接触Socket编程的是在学校的java课上,可那时候没心学java,老师讲的Socket也没怎么理会,上机操作时,上网拷了一段C#的客户端和服务端代码,分别与java写的服务端和客户端进行通信。至于整个通信流程是怎样的没理会,直到写上一篇博文时才清楚。
还记得那时候上课老师问过如果一个服务端要跟两个客户端通信,那怎么办?接着他复制粘贴了一下创建Socket,绑定,监听那几行代码。
1 ServerSocket ss1 = new ServerSocket(8081); 2 Socket s1 = ss1.accept(); 3 ServerSocket ss2 = new ServerSocket(8082); 4 Socket s2 = ss2.accept();
其实这样是多开了端口,的确是一个服务端对两个客户端通信了,但真正的一对多通信肯定不是这样吧,否则作为一台服务器,面对那么大的并发量,要开多少个端口才完事。如果我没理解错的话,java的accept也是同步的,这样就意味着一个客户端1跟这个服务端连接了之后,需要再来一个客户端2 (或者在那个客户端1) 与另一个端口连接了,才能正常通信。这样个人觉得不科学。
上网谷歌了一下,找到了一篇博文,是java的Socket的一对多通信,里面的一句话我一看眼发亮了:多执行一次Accept()。想C#与java类似的,于是尝试了一下,果然行。唉!看来学Socket编程的,都是参考java那部分的文章。
结合了对多线程的皮毛理解,想了一个办法实现一个服务端的与多个客户端交互,而且是用同一端口。大致是这样,在服务端的主线程执行一个循环去Accept客户端的Connet。每Accept一次,就开一个线程去负责与这个客户端通信。下面就上代码
首先是一些需要用到的变量
1 static int socketCount; //已经开的线程 2 static object threadFlag; //多线程的锁旗标 3 const int MAX_SOCKET_COUNT = 3; //最大接受客户端数量
接着是Main方法里面的代码
1 threadFlag = new object(); 2 socketCount = 0; 3 4 Socket serverScoket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); 5 IPEndPoint serverPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081); 6 7 serverScoket.Bind(serverPoint); 8 serverScoket.Listen(10);
这里跟往常的Socket通信一样。建立终结点和Socket,绑定和监听。下面的就涉及到多线程了。
1 Thread t = null; 2 Socket clientSocket = null; 3 try 4 { 5 while (true) 6 { 7 8 //判断当前已开的线程数是否超出最大值,超出了则要等待 9 while (socketCount >= MAX_SOCKET_COUNT) Thread.Sleep(1000); 10 clientSocket = serverScoket.Accept(); 11 //累计变量自增 12 socketCount++; 13 IPEndPoint clientPoint = clientSocket.RemoteEndPoint as IPEndPoint; 14 Console.WriteLine("client {0}:{1} connect",clientPoint.Address,clientPoint.Port); 15 t = new Thread(new ParameterizedThreadStart(ThreadConnect)); 16 t.Start(clientSocket); 17 } 18 } 19 finally 20 { 21 serverScoket.Close(); 22 serverScoket.Dispose(); 23 Environment.Exit(0); 24 25 }
用了一个While循环去不断地有限制地接受客户端。每当接受了一个客户端,就开一个线程去处理它的通信。同时已开启线程数量累加一个。如果已开启的线程数大于最大值的话是停止接受的。防止开的线程过多压坏了服务端。
在接受时只是用了同步接受,没采用异步,因为感觉没必要,反正接受包在了一个死循环里面,还没接收到就让主线程一直卡在那句里面。等待接受了,才开始下一次接受等待。不过这里用的一个死循环觉得挺不妥的。没办法跳出循环,关闭Socket,释放资源等一系列操作不知道在哪里执行好。
最后上一段线程执行方法代码
1 static void ThreadConnect(object clientObj) 2 { 3 Socket clientSocket = clientObj as Socket; 4 IPEndPoint clientPoint = clientSocket.RemoteEndPoint as IPEndPoint; 5 if (clientSocket == null) 6 { 7 lock (threadFlag) 8 { 9 socketCount--; 10 } 11 return; 12 } 13 clientSocket.Send(Encoding.ASCII.GetBytes("Hello world"),SocketFlags.None); 14 byte[] datas; 15 int rec; 16 while (true) 17 { 18 datas = new byte[1024]; 19 rec = clientSocket.Receive(datas); 20 //当客户端发来的消息长度为0时,表明结束通信 21 if (rec == 0) break; 22 23 string msg= "Msg has been receive length is "+rec; 24 clientSocket.Send(Encoding.ASCII.GetBytes(msg),SocketFlags.None); 25 } 26 Console.WriteLine("client {0}:{1} disconnect", clientPoint.Address, clientPoint.Port); 27 lock (threadFlag) 28 { 29 //减少当前已开的线程数 30 socketCount--; 31 clientSocket.Close(); 32 clientSocket.Dispose(); 33 } 34 }
整个消息接收和发送过程都放在了死循环里面,由客户端发来的信息的长度来判定客户端是否终止通信,从而断定是否跳出循环。不过消息的收发可以用异步的,只不过这里没用上而已,感觉这里应该用。在最后的地方还要关闭Socket,释放资源。同时也要减少已开的线程数,毕竟这个线程已经完了。
在写完这篇文章时突然想到 “Sokect连接池 ”这个名词,接下来也尝试一下。用那个来做应该会更好。这个小程序的确简陋了,期待各位园友拍砖和吐槽。谢谢