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连接池 ”这个名词,接下来也尝试一下。用那个来做应该会更好。这个小程序的确简陋了,期待各位园友拍砖和吐槽。谢谢

原文地址:https://www.cnblogs.com/HopeGi/p/3012112.html