C#實(shí)現(xiàn)TCP客戶端和服務(wù)器的基本功能
效果展示
服務(wù)器端實(shí)現(xiàn)
首先,我們實(shí)現(xiàn)TCP服務(wù)器。以下是服務(wù)器端所需的類和代碼:
using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; namespace TCPIP_Test { public class TcpIp_Server { Socket serverSocket; // 服務(wù)器端Socket,用于服務(wù)器與客戶端的通信 Socket clientSocket; // 客戶端Socket,用于與客戶端建立連接 TcpListener tcpListener; // 負(fù)責(zé)監(jiān)聽(tīng)客戶端的連接請(qǐng)求 Thread tcpListenerThread; // 監(jiān)聽(tīng)連接請(qǐng)求的線程 Dictionary<string, Socket> dicClientSockets = new Dictionary<string, Socket>(); // 存儲(chǔ)客戶端Socket的集合,以客戶端的IP/端口作為鍵 Dictionary<string, Thread> dicReceiveMsg = new Dictionary<string, Thread>(); // 存儲(chǔ)每個(gè)客戶端接收消息的線程集合 string _IP; // 服務(wù)器的IP地址 int _Port; // 服務(wù)器的端口 IPAddress _ipAddress; // 服務(wù)器的IPAddress對(duì)象 EndPoint _endPoint; // 服務(wù)器的端點(diǎn),包括IP地址和端口 bool linked = false; // 標(biāo)識(shí)服務(wù)器是否已開(kāi)始監(jiān)聽(tīng)客戶端連接 bool unlinked = false; // 標(biāo)識(shí)服務(wù)器是否已關(guān)閉連接 // 構(gòu)造函數(shù),初始化服務(wù)器的IP和端口 public TcpIp_Server(string ip, int port) { serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 初始化Socket對(duì)象,使用TCP協(xié)議 _IP = ip; _Port = port; _ipAddress = IPAddress.Parse(ip); // 將IP地址字符串轉(zhuǎn)換為IPAddress對(duì)象 _endPoint = new IPEndPoint(_ipAddress, port); // 構(gòu)造服務(wù)器端點(diǎn) } // 判斷服務(wù)器是否已綁定到指定的端口 private bool _IsBound; public bool IsBound { get { return _IsBound = serverSocket.IsBound; } } // 判斷服務(wù)器Socket是否已連接 private bool _Connected; public bool Connected { get { return _Connected = serverSocket.Connected; } } // 綁定服務(wù)器并開(kāi)始監(jiān)聽(tīng)客戶端連接 public bool Bind(ref string msg) { try { if (!linked) { tcpListener = new TcpListener(_ipAddress, _Port); // 創(chuàng)建TcpListener對(duì)象,綁定IP和端口 tcpListener.Start(); // 啟動(dòng)監(jiān)聽(tīng) linked = true; // 標(biāo)記已開(kāi)始監(jiān)聽(tīng) ThreadStart threadStart = new ThreadStart(ListenConnectRequest); // 創(chuàng)建一個(gè)委托,執(zhí)行監(jiān)聽(tīng)連接請(qǐng)求的方法 tcpListenerThread = new Thread(threadStart); // 創(chuàng)建一個(gè)新線程來(lái)監(jiān)聽(tīng)客戶端請(qǐng)求 tcpListenerThread.IsBackground = true; // 設(shè)置為后臺(tái)線程 tcpListenerThread.Start(); // 啟動(dòng)監(jiān)聽(tīng)線程 msg = $"[Info] 服務(wù)器{tcpListener.LocalEndpoint.ToString()}監(jiān)聽(tīng)成功!"; // 返回監(jiān)聽(tīng)成功的消息 return true; } else { msg = $"[Info] 已監(jiān)聽(tīng)"; // 如果已經(jīng)開(kāi)始監(jiān)聽(tīng),則返回已監(jiān)聽(tīng)消息 return true; } } catch (Exception ex) { msg = $"[Err] 連接失敗!信息={ex}"; // 如果發(fā)生異常,返回錯(cuò)誤信息 return false; } } // 斷開(kāi)與指定客戶端的連接 public bool DisConnectClient(string client, ref string msg) { try { if (dicClientSockets.ContainsKey(client)) { dicClientSockets[client].Close(); // 關(guān)閉與客戶端的連接 msg = $"[Info] 斷開(kāi)客戶端{(lán)client}連接"; // 返回?cái)嚅_(kāi)成功的消息 return true; } else { msg = $"[Info] 客戶端{(lán)client}已斷開(kāi)"; // 如果客戶端已斷開(kāi),返回已斷開(kāi)消息 return true; } } catch (Exception ex) { msg = $"[Err] 斷開(kāi)失敗!信息={ex}"; // 如果發(fā)生異常,返回錯(cuò)誤信息 return false; } } // 關(guān)閉服務(wù)器監(jiān)聽(tīng)并斷開(kāi)所有客戶端連接 public bool ShutDown(string[] clientList, ref string msg) { try { if (linked) { linked = false; // 標(biāo)記服務(wù)器停止監(jiān)聽(tīng) tcpListener.Stop(); // 停止TcpListener監(jiān)聽(tīng) for (int i = 0; i < clientList.Length; i++) { dicClientSockets[clientList[i]].Close(); // 關(guān)閉每個(gè)客戶端的連接 } msg = $"[Info] 服務(wù)器監(jiān)聽(tīng)斷開(kāi)"; // 返回關(guān)閉監(jiān)聽(tīng)的消息 return true; } else { unlinked = true; // 標(biāo)記已斷開(kāi) msg = $"[Info] 已斷開(kāi)"; // 返回已斷開(kāi)消息 return true; } } catch (Exception ex) { tcpListener.Stop(); // 停止TcpListener unlinked = true; // 標(biāo)記已斷開(kāi) msg = $"[Info] 已斷開(kāi)!!!"; // 返回關(guān)閉連接的消息 return false; } } // 向指定客戶端發(fā)送數(shù)據(jù) public bool SendToClient(string client, string cmd, ref string msg) { try { if (!string.IsNullOrEmpty(cmd)) { dicClientSockets[client].Send(Encoding.UTF8.GetBytes(cmd)); // 將命令數(shù)據(jù)發(fā)送給客戶端 msg = $"[Info] 發(fā)送{client}信息={cmd}"; // 返回發(fā)送成功的消息 return true; } else return false; // 如果命令為空,返回發(fā)送失敗 } catch (Exception ex) { msg = $"[Err] 發(fā)送信息失敗!信息={ex}"; // 如果發(fā)送失敗,返回錯(cuò)誤信息 return false; } } // 監(jiān)聽(tīng)客戶端的連接請(qǐng)求 private void ListenConnectRequest() { while (linked) { try { Socket clientSocket = tcpListener.AcceptSocket(); // 等待并接受客戶端連接 ParameterizedThreadStart parameterizedThreadStart = new ParameterizedThreadStart(ReceiveData); // 創(chuàng)建接收數(shù)據(jù)的線程委托 Thread receiveMsgThread = new Thread(parameterizedThreadStart); // 創(chuàng)建一個(gè)線程來(lái)接收客戶端數(shù)據(jù) receiveMsgThread.IsBackground = true; // 設(shè)置為后臺(tái)線程 receiveMsgThread.Start(clientSocket); // 啟動(dòng)線程并傳入客戶端Socket dicClientSockets.Add(clientSocket.RemoteEndPoint.ToString(), clientSocket); // 將客戶端Socket加入字典 dicReceiveMsg.Add(clientSocket.RemoteEndPoint.ToString(), receiveMsgThread); // 將接收消息線程加入字典 Form_Server._AddListEvent(clientSocket.RemoteEndPoint.ToString()); // 更新UI列表,顯示已連接的客戶端 } catch (Exception ex) { Form_Server._ShowServerLogEvent($"[Err] 監(jiān)聽(tīng)失??!信息={ex}"); // 監(jiān)聽(tīng)失敗時(shí)顯示錯(cuò)誤日志 } } } // 接收客戶端發(fā)送的數(shù)據(jù) public void ReceiveData(Object obj) { Socket clientSocket = (Socket)obj; // 將傳入的參數(shù)轉(zhuǎn)換為Socket string str = clientSocket.RemoteEndPoint.ToString(); // 獲取客戶端的遠(yuǎn)程端點(diǎn)(IP+端口) while (true) { try { byte[] buffer = new byte[1024 * 1024]; // 創(chuàng)建緩存區(qū),大小為1MB int length = clientSocket.Receive(buffer); // 接收客戶端數(shù)據(jù) string message = Encoding.UTF8.GetString(buffer, 0, length); // 將字節(jié)數(shù)組轉(zhuǎn)換為字符串 if (length != 0) { Form_Server._ShowServerLogEvent($"[Info] 接收客戶端{(lán)clientSocket.RemoteEndPoint}信息={message}"); // 顯示接收到的消息 } } catch (Exception) { Form_Server._RemoveListEvent(str); // 從UI列表中移除已斷開(kāi)的客戶端 dicClientSockets.Remove(str); // 從客戶端字典中移除該客戶端的Socket dicReceiveMsg.Remove(str); // 從接收消息線程字典中移除該客戶端的線程 break; // 退出接收數(shù)據(jù)的循環(huán) } } } } }
代碼解析:
服務(wù)器初始化: 在構(gòu)造函數(shù)中,初始化了服務(wù)器的Socket、監(jiān)聽(tīng)端口和IP地址。
綁定與監(jiān)聽(tīng): Bind() 方法會(huì)啟動(dòng)一個(gè)監(jiān)聽(tīng)線程,等待客戶端連接請(qǐng)求。
客戶端連接管理: 客戶端連接通過(guò) ListenConnectRequest() 方法處理。每當(dāng)有客戶端連接時(shí),創(chuàng)建一個(gè)新的線程來(lái)接收客戶端數(shù)據(jù)。
發(fā)送與接收數(shù)據(jù): SendToClient() 方法用于發(fā)送數(shù)據(jù)到指定客戶端,而 ReceiveData() 方法則接收客戶端發(fā)送的數(shù)據(jù)。
斷開(kāi)與關(guān)閉: DisConnectClient() 和 ShutDown() 方法分別用于斷開(kāi)客戶端連接和關(guān)閉服務(wù)器監(jiān)聽(tīng)。
接下來(lái)是服務(wù)器界面的代碼:
using System; using System.Drawing; using System.Net; using System.Net.Sockets; using System.Windows.Forms; namespace TCPIP_Test { // 委托用于更新服務(wù)器日志 public delegate void ShowServerLogDelegate(string message); // 委托用于向客戶端列表中添加客戶端 public delegate void AddListDelegate(string value); // 委托用于從客戶端列表中移除客戶端 public delegate void RemoveListDelegate(string value); public partial class Form_Server : Form { // 靜態(tài)委托實(shí)例,用于更新日志、添加或移除客戶端 public static ShowServerLogDelegate _ShowServerLogEvent = null; public static AddListDelegate _AddListEvent = null; public static RemoveListDelegate _RemoveListEvent = null; // TCP服務(wù)器實(shí)例 TcpIp_Server tcpIp_Server; string ip; // 服務(wù)器IP地址 int port; // 服務(wù)器端口 string msg; // 用于存儲(chǔ)消息 Form_Client form_Client; // 客戶端窗體實(shí)例 String[] clientList; // 當(dāng)前連接的客戶端列表 // 構(gòu)造函數(shù),初始化窗體 public Form_Server() { InitializeComponent(); } // 窗體加載時(shí)執(zhí)行的操作 private void Form_Server_Load(object sender, EventArgs e) { // 訂閱日志、添加客戶端和移除客戶端事件 _ShowServerLogEvent += ShowServerLog; _AddListEvent += AddList; _RemoveListEvent += RemoveList; // 獲取本機(jī)的IP地址并添加到ComboBox控件 GetIP(); // 設(shè)置默認(rèn)端口為5000 port = int.Parse(textBox_Port.Text = "5000"); } // 獲取本機(jī)所有的IP地址,并將IPv4地址添加到IP地址選擇框 private void GetIP() { string name = Dns.GetHostName(); // 獲取主機(jī)名 IPAddress[] iPAddress = Dns.GetHostAddresses(name); // 獲取所有IP地址 foreach (IPAddress item in iPAddress) { // 只添加IPv4地址到ComboBox中 if (item.AddressFamily == AddressFamily.InterNetwork) comboBox_IP.Items.Add(item.ToString()); } // 設(shè)置默認(rèn)選擇為第一個(gè)IP地址 comboBox_IP.SelectedIndex = 0; } // 開(kāi)始監(jiān)聽(tīng)按鈕點(diǎn)擊事件 private void button_Listen_Click(object sender, EventArgs e) { // 檢查是否已輸入IP和端口 if (comboBox_IP.SelectedItem == null || textBox_Port.Text == "") { MessageBox.Show("請(qǐng)輸入IP和Port!"); return; } ip = comboBox_IP.SelectedItem.ToString(); // 獲取選擇的IP地址 port = int.Parse(textBox_Port.Text); // 獲取端口號(hào) // 創(chuàng)建TCP服務(wù)器實(shí)例并初始化 tcpIp_Server = new TcpIp_Server(ip, port); _ShowServerLogEvent($"[Info] 服務(wù)器初始化完成"); // 啟動(dòng)服務(wù)器監(jiān)聽(tīng) _ShowServerLogEvent($"[Info] 服務(wù)器開(kāi)始監(jiān)聽(tīng)..."); if(tcpIp_Server.Bind(ref msg)) button_Listen.BackColor = Color.YellowGreen; // 設(shè)置監(jiān)聽(tīng)按鈕顏色為綠色 _ShowServerLogEvent($"{msg}"); // 顯示監(jiān)聽(tīng)狀態(tài)消息 } // 停止監(jiān)聽(tīng)按鈕點(diǎn)擊事件 private void Button_ShutDown_Click(object sender, EventArgs e) { // 停止服務(wù)器監(jiān)聽(tīng) _ShowServerLogEvent($"[Info] 服務(wù)器停止監(jiān)聽(tīng)..."); tcpIp_Server.ShutDown(clientList, ref msg); listBox_Client.Items.Clear(); // 清空客戶端列表 label_ClientCount.Text = "0"; // 更新客戶端數(shù)量顯示 button_Listen.BackColor = Color.LightGray; // 恢復(fù)監(jiān)聽(tīng)按鈕顏色 _ShowServerLogEvent($"{msg}"); // 顯示停止監(jiān)聽(tīng)的消息 } // 單個(gè)客戶端發(fā)送消息按鈕點(diǎn)擊事件 private void Button_SendOnce_Click(object sender, EventArgs e) { // 檢查是否選擇了客戶端 if (listBox_Client.SelectedIndex == -1) { MessageBox.Show(new Form { TopMost = true }, $"請(qǐng)選擇客戶端!", "Error"); return; } // 獲取選擇的客戶端和消息內(nèi)容 _ShowServerLogEvent($"[Info] 服務(wù)器單發(fā)送信息..."); string client = listBox_Client.SelectedItem.ToString(); string cmd = textBox_Message.Text; // 向客戶端發(fā)送消息 tcpIp_Server.SendToClient(client, cmd, ref msg); _ShowServerLogEvent($"{msg}"); // 顯示發(fā)送狀態(tài)消息 } // 向所有客戶端發(fā)送消息按鈕點(diǎn)擊事件 private void button_SendAll_Click(object sender, EventArgs e) { _ShowServerLogEvent($"[Info] 服務(wù)器多發(fā)送信息..."); string cmd = textBox_Message.Text; // 獲取消息內(nèi)容 for (int i = 0; i < listBox_Client.Items.Count; i++) { // 向所有客戶端發(fā)送消息 tcpIp_Server.SendToClient(listBox_Client.Items[i].ToString(), cmd, ref msg); _ShowServerLogEvent($"{msg}"); // 顯示每次發(fā)送的消息 } } // 清空日志按鈕點(diǎn)擊事件 private void Button_Clear_Click(object sender, EventArgs e) { richTextBox_Log.Text = ""; // 清空日志框 } // 打開(kāi)客戶端按鈕點(diǎn)擊事件(目前被注釋掉) private void button_OpenClient_Click(object sender, EventArgs e) { // 這里的代碼被注釋掉,表示當(dāng)前功能未啟用 //if (form_Client == null || form_Client.IsDisposed) { // form_Client = new Form_Client(); // form_Client.Show(); //} //else { // form_Client.TopMost = true; //} } // 服務(wù)器窗體關(guān)閉時(shí)的處理 private void Form_Server_FormClosing(object sender, FormClosingEventArgs e) { // 確保在窗體關(guān)閉時(shí)關(guān)閉服務(wù)器 if (tcpIp_Server != null) tcpIp_Server.ShutDown(clientList, ref msg); _ShowServerLogEvent($"{msg}"); // 顯示服務(wù)器關(guān)閉消息 _ShowServerLogEvent -= ShowServerLog; // 取消訂閱日志事件 } // 顯示服務(wù)器日志的方法(線程安全更新UI) private void ShowServerLog(string message) { if (InvokeRequired) { this.BeginInvoke(new Action(() => ShowServerLog(message))); // 在UI線程更新日志 } else { int maxLine = 100; // 設(shè)置日志最大行數(shù) if (this.richTextBox_Log.Lines.Length > maxLine) { // 如果日志行數(shù)超過(guò)最大行數(shù),刪除最舊的日志 this.richTextBox_Log.Text = richTextBox_Log.Text.Substring(richTextBox_Log.Lines[0].Length + 1); } // 在日志框中追加新的日志 richTextBox_Log.AppendText($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss(fff)")} {message}\r\n"); richTextBox_Log.SelectionStart = richTextBox_Log.Text.Length; richTextBox_Log.ScrollToCaret(); // 滾動(dòng)到日志的最新位置 } } // 向客戶端列表中添加客戶端(線程安全操作) private void AddList(string value) { if (InvokeRequired) { this.BeginInvoke(new Action(() => AddList(value))); // 在UI線程更新客戶端列表 } else { listBox_Client.Items.Add(value); // 添加客戶端到列表 // 更新客戶端列表數(shù)組 clientList = new String[listBox_Client.Items.Count]; int i = 0; foreach (var item in listBox_Client.Items) { clientList[i] = item.ToString(); } label_ClientCount.Text = clientList.Length.ToString(); // 更新客戶端數(shù)量顯示 } } // 從客戶端列表中移除客戶端(線程安全操作) private void RemoveList(string value) { if (InvokeRequired) { this.BeginInvoke(new Action(() => RemoveList(value))); // 在UI線程更新客戶端列表 } else { listBox_Client.Items.Remove(value); // 從列表中移除客戶端 // 更新客戶端列表數(shù)組 int i = 0; foreach (var item in listBox_Client.Items) { clientList[i] = item.ToString(); } label_ClientCount.Text = clientList.Length.ToString(); // 更新客戶端數(shù)量顯示 } } } }
功能解析:
服務(wù)器配置與啟動(dòng):選擇IP地址和端口,啟動(dòng)服務(wù)器監(jiān)聽(tīng)客戶端連接。
客戶端管理:顯示連接的客戶端,支持單個(gè)或所有客戶端發(fā)送消息。
日志記錄:實(shí)時(shí)顯示服務(wù)器日志,記錄連接、消息發(fā)送等操作。
停止服務(wù)器:停止監(jiān)聽(tīng),清空客戶端列表,斷開(kāi)連接。
客戶端實(shí)現(xiàn)
服務(wù)器端實(shí)現(xiàn)完成后,接下來(lái)我們來(lái)實(shí)現(xiàn)TCP客戶端??蛻舳私缑娲a與服務(wù)器端類似,主要使用以下類:
using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace TCPIP_Test { // 定義TCP客戶端類 public class TcpIp_Client { // 客戶端Socket對(duì)象,用于與服務(wù)器建立TCP連接 Socket clientSocket; // 服務(wù)器的IP地址 string _IP; // 服務(wù)器的端口 int _Port; // 服務(wù)器的IPAddress對(duì)象 IPAddress _ipAddress; // 服務(wù)器的IPEndPoint對(duì)象,包含IP地址和端口 IPEndPoint _iPEndPoint; // 標(biāo)識(shí)連接狀態(tài) bool linked = false; // 標(biāo)識(shí)是否曾經(jīng)斷開(kāi)連接 bool unlinked = false; // 構(gòu)造函數(shù),初始化IP地址和端口 public TcpIp_Client(string ip, int port) { clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 創(chuàng)建TCP連接的Socket對(duì)象 _IP = ip; // 設(shè)置IP地址 _Port = port; // 設(shè)置端口號(hào) _ipAddress = IPAddress.Parse(ip); // 將字符串IP地址解析為IPAddress對(duì)象 _iPEndPoint = new IPEndPoint(_ipAddress, port); // 創(chuàng)建端點(diǎn)對(duì)象,包含IP地址和端口 } // 當(dāng)前連接狀態(tài) private bool _Connected; public bool Connected { get { return _Connected = clientSocket.Connected; } // 返回Socket的連接狀態(tài) } // 連接到服務(wù)器 public bool ConnectServer(ref string msg) { try { if (!linked) { // 如果沒(méi)有連接 if (unlinked) { // 如果曾經(jīng)斷開(kāi)連接,重新創(chuàng)建Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); } // 連接到服務(wù)器 clientSocket.Connect(_iPEndPoint); if (clientSocket.Connected) { // 如果連接成功 linked = true; // 設(shè)置連接狀態(tài)為已連接 msg = $"[Info] 服務(wù)器{clientSocket.LocalEndPoint.ToString()}連接成功!"; // 設(shè)置成功消息 ReceiveData(); // 調(diào)用方法開(kāi)始接收數(shù)據(jù) return true; } else { // 如果連接失敗 msg = $"[Info] 客戶端連接失?。?; return false; } } else { // 如果已經(jīng)連接 msg = $"[Info] 已連接"; return true; } } catch (Exception ex) { msg = $"[Err] 連接失??!信息={ex}"; // 連接失敗時(shí)設(shè)置錯(cuò)誤消息 return false; } } // 斷開(kāi)與服務(wù)器的連接 public bool DisConnectServer(ref string msg) { try { if (linked) { // 如果已連接 linked = false; // 設(shè)置連接狀態(tài)為未連接 clientSocket.Disconnect(false); // 斷開(kāi)連接并釋放資源 unlinked = true; // 標(biāo)記為已斷開(kāi) msg = $"[Info] 客戶端連接斷開(kāi)"; return true; } else { // 如果已經(jīng)是斷開(kāi)狀態(tài) msg = $"[Info] 已斷開(kāi)"; return true; } } catch (Exception ex) { msg = $"[Err] 斷開(kāi)失??!信息={ex}"; // 斷開(kāi)連接失敗時(shí)設(shè)置錯(cuò)誤消息 return false; } } // 向服務(wù)器發(fā)送消息 public bool SendToServer(string cmd, ref string msg) { try { if (clientSocket.Connected) { // 如果已連接 clientSocket.Send(Encoding.UTF8.GetBytes(cmd)); // 將命令轉(zhuǎn)換為字節(jié)并發(fā)送 msg = $"[Info] 發(fā)送{clientSocket.LocalEndPoint.ToString()}信息={cmd}"; // 設(shè)置發(fā)送成功的消息 return true; } else { // 如果未連接 MessageBox.Show(new Form { TopMost = true }, "未連接!", "Info", MessageBoxButtons.OK); // 彈出提示框 msg = $"未連接!"; return false; } } catch (Exception ex) { msg = $"[Err] 發(fā)送信息失?。⌒畔?{ex}"; // 發(fā)送失敗時(shí)設(shè)置錯(cuò)誤消息 return false; } } // 接收來(lái)自服務(wù)器的數(shù)據(jù) private void ReceiveData() { string str = ""; // 存儲(chǔ)接收到的消息 Task.Run(() => { while (linked) { // 只要連接保持有效,就持續(xù)接收數(shù)據(jù) try { if (clientSocket.Connected) { Thread.Sleep(10); // 為了避免占用過(guò)多的CPU時(shí)間,稍微暫停一下 } else { continue; // 如果連接斷開(kāi),繼續(xù)檢查連接狀態(tài) } byte[] data = new byte[1024]; // 緩沖區(qū)用于接收數(shù)據(jù) int length = clientSocket.Receive(data); // 接收服務(wù)器發(fā)送的數(shù)據(jù) string message = Encoding.UTF8.GetString(data, 0, length); // 將接收到的字節(jié)數(shù)據(jù)轉(zhuǎn)換為字符串 if (message != "") { str = message; // 存儲(chǔ)接收到的消息 Form_Client._ShowClientLog($"[Info] 接收服務(wù)器{clientSocket.LocalEndPoint.ToString()}信息={message}"); // 顯示接收到的消息 } } catch (Exception ex) { Form_Client._ShowClientLog($"[Err] 接收服務(wù)器信息失??!信息={ex}"); // 如果接收失敗,顯示錯(cuò)誤信息 } } }); } } }
代碼解析:
TcpIp_Client類:該類用于實(shí)現(xiàn)一個(gè)TCP客戶端,提供連接服務(wù)器、斷開(kāi)連接、發(fā)送消息、接收消息等功能。
構(gòu)造函數(shù):接受服務(wù)器的IP地址和端口號(hào),初始化Socket并創(chuàng)建連接端點(diǎn)(IPEndPoint)。
ConnectServer方法:建立與服務(wù)器的TCP連接。連接成功后,啟動(dòng)接收數(shù)據(jù)的線程。
DisConnectServer方法:斷開(kāi)與服務(wù)器的連接并釋放相關(guān)資源。
SendToServer方法:將給定的命令發(fā)送到服務(wù)器。
ReceiveData方法:接收來(lái)自服務(wù)器的消息,并在接收到數(shù)據(jù)時(shí)將其打印到客戶端界面或日志中。
關(guān)鍵技術(shù):
Socket編程:使用Socket類進(jìn)行TCP連接的建立、數(shù)據(jù)的發(fā)送和接收。
多線程:ReceiveData方法通過(guò)Task.Run()啟動(dòng)一個(gè)新的線程來(lái)異步接收數(shù)據(jù)。
消息編碼:使用UTF8對(duì)消息進(jìn)行編碼和解碼。
客戶端界面的實(shí)現(xiàn)代碼如下:
using System; using System.Drawing; using System.Net; using System.Net.Sockets; using System.Windows.Forms; namespace TCPIP_Test { // 定義一個(gè)委托,用于顯示客戶端日志信息 public delegate void EventShowClientLog(string message); // 客戶端窗口類 public partial class Form_Client : Form { // 聲明委托實(shí)例,用于日志顯示 public static EventShowClientLog _ShowClientLog = null; // TCP客戶端實(shí)例 TcpIp_Client tcpIp_Client; // 存儲(chǔ)IP地址和端口 string ip; int port; string msg; // 服務(wù)器窗口實(shí)例 Form_Server form_Server; // 構(gòu)造函數(shù),初始化組件 public Form_Client() { InitializeComponent(); } // 窗體加載時(shí)的事件處理 private void Form1_Load(object sender, EventArgs e) { // 綁定委托到日志顯示方法 _ShowClientLog += ShowClientLog; // 獲取本機(jī)IP地址并填充到下拉框中 GetIP(); // 默認(rèn)端口5000 port = int.Parse(textBox_Port.Text = "5000"); } // 獲取本機(jī)IP地址的方法 private void GetIP() { // 獲取本機(jī)的計(jì)算機(jī)名 string name = Dns.GetHostName(); // 獲取所有IP地址 IPAddress[] iPAddress = Dns.GetHostAddresses(name); // 遍歷所有IP地址,只添加IPv4地址到下拉框中 foreach (IPAddress item in iPAddress) { if (item.AddressFamily == AddressFamily.InterNetwork) comboBox_IP.Items.Add(item.ToString()); } // 默認(rèn)選擇第一個(gè)IP地址 comboBox_IP.SelectedIndex = 0; } // 連接按鈕點(diǎn)擊事件處理 private void Button_Connect_Click(object sender, EventArgs e) { // 判斷是否選擇了IP地址和端口 if (comboBox_IP.SelectedItem == null || textBox_Port.Text == "") { MessageBox.Show("請(qǐng)輸入IP和Port!"); return; } // 獲取選擇的IP和端口 ip = comboBox_IP.SelectedItem.ToString(); port = int.Parse(textBox_Port.Text); // 創(chuàng)建TCP客戶端實(shí)例并嘗試連接到服務(wù)器 tcpIp_Client = new TcpIp_Client(ip, port); _ShowClientLog($"[Info] 客戶端初始化完成"); _ShowClientLog($"[Info] 客戶端開(kāi)始連接..."); // 嘗試連接服務(wù)器 if (tcpIp_Client.ConnectServer(ref msg)) button_Connect.BackColor = Color.YellowGreen; // 連接成功后,改變按鈕顏色 _ShowClientLog($"{msg}"); } // 斷開(kāi)連接按鈕點(diǎn)擊事件處理 private void Button_DisConnect_Click(object sender, EventArgs e) { _ShowClientLog($"[Info] 客戶端斷開(kāi)連接..."); // 調(diào)用斷開(kāi)連接方法 tcpIp_Client.DisConnectServer(ref msg); button_Connect.BackColor = Color.LightGray; // 斷開(kāi)連接后,恢復(fù)按鈕顏色 _ShowClientLog($"{msg}"); } // 發(fā)送一次數(shù)據(jù)按鈕點(diǎn)擊事件處理 private void Button_SendOnce_Click(object sender, EventArgs e) { _ShowClientLog($"[Info] 客戶端發(fā)送信息..."); // 獲取輸入框中的信息 string cmd = textBox_Message.Text; // 發(fā)送數(shù)據(jù)到服務(wù)器 tcpIp_Client.SendToServer(cmd, ref msg); _ShowClientLog($"{msg}"); } // 顯示日志信息的方法 private void ShowClientLog(string message) { // 判斷是否需要在UI線程中更新 if (richTextBox_Log.InvokeRequired) { EventShowClientLog d = new EventShowClientLog(ShowClientLog); richTextBox_Log.Invoke(d, new object[] { message }); } else { // 最大顯示行數(shù)限制 int maxLine = 100; // 如果超過(guò)最大行數(shù),刪除最舊的日志行 if (this.richTextBox_Log.Lines.Length > maxLine) { this.richTextBox_Log.Text = richTextBox_Log.Text.Substring(richTextBox_Log.Lines[0].Length + 1); } // 添加日志到文本框,并顯示當(dāng)前時(shí)間戳 richTextBox_Log.AppendText($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss(fff)")} {message}\r\n"); // 滾動(dòng)到文本框底部 richTextBox_Log.SelectionStart = richTextBox_Log.Text.Length; richTextBox_Log.ScrollToCaret(); } } // 清除日志按鈕點(diǎn)擊事件處理 private void Button_Clear_Click(object sender, EventArgs e) { // 清空日志顯示框 richTextBox_Log.Text = ""; } // 打開(kāi)服務(wù)器窗口按鈕點(diǎn)擊事件處理 private void button_OpenServer_Click(object sender, EventArgs e) { // 如果服務(wù)器窗口不存在或已關(guān)閉,則新建一個(gè)服務(wù)器窗口 if (form_Server == null || form_Server.IsDisposed) { form_Server = new Form_Server(); form_Server.Show(); } else { // 如果服務(wù)器窗口已經(jīng)打開(kāi),確保它位于最前面 form_Server.TopMost = true; } } } }
代碼解析:
- 1.EventShowClientLog 委托 用于線程安全地更新客戶端日志。
- 2.Form1_Load() 加載時(shí)獲取本機(jī)IP地址并填充IP選擇框,設(shè)置默認(rèn)端口。
- 3.GetIP() 獲取并顯示本機(jī)所有IPv4地址。
- 4.Button_Connect_Click()連接服務(wù)器,成功后更新UI顯示連接信息。
- 5.Button_DisConnect_Click()斷開(kāi)與服務(wù)器的連接,并顯示斷開(kāi)信息。
- 6.Button_SendOnce_Click() 發(fā)送文本框內(nèi)容到服務(wù)器,并顯示發(fā)送結(jié)果。
- 7.ShowClientLog() 顯示日志信息,超出限制時(shí)自動(dòng)刪除最舊日志。
- 8.Button_Clear_Click() 清空日志框內(nèi)容。
- 9.button_OpenServer_Click() 打開(kāi)或顯示服務(wù)器窗口。
總結(jié)
本文介紹了如何使用C#實(shí)現(xiàn)TCP客戶端和服務(wù)器,并通過(guò)簡(jiǎn)單的代碼示例展示了如何設(shè)置客戶端與服務(wù)器的通信。通過(guò)設(shè)置監(jiān)聽(tīng)端口和獲取客戶端IP地址,您可以在客戶端和服務(wù)器之間建立連接并互相發(fā)送消息。通過(guò)進(jìn)一步的優(yōu)化和擴(kuò)展,您可以實(shí)現(xiàn)更為復(fù)雜的網(wǎng)絡(luò)通信功能。
以上就是C#實(shí)現(xiàn)TCP客戶端和服務(wù)器的基本功能的詳細(xì)內(nèi)容,更多關(guān)于C# TCP客戶端和服務(wù)器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#中LinkedList<T>的存儲(chǔ)結(jié)構(gòu)詳解
這篇文章主要介紹了深度解析C#中LinkedList<T>的存儲(chǔ)結(jié)構(gòu),本文將從鏈表的基礎(chǔ)特性、C#中LinkedList的底層實(shí)現(xiàn)邏輯,.NET的不同版本對(duì)于Queue的不同實(shí)現(xiàn)方式的原因分析等幾個(gè)視角進(jìn)行簡(jiǎn)單的解讀,需要的朋友可以參考下2023-12-12使用C#實(shí)現(xiàn)讀取PDF中所有文本內(nèi)容
這篇文章主要為大家詳細(xì)介紹了如何使用C#實(shí)現(xiàn)讀取PDF中所有文本內(nèi)容,文中的示例代碼簡(jiǎn)潔易懂,具有一定的學(xué)習(xí)價(jià)值,有需要的小伙伴可以了解下2024-02-02