本文将介绍怎样利用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(); // 停止TcpListener
- unlinked = 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;
- }
- else
- return 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); // 启动线程并传入客户端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] 监听失败!信息={ex}"); // 监听失败时显示错误日志
- }
- }
- }
- // 接收客户端发送的数据
- public void ReceiveData(Object obj) {
- Socket clientSocket = (Socket)obj; // 将传入的参数转换为Socket
- string str = clientSocket.RemoteEndPoint.ToString(); // 获取客户端的远程端点(IP+端口)
- while (true) {
- try {
- byte[] buffer = new byte[1024 * 1024]; // 创建缓存区,大小为1MB
- int 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); // 从客户端字典中移除该客户端的Socket
- dicReceiveMsg.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();
-
- // 设置默认端口为5000
- port = 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) {
- // 如果曾经断开连接,重新创建Socket
- clientSocket = 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对消息进行编码息争码。
客户端界面的实今世码如下:
代码剖析:
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
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |