首页 > 技术文章 > .NET下WPF学习之Socket通信

memoyu 2019-04-24 20:51 原文

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代码(客户端类似):

 1 <Window x:Class="SocketDemo.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 6         xmlns:local="clr-namespace:SocketDemo"
 7         mc:Ignorable="d"
 8         Title="MainWindow" Height="472.5" Width="605">
 9     <StackPanel>
10         <Canvas Margin="10,20" Height="30">
11             <Label Content="IP:" Height="30" Width="30"  FontSize="18" HorizontalContentAlignment="Center" Canvas.Left="8"/>
12             <TextBox x:Name="txtIp" Text="192.168.0.4" Height="30" Width="150" FontSize="20" HorizontalContentAlignment="Center" Canvas.Left="41" />
13             <Label Content="Port:" Height="30" Width="50" FontSize="18" HorizontalContentAlignment="Center" Canvas.Left="210"/>
14             <TextBox x:Name="txtPort" Text="45000"  Height="30" Width="150" FontSize="20" HorizontalContentAlignment="Center" Canvas.Left="263"  />
15             <Button x:Name="btnStartServer" Content="开启服务" Height="30" Width="100" Canvas.Left="460"/>
16         </Canvas>
17         <TextBox Name="txtLog" Height="300" AcceptsReturn="True" TextWrapping="Wrap"></TextBox>
18         <Canvas Margin="0,20" Height="30">
19             <TextBox x:Name="txtMsg" Height="30" Width="450" FontSize="20" HorizontalContentAlignment="Center" Canvas.Left="0" />
20             <Button x:Name="btnSendMsg" Content="发送消息" Height="30" Width="100" Canvas.Left="470" />
21         </Canvas>
22     </StackPanel>
23 </Window>
服务端XAML(客户端与服务端相似)

具体服务端实现:

  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     /// <summary>
 22     /// MainWindow.xaml 的交互逻辑
 23     /// </summary>
 24     public partial class MainWindow : Window
 25     {
 26         List<Socket> clientScoketLis = new List<Socket>();//存储连接服务器端的客户端的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         /// <summary>
 43         /// 关闭事件
 44         /// </summary>
 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         /// <summary>
 61         /// 开启服务事件
 62         /// </summary>
 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         /// <summary>
 80         /// 线程池线程执行的接受客户端连接方法
 81         /// </summary>
 82         /// <param name="obj">传入的Socket</param>
 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         /// <summary>
104         /// 不断接收客户端信息子线程方法
105         /// </summary>
106         /// <param name="obj">参数Socke对象</param>
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                 try
116                 {
117                     //接收消息,返回字节长度
118                     len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
119                 }
120                 catch (Exception ex)
121                 {
122                     //7、关闭Socket
123                     //异常退出
124                     try
125                     {
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、关闭Socket
137                     //小于0表示正常退出
138                     try
139                     {
140                         ClientExit(string.Format("客户端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
141                     }
142                     catch (Exception)
143                     {
144                     }
145                     return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
146                 }
147                 //将消息显示到TxtLog
148                 string msgStr = Encoding.Default.GetString(data , 0 , len);
149                 //拼接字符串
150                 AppendTxtLogText(string.Format("接收到客户端:{0}的消息:{1}" , proxSocket.RemoteEndPoint.ToString() , msgStr));
151             }
152         }
153 
154         /// <summary>
155         /// 消息发送事件
156         /// </summary>
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         /// <summary>
171         /// 向文本框中追加信息
172         /// </summary>
173         /// <param name="str"></param>
174         private void AppendTxtLogText( string str)
175         {
176             if (!(txtLog.Dispatcher.CheckAccess()))//判断跨线程访问
177             {
178                 ////同步方法
179                 //this.Dispatcher.Invoke(new Action<string>( s => 
180                 //{
181                 //    this.txtLog.Text = string.Format("{0}\r\n{1}" , s , txtLog.Text);
182                 //}) ,str);
183                 //异步方法
184                 this.Dispatcher.BeginInvoke(new Action<string>(s =>
185                 {
186                     this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);
187                 }), str);
188             }
189             else
190             { 
191             this.txtLog.Text = string.Format("{0}\r\n{1}", str, txtLog.Text);
192             }
193         }
194         /// <summary>
195         /// 客户端退出调用
196         /// </summary>
197         /// <param name="msg"></param>
198         private void ClientExit(string msg , Socket proxSocket)
199         {
200             AppendTxtLogText(msg);
201             clientScoketLis.Remove(proxSocket);//移除集合中的连接Socket
202 
203             try
204             {
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     /// <summary>
 21     /// ClientWindows.xaml 的交互逻辑
 22     /// </summary>
 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         /// <summary>
 34         /// 窗口关闭事件
 35         /// </summary>
 36         private void ClientWindows_Closing(object sender, System.ComponentModel.CancelEventArgs e)
 37         {
 38             ServerExit(null,_socket);//向服务端说我下线了。
 39         }
 40 
 41         /// <summary>
 42         /// 连接按钮事件
 43         /// </summary>
 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         /// <summary>
 65         /// 不断接收客户端信息子线程方法
 66         /// </summary>
 67         /// <param name="obj">参数Socke对象</param>
 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                     try
101                     {
102                         ServerExit(string.Format("服务端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
103                     }
104                     catch (Exception)
105                     {
106 
107                     }
108                     return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
109                 }
110                 //将消息显示到TxtLog
111                 string msgStr = Encoding.Default.GetString(data, 0, len);
112                 //拼接字符串
113                 AppendTxtLogText(string.Format("接收到服务端:{0}的消息:{1}", proxSocket.RemoteEndPoint.ToString(), msgStr));
114             }
115         }
116 
117         /// <summary>
118         /// 客户端退出调用
119         /// </summary>
120         /// <param name="msg"></param>
121         private void ServerExit(string msg, Socket proxSocket)
122         {
123             AppendTxtLogText(msg);
124             try
125             {
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         /// <summary>
138         /// 发送信息按钮事件
139         /// </summary>
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         /// <summary>
149         /// 向文本框中追加信息
150         /// </summary>
151         /// <param name="str"></param>
152         private void AppendTxtLogText(string str)
153         {
154             if (!(txtLog.Dispatcher.CheckAccess()))//判断跨线程访问
155             {
156                 ////同步方法
157                 //this.Dispatcher.Invoke(new Action<string>( s => 
158                 //{
159                 //    this.txtLog.Text = string.Format("{0}\r\n{1}" , s , txtLog.Text);
160                 //}) ,str);
161                 //异步方法
162                 this.Dispatcher.BeginInvoke(new Action<string>(s =>
163                 {
164                     this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);
165                 }), str);
166             }
167             else
168             {
169                 this.txtLog.Text = string.Format("{0}\r\n{1}", str, txtLog.Text);
170             }
171         }
172     }
173 }
客户端实现代码

运行展示:

 

推荐阅读