.NET下WPF學習之Socket通信


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 }
客戶端實現代碼

運行展示:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM