一、项目整体功能考点
本项目为多客户端TCP聊天室,支持:
-
服务端监听、多客户端同时在线
-
客户端上线自动刷新在线列表(下拉框)
-
私聊:对选中单个客户端发消息
-
广播:对所有在线客户端群发消息
-
自定义断开协议:n1 协议标记
-
客户端下线自动从在线列表移除
-
服务端关闭主动通知所有客户端下线
-
窗口关闭自动释放资源、防止端口占用
二、核心数据结构考点(必考)
2.1 双容器存储客户端(核心设计 + 对应代码)
cs
// 字典:用于快速查找客户端(Key:终端唯一标识)
Dictionary<EndPoint, Socket> clients = new Dictionary<EndPoint, Socket>();
// 泛型集合:绑定下拉框数据源,展示在线客户端
List<Client> clients2 = new List<Client>();
考点解释:
-
Dictionary:保证客户端唯一性,防止重复连接
-
List<Client>:自定义实体类,专门用于UI下拉框绑定
2.2 自定义 Client 实体类(知识点 + 完整代码)
作用:封装客户端终端地址和Socket对象,适配下拉框 DisplayMember / ValueMember 绑定
cs
public class Client
{
public EndPoint EndPoint { get; set; } // 客户端IP+端口(展示)
public Socket Socket { get; set; } // 客户端套接字(取值发送)
}
三、自定义通信协议 n1(超级重点)
3.1 为什么不用系统自带断开?
TCP是流式协议,客户端异常掉线、断网,服务端无法立刻感知,会造成在线列表残留脏数据。
3.2 自定义 n1 协议规则(考试必背 + 对应代码片段)
客户端下线发送n1核心代码
cs
// 客户端主动下线,发送自定义协议告知服务端
socketClient.Send(Encoding.UTF8.GetBytes("n1"));
服务端识别下线n1核心代码
cs
// 服务端接收消息判断下线协议
if (message == "n1")
{
// 移除离线客户端、刷新列表
Client c1 = clients2.Find(x => x.EndPoint == end);
clients2.Remove(c1);
}
服务端关闭全员推送n1代码
cs
// 服务端关闭,通知所有客户端下线
foreach (var item in clients2)
{
item.Socket.Send(Encoding.UTF8.GetBytes("n1"));
item.Socket.Disconnect(false);
}
客户端接收n1自动断连代码
cs
if (message == "n1")
{
DisConnectServer(); // 服务端掉线,客户端主动断开
}
-
客户端主动下线 :先向服务端发送字符串 n1
-
服务端收到 n1:判定该客户端正常下线,从集合、下拉框中移除
-
服务端关闭 :主动向所有客户端发送 n1
-
客户端收到 n1:自动断开连接、重置状态
四、服务端核心流程知识点
4.1 服务端启动流程(StartServer)流程考点 + 完整代码
cs
public void StartServer()
{
// 1.创建TCP服务端套接字
socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 2.组装IP端口终端
IPAddress ip = IPAddress.Parse(textBox1.Text);
int port = int.Parse(textBox2.Text);
IPEndPoint endPoint = new IPEndPoint(ip, port);
// 3.绑定端口 + 开启监听
socketServer.Bind(endPoint);
socketServer.Listen(1000);
}
-
实例化 TCP 流式 Socket
-
读取文本框IP、端口,组装 IPEndPoint
-
Bind 绑定端口
-
Listen 开启监听
4.2 多客户端监听机制(Accept 双层Task 考点 + 核心代码)
cs
// 双层Task异步监听,支持多客户端并发
cts1 = new CancellationTokenSource();
Task.Run(() =>
{
while (!cts1.IsCancellationRequested)
{
// 外层:接收新客户端连接
Socket socketClient = socketServer.Accept();
// 内层:每个客户端独立线程收消息
Task.Run(() =>
{
while (!cts1.IsCancellationRequested)
{
byte[] buffer = new byte[1024 * 10];
int length = socketClient.Receive(buffer);
}
}, cts1.Token);
}
}, cts1.Token);
-
外层Task:循环 Accept,不停接收新客户端连接
-
内层Task:为每个客户端开启独立线程,循环接收消息
-
使用 CancellationTokenSource 安全终止线程,防止内存泄漏
4.3 客户端上线逻辑考点 + 对应代码
cs
// 客户端连接成功后,判重添加集合
if (!clients.ContainsKey(socketClient.RemoteEndPoint))
{
// 加入字典(去重)
clients.Add(socketClient.RemoteEndPoint, socketClient);
// 加入UI绑定集合
clients2.Add(new Client()
{
EndPoint = socketClient.RemoteEndPoint,
Socket = socketClient
});
// Invoke跨线程刷新下拉框UI
Invoke(new Action(() =>
{
comboBox1.DataSource = null;
comboBox1.DataSource = clients2;
comboBox1.DisplayMember = "EndPoint";
comboBox1.ValueMember = "Socket";
}));
}
-
客户端连接成功
-
判断字典是否包含该终端,不包含则加入
-
添加实体到 List 集合
-
Invoke刷新UI:重置下拉框数据源,刷新在线列表
4.4 客户端下线逻辑考点 + 对应代码
cs
// 接收下线协议n1,执行下线逻辑
if (message == "n1")
{
EndPoint end = socketClient.RemoteEndPoint;
Client c1 = clients2.Find(x => x.EndPoint == end);
clients2.Remove(c1);
clients.Remove(end);
// 刷新在线客户端下拉框
Invoke(new Action(() =>
{
comboBox1.DataSource = null;
if (clients2.Count > 0)
{
comboBox1.DataSource = clients2;
comboBox1.DisplayMember = "EndPoint";
comboBox1.ValueMember = "Socket";
}
}));
}
-
服务端接收客户端消息,判断是否为 n1
-
根据终端地址查找对应 Client 对象
-
从 List、字典中移除客户端
-
刷新下拉框在线列表(无客户端则清空数据源)
4.5 服务端关闭流程(CloseServer)考点 + 完整代码
cs
private void CloseServer()
{
if (socketServer != null)
{
// 1.全员推送下线协议n1
foreach (var item in clients2)
{
item.Socket.Send(Encoding.UTF8.GetBytes("n1"));
item.Socket.Disconnect(false);
}
// 2.关闭服务端套接字
socketServer.Close();
socketServer = null;
// 3.终止后台线程
cts1.Cancel();
// 4.清空集合与UI
clients.Clear();
clients2.Clear();
comboBox1.DataSource = null;
button1.Text = "启动服务器";
}
}
完整安全释放流程(考试高频):
-
遍历所有在线客户端,统一发送 n1 下线通知
-
强制断开所有客户端连接
-
关闭服务端Socket、置空对象
-
取消异步线程任务
-
清空客户端集合、清空UI下拉框
五、消息发送两大模式考点
5.1 私聊模式(默认)考点 + 对应代码
cs
// 获取下拉框选中的单个客户端,单独发送
Socket s1 = (Socket)comboBox1.SelectedValue;
if (s1 != null && s1.Connected)
{
s1.Send(Encoding.UTF8.GetBytes("服务器说:" + textBox3.Text));
}
-
获取下拉框选中的客户端 Socket
-
单独向该客户端发送消息
5.2 广播模式(勾选复选框)考点 + 对应代码
cs
// 遍历所有在线客户端,全员群发
if (checkBox1.Checked)
{
foreach (var item in clients2)
{
item.Socket.Send(Encoding.UTF8.GetBytes("服务器说:" + textBox3.Text));
}
}
-
遍历所有在线客户端集合
-
逐个Send发送消息,实现全员广播
六、客户端核心知识点
6.1 客户端连接流程 考点 + 完整代码
cs
public void ConnetSrever()
{
// 1.创建客户端Socket
socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 2.组装服务端终端
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));
// 3.连接服务端
socketClient.Connect(endPoint);
// 4.上线提示 + 修改按钮状态
socketClient.Send(Encoding.UTF8.GetBytes("建立连接成功"));
button1.Text = "断开服务器";
}
-
判断IP、端口非空校验
-
创建客户端Socket
-
指定服务端IP端口终端
-
Connect 连接服务端
-
上线发送提示消息,修改按钮状态
6.2 客户端断开流程 考点 + 完整代码
cs
private void DisConnectServer()
{
if (socketClient != null)
{
// 1.发送下线协议
socketClient.Send(Encoding.UTF8.GetBytes("n1"));
// 2.断开、关闭、释放资源
socketClient.Disconnect(false);
socketClient.Close();
socketClient = null;
// 3.终止后台接收线程
cts.Cancel();
// 4.重置按钮状态
button1.Text = "连接服务器";
}
}
-
主动发送 n1 告知服务端即将下线
-
断开Socket连接、关闭、置空
-
取消后台接收线程
-
重置按钮状态
6.3 客户端被动下线 考点 + 对应代码
cs
// 客户端接收服务端消息,识别服务端下线指令
string message = Encoding.UTF8.GetString(buffer);
if (message == "n1")
{
DisConnectServer();// 自动断开连接
}
客户端接收消息,若消息为 n1,代表服务端关闭,自动执行断开连接方法。
七、必考通用考点(TCP聊天室通用)
-
子线程不能操作UI:所有控件更新必须使用 Invoke
-
TCP无边界问题:原生代码使用 Available(了解即可,考试不扣分)
-
双集合优点:字典查改快、List适配UI绑定
-
自定义协议意义:解决TCP断线不感知、在线列表残留问题
-
资源释放:窗口关闭、服务器关闭必须彻底释放Socket和线程
-
按钮状态互斥:连接/断开、启动/关闭文字切换,防止重复操作
八、简答题满分模版(直接背诵)
本TCP聊天室采用服务端监听、多客户端接入模式。服务端通过字典和泛型集合双容器管理在线客户端,实现客户端唯一性校验与UI在线列表展示。通过自定义n1断线协议,解决TCP协议无法主动感知客户端掉线的问题,保证在线列表数据准确。程序支持单客户端私聊与全员广播两种消息发送模式,通过双层异步任务实现多客户端并发通信。所有子线程UI更新采用Invoke调度,所有连接关闭时执行完整资源释放,保证程序稳定运行、无端口占用与内存泄漏。