我们做软件工作的虽然每天都离不开网络,可网络协议细节却不是每个人都会接触和深入了解。我今天就来和大家一起学习下Socket,并写一个简单的聊天程序。
一些基础类
首先我们每天打开浏览器访问网页信息都是使用的HTTP/HTTPS协议,而HTTP是通过的TCP建立的连接。TCP底层又是通过的Socket套接字进行的通信。所以他们之间的抽象关系是:
IPEndPoint、Dns、IPAddress基础作用如下:
//根据DNS获取域名绑定的IPforeach(varaddressinDns.GetHostEntry("www.baidu.com").AddressList){Console.WriteLine($"百度IP:{address}");}//字符串转IP地址IPAddressipAddress=IPAddress.Parse("192.168.1.101");//通过IP和端口构造IPEndPoint对象,用于远程连接//通过IP可以确定一台电脑,通过端口可以确定电脑上的一个程序IPEndPointipEndPoint=newIPEndPoint(ipAddress,80);利用Socket编写聊天程序
我们首先从Socket开始讲起。
要实现Socket通信,先得有个服务端的监听,再有个客户端的连接,然后客户端和服务端就可以通信了。如下:
服务端代码如下
//1创建Socket对象socketServer=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//2绑定ip和端口IPAddressip=IPAddress.Parse("127.0.0.1");IPEndPointipEndPoint=newIPEndPoint(ip,50001);socketServer.Bind(ipEndPoint);//3、开启侦听(等待客户机发出的连接),并设置最大客户端连接数为10socketServer.Listen(10);//4、【阻塞】,等待客户端连接SocketnewSocket=socketServer.Accept();//5、【阻塞】,等待读取客户端发送过来的数据byte[]data=newbyte[1024*1024];intreadLeng=newSocket.Receive(data,0,data.Length,SocketFlags.None);//6、读取数据varmsg=Encoding.UTF8.GetString(data,0,readLeng);客户端代码如下
//1创建Socket对象socketClient=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//2连接到服务端IPAddressip=IPAddress.Parse("127.0.0.1");IPEndPointipEndPoint=newIPEndPoint(ip,50001);socketClient.Connect(ipEndPoint);//3发送消息到服务端socketClient.Send(Encoding.UTF8.GetBytes("你好,农码一生"));到此,我们就可以开启服务端的服务,并接受客户端的发来的消息了。
不过,这里有个很大的问题,服务端只能建立一个客户端连接和接受一次客户端发来的消息。如果想要连接更多的客户端和接受无数次的消息,服务端代码两处阻塞的地方需要另外开一个线程然后包到循环里面去。
修改后的服务端代码如下:
void....(){//1创建Socket对象socketServer=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//2绑定ip和端口IPAddressip=IPAddress.Parse("127.0.0.1");IPEndPointipEndPoint=newIPEndPoint(ip,50001);socketServer.Bind(ipEndPoint);//3、开启侦听(等待客户机发出的连接),并设置最大客户端连接数为10socketServer.Listen(10);//开启新的线程,循环等待新的客户端连接Task.Run(()=>{Accept(socketServer);});}voidAccept(Socketsocket){while(true){//4、【阻塞】,等待客户端连接SocketnewSocket=socket.Accept();//开启新的线程,循环等待接收新的数据Task.Run(()=>{Receive(newSocket);});}}voidReceive(SocketnewSocket){while(true){//5、【阻塞】,等待读取客户端发送过来的数据byte[]data=newbyte[1024*1024];intreadLeng=newSocket.Receive(data,0,data.Length,SocketFlags.None);//6、读取数据varmsg=Encoding.UTF8.GetString(data,0,readLeng);}}到此,服务端就可以接受多个客户端的连接和接收多次客户端发来的消息了。不过我们可能还需客服端能接收服务端发来的消息,这个你可以自己尝试下。文末会提供完整的代码参考。
注意:用Socket来编写聊天软件是长连接,有状态的。不确定服务端什么时候会发送消息过来,我们也可以连续发送消息而不响应。所以,对于消息的接收就需要开一个新的线程循环接收。
而对于HTTP来说,虽然它是也是通过TCP建立的通信,但在数据请求完毕后会马上关闭连接,这个过程很短。每次访问都会建立一个新的连接,是无状态的。
对于浏览器来说是一问一答的形式,先发送请求(Send),然后接收响应(Receive)所以就可以做到不开启新的线程,直接有序的同步的完成。这个在下一篇《模拟浏览器的请求和服务端的响应》会具体分析。
利用TCP编写聊天程序
利用TcpListener、TcpClient来实现同上面相同的功能。
服务端代码
void...(){//1开启监听TcpListenertcpListener=newTcpListener(IPAddress.Parse("127.0.0.1"),9999);tcpListener.Start(10);//最多同时接收10个用户连接//开启一个线程,循环等待客户端的连接Task.Run(()=>{Accept();});}//等待客户端的连接voidAccept(){while(true){//2【阻塞】等待客户端的连接TcpClienttcpClient=tcpListener.AcceptTcpClient();NetworkStreamnetworkStream=tcpClient.GetStream();//开启一个新的线程等待新的消息Task.Run(()=>{Read(networkStream,tcpClient);});}}//接收消息voidRead(NetworkStreamnetworkStream){while(true){byte[]buffer=newbyte[1024*1024];//3【阻塞】等待接收新的消息varreadLen=networkStream.Read(buffer,0,buffer.Length);varmsg=Encoding.UTF8.GetString(buffer,0,readLen);}}客户端代码
//1连接服务端TcpClienttcpClient=newTcpClient();tcpClient.Connect(IPAddress.Parse(textBox1.Text),int.Parse(textBox2.Text));//2发送消息到服务端byte[]buffer=Encoding.UTF8.GetBytes("你好,农码一生");networkStream.Write(buffer,0,buffer.Length);用TcpListener、TcpClient的实现也算ok了,TcpListener代码写的服务端和Socket通信也是完成没问题的,因为他们最后都是Socket。
对此你有觉得比Socket简单和容易理解?其实我更习惯Socket。
注意:
//1、断开连接使用socketClient.Shutdown(SocketShutdown.Both);socketClient.Close();//2、服务端需要判断intreadLeng=newSocket.Receive(data,0,data.Length,SocketFlags.None);if(readLeng==0)//客户端断开连接{//停止会话(禁用Socket上的发送和接收,该方法允许Socket对象一直等待,直到将内部缓冲区的数据发送完为止)newSocket.Shutdown(SocketShutdown.Both);//关闭连接newSocket.Close();//跳出循环return;}