Socket通信
关于Socket
Socket作为进程通信的机制,是处于网络层中的应用层,说白了就是两个程序间通信用的。
它的形式与电话插座类似,电话的通话双方相当于两个互相通信的程序,电话号相当于IP。
网络通信三要素
- IP地址(网络上主机设备的唯一标识,识别一台唯一的主机)
- 端口号(定位程序,确定两个通信的程序)
有效端口:0~65535,其中0~1023由系统使用,称为公认端口,他们紧密绑定与一些服务。从1024~49151是一些松散的绑定于一些服务,需要注册的一些端口,称为注册端口,剩下的49152~65535为动态端口、私有端口,我们一般开发都是使用这一频段的端口.
- 传输协议(用什么样的方式进行交互)
常见协议:TCP(面向连接,提供可靠的服务),UDP(无连接,传输速度快),一般使用TCP。
服务端于客户端Socket通信流程
重点记忆两个端的步骤:
服务端: 客户端:
1、创建Socket对象(负责侦听) 1、创建Socket对象
2、绑定端口 2、连接服务器端
3、开启侦听 3、发送消息、接受消息
4、开始接受客户端连接(不断接收,涉及多线程) 4、停止连接
5、创建一个代理Socket对象(负责通信) 5、关闭Socket对象
6、发送、接收消息
7、关闭Socket对象
实现代码
服务端XAML代码(客户端类似):
19 10 17 2318 22
具体服务端实现:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Net; 5 using System.Net.Sockets; 6 using System.Text; 7 using System.Threading; 8 using System.Threading.Tasks; 9 using System.Windows; 10 using System.Windows.Controls; 11 using System.Windows.Data; 12 using System.Windows.Documents; 13 using System.Windows.Input; 14 using System.Windows.Media; 15 using System.Windows.Media.Imaging; 16 using System.Windows.Navigation; 17 using System.Windows.Shapes; 18 19 namespace SocketDemo 20 { 21 ///22 /// MainWindow.xaml 的交互逻辑 23 /// 24 public partial class MainWindow : Window 25 { 26 ListclientScoketLis = new List ();//存储连接服务器端的客户端的Socket 27 public MainWindow() 28 { 29 InitializeComponent(); 30 Loaded += MainWindow_Loaded; 31 btnStartServer.Click += BtnStartServer_Click;//事件注册 32 btnSendMsg.Click += BtnSendMsg_Click; 33 Closing += MainWindow_Closing; 34 } 35 36 37 private void MainWindow_Loaded(object sender, RoutedEventArgs e) 38 { 39 ClientWindows clientWindows = new ClientWindows(); 40 clientWindows.Show(); 41 } 42 /// 43 /// 关闭事件 44 /// 45 private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) 46 { 47 //使用foreach出现 “集合已修改;可能无法执行枚举操作”,ClientExit源于方法中对list集合进行了Remove,所造成的异常。 48 //msdn的解释:foreach 语句是对枚举数的包装,它只允许从集合中读取,不允许写入集合。也就是,不能在foreach里遍历的时侯把它的元素进行删除或增加的操作的 49 //foreach (var socket in clientScoketLis) 50 //{ 51 // ClientExit(null , socket); 52 //} 53 //改成for循环即可 54 for (int i = 0; i < clientScoketLis.Count; i++)//向每个客户端说我下线了 55 { 56 ClientExit(null, clientScoketLis[i]); 57 } 58 } 59 60 ///61 /// 开启服务事件 62 /// 63 private void BtnStartServer_Click(object sender, RoutedEventArgs e) 64 { 65 //1、创建Socket对象 66 //参数:寻址方式,当前为Ivp4 指定套接字类型 指定传输协议Tcp; 67 Socket socket = new Socket(AddressFamily.InterNetwork , SocketType.Stream , ProtocolType.Tcp); 68 //2、绑定端口、IP 69 IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(this.txtIp.Text) , int.Parse(txtPort.Text)); 70 socket.Bind(iPEndPoint); 71 //3、开启侦听 10为队列最多接收的数量 72 socket.Listen(10);//如果同时来了100个连接请求,只能处理一个,队列中10个在等待连接的客户端,其他的则返回错误消息。 73 74 //4、开始接受客户端的连接 ,连接会阻塞主线程,故使用线程池。 75 ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptClientConnect),socket); 76 77 78 } 79 ///80 /// 线程池线程执行的接受客户端连接方法 81 /// 82 /// 传入的Socket 83 private void AcceptClientConnect(object obj) 84 { 85 //转换Socket 86 var serverSocket = obj as Socket; 87 88 AppendTxtLogText("服务端开始接收客户端连接!"); 89 90 //不断接受客户端的连接 91 while (true) 92 { 93 //5、创建一个负责通信的Socket 94 Socket proxSocket = serverSocket.Accept(); 95 AppendTxtLogText(string.Format("客户端:{0}连接上了!", proxSocket.RemoteEndPoint.ToString())); 96 //将连接的Socket存入集合 97 clientScoketLis.Add(proxSocket); 98 //6、不断接收客户端发送来的消息 99 ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveClientMsg) , proxSocket);100 }101 102 }103 ///104 /// 不断接收客户端信息子线程方法105 /// 106 /// 参数Socke对象107 private void ReceiveClientMsg(object obj)108 {109 var proxSocket = obj as Socket;110 //创建缓存内存,存储接收的信息 ,不能放到while中,这块内存可以循环利用111 byte[] data = new byte[1020*1024];112 while (true)113 {114 int len;115 try116 {117 //接收消息,返回字节长度118 len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);119 }120 catch (Exception ex)121 {122 //7、关闭Socket123 //异常退出124 try125 {126 ClientExit(string.Format("客户端:{0}非正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);127 }128 catch (Exception)129 {130 }131 return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束132 }133 134 if (len <= 0)//判断接收的字节数135 {136 //7、关闭Socket137 //小于0表示正常退出138 try139 {140 ClientExit(string.Format("客户端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);141 }142 catch (Exception)143 {144 }145 return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束146 }147 //将消息显示到TxtLog148 string msgStr = Encoding.Default.GetString(data , 0 , len);149 //拼接字符串150 AppendTxtLogText(string.Format("接收到客户端:{0}的消息:{1}" , proxSocket.RemoteEndPoint.ToString() , msgStr));151 }152 }153 154 ///155 /// 消息发送事件156 /// 157 private void BtnSendMsg_Click(object sender, RoutedEventArgs e)158 {159 foreach (Socket proxSocket in clientScoketLis)160 {161 if (proxSocket.Connected)//判断客户端是否还在连接162 {163 byte[] data = Encoding.Default.GetBytes(this.txtMsg.Text);164 //6、发送消息165 proxSocket.Send(data , 0 , data.Length , SocketFlags.None); //指定套接字的发送行为166 this.txtMsg.Text = null;167 }168 }169 }170 ///171 /// 向文本框中追加信息172 /// 173 /// 174 private void AppendTxtLogText( string str)175 {176 if (!(txtLog.Dispatcher.CheckAccess()))//判断跨线程访问177 {178 ////同步方法179 //this.Dispatcher.Invoke(new Action( s => 180 //{181 // this.txtLog.Text = string.Format("{0}\r\n{1}" , s , txtLog.Text);182 //}) ,str);183 //异步方法184 this.Dispatcher.BeginInvoke(new Action (s =>185 {186 this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);187 }), str);188 }189 else190 { 191 this.txtLog.Text = string.Format("{0}\r\n{1}", str, txtLog.Text);192 }193 }194 /// 195 /// 客户端退出调用196 /// 197 /// 198 private void ClientExit(string msg , Socket proxSocket)199 {200 AppendTxtLogText(msg);201 clientScoketLis.Remove(proxSocket);//移除集合中的连接Socket202 203 try204 {205 if (proxSocket.Connected)//如果是连接状态206 {207 proxSocket.Shutdown(SocketShutdown.Both);//关闭连接208 proxSocket.Close(100);//100秒超时间209 }210 }211 catch (Exception ex)212 {213 }214 }215 }216 }
具体客户端实现:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Net; 5 using System.Net.Sockets; 6 using System.Text; 7 using System.Threading; 8 using System.Threading.Tasks; 9 using System.Windows; 10 using System.Windows.Controls; 11 using System.Windows.Data; 12 using System.Windows.Documents; 13 using System.Windows.Input; 14 using System.Windows.Media; 15 using System.Windows.Media.Imaging; 16 using System.Windows.Shapes; 17 18 namespace SocketDemo 19 { 20 ///21 /// ClientWindows.xaml 的交互逻辑 22 /// 23 public partial class ClientWindows : Window 24 { 25 private Socket _socket; 26 public ClientWindows() 27 { 28 InitializeComponent(); 29 btnSendMsg.Click += BtnSendMsg_Click;//注册事件 30 btnConnect.Click += BtnConnect_Click; 31 Closing += ClientWindows_Closing; 32 } 33 ///34 /// 窗口关闭事件 35 /// 36 private void ClientWindows_Closing(object sender, System.ComponentModel.CancelEventArgs e) 37 { 38 ServerExit(null,_socket);//向服务端说我下线了。 39 } 40 41 ///42 /// 连接按钮事件 43 /// 44 private void BtnConnect_Click(object sender, RoutedEventArgs e) 45 { 46 //1、创建Socket对象 47 Socket socket = new Socket(AddressFamily.InterNetwork , SocketType.Stream , ProtocolType.Tcp); 48 _socket = socket; 49 //2、连接服务器,绑定IP 与 端口 50 IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(txtIp.Text) , int.Parse(txtPort.Text)); 51 try 52 { 53 socket.Connect(iPEndPoint); 54 } 55 catch (Exception) 56 { 57 MessageBox.Show("连接失败,请重新连接!","提示"); 58 return; 59 } 60 //3、接收消息 61 ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveServerMsg),socket); 62 } 63 64 ///65 /// 不断接收客户端信息子线程方法 66 /// 67 /// 参数Socke对象 68 private void ReceiveServerMsg(object obj) 69 { 70 var proxSocket = obj as Socket; 71 //创建缓存内存,存储接收的信息 ,不能放到while中,这块内存可以循环利用 72 byte[] data = new byte[1020 * 1024]; 73 while (true) 74 { 75 int len; 76 try 77 { 78 //接收消息,返回字节长度 79 len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None); 80 } 81 catch (Exception ex) 82 { 83 //7、关闭Socket 84 //异常退出 85 try 86 { 87 ServerExit(string.Format("服务端:{0}非正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket); 88 } 89 catch (Exception) 90 { 91 92 } 93 return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束 94 } 95 96 if (len <= 0)//判断接收的字节数 97 { 98 //7、关闭Socket 99 //小于0表示正常退出100 try101 {102 ServerExit(string.Format("服务端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);103 }104 catch (Exception)105 {106 107 }108 return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束109 }110 //将消息显示到TxtLog111 string msgStr = Encoding.Default.GetString(data, 0, len);112 //拼接字符串113 AppendTxtLogText(string.Format("接收到服务端:{0}的消息:{1}", proxSocket.RemoteEndPoint.ToString(), msgStr));114 }115 }116 117 ///118 /// 客户端退出调用119 /// 120 /// 121 private void ServerExit(string msg, Socket proxSocket)122 {123 AppendTxtLogText(msg);124 try125 {126 if (proxSocket.Connected)//如果是连接状态127 {128 proxSocket.Shutdown(SocketShutdown.Both);//关闭连接129 proxSocket.Close(100);//100秒超时间130 }131 }132 catch (Exception ex)133 {134 }135 }136 137 ///138 /// 发送信息按钮事件139 /// 140 private void BtnSendMsg_Click(object sender, RoutedEventArgs e)141 {142 byte[] data = Encoding.Default.GetBytes(this.txtMsg.Text);143 //6、发送消息144 _socket.Send(data, 0, data.Length, SocketFlags.None); //指定套接字的发送行为145 this.txtMsg.Text = null;146 }147 148 ///149 /// 向文本框中追加信息150 /// 151 /// 152 private void AppendTxtLogText(string str)153 {154 if (!(txtLog.Dispatcher.CheckAccess()))//判断跨线程访问155 {156 ////同步方法157 //this.Dispatcher.Invoke(new Action( s => 158 //{159 // this.txtLog.Text = string.Format("{0}\r\n{1}" , s , txtLog.Text);160 //}) ,str);161 //异步方法162 this.Dispatcher.BeginInvoke(new Action (s =>163 {164 this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);165 }), str);166 }167 else168 {169 this.txtLog.Text = string.Format("{0}\r\n{1}", str, txtLog.Text);170 }171 }172 }173 }
运行展示: