1 流程
2 示例
看下面一個服務器端的代碼:
namespace MyScoketTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); } /// <summary> /// 開始監聽 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_Start_Click(object sender, EventArgs e) { //(1)創建套接字 Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //(2)綁定IP和端口 int port = 2018; string host = "127.0.0.1"; //指示服務器應偵聽所有網絡接口上的客戶端活動。此字段爲只讀。 IPAddress iP = IPAddress.Any; //創建端口號對象 IPEndPoint point = new IPEndPoint(iP, port); //綁定 socketWatch.Bind(point); //(3)監聽 socketWatch.Listen(10); ShowMsg($"監聽成功"); //(4)等待客戶端連接,並且創建一個負責通信的Socket Socket socket = socketWatch.Accept(); //RemoteEndPoint是當前客戶端連接成功的IP ShowMsg($"{socket.RemoteEndPoint.ToString()}:連接成功"); } private void ShowMsg(string str) { this.textBox1.AppendText(str + "\r\n"); } private void textBox1_TextChanged(object sender, EventArgs e) { } } }
運行成功之後點擊開始監聽按鈕,結果如下:
現在有2個問題,而且問題都是出現在代碼 Socket socket = socketWatch.Accept()這裏:
一是界面會卡死:在這個地方,會一直等待連接,如果客戶端一直不連接,winform界面就會卡主,因爲主線程阻塞了,界面的操作無法響應。所以我們可以開啓一個新線程來執行。
二是一次只能連接一個客戶端:這個問題可以通過寫在循環裏面來解決,這樣的話就能一直接收別的客戶端出來的申請連接。
還有在窗體加載的加一段代碼,防止客戶端鏈接的時候出現跨線程的錯誤:
private void Form1_Load(object sender, EventArgs e) { //當某個線程不是控件的創建線程嘗試訪問該控件的一個或多個屬性時,這通常會導致不可預知的結果。 常見的無效線程活動是對訪問控件屬性的錯誤線程的調用 Handle 。 設置 CheckForIllegalCrossThreadCalls 爲 true 可以在調試時更輕鬆地查找和診斷此線程活動。 Control.CheckForIllegalCrossThreadCalls = false; }
更改後的代碼:
namespace MyScoketTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); } /// <summary> /// 開始監聽 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_Start_Click(object sender, EventArgs e) { //(1)創建套接字 Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //(2)綁定IP和端口 int port = 2018; string host = "127.0.0.1"; //IPAddress.Any 指示服務器應偵聽所有網絡接口上的客戶端活動。此字段爲只讀。 // IPAddress iP = IPAddress.Any; IPAddress ip = IPAddress.Parse(host); //創建端口號對象 IPEndPoint point = new IPEndPoint(ip, port); //綁定 socketWatch.Bind(point); //(3)監聽 socketWatch.Listen(10); ShowMsg($"監聽成功"); //使用委託異步的方式界面界面卡死 Action<Socket> action = Listen; action.BeginInvoke(socketWatch, null, null); //Thread thread = new Thread(new ThreadStart(Listen)); } private void ShowMsg(string str) { this.textBox1.AppendText(str + "\r\n"); } void Listen(Socket socketWatch) { //(4)等待客戶端連接,並且創建一個負責通信的Socket while (true) { Socket socket = socketWatch.Accept(); //RemoteEndPoint是當前客戶端連接成功的IP ShowMsg($"{socket.RemoteEndPoint.ToString()}:連接成功"); } } private void textBox1_TextChanged(object sender, EventArgs e) { } private void Form1_Load(object sender, EventArgs e) { //當某個線程不是控件的創建線程嘗試訪問該控件的一個或多個屬性時,這通常會導致不可預知的結果。 常見的無效線程活動是對訪問控件屬性的錯誤線程的調用 Handle 。 設置 CheckForIllegalCrossThreadCalls 爲 true 可以在調試時更輕鬆地查找和診斷此線程活動。 Control.CheckForIllegalCrossThreadCalls = false; } } }
上面是服務端的代碼,我們可以暫時通過telnet來進行客戶端的測試,執行cmd執行,打開命令提示符界面,輸入telnet 127.0.0.1 2018,
點擊回車,結果:
上面的代碼成功實現了連接,下面就需要我們進行通信的處理,Listen方法給爲如下:
void Listen(Socket socketWatch) { //(4)等待客戶端連接,並且創建一個負責通信的Socket while (true) { //通信的Socket對象 Socket socket = socketWatch.Accept(); //RemoteEndPoint是當前客戶端連接成功的IP ShowMsg($"{socket.RemoteEndPoint.ToString()}:連接成功"); //連接成功之後,服務器應該接收來自於客戶端的消息,這是由負責通信的socket來實現 byte[] buffer = new byte[1024 * 1024 * 2];//2M //realLength實際接收的有效字節數 int realLength = socket.Receive(buffer); string str = Encoding.UTF8.GetString(buffer, 0, realLength); ShowMsg($"{socket.RemoteEndPoint.ToString()}:{str}"); } }
運行起來,然後發送數據,但是卻發現只能接收一次數據,後面無論怎麼發送都不過去了,如下,只發送了一個字母a:
這是因爲下面這行代碼寫在了循環裏面:
//通信的Socket對象 Socket socket = socketWatch.Accept();
比如說第一次循環的時候連接上這個客戶端,這個客戶端發了消息之後再次循環的時候又調了一次上面的代碼,相當於創建了一個新的Socket連接, 上一次通信的連接沒有了,所以就接收不到後面的信息了。所以我們需要拿到循環外面,不止如此,還要將下面Receive方法再開闢一個線程來執行,防止卡死。這樣的話既能接收多個客戶端的連接信息,來一個客戶端信息,就給這個客戶端分配一個Socket對象,專門負責這個客戶端和服務端的通信,也能爲每一個客戶端的信息開闢一個子線程來專門去處理信息:
如下,Listen方法:
void Listen(Socket socketWatch) { //(4)等待客戶端連接,並且創建一個負責通信的Socket while (true) { //通信的Socket對象,在循環中,每來一個客戶端信息,就給這個客戶端分配一個Socket對象,專門負責這個客戶端和服務端的通信 Socket socket = socketWatch.Accept(); //RemoteEndPoint是當前客戶端連接成功的IP ShowMsg($"{socket.RemoteEndPoint.ToString()}:連接成功"); Action<Socket> action = Receive; action.BeginInvoke(socket, null, null); } } /// <summary> /// 接收信息--在這裏也是專門弄成每一個客戶端連接信息都有專門的線程來負責。 /// </summary> /// <param name="socket"></param> private void Receive(Socket socket) { while (true) { //連接成功之後,服務器應該接收來自於客戶端的消息,這是由負責通信的socket來實現 byte[] buffer = new byte[1024 * 1024 * 2];//2M //realLength實際接收的有效字節數 int realLength = socket.Receive(buffer); string str = Encoding.UTF8.GetString(buffer, 0, realLength); ShowMsg($"{socket.RemoteEndPoint.ToString()}:{str}"); } }
此時運行之後上述問題已經解決了。但是又出現新問題了,關閉客戶端之後,服務端一直在接收空信息,陷入死循環中了。
我們現在需要知道一件事:int realLength = socket.Receive(buffer);看這行代碼,如果客戶端A和服務器端已經連接上了,子線程A運行到這行代碼之後就會阻塞,只有客戶端發來信息之後纔會繼續向下執行,還有就是客戶端關閉連接之後子線程A也會取消在這個地方的阻塞,繼續向下執行,所以我們可以判斷一下接收到信息的長度,如果爲0,則表示客戶端已經關閉了。凡是在網絡中傳輸數據,或者說操縱網絡數據,肯定會引發各種異常,不管是斷電,斷網。所以我們需要在有可能出異常的地方儘量都加上try/catch,比如下面的代碼直接在循環中價格try/catch,catch中不用加其它處理,萬一客戶端出現問題了,只要重新連接上,我們這邊照樣能接收發送數據。這樣用戶也不會看出來出問題了。
/// <summary> /// 接收信息 /// </summary> /// <param name="socket"></param> private void Receive(Socket socket) { while (true) { try { //連接成功之後,服務器應該接收來自於客戶端的消息,這是由負責通信的socket來實現 byte[] buffer = new byte[1024 * 1024 * 2];//2M //realLength實際接收的有效字節數 int realLength = socket.Receive(buffer); if (realLength == 0) { break; } string str = Encoding.UTF8.GetString(buffer, 0, realLength); ShowMsg($"{socket.RemoteEndPoint.ToString()}:{str}"); } catch{ } } }
其實現在還是有一個問題,那就是Listen方法中,每一次循環的時候都會調用accept方法,如果存在多個客戶端和服務器連接之後,服務器端想向客戶端發消息的話,就不知道到底會給誰發送消息,因爲循環的原因,總是默認給最近一次連接服務器的那個客戶端發消息。所以我們可以加一個下拉框,因爲每次服務器接收客戶端申請的時候都能知道申請的客戶端的IP和端口信息,我們將其存起來,發送消息的時候選擇對應的客戶端就可以了。
所以最終服務器端的代碼更改爲:
public partial class Form1 : Form { public Form1() { InitializeComponent(); } /// <summary> /// 開始監聽 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_Start_Click(object sender, EventArgs e) { try { //(1)創建套接字 Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //(2)綁定IP和端口 int port = 2018; string host = "127.0.0.1"; //IPAddress.Any 指示服務器應偵聽所有網絡接口上的客戶端活動。此字段爲只讀。 // IPAddress iP = IPAddress.Any; IPAddress ip = IPAddress.Parse(host); //創建端口號對象 IPEndPoint point = new IPEndPoint(ip, port); //綁定 socketWatch.Bind(point); //(3)監聽 socketWatch.Listen(10); ShowMsg($"監聽成功"); //使用委託異步的方式界面界面卡死 Action<Socket> action = Listen; action.BeginInvoke(socketWatch, null, null); //Thread thread = new Thread(new ThreadStart(Listen)); } catch { } } private void ShowMsg(string str) { this.textBox1.AppendText(str + "\r\n"); } //放到方法外是因爲點擊發送消息按鈕的時候需要通過當前的socket對象給客戶端發送消息。 Socket socket; Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>(); /// <summary> /// 監聽 /// </summary> /// <param name="socketWatch"></param> void Listen(Socket socketWatch) { //(4)等待客戶端連接,並且創建一個負責通信的Socket while (true) { try { //通信的Socket對象 socket = socketWatch.Accept(); #region 這是服務器給多個客戶端中的某一個客戶發送信息處理的一部分代碼 //存儲客戶端信息以及處理這個客戶端的socket對象 dicSocket[socket.RemoteEndPoint.ToString()] = socket; //向下拉框添加值 this.comboBox1.Items.Add(socket.RemoteEndPoint.ToString()); #endregion //RemoteEndPoint是當前客戶端連接成功的IP ShowMsg($"{socket.RemoteEndPoint.ToString()}:連接成功"); Action<Socket> action = Receive; action.BeginInvoke(socket, null, null); } catch { } } } /// <summary> /// 接收信息 /// </summary> /// <param name="socket"></param> private void Receive(Socket socket) { while (true) { try { //連接成功之後,服務器應該接收來自於客戶端的消息,這是由負責通信的socket來實現 byte[] buffer = new byte[1024 * 1024 * 2];//2M //realLength實際接收的有效字節數 int realLength = socket.Receive(buffer); if (realLength == 0) { //向下拉框移除關閉的客戶端信息 this.comboBox1.Items.Remove(socket.RemoteEndPoint.ToString()); break; } string str = Encoding.UTF8.GetString(buffer, 0, realLength); ShowMsg($"{socket.RemoteEndPoint.ToString()}:{str}"); } catch { } } } private void textBox1_TextChanged(object sender, EventArgs e) { } private void Form1_Load(object sender, EventArgs e) { //當某個線程不是控件的創建線程嘗試訪問該控件的一個或多個屬性時,這通常會導致不可預知的結果。 常見的無效線程活動是對訪問控件屬性的錯誤線程的調用 Handle 。 設置 CheckForIllegalCrossThreadCalls 爲 true 可以在調試時更輕鬆地查找和診斷此線程活動。 Control.CheckForIllegalCrossThreadCalls = false; } /// <summary> /// 服務器給客戶端發送消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { try { string str = this.textBox2.Text.Trim(); byte[] buffer = Encoding.UTF8.GetBytes(str); //獲取選中的客戶端socket對象,給對應的客戶端發送信息 string ip = this.comboBox1.SelectedItem.ToString(); dicSocket[ip].Send(buffer); } catch (Exception ex) { } } }
客戶端代碼:
public partial class Form1 : Form { public Form1() { InitializeComponent(); } //客戶端發送信息的Socket Socket socketSend; /// <summary> /// 客戶端連接 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnConnection_Click(object sender, EventArgs e) { try { //(1)創建套接字 socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //(2)連接 這是服務器應用程序的IP和端口號 int port = 2018; string host = "127.0.0.1"; IPAddress ip = IPAddress.Parse(host); IPEndPoint point = new IPEndPoint(ip, port); socketSend.Connect(point); ShowMsg("連接成功"); //開啓子線程,不停的接收服務器消息 Action action = Receive; Task.Run(action); } catch { } } /// <summary> /// 接收服務端信息 /// 記住,要想接收到服務端的消息,必須拿到當前客戶端和服務端 /// 通信的Socket對象:socketSend,通過它來獲取消息 /// </summary> private void Receive() { while (true) { try { //連接成功之後,服務器應該接收來自於客戶端的消息,這是由負責通信的socket來實現 byte[] buffer = new byte[1024 * 1024 * 2];//2M //realLength實際接收的有效字節數 int realLength = socketSend.Receive(buffer); if (realLength == 0) { break; } string str = Encoding.UTF8.GetString(buffer, 0, realLength); ShowMsg($"{socketSend.RemoteEndPoint.ToString()}:{str}"); } catch { } } } /// <summary> /// 展示傳來的消息 /// </summary> /// <param name="str"></param> private void ShowMsg(string str) { this.textBox1.AppendText(str + "\r\n"); } /// <summary> /// 發送消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { byte[] buffer = Encoding.UTF8.GetBytes(textBox2.Text.Trim()); //發送信息 socketSend.Send(buffer); } private void Form1_Load(object sender, EventArgs e) { //取消 Control.CheckForIllegalCrossThreadCalls = false; } }
先啓動服務端,開啓監聽,然後啓動客戶端,連接,發送消息,結果:
擴展
上面代碼實現了一個簡答的聊天發送接收,下面實現文件的發送接收:
完整服務器端:
namespace MyScoketTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); } /// <summary> /// 開始監聽 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_Start_Click(object sender, EventArgs e) { try { //(1)創建套接字 Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //(2)綁定IP和端口 int port = 2018; string host = "127.0.0.1"; //IPAddress.Any 指示服務器應偵聽所有網絡接口上的客戶端活動。此字段爲只讀。 // IPAddress iP = IPAddress.Any; IPAddress ip = IPAddress.Parse(host); //創建端口號對象 IPEndPoint point = new IPEndPoint(ip, port); //綁定 socketWatch.Bind(point); //(3)監聽 socketWatch.Listen(10); ShowMsg($"監聽成功"); //使用委託異步的方式界面界面卡死 Action<Socket> action = Listen; action.BeginInvoke(socketWatch, null, null); //Thread thread = new Thread(new ThreadStart(Listen)); } catch { } } private void ShowMsg(string str) { this.textBox1.AppendText(str + "\r\n"); } //放到方法外是因爲點擊發送消息按鈕的時候需要通過當前的socket對象給客戶端發送消息。 Socket socket; Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>(); /// <summary> /// 監聽 /// </summary> /// <param name="socketWatch"></param> void Listen(Socket socketWatch) { //(4)等待客戶端連接,並且創建一個負責通信的Socket while (true) { try { //通信的Socket對象 socket = socketWatch.Accept(); #region 這是服務器給多個客戶端中的某一個客戶發送信息處理的一部分代碼 //存儲客戶端信息以及處理這個客戶端的socket對象 dicSocket[socket.RemoteEndPoint.ToString()] = socket; //向下拉框添加值 this.comboBox1.Items.Add(socket.RemoteEndPoint.ToString()); #endregion //RemoteEndPoint是當前客戶端連接成功的IP ShowMsg($"{socket.RemoteEndPoint.ToString()}:連接成功"); Action<Socket> action = Receive; action.BeginInvoke(socket, null, null); } catch { } } } /// <summary> /// 接收信息 /// </summary> /// <param name="socket"></param> private void Receive(Socket socket) { while (true) { try { //連接成功之後,服務器應該接收來自於客戶端的消息,這是由負責通信的socket來實現 byte[] buffer = new byte[1024 * 1024 * 2];//2M //realLength實際接收的有效字節數 int realLength = socket.Receive(buffer); if (realLength == 0) { //向下拉框移除關閉的客戶端信息 this.comboBox1.Items.Remove(socket.RemoteEndPoint.ToString()); break; } string str = Encoding.UTF8.GetString(buffer, 0, realLength); ShowMsg($"{socket.RemoteEndPoint.ToString()}:{str}"); } catch { } } } private void textBox1_TextChanged(object sender, EventArgs e) { } private void Form1_Load(object sender, EventArgs e) { //當某個線程不是控件的創建線程嘗試訪問該控件的一個或多個屬性時,這通常會導致不可預知的結果。 常見的無效線程活動是對訪問控件屬性的錯誤線程的調用 Handle 。 設置 CheckForIllegalCrossThreadCalls 爲 true 可以在調試時更輕鬆地查找和診斷此線程活動。 Control.CheckForIllegalCrossThreadCalls = false; } /// <summary> /// 服務器給客戶端發送消息(僅限於文字消息) /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { try { string str = this.textBox2.Text.Trim(); byte[] buffer = Encoding.UTF8.GetBytes(str); #region 爲了既能夠發送文本消息,也能夠發送文件,所以在這裏對發送的數據做處理 List<byte> list = new List<byte>(); //定義傳送文本消息第一位是0 list.Add(0); list.AddRange(buffer); byte[] newBuffer = list.ToArray(); #endregion //獲取選中的客戶端socket對象,給對應的客戶端發送信息 string ip = this.comboBox1.SelectedItem.ToString(); dicSocket[ip].Send(newBuffer); } catch (Exception ex) { } } /// <summary> /// 選擇要發送的文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); //ofd.InitialDirectory = ""; 可以定義默認路徑 ofd.Title = "請選擇要發送的文件"; ofd.Filter = "所有文件|*.*"; ofd.ShowDialog(); //返回的是選中文件的絕對路徑 this.textBox3.Text = ofd.FileName; } /// <summary> /// 發送文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button2_Click(object sender, EventArgs e) { //獲取要發送的文件路徑 string filePath = this.textBox3.Text.Trim(); using (FileStream fsRead = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { //5M byte[] buffer = new byte[1024 * 1024 * 5]; int r = fsRead.Read(buffer, 0, buffer.Length); List<byte> list = new List<byte>(); list.Add(1); list.AddRange(buffer); byte[] bufferNew = list.ToArray(); // r + 1:因爲加了一位 dicSocket[this.comboBox1.SelectedItem.ToString()].Send(bufferNew, 0, r + 1, SocketFlags.None); } } /// <summary> /// 震動 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button3_Click(object sender, EventArgs e) { byte[] buffer = new byte[1]; buffer[0] = 2; dicSocket[comboBox1.SelectedItem.ToString()].Send(buffer); } } }
界面:
完整客戶端:
namespace SocketClient { public partial class Form1 : Form { public Form1() { InitializeComponent(); } //客戶端發送信息的Socket Socket socketSend; /// <summary> /// 客戶端連接 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnConnection_Click(object sender, EventArgs e) { try { //(1)創建套接字 socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //(2)連接 這是服務器應用程序的IP和端口號 int port = 2018; string host = "127.0.0.1"; IPAddress ip = IPAddress.Parse(host); IPEndPoint point = new IPEndPoint(ip, port); socketSend.Connect(point); ShowMsg("連接成功"); //開啓子線程,不停的接收服務器消息 Action action = Receive; Task.Run(action); } catch { } } /// <summary> /// 接收服務端信息 /// 記住,要想接收到服務端的消息,必須拿到當前客戶端和服務端 /// 通信的Socket對象:socketSend,通過它來獲取消息 /// </summary> private void Receive() { while (true) { try { //連接成功之後,服務器應該接收來自於客戶端的消息,這是由負責通信的socket來實現 byte[] buffer = new byte[1024 * 1024 * 2];//2M //realLength實際接收的有效字節數 int realLength = socketSend.Receive(buffer); if (realLength == 0) { break; } if (buffer[0] == 0) { //文字消息 //從第二位開始 string str = Encoding.UTF8.GetString(buffer, 1, realLength - 1); ShowMsg($"{socketSend.RemoteEndPoint.ToString()}:{str}"); } else if (buffer[0] == 1) { //文件 注意在這裏是不知道傳來的文件後綴是什麼的,到底是txt還是jpg,還是其它,要想實現這個功能也可以自己再定義一個規則,比如第二位來標誌:0是txt,1是jpg,2是png,等等等。還有這裏也不支持大文件的傳輸 SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.Title = "請選擇要保存的路徑"; saveFileDialog.Filter = "所有文件|*.*"; saveFileDialog.ShowDialog(this); //選中的保存路徑 string path = saveFileDialog.FileName; using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write)) { fs.Write(buffer, 1, realLength - 1); } } else if (buffer[0] == 2) { //震動 用界面的移動表示震動 for (int i = 0; i < 500; i++) { this.Location = new Point(200,200); this.Location = new Point(200, 200); } } } catch { } } // socketSend.Close(); } /// <summary> /// 展示傳來的消息 /// </summary> /// <param name="str"></param> private void ShowMsg(string str) { this.textBox1.AppendText(str + "\r\n"); } /// <summary> /// 發送消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { byte[] buffer = Encoding.UTF8.GetBytes(textBox2.Text.Trim()); //發送信息 socketSend.Send(buffer); } private void Form1_Load(object sender, EventArgs e) { //取消 Control.CheckForIllegalCrossThreadCalls = false; } } }
界面:
關於上面代碼爲什麼會使用過IPAddress.Any的補充:
IPAddress.Any 解決本地ip和服務器ip切換問題
IPAddress.Any表示本機ip,換言之,如果服務器綁定此地址,則表示偵聽本機所有ip對應的那個端口(本機可能有多個ip或只有一個ip)
IPAddress.Any微軟給出的解釋是:Provides an IP address that indicates that the server must listen for client activity on all network interfaces. This field is read-only.翻譯過來就是:提供一個iP地址來指示服務器必須監聽所有網卡上的客戶端活動。此字段爲只讀的。也就是說,對雙卡網或者多網卡的機器,每個網卡都會有一個獨立的ip,如果使用了IPAddress.Any就表示服務器必須監聽本機所有網卡上的指定端口。
比如雙網卡機器,內網ip爲192.168.0.1,外網ip爲120.210.1.1,服務器可以同時監聽192.168.0.1:80和120.210.1.1:80。
localipAddress = Dns.Resolve(IPAddress.Any.ToString()).AddressList[0];
m_RecSocket = new TcpListener(localipAddress, m_localPort);
的寫法可以改成
m_RecSocket = new TcpListener(IPAddress.Any, m_localPort);
Socket心跳包
參考:https://www.bilibili.com/video/BV1FW411v7as?p=9&spm_id_from=pageDriver
相關知識補充
轉載自安靜點老大:
https://www.cnblogs.com/anjingdian/p/15323326.html