当前位置: 首页 > news >正文

C#实现TCP客户端和服务器

本文将介绍如何使用C#实现TCP客户端和服务器的基本功能,客户端与服务器可以相互发送消息。

效果展示

服务器端实现

首先,我们实现TCP服务器。以下是服务器端所需的类和代码:

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; // 服务器端Socket,用于服务器与客户端的通信Socket clientSocket; // 客户端Socket,用于与客户端建立连接TcpListener tcpListener; // 负责监听客户端的连接请求Thread tcpListenerThread; // 监听连接请求的线程Dictionary<string, Socket> dicClientSockets = new Dictionary<string, Socket>(); // 存储客户端Socket的集合,以客户端的IP/端口作为键Dictionary<string, Thread> dicReceiveMsg = new Dictionary<string, Thread>(); // 存储每个客户端接收消息的线程集合string _IP; // 服务器的IP地址int _Port; // 服务器的端口IPAddress _ipAddress; // 服务器的IPAddress对象EndPoint _endPoint; // 服务器的端点,包括IP地址和端口bool linked = false; // 标识服务器是否已开始监听客户端连接bool unlinked = false; // 标识服务器是否已关闭连接// 构造函数,初始化服务器的IP和端口public TcpIp_Server(string ip, int port) {serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 初始化Socket对象,使用TCP协议_IP = ip;_Port = port;_ipAddress = IPAddress.Parse(ip); // 将IP地址字符串转换为IPAddress对象_endPoint = new IPEndPoint(_ipAddress, port); // 构造服务器端点}// 判断服务器是否已绑定到指定的端口private bool _IsBound;public bool IsBound {get { return _IsBound = serverSocket.IsBound; }}// 判断服务器Socket是否已连接private bool _Connected;public bool Connected {get { return _Connected = serverSocket.Connected; }}// 绑定服务器并开始监听客户端连接public bool Bind(ref string msg) {try {if (!linked) {tcpListener = new TcpListener(_ipAddress, _Port); // 创建TcpListener对象,绑定IP和端口tcpListener.Start(); // 启动监听linked = true; // 标记已开始监听ThreadStart threadStart = new ThreadStart(ListenConnectRequest); // 创建一个委托,执行监听连接请求的方法tcpListenerThread = new Thread(threadStart); // 创建一个新线程来监听客户端请求tcpListenerThread.IsBackground = true; // 设置为后台线程tcpListenerThread.Start(); // 启动监听线程msg = $"[Info] 服务器{tcpListener.LocalEndpoint.ToString()}监听成功!"; // 返回监听成功的消息return true;}else {msg = $"[Info] 已监听"; // 如果已经开始监听,则返回已监听消息return true;}}catch (Exception ex) {msg = $"[Err] 连接失败!信息={ex}"; // 如果发生异常,返回错误信息return false;}}// 断开与指定客户端的连接public bool DisConnectClient(string client, ref string msg) {try {if (dicClientSockets.ContainsKey(client)) {dicClientSockets[client].Close(); // 关闭与客户端的连接msg = $"[Info] 断开客户端{client}连接"; // 返回断开成功的消息return true;}else {msg = $"[Info] 客户端{client}已断开"; // 如果客户端已断开,返回已断开消息return true;}}catch (Exception ex) {msg = $"[Err] 断开失败!信息={ex}"; // 如果发生异常,返回错误信息return false;}}// 关闭服务器监听并断开所有客户端连接public bool ShutDown(string[] clientList, ref string msg) {try {if (linked) {linked = false; // 标记服务器停止监听tcpListener.Stop(); // 停止TcpListener监听for (int i = 0; i < clientList.Length; i++) {dicClientSockets[clientList[i]].Close(); // 关闭每个客户端的连接}msg = $"[Info] 服务器监听断开"; // 返回关闭监听的消息return true;}else {unlinked = true; // 标记已断开msg = $"[Info] 已断开"; // 返回已断开消息return true;}}catch (Exception ex) {tcpListener.Stop(); // 停止TcpListenerunlinked = true; // 标记已断开msg = $"[Info] 已断开!!!"; // 返回关闭连接的消息return false;}}// 向指定客户端发送数据public bool SendToClient(string client, string cmd, ref string msg) {try {if (!string.IsNullOrEmpty(cmd)) {dicClientSockets[client].Send(Encoding.UTF8.GetBytes(cmd)); // 将命令数据发送给客户端msg = $"[Info] 发送{client}信息={cmd}"; // 返回发送成功的消息return true;}elsereturn false; // 如果命令为空,返回发送失败}catch (Exception ex) {msg = $"[Err] 发送信息失败!信息={ex}"; // 如果发送失败,返回错误信息return false;}}// 监听客户端的连接请求private void ListenConnectRequest() {while (linked) {try {Socket clientSocket = tcpListener.AcceptSocket(); // 等待并接受客户端连接ParameterizedThreadStart parameterizedThreadStart = new ParameterizedThreadStart(ReceiveData); // 创建接收数据的线程委托Thread receiveMsgThread = new Thread(parameterizedThreadStart); // 创建一个线程来接收客户端数据receiveMsgThread.IsBackground = true; // 设置为后台线程receiveMsgThread.Start(clientSocket); // 启动线程并传入客户端SocketdicClientSockets.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] 监听失败!信息={ex}"); // 监听失败时显示错误日志}}}// 接收客户端发送的数据public void ReceiveData(Object obj) {Socket clientSocket = (Socket)obj; // 将传入的参数转换为Socketstring str = clientSocket.RemoteEndPoint.ToString(); // 获取客户端的远程端点(IP+端口)while (true) {try {byte[] buffer = new byte[1024 * 1024]; // 创建缓存区,大小为1MBint length = clientSocket.Receive(buffer); // 接收客户端数据string message = Encoding.UTF8.GetString(buffer, 0, length); // 将字节数组转换为字符串if (length != 0) {Form_Server._ShowServerLogEvent($"[Info] 接收客户端{clientSocket.RemoteEndPoint}信息={message}"); // 显示接收到的消息}}catch (Exception) {Form_Server._RemoveListEvent(str); // 从UI列表中移除已断开的客户端dicClientSockets.Remove(str); // 从客户端字典中移除该客户端的SocketdicReceiveMsg.Remove(str); // 从接收消息线程字典中移除该客户端的线程break; // 退出接收数据的循环}}}}
}

代码解析:
服务器初始化: 在构造函数中,初始化了服务器的Socket、监听端口和IP地址。
绑定与监听: Bind() 方法会启动一个监听线程,等待客户端连接请求。
客户端连接管理: 客户端连接通过 ListenConnectRequest() 方法处理。每当有客户端连接时,创建一个新的线程来接收客户端数据。
发送与接收数据: SendToClient() 方法用于发送数据到指定客户端,而 ReceiveData() 方法则接收客户端发送的数据。
断开与关闭: DisConnectClient() 和 ShutDown() 方法分别用于断开客户端连接和关闭服务器监听。

接下来是服务器界面的代码:

using System;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;namespace TCPIP_Test {// 委托用于更新服务器日志public delegate void ShowServerLogDelegate(string message);// 委托用于向客户端列表中添加客户端public delegate void AddListDelegate(string value);// 委托用于从客户端列表中移除客户端public delegate void RemoveListDelegate(string value);public partial class Form_Server : Form {// 静态委托实例,用于更新日志、添加或移除客户端public static ShowServerLogDelegate _ShowServerLogEvent = null;public static AddListDelegate _AddListEvent = null;public static RemoveListDelegate _RemoveListEvent = null;// TCP服务器实例TcpIp_Server tcpIp_Server;string ip;  // 服务器IP地址int port;  // 服务器端口string msg;  // 用于存储消息Form_Client form_Client;  // 客户端窗体实例String[] clientList;  // 当前连接的客户端列表// 构造函数,初始化窗体public Form_Server() {InitializeComponent();}// 窗体加载时执行的操作private void Form_Server_Load(object sender, EventArgs e) {// 订阅日志、添加客户端和移除客户端事件_ShowServerLogEvent += ShowServerLog;_AddListEvent += AddList;_RemoveListEvent += RemoveList;// 获取本机的IP地址并添加到ComboBox控件GetIP();// 设置默认端口为5000port = int.Parse(textBox_Port.Text = "5000");}// 获取本机所有的IP地址,并将IPv4地址添加到IP地址选择框private void GetIP() {string name = Dns.GetHostName();  // 获取主机名IPAddress[] iPAddress = Dns.GetHostAddresses(name);  // 获取所有IP地址foreach (IPAddress item in iPAddress) {// 只添加IPv4地址到ComboBox中if (item.AddressFamily == AddressFamily.InterNetwork)comboBox_IP.Items.Add(item.ToString());}// 设置默认选择为第一个IP地址comboBox_IP.SelectedIndex = 0;}// 开始监听按钮点击事件private void button_Listen_Click(object sender, EventArgs e) {// 检查是否已输入IP和端口if (comboBox_IP.SelectedItem == null || textBox_Port.Text == ""){MessageBox.Show("请输入IP和Port!");return;}ip = comboBox_IP.SelectedItem.ToString();  // 获取选择的IP地址port = int.Parse(textBox_Port.Text);  // 获取端口号// 创建TCP服务器实例并初始化tcpIp_Server = new TcpIp_Server(ip, port);_ShowServerLogEvent($"[Info] 服务器初始化完成");// 启动服务器监听_ShowServerLogEvent($"[Info] 服务器开始监听...");if(tcpIp_Server.Bind(ref msg))button_Listen.BackColor = Color.YellowGreen;  // 设置监听按钮颜色为绿色_ShowServerLogEvent($"{msg}");  // 显示监听状态消息}// 停止监听按钮点击事件private void Button_ShutDown_Click(object sender, EventArgs e) {// 停止服务器监听_ShowServerLogEvent($"[Info] 服务器停止监听...");tcpIp_Server.ShutDown(clientList, ref msg);listBox_Client.Items.Clear();  // 清空客户端列表label_ClientCount.Text = "0";  // 更新客户端数量显示button_Listen.BackColor = Color.LightGray;  // 恢复监听按钮颜色_ShowServerLogEvent($"{msg}");  // 显示停止监听的消息}// 单个客户端发送消息按钮点击事件private void Button_SendOnce_Click(object sender, EventArgs e) {// 检查是否选择了客户端if (listBox_Client.SelectedIndex == -1) {MessageBox.Show(new Form { TopMost = true }, $"请选择客户端!", "Error");return;}// 获取选择的客户端和消息内容_ShowServerLogEvent($"[Info] 服务器单发送信息...");string client = listBox_Client.SelectedItem.ToString();string cmd = textBox_Message.Text;// 向客户端发送消息tcpIp_Server.SendToClient(client, cmd, ref msg);_ShowServerLogEvent($"{msg}");  // 显示发送状态消息}// 向所有客户端发送消息按钮点击事件private void button_SendAll_Click(object sender, EventArgs e) {_ShowServerLogEvent($"[Info] 服务器多发送信息...");string cmd = textBox_Message.Text;  // 获取消息内容for (int i = 0; i < listBox_Client.Items.Count; i++) {// 向所有客户端发送消息tcpIp_Server.SendToClient(listBox_Client.Items[i].ToString(), cmd, ref msg);_ShowServerLogEvent($"{msg}");  // 显示每次发送的消息}}// 清空日志按钮点击事件private void Button_Clear_Click(object sender, EventArgs e) {richTextBox_Log.Text = "";  // 清空日志框}// 打开客户端按钮点击事件(目前被注释掉)private void button_OpenClient_Click(object sender, EventArgs e) {// 这里的代码被注释掉,表示当前功能未启用//if (form_Client == null || form_Client.IsDisposed) {//    form_Client = new Form_Client();//    form_Client.Show();//}//else {//    form_Client.TopMost = true;//}}// 服务器窗体关闭时的处理private void Form_Server_FormClosing(object sender, FormClosingEventArgs e) {// 确保在窗体关闭时关闭服务器if (tcpIp_Server != null)tcpIp_Server.ShutDown(clientList, ref msg);_ShowServerLogEvent($"{msg}");  // 显示服务器关闭消息_ShowServerLogEvent -= ShowServerLog;  // 取消订阅日志事件}// 显示服务器日志的方法(线程安全更新UI)private void ShowServerLog(string message) {if (InvokeRequired) {this.BeginInvoke(new Action(() => ShowServerLog(message)));  // 在UI线程更新日志}else {int maxLine = 100;  // 设置日志最大行数if (this.richTextBox_Log.Lines.Length > maxLine) {// 如果日志行数超过最大行数,删除最旧的日志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();  // 滚动到日志的最新位置}}// 向客户端列表中添加客户端(线程安全操作)private void AddList(string value) {if (InvokeRequired) {this.BeginInvoke(new Action(() => AddList(value)));  // 在UI线程更新客户端列表}else {listBox_Client.Items.Add(value);  // 添加客户端到列表// 更新客户端列表数组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();  // 更新客户端数量显示}}// 从客户端列表中移除客户端(线程安全操作)private void RemoveList(string value) {if (InvokeRequired) {this.BeginInvoke(new Action(() => RemoveList(value)));  // 在UI线程更新客户端列表}else {listBox_Client.Items.Remove(value);  // 从列表中移除客户端// 更新客户端列表数组int i = 0;foreach (var item in listBox_Client.Items) {clientList[i] = item.ToString();}label_ClientCount.Text = clientList.Length.ToString();  // 更新客户端数量显示}}}
}

功能解析:
服务器配置与启动:选择IP地址和端口,启动服务器监听客户端连接。
客户端管理:显示连接的客户端,支持单个或所有客户端发送消息。
日志记录:实时显示服务器日志,记录连接、消息发送等操作。
停止服务器:停止监听,清空客户端列表,断开连接。

客户端实现

服务器端实现完成后,接下来我们来实现TCP客户端。客户端界面代码与服务器端类似,主要使用以下类:

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对象,用于与服务器建立TCP连接Socket clientSocket;// 服务器的IP地址string _IP;// 服务器的端口int _Port;// 服务器的IPAddress对象IPAddress _ipAddress;// 服务器的IPEndPoint对象,包含IP地址和端口IPEndPoint _iPEndPoint;// 标识连接状态bool linked = false;// 标识是否曾经断开连接bool unlinked = false;// 构造函数,初始化IP地址和端口public TcpIp_Client(string ip, int port) {clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 创建TCP连接的Socket对象_IP = ip; // 设置IP地址_Port = port; // 设置端口号_ipAddress = IPAddress.Parse(ip); // 将字符串IP地址解析为IPAddress对象_iPEndPoint = new IPEndPoint(_ipAddress, port); // 创建端点对象,包含IP地址和端口}// 当前连接状态private bool _Connected;public bool Connected {get { return _Connected = clientSocket.Connected; } // 返回Socket的连接状态}// 连接到服务器public bool ConnectServer(ref string msg) {try {if (!linked) { // 如果没有连接if (unlinked) {// 如果曾经断开连接,重新创建SocketclientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);}// 连接到服务器clientSocket.Connect(_iPEndPoint);if (clientSocket.Connected) { // 如果连接成功linked = true; // 设置连接状态为已连接msg = $"[Info] 服务器{clientSocket.LocalEndPoint.ToString()}连接成功!"; // 设置成功消息ReceiveData(); // 调用方法开始接收数据return true;} else { // 如果连接失败msg = $"[Info] 客户端连接失败!";return false;}} else { // 如果已经连接msg = $"[Info] 已连接";return true;}}catch (Exception ex) {msg = $"[Err] 连接失败!信息={ex}"; // 连接失败时设置错误消息return false;}}// 断开与服务器的连接public bool DisConnectServer(ref string msg) {try {if (linked) { // 如果已连接linked = false; // 设置连接状态为未连接clientSocket.Disconnect(false); // 断开连接并释放资源unlinked = true; // 标记为已断开msg = $"[Info] 客户端连接断开";return true;} else { // 如果已经是断开状态msg = $"[Info] 已断开";return true;}}catch (Exception ex) {msg = $"[Err] 断开失败!信息={ex}"; // 断开连接失败时设置错误消息return false;}}// 向服务器发送消息public bool SendToServer(string cmd, ref string msg) {try {if (clientSocket.Connected) { // 如果已连接clientSocket.Send(Encoding.UTF8.GetBytes(cmd)); // 将命令转换为字节并发送msg = $"[Info] 发送{clientSocket.LocalEndPoint.ToString()}信息={cmd}"; // 设置发送成功的消息return true;} else { // 如果未连接MessageBox.Show(new Form { TopMost = true }, "未连接!", "Info", MessageBoxButtons.OK); // 弹出提示框msg = $"未连接!";return false;}}catch (Exception ex) {msg = $"[Err] 发送信息失败!信息={ex}"; // 发送失败时设置错误消息return false;}}// 接收来自服务器的数据private void ReceiveData() {string str = ""; // 存储接收到的消息Task.Run(() => {while (linked) { // 只要连接保持有效,就持续接收数据try {if (clientSocket.Connected) {Thread.Sleep(10); // 为了避免占用过多的CPU时间,稍微暂停一下} else {continue; // 如果连接断开,继续检查连接状态}byte[] data = new byte[1024]; // 缓冲区用于接收数据int length = clientSocket.Receive(data); // 接收服务器发送的数据string message = Encoding.UTF8.GetString(data, 0, length); // 将接收到的字节数据转换为字符串if (message != "") {str = message; // 存储接收到的消息Form_Client._ShowClientLog($"[Info] 接收服务器{clientSocket.LocalEndPoint.ToString()}信息={message}"); // 显示接收到的消息}}catch (Exception ex) {Form_Client._ShowClientLog($"[Err] 接收服务器信息失败!信息={ex}"); // 如果接收失败,显示错误信息}}});}}
}

代码解析:
TcpIp_Client类:该类用于实现一个TCP客户端,提供连接服务器、断开连接、发送消息、接收消息等功能。
构造函数:接受服务器的IP地址和端口号,初始化Socket并创建连接端点(IPEndPoint)。
ConnectServer方法:建立与服务器的TCP连接。连接成功后,启动接收数据的线程。
DisConnectServer方法:断开与服务器的连接并释放相关资源。
SendToServer方法:将给定的命令发送到服务器。
ReceiveData方法:接收来自服务器的消息,并在接收到数据时将其打印到客户端界面或日志中。
关键技术:
Socket编程:使用Socket类进行TCP连接的建立、数据的发送和接收。
多线程:ReceiveData方法通过Task.Run()启动一个新的线程来异步接收数据。
消息编码:使用UTF8对消息进行编码和解码。

客户端界面的实现代码如下:

using System;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;namespace TCPIP_Test
{// 定义一个委托,用于显示客户端日志信息public delegate void EventShowClientLog(string message);// 客户端窗口类public partial class Form_Client : Form{// 声明委托实例,用于日志显示public static EventShowClientLog _ShowClientLog = null;// TCP客户端实例TcpIp_Client tcpIp_Client;// 存储IP地址和端口string ip;int port;string msg;// 服务器窗口实例Form_Server form_Server;// 构造函数,初始化组件public Form_Client(){InitializeComponent();}// 窗体加载时的事件处理private void Form1_Load(object sender, EventArgs e){// 绑定委托到日志显示方法_ShowClientLog += ShowClientLog;// 获取本机IP地址并填充到下拉框中GetIP();// 默认端口5000port = int.Parse(textBox_Port.Text = "5000");}// 获取本机IP地址的方法private void GetIP(){// 获取本机的计算机名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());}// 默认选择第一个IP地址comboBox_IP.SelectedIndex = 0;}// 连接按钮点击事件处理private void Button_Connect_Click(object sender, EventArgs e){// 判断是否选择了IP地址和端口if (comboBox_IP.SelectedItem == null || textBox_Port.Text == ""){MessageBox.Show("请输入IP和Port!");return;}// 获取选择的IP和端口ip = comboBox_IP.SelectedItem.ToString();port = int.Parse(textBox_Port.Text);// 创建TCP客户端实例并尝试连接到服务器tcpIp_Client = new TcpIp_Client(ip, port);_ShowClientLog($"[Info] 客户端初始化完成");_ShowClientLog($"[Info] 客户端开始连接...");// 尝试连接服务器if (tcpIp_Client.ConnectServer(ref msg))button_Connect.BackColor = Color.YellowGreen; // 连接成功后,改变按钮颜色_ShowClientLog($"{msg}");}// 断开连接按钮点击事件处理private void Button_DisConnect_Click(object sender, EventArgs e){_ShowClientLog($"[Info] 客户端断开连接...");// 调用断开连接方法tcpIp_Client.DisConnectServer(ref msg);button_Connect.BackColor = Color.LightGray; // 断开连接后,恢复按钮颜色_ShowClientLog($"{msg}");}// 发送一次数据按钮点击事件处理private void Button_SendOnce_Click(object sender, EventArgs e){_ShowClientLog($"[Info] 客户端发送信息...");// 获取输入框中的信息string cmd = textBox_Message.Text;// 发送数据到服务器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{// 最大显示行数限制int maxLine = 100;// 如果超过最大行数,删除最旧的日志行if (this.richTextBox_Log.Lines.Length > maxLine){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();}}// 清除日志按钮点击事件处理private void Button_Clear_Click(object sender, EventArgs e){// 清空日志显示框richTextBox_Log.Text = "";}// 打开服务器窗口按钮点击事件处理private void button_OpenServer_Click(object sender, EventArgs e){// 如果服务器窗口不存在或已关闭,则新建一个服务器窗口if (form_Server == null || form_Server.IsDisposed){form_Server = new Form_Server();form_Server.Show();}else{// 如果服务器窗口已经打开,确保它位于最前面form_Server.TopMost = true;}}}
}

代码解析:
1.EventShowClientLog 委托   用于线程安全地更新客户端日志。
2.Form1_Load()   加载时获取本机IP地址并填充IP选择框,设置默认端口。
3.GetIP()   获取并显示本机所有IPv4地址。
4.Button_Connect_Click()连接服务器,成功后更新UI显示连接信息。
5.Button_DisConnect_Click()断开与服务器的连接,并显示断开信息。
6.Button_SendOnce_Click()   发送文本框内容到服务器,并显示发送结果。
7.ShowClientLog()   显示日志信息,超出限制时自动删除最旧日志。
8.Button_Clear_Click()  清空日志框内容。
9.button_OpenServer_Click()   打开或显示服务器窗口。

总结

本文介绍了如何使用C#实现TCP客户端和服务器,并通过简单的代码示例展示了如何设置客户端与服务器的通信。通过设置监听端口和获取客户端IP地址,您可以在客户端和服务器之间建立连接并互相发送消息。通过进一步的优化和扩展,您可以实现更为复杂的网络通信功能。

源码地址:https://download.csdn.net/download/weixin_44643352/90058182

相关文章:

C#实现TCP客户端和服务器

本文将介绍如何使用C#实现TCP客户端和服务器的基本功能&#xff0c;客户端与服务器可以相互发送消息。 效果展示 服务器端实现 首先&#xff0c;我们实现TCP服务器。以下是服务器端所需的类和代码&#xff1a; using System; using System.Collections.Generic; using Syste…...

数据库编程: JDBC 中数据库驱动包的安装,配置及引用

目录 驱动包的下载安装 1. 去oracle 官方网站下载 2. github 的开源软件 3. 中央仓库(推荐使用这个) 驱动包的配置及引用 1. 第一步: 打开idea, 在 idea 中创建新的项目 2. 然后我们要在 jdbc_1 目录下创建一个新的目录包 3. 最后引入MySQL 的驱动包, 作为项目的依赖 尾…...

mx linux 在konsole终端中无法输入中文的解决方法

在mx linux系统中&#xff0c;浏览器可以正常输入中文&#xff0c;但是终端窗口中无法输入中文的解决方法&#xff0c;可以通过以下步骤安装 fcitx - frontend - qt5 组件&#xff1a; 1. 打开终端。你可以通过系统菜单或者快捷键&#xff08;如 Ctrl Alt T &#xff09;来…...

./configure 安装ngnix的命令

./configure 是一个在 Unix 和类 Unix 系统中常用的 shell 脚本命令&#xff0c;主要用于配置软件源代码包&#xff0c;以便进行编译和安装。这个命令通常在从源代码编译软件之前执行&#xff0c;它会自动检测系统的配置并生成适合该系统的 Makefile。 以下是 ./configure 脚本…...

网络安全之接入控制

网络安全之接入控制 身份鉴别 ​ 定义:验证主题真实身份与其所声称的身份是否符合的过程&#xff0c;主体可以是用户、进程、主机。同时也可实现防重放&#xff0c;防假冒。 ​ 分类:单向鉴别、双向鉴别、三向鉴别。 ​ 主题身份标识信息:密钥、用户名和口令、证书和私钥 I…...

2.1 关系模型

关系模型的基本概念 关系&#xff1a;二维表&#xff0c;通常在数据库中表现为一个表&#xff1b; 属性&#xff1a;关系中的一个列即为关系的一个属性&#xff1b; 域&#xff1a; 每个属性的取值范围即为该属性的域&#xff1b; 元组&#xff1a;关系中的一个行是一个元组…...

深入浅出 Go 语言 sync包中的互斥锁、条件变量

深入浅出 Go 语言 sync包中的互斥锁、条件变量 引言 在并发编程中&#xff0c;多个 Goroutine 同时访问共享资源可能会导致数据竞争&#xff08;Race Condition&#xff09;&#xff0c;进而引发程序的不一致性或崩溃。为了确保并发程序的正确性和稳定性&#xff0c;Go 语言提…...

利用Python实现子域名简单收集

免责申明 本文仅是用于学习研究子域名信息收集&#xff0c;请勿用在非法途径上&#xff0c;若将其用于非法目的&#xff0c;所造成的一切后果由您自行承担&#xff0c;产生的一切风险和后果与笔者无关&#xff1b;本文开始前请认真详细学习《‌中华人民共和国网络安全法》【学法…...

npm发布插件到私有仓库保姆级教程

在开发项目的过程中&#xff0c;我们经常需要安装插件依赖&#xff0c;那么怎么把自己开发的组件封装成一个插件&#xff0c;并发布到npm 插件市场或者上传到私有仓库里面呢&#xff1f;今天总结下自己发布插件到私有仓库的记录&#xff1a; 一、创建组件 执行命令创建一个空…...

利用Java easyExcel库实现高效Excel数据处理

在Java应用程序中&#xff0c;处理Excel文件是一项常见任务&#xff0c;尤其是在需要读取、写入或分析大量数据时。easyExcel是一个基于Java的高性能Excel处理库&#xff0c;它提供了简洁的API和优化的性能&#xff0c;以简化Excel文件的处理。本文将指导您如何使用easyExcel库…...

基于Springboot的校园交友网站设计与实现

1.1 管理信息系统概述 管理信息系统是计算机在信息管理领域的一种实用技术。通过运用管理科学、数学和计算机应用的原理及方法&#xff0c;在符合软件工程规范的原则下&#xff0c;形成一套完整的理论和方法体系。是一个以人、计算机和其他外部设备组成的可以进行信息的收集、…...

android studio 读写文件操作(应用场景三)

android studio版本&#xff1a;2023.3.1 patch2 例程&#xff1a;filesaveandread 其实我写这个都是我记录我要做后个数独小游戏&#xff0c;每一个都是为了解决一个问题。即是分享也是备忘&#xff0c;反正我什么都不会&#xff0c;就是一顿瞎改&#xff0c;不行就研究。这…...

小程序 —— Day1

组件 — view和scroll-view view 类似于HTML中的div&#xff0c;是一个块级元素 案例&#xff1a;通过view组件实现页面的基础布局 scroll-view 可滚动的视图区域&#xff0c;用来实现滚动列表效果 案例&#xff1a;实现纵向滚动效果 scroll-x属性&#xff1a;允许横向滚动…...

使用 PyTorch 和 Horovod 来编写一个简单的分布式训练 demo

使用 PyTorch 和 Horovod 来编写一个简单的分布式训练 demo&#xff0c;可以帮助你理解如何在多GPU或多节点环境中高效地训练深度学习模型。Horovod 是 Uber 开发的一个用于分布式训练的框架&#xff0c;它支持 TensorFlow、Keras、PyTorch 等多个机器学习库。下面是一个基于 P…...

【Linux】以 CentOS 为例备份与恢复/home分区,并调整分区容量

在 Linux 系统中&#xff0c;这里举例对 /home 目录进行备份、重建和恢复操作&#xff0c;并调整分区大小、更换文件系统或修复损坏的分区等。 〇、前提条件 确认文件系统类型为 xfs。 确认 /home 目录确实没有重要数据&#xff0c;或者已经做好了数据备份。 确保在执行这些…...

OpenAI 12Days 第二天 强化微调(RFT):推动语言模型在科学研究中的应用

OpenAI 12Days 第二天 强化微调&#xff08;RFT&#xff09;&#xff1a;推动语言模型在科学研究中的应用 文章目录 OpenAI 12Days 第二天 强化微调&#xff08;RFT&#xff09;&#xff1a;推动语言模型在科学研究中的应用RFT的工作原理与应用领域案例研究&#xff1a;基因突变…...

神经网络的梯度反向传播计算过程,举例说明

目录 神经网络的梯度反向传播计算过程 网络结构 权重和偏置 激活函数 前向传播 损失函数 反向传播 参数更新 举例 神经网络的梯度反向传播计算过程 为了说明神经网络的梯度反向传播计算过程,我们考虑一个简单的全连接网络,该网络有一个输入层、一个隐藏层和一个输出…...

定点数乘法:补码一位算法(booth算法)

方法 初始化 将被乘数A放在寄存器A中。 将乘数B放在寄存器B中&#xff0c;并在最低位添加一个额外的位Q(-1) 0。 结果寄存器P初始化为0&#xff0c;长度为2n位。 迭代过程&#xff08;重复n次&#xff09; 对于i从0到n-1&#xff1a; 检查乘数B的最后两位&#xff08;B 和…...

robots.txt

robots.txt 文件是网站管理者用来告知搜索引擎爬虫&#xff08;也称为机器人或蜘蛛&#xff09;哪些页面可以抓取&#xff0c;哪些页面不应该被抓取的一种文本文件。它位于网站的根目录下&#xff0c;并且文件名必须全部小写。这个文件对于SEO&#xff08;搜索引擎优化&#xf…...

如何用 JavaScript 操作 DOM 元素?

如何用 JavaScript 操作 DOM 元素&#xff1f;——结合实际项目代码示例讲解 在前端开发中&#xff0c;DOM&#xff08;文档对象模型&#xff09;操作是与页面交互的核心。通过 DOM 操作&#xff0c;开发者可以动态地修改页面内容、响应用户交互、控制样式等。JavaScript 提供…...

vue3使用后端传递的文件流进行文件预览

文章目录 一、 注意事项1、responseType设置为&#xff1a;arraybuffer2、Blob设置type&#xff0c;来源于后台封装的response.headers[content-type]3、使用encodeURIComponent()&#xff0c;避免符号影响文件名 二、java接口 一、 注意事项 1、responseType设置为&#xff1…...

ubuntu20.04设置远程桌面

安装xrdp sudo apt install xrdp 2、 检查xrdp状态 sudo systemctl status xrdp3、&#xff08;若为Ubuntu 20&#xff09;添加xrdp至ssl-cert sudo adduser xrdp ssl-cert 4、重启服务 sudo systemctl restart xrdp最后可以远程了&#xff0c;注意一个账号只能一个登录...

在vue3里使用scss实现简单的换肤功能

实现的换肤功能&#xff1a;主题色切换、亮色模式和暗黑模式切换、背景图切换 主题色就是网站主色&#xff0c;可以配置到组件库上面&#xff1b;亮色模式又分为两种风格&#xff1a;纯白风格和背景图风格&#xff0c;不需要背景图的话可以删掉这部分逻辑和相关定义&#xff1b…...

flyway执行sql遇到变量执行报错解决

前两天在公司使用flyway工具执行sql时&#xff0c;开发写的sql里面有变量&#xff0c;于是这个flyway工具不识别这个变量直接报错&#xff0c;不接着往下执行了。报错信息如下&#xff1a; flyway工具执行sql报错 information: No value provided for placeholder: ${ep1} 于是…...

解谜类游戏《迷失岛2》等如何抽象出一套通用高效开发框架?

解谜类游戏以精妙的谜题设计和引人入胜的故事叙述为特点&#xff0c;考验着玩家的智慧与观察力。《迷失岛2》与《南瓜先生2九龙城寨》正是这一领域的佳作。游戏以独特的艺术风格和玩法设计吸引了大量玩家&#xff0c;而它们背后隐藏着一套强大的框架。 上海胖布丁游戏的技术总…...

Ant Design Vue v4版本如何解决1px没有被postcss-px2rem转成rem的问题

背景说明 如果你的 Ant Design Vue 项目有要做适配的需求&#xff0c;那首先要选择一种适配方案。笔者选择的是用 postcss-px2rem 进行适配。笔者在配置了 postcss-px2rem的相关配置后&#xff0c;发现 postcss-px2rem 没有对 Ant Design Vue 进行适配。在网上看了一些文章之后…...

【系统架构设计师论文】云上自动化运维及其应用

随着云计算技术的迅猛发展,企业对云资源的需求日益增长。为了应对这一挑战,云上自动化运维(CloudOps)应运而生,它结合了DevOps理念和技术,通过自动化工具和流程来提高云环境的管理效率和服务质量。本文将探讨云上自动化运维的主要衡量指标,并详细介绍一个实际项目中如何…...

河南地质灾害资质办理的政策

一、资质分类 资质等级&#xff1a; 甲级资质&#xff1a;由自然资源部审批管理&#xff0c;适用于承担大型地质灾害防治项目。 乙级资质&#xff1a;由省、自治区、直辖市自然资源主管部门审批管理&#xff0c;适用于承担中型地质灾害防治项目。 丙级资质&#xff1a;由省…...

单例模式--懒汉 饿汉模式

一.啥是单例模式? 先介绍一下设计模式&#xff1a; 设计模式好⽐象棋中的 "棋谱". 红⽅当头炮, ⿊⽅⻢来跳. 针对红⽅的⼀些⾛法, ⿊⽅应招的时候有⼀些固定的套路. 按照套路来⾛局势就不会吃亏. 软件开发中也有很多常⻅的 "问题场景". 针对这些问题…...

HDD 2025年技术趋势深度分析报告

随着数据量的指数级增长以及人工智能&#xff08;AI&#xff09;、物联网&#xff08;IoT&#xff09;、云计算和视频监控等领域的需求激增&#xff0c;硬盘驱动器&#xff08;HDD&#xff09;行业正面临着前所未有的挑战与机遇。本报告旨在深入剖析2025年HDD技术的发展方向&am…...

关于uni-app的uni.showModal和indexOf的使用

这里使用showModal时&#xff0c;这个里面的content需要使用到字符串的形式&#xff0c;不能用到number类型 uni.showModal({title: 提示,content: "hello",success: function (res) {if (res.confirm) {console.log(用户点击确定);} else if (res.cancel) {console…...

Spring Data Elasticsearch

简介说明 spring-data-elasticsearch是比较好用的一个elasticsearch客户端&#xff0c;本文介绍如何使用它来操作ES。本文使用spring-boot-starter-data-elasticsearch&#xff0c;它内部会引入spring-data-elasticsearch。 Spring Data ElasticSearch有下边这几种方法操作El…...

汇编语言简要记录-1

汇编语言与汇编指令 汇编语言的主题是汇编指令 汇编指令与机器指令的差别在于指令的表示方法上 1、汇编指令是机器机器指令便于记忆的书写格式 2、汇编指令是机器指令的助记符 ag&#xff1a;机器指令 1000100111011000操作&#xff1a;将寄存器BX的值送到AX中汇编指令 MOV …...

Java程序猿搬砖笔记(十七)

文章目录 MySQL触发器ElasticSearch按日期分组查询每天的文档数量MySQL中order by排序将null排在最前或者最后面swagger3.0默认访问路径swagger3.0模块化配置MySQL中要少用UNION&#xff0c;多用UNION ALLElasticSearch Bucket & Metric聚合分析及嵌套聚合Mysql case when做…...

代码设计:设计模式:观察者模式

文章目录 定义类结构应用总结 定义 实现响应式编程的代码设计&#xff0c;即触发事件或数据变化时&#xff0c;将数据从被观察者类通过观察器传递给观察者处理&#xff0c;即被观察者类间接调用观察者类的方法处理事件或数据 类结构 被观察者类、观察器类、观察者类 被观察…...

第32天:安全开发-JavaEE应用Servlet路由技术JDBCMybatis数据库生命周期

时间轴&#xff1a; 32天主要学习内容&#xff1a; 1、JavaEE-HTTP-Servlet技术 2、JavaEE-数据库-JDBC&Mybatis java技术使用历史&#xff08;2023 &#xff09;&#xff1a; JavaEE-HTTP-Servlet&路由&周期: java学习范围&#xff1a; 3、Java: 功能:数据…...

如何使用Apache HttpClient来执行GET、POST、PUT和DELETE请求

Apache HttpClient 是一个功能强大且灵活的库&#xff0c;用于在Java中处理HTTP请求。 它支持多种HTTP方法&#xff0c;包括GET、POST、PUT和DELETE等。 本教程将演示如何使用Apache HttpClient来执行GET、POST、PUT和DELETE请求。 Maven依赖 要使用Apache HttpClient&…...

Next.js 系统性教学:加载界面、重定向与路由分组

更多有关Next.js教程&#xff0c;请查阅&#xff1a; 【目录】Next.js 独立开发系列教程-CSDN博客 目录 1. 加载界面与流式渲染 1.1 加载界面 (loading.js) 1.2 流式渲染 2. 路由重定向 2.1 基于服务器的重定向 2.2 动态重定向 2.3 中间件中的重定向 3. 路由分组 3.1…...

哪款云手机适合多开?常用云手机功能对比

在全球化和数字化时代&#xff0c;云手机以其独特的灵活性和高效性&#xff0c;成为多账号运营和数字营销的热门工具。云手机能够解决传统设备管理的诸多痛点&#xff0c;例如账号关联、硬件成本高等问题。本文将为您推荐多款优质云手机品牌&#xff0c;帮助您选择最适合的工具…...

基于openzeppelin插件的智能合约升级

一、作用以及优点 部署可升级合约&#xff0c;插件自动部署proxy和proxyAdmin合约&#xff0c;帮助管理合约升级和交互&#xff1b;升级已部署合约&#xff0c;通过插件快速升级合约&#xff0c;脚本开发方便快捷&#xff1b;管理代理管理员的权限&#xff0c;只有proxyAdmin的…...

WGAN生成对抗网络数据生成

数据生成 | WGAN生成对抗网络数据生成 目录 数据生成 | WGAN生成对抗网络数据生成生成效果基本描述程序设计参考资料 生成效果 基本描述 1.WGAN生成对抗网络&#xff0c;数据生成&#xff0c;样本生成程序&#xff0c;MATLAB程序&#xff1b; 2.适用于MATLAB 2020版及以上版本&…...

SQL面试题——拼多多SQL面试题 求连续段的起始位置和结束位置

拼多多SQL面试题 求连续段的起始位置和结束位置 今天的题目来自拼多多,我们先看一下题目描述 有一张表ids记录了id,id不重复,但是会存在间断,求出连续段的开始位置和结束位置 +---+ | id| +---+ | 1| | 2| | 3| | 5| | 6| | 8| | 10| | 12| | 13| | 14| | 15| +--…...

Contextual Affinity Distillation for Image Anomaly Detection

Contextual Affinity Distillation for Image Anomaly Detection 日本东北大学 摘要 先前对无监督工业异常检测的研究主要通过匹配或学习局部特征表示来关注“结构”类型的异常&#xff0c;例如裂纹和颜色污染。虽然在这种异常上实现了显着的高检测性能&#xff0c;但他们面…...

如何在HTML中修改光标的位置(全面版)

如何在HTML中修改光标的位置&#xff08;全面版&#xff09; 在Web开发中&#xff0c;控制光标位置是一个重要的技巧&#xff0c;尤其是在表单处理、富文本编辑器开发或格式化输入的场景中。HTML中的光标位置操作不仅适用于表单元素&#xff08;如<input>和<textarea…...

Spring Cloud Alibaba(六)

目录&#xff1a; 分布式链路追踪-SkyWalking为什么需要链路追踪什么是SkyWalkingSkyWalking核心概念什么是探针Java AgentJava探针日志监控实现之环境搭建Java探针日志监控实现之探针实现编写探针类TestAgent搭建 ElasticsearchSkyWalking服务环境搭建搭建微服务微服务接入Sky…...

Http请求系列---【http的几个请求时间分别代表什么?以及如何设置?】

在HTTP客户端编程中&#xff0c;通常涉及以下几种关键的超时设置&#xff1a; 连接超时 (connectTimeout)&#xff1a; 定义&#xff1a;在与服务器建立连接时等待的最大时间。这包括DNS解析时间、连接建立时间等。作用&#xff1a;如果在指定的时间内无法建立连接&#xff0c;…...

如何将CSDN博客下载为PDF文件

1.打开CSDN文章内容 2.按键盘上的f12键&#xff08;或者右键—审查元素&#xff09;进入浏览器调试模式&#xff0c;点击控制台&#xff08;Console&#xff09;进入控制台 3.在控制台输入以下代码&#xff0c;回车 4.在弹出的打印页面中将布局设置成横向&#xff0c;纵向会…...

关于IDEA 2024.2.1 Java EE 无框架配置Tomcat环境以及servlet使用教程

前言 这里的IDEA使用的是专业版&#xff0c;大学生认证后即可使用&#xff0c;社区版没有接触过暂不提&#xff0c;如果你是社区版&#xff0c;那么很可惜&#xff0c;本博客并不适用。本博客适用于java web刚入门的朋友学习使用&#xff0c;并不适用于高级部署。注意&#xf…...

【23种设计模式】七种设计原则:理论与 Java 实践

文章目录 23 种设计模式之七种设计原则&#xff1a;理论与 Java 实践一、单一职责原则&#xff08;SRP - Single Responsibility Principle&#xff09;&#xff08;一&#xff09;理论介绍&#xff08;二&#xff09;Java 实现示例&#xff08;三&#xff09;关键步骤&#xf…...

数据库与数据库管理系统概述

title: 数据库与数据库管理系统概述 date: 2024/12/7 updated: 2024/12/7 author: cmdragon excerpt: 在信息化迅速发展的时代,数据已成为企业和组织的重要资产。数据库与数据库管理系统(DBMS)是高效存储、管理和利用数据的核心工具。本文首先定义了数据库的基本概念和特…...