c#Socket学习,使用Socket创建一个在线聊天,客户端代码实现(4)
·
客户端代码
技术点
- 传输方式:TCP 协议,长度前缀(4 byte Big-Endian)+ UTF-8 JSON 解决粘包/半包。
- 异步接收:独立后台线程
ReceiveServerMessages持续阻塞接收。 - 心跳机制:可手动
/heartbeat或未来定时发送,服务器回 pong 确认存活。 - 消息路由:统一入口
ProcessServerMessage按MessageTypeEnum分发到对应 Handle*Message 方法。 - 断线自退出:接收线程检测到
SocketError.ConnectionReset或read==0时,自动Environment.Exit(0),防止僵尸进程。 - 用户友好命令行:使用命令行发送消息和设置自己的信息
项目流程
- 入口
设置控制台标题 → 注册进程退出事件 →InitializeClient()发起 TCP 连接。 - 连接
启动后台线程ReceiveServerMessages循环解析“长度头+JSON”数据。
主线程进入HandleConsoleInput阻塞等待用户命令。 - 收发
接收线程:收到完整一帧 →ProcessServerMessage→ 控制台彩色输出。
发送线程:用户键入命令 → 构造MessageStyle→SendMessageToServer→ 拼接长度头 →Socket.Send。 - 退出
用户/exit或Ctrl-C→ProcessExit事件Shutdown/Close Socket→ 接收线程退出 → 进程结束。
代码
初始化 & 连接
/// <summary>
/// 客户端Socket
/// </summary>
private static Socket _clientSocket;
/// <summary>
/// 服务器IP
/// </summary>
private static string _serverIp = "127.0.0.1";
/// <summary>
/// 服务器端口
/// </summary>
private static int _serverPort = 23841;
/// <summary>
/// 客户端ID
/// </summary>
private static string _clientId;
/// <summary>
/// 自己的用户名
/// </summary>
private static string _clientUserName;
private static void Main(string[] args)
{
Console.Title = "Socket客户端";
RegisterApplicationExitEvent();
if (InitializeClient() == true)
{
// 控制台交互
HandleConsoleInput();
}
Console.WriteLine("按任意键退出...");
Console.ReadKey();
}
/// <summary>
/// 初始化socket客户端
/// </summary>
/// <returns></returns>
private static bool InitializeClient()
{
try
{
// 初始化Socket
_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 连接服务器
Console.Write($"正在连接服务器 {_serverIp}:{_serverPort}...");
_clientSocket.Connect(new IPEndPoint(IPAddress.Parse(_serverIp), _serverPort));
Console.WriteLine("连接成功!");
// 启动接收服务器消息的线程
Thread receiveThread = new Thread(ReceiveServerMessages);
receiveThread.IsBackground = true;
receiveThread.Start();
return true;
}
catch (SocketException ex)
{
Console.WriteLine($"\n连接服务器失败: {ex.SocketErrorCode} - {ex.Message}");
Console.WriteLine("请检查服务器是否启动,或IP/端口是否正确");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"\n客户端启动失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 接收服务器消息的线程
/// </summary>
private static void ReceiveServerMessages()
{
// 复用缓冲区,避免重复创建
byte[] tempBuffer = new byte[1024];
byte[] lengthBuffer = new byte[4];
try
{
using (MemoryStream messageBuffer = new MemoryStream())
{
while (true)
{
// 1. 读取4字节的消息长度(大端序)
int lengthBytesRead = 0;
while (lengthBytesRead < 4)
{
int read = _clientSocket.Receive(
lengthBuffer,
lengthBytesRead,
4 - lengthBytesRead,
SocketFlags.None);
if (read == 0)
{
throw new SocketException((int) SocketError.ConnectionReset);
}
lengthBytesRead += read;
}
// 转换长度(网络字节序→主机字节序)
int messageLength = BitConverter.ToInt32(lengthBuffer, 0);
messageLength = IPAddress.NetworkToHostOrder(messageLength);
// 2. 读取完整消息内容
messageBuffer.SetLength(0);
int totalBytesRead = 0;
while (totalBytesRead < messageLength)
{
int bytesToRead = Math.Min(tempBuffer.Length, messageLength - totalBytesRead);
int read = _clientSocket.Receive(tempBuffer, 0, bytesToRead, SocketFlags.None);
if (read == 0)
{
throw new SocketException((int) SocketError.ConnectionReset);
}
messageBuffer.Write(tempBuffer, 0, read);
totalBytesRead += read;
}
// 3. 解析消息
string message = Encoding.UTF8.GetString(messageBuffer.ToArray());
ProcessServerMessage(message);
}
}
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.ConnectionReset ||
ex.SocketErrorCode == SocketError.ConnectionAborted)
{
Console.WriteLine($"\n[服务器] 连接已断开");
}
else
{
Console.WriteLine($"\n[错误] 接收消息失败: {ex.SocketErrorCode} - {ex.Message}");
}
}
catch (Exception ex)
{
Console.WriteLine($"\n[错误] 接收消息失败: {ex.Message}");
}
finally
{
// 断开连接后退出控制台输入循环
Environment.Exit(0);
}
}
消息接收
/// <summary>
/// 处理服务器发送的消息
/// </summary>
/// <param name="message">服务器消息(JSON字符串)</param>
private static void ProcessServerMessage(string message)
{
// 空消息直接忽略
if (string.IsNullOrWhiteSpace(message))
{
Console.WriteLine("\n[服务器] 收到空消息");
return;
}
try
{
// 反序列化基础消息结构
MessageStyle serverMessage = JsonConvert.DeserializeObject<MessageStyle>(message);
// 消息结构验证
if (serverMessage == null)
{
Console.WriteLine($"\n[服务器] 收到无效的消息格式: {message}");
return;
}
// 根据消息类型处理不同逻辑
HandleMessageByType(serverMessage);
}
catch (JsonSerializationException jsonEx)
{
// JSON反序列化失败(非标准MessageStyle格式)
Console.WriteLine($"\n[服务器] 收到非标准JSON消息: {message}");
Console.WriteLine($"[反序列化错误] {jsonEx.Message}");
}
catch (Exception ex)
{
// 其他异常
Console.WriteLine($"\n[服务器] 处理消息时发生错误: {ex.Message}");
Console.WriteLine($"[原始消息] {message}");
}
}
```
```csharp
/// <summary>
/// 按消息类型处理服务器消息
/// </summary>
/// <param name="serverMessage">解析后的消息对象</param>
private static void HandleMessageByType(MessageStyle serverMessage)
{
switch (serverMessage.Type)
{
case MessageTypeEnum.Welcome:
HandleWelcomeMessage(serverMessage);
break;
case MessageTypeEnum.Text:
Console.WriteLine($"\n[服务器文本] {serverMessage.Message}");
break;
case MessageTypeEnum.Broadcast:
Console.WriteLine($"\n[服务器广播] {serverMessage.Message}");
break;
case MessageTypeEnum.Heartbeat:
Console.WriteLine($"\n[服务器心跳] {serverMessage.Message}");
break;
case MessageTypeEnum.Unknown:
Console.WriteLine($"\n[服务器未知类型消息] {serverMessage.Message}");
break;
case MessageTypeEnum.PrivateMsg:
HandlePrivateMessage(serverMessage);
break;
case MessageTypeEnum.SetUserName:
HandleSetUserNameMessage(serverMessage);
break;
case MessageTypeEnum.ListRequest:
HandleOnlineListResponse(serverMessage);
break;
default:
Console.WriteLine($"\n[服务器未定义消息类型] 类型: {serverMessage.Type}, 内容: {serverMessage.Message}");
break;
}
}
处理欢迎消息
/// <summary>
/// 处理欢迎消息
/// </summary>
/// <param name="serverMessage">服务器消息对象</param>
private static void HandleWelcomeMessage(MessageStyle serverMessage)
{
try
{
// 记录客户端ID
if (!string.IsNullOrWhiteSpace(serverMessage.ClientId))
{
_clientId = serverMessage.ClientId;
}
// 解析欢迎消息内容
WelComMessageModel welcomeContent =
JsonConvert.DeserializeObject<WelComMessageModel>(serverMessage.Message);
Console.WriteLine($"\n[服务器欢迎] {welcomeContent?.Message ?? "欢迎连接服务器"}");
Console.WriteLine($"[客户端ID] {_clientId}");
}
catch (Exception ex)
{
Console.WriteLine($"\n[服务器] 欢迎消息解析失败: {ex.Message}");
Console.WriteLine($"[原始欢迎消息] {serverMessage.Message}");
}
}
处理私信消息
/// <summary>
/// 处理私聊消息
/// </summary>
/// <param name="serverMessage">服务器消息对象</param>
private static void HandlePrivateMessage(MessageStyle serverMessage)
{
try
{
StandMessageModel privateMsg = JsonConvert.DeserializeObject<StandMessageModel>(serverMessage.Message);
string senderName = string.IsNullOrWhiteSpace(privateMsg.SenderClientId)
? privateMsg.SenderClientId
: $"{privateMsg.SenderName ?? "未知用户"}({privateMsg.SenderClientId})";
Console.WriteLine($"\n[私聊消息] 来自 {senderName}: {privateMsg.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"\n[服务器] 私聊消息解析失败: {ex.Message}");
Console.WriteLine($"[原始私聊消息] {serverMessage.Message}");
}
}
处理设置用户名相关消息
/// <summary>
/// 处理设置用户名相关消息
/// </summary>
/// <param name="serverMessage">服务器消息对象</param>
private static void HandleSetUserNameMessage(MessageStyle serverMessage)
{
try
{
StandMessageModel userNameMsg = JsonConvert.DeserializeObject<StandMessageModel>(serverMessage.Message);
_clientUserName = userNameMsg.Message;
Console.WriteLine($"\n[服务器] 用户名设置成功: {_clientUserName}");
Console.WriteLine($"[当前信息] ID:{_clientId}, 用户名:{_clientUserName}");
}
catch (Exception ex)
{
Console.WriteLine($"\n[服务器] 用户名消息解析失败: {ex.Message}");
Console.WriteLine($"[原始用户名消息] {serverMessage.Message}");
}
}
处理服务器返回的在线列表消息
/// <summary>
/// 处理服务器返回的在线列表消息
/// </summary>
/// <param name="serverMessage">服务器消息对象</param>
private static void HandleOnlineListResponse(MessageStyle serverMessage)
{
try
{
OnlineListResponseModel listModel =
JsonConvert.DeserializeObject<OnlineListResponseModel>(serverMessage.Message);
if (listModel == null || listModel.Users == null)
{
Console.WriteLine("\n[服务器] 在线列表数据格式错误");
return;
}
Console.WriteLine("\n===== 当前在线用户列表 =====");
Console.WriteLine($"总在线人数: {listModel.TotalCount}");
if (listModel.Users.Count == 0)
{
Console.WriteLine(" 暂无在线用户");
}
else
{
for (int i = 0; i < listModel.Users.Count; i++)
{
var user = listModel.Users[i];
string userName = string.IsNullOrWhiteSpace(user.UserName) ? "未命名" : user.UserName;
string activeTime = user.LastActiveTime.ToString("HH:mm:ss");
Console.WriteLine($" [{i + 1}] ID: {user.ClientId} | 用户名: {userName} | 最后活跃: {activeTime}");
}
}
Console.WriteLine("===========================\n");
}
catch (Exception ex)
{
Console.WriteLine($"\n[服务器] 解析在线列表失败: {ex.Message}");
Console.WriteLine($"[原始消息] {serverMessage.Message}");
}
}
发送消息
/// <summary>
/// 发送消息到服务器,最基础的方法
/// </summary>
/// <param name="message">JSON格式的完整消息</param>
private static void SendMessageToServer(string message)
{
if (_clientSocket == null || !_clientSocket.Connected)
{
Console.WriteLine("未连接到服务器,无法发送消息");
return;
}
try
{
// 转为UTF8字节数组
byte[] jsonBytes = Encoding.UTF8.GetBytes(message);
// 计算长度并转换为网络字节序(大端序)
int length = jsonBytes.Length;
int networkOrderLength = IPAddress.HostToNetworkOrder(length);
byte[] lengthBytes = BitConverter.GetBytes(networkOrderLength);
// 拼接长度+内容
byte[] buffer = new byte[4 + jsonBytes.Length];
Array.Copy(lengthBytes, 0, buffer, 0, 4);
Array.Copy(jsonBytes, 0, buffer, 4, jsonBytes.Length);
// 发送
_clientSocket.Send(buffer);
}
catch (SocketException ex)
{
Console.WriteLine($"[发送失败] 网络异常: {ex.SocketErrorCode} - {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"[发送失败] {ex.Message}");
}
}
发送消息,传入消息类型,消息的json
/// <summary>
/// 发送消息
/// </summary>
/// <param name="messageType">消息类型</param>
/// <param name="message">消息内容</param>
private static void SendMessage(MessageTypeEnum messageType, string message)
{
string jsonMessage = "";
switch (messageType)
{
case MessageTypeEnum.Unknown:
StandMessageModel unknownModel = new StandMessageModel(_clientId, _clientId, message);
jsonMessage = JsonConvert.SerializeObject(unknownModel);
MessageStyle unknownStyle = new MessageStyle(_clientId, jsonMessage, messageType);
SendMessageToServer(JsonConvert.SerializeObject(unknownStyle));
break;
case MessageTypeEnum.Welcome:
Console.WriteLine("不能发送欢迎消息,只能让服务器发送");
break;
case MessageTypeEnum.Text:
StandMessageModel textModel = new StandMessageModel(_clientId, _clientId, message);
jsonMessage = JsonConvert.SerializeObject(textModel);
MessageStyle textStyle = new MessageStyle(_clientId, jsonMessage, messageType);
SendMessageToServer(JsonConvert.SerializeObject(textStyle));
Console.WriteLine($"[已发送文本] {message}");
break;
case MessageTypeEnum.Broadcast:
MessageStyle broadcastStyle = new MessageStyle(_clientId, message, messageType);
SendMessageToServer(JsonConvert.SerializeObject(broadcastStyle));
break;
case MessageTypeEnum.Heartbeat:
StandMessageModel heartbeatModel = new StandMessageModel(_clientId, _clientId, message);
jsonMessage = JsonConvert.SerializeObject(heartbeatModel);
MessageStyle heartbeatStyle = new MessageStyle(_clientId, jsonMessage, messageType);
SendMessageToServer(JsonConvert.SerializeObject(heartbeatStyle));
break;
case MessageTypeEnum.SetUserName:
StandMessageModel setUserNameModel = new StandMessageModel(_clientId, _clientId, message);
jsonMessage = JsonConvert.SerializeObject(setUserNameModel);
MessageStyle setUserNameStyle = new MessageStyle(_clientId, jsonMessage, messageType);
SendMessageToServer(JsonConvert.SerializeObject(setUserNameStyle));
break;
case MessageTypeEnum.ListRequest:
StandMessageModel listRequestModel = new StandMessageModel(_clientId, _clientId, message);
jsonMessage = JsonConvert.SerializeObject(listRequestModel);
MessageStyle listRequestStyle = new MessageStyle(_clientId, jsonMessage, messageType);
SendMessageToServer(JsonConvert.SerializeObject(listRequestStyle));
break;
case MessageTypeEnum.PrivateMsg:
// 这里需要目标ID,应该用 SendPrivateMsg 方法
Console.WriteLine("[错误] 请使用 SendPrivateMsg 方法发送私聊消息");
break;
default:
Console.WriteLine($"[错误] 不支持的消息类型: {messageType}");
break;
}
}
发送私聊消息
/// <summary>
/// 发送私聊消息
/// </summary>
/// <param name="targetId">目标客户端ID</param>
/// <param name="message">消息内容</param>
private static void SendPrivateMsg(string targetId, string message)
{
StandMessageModel standMessageModel = new StandMessageModel(_clientId, targetId, message);
string msgContent = JsonConvert.SerializeObject(standMessageModel);
MessageStyle messageStyle = new MessageStyle(_clientId, msgContent, MessageTypeEnum.PrivateMsg);
// 发送消息到服务器
SendMessageToServer(JsonConvert.SerializeObject(messageStyle));
}
控制台命令
/// <summary>
/// 处理控制台输入
/// </summary>
private static void HandleConsoleInput()
{
Console.WriteLine("\n===== 客户端命令 =====");
DefaultConsoleLine();
Console.WriteLine("======================\n");
while (true)
{
try
{
Console.Write("> ");
string input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
continue;
// 移除首尾空格并按空格分割命令和参数
var parts = input.Trim().Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0) continue;
string command = parts[0].ToUpper();
string[] args = parts.Length > 1 ? parts.Skip(1).ToArray() : new string[0];
switch (command)
{
case "/EXIT":
Console.WriteLine("正在退出客户端...");
Environment.Exit(0);
break;
case "/MYID":
if (string.IsNullOrEmpty(_clientId))
{
Console.WriteLine("[提示] 尚未收到服务器分配的客户端ID");
}
else
{
Console.WriteLine($"[我的客户端ID] {_clientId}");
}
break;
case "/GETNAME":
if (string.IsNullOrEmpty(_clientUserName))
{
Console.WriteLine("[提示] 尚未设置用户名,请使用 /setname 用户名 命令设置");
}
else
{
Console.WriteLine($"[我的用户名] {_clientUserName}");
}
break;
case "/SETNAME":
if (args.Length >= 1)
{
string newName = string.Join(" ", args);
SendMessage(MessageTypeEnum.SetUserName, newName);
Console.WriteLine($"[提示] 正在设置用户名为: {newName}");
}
else
{
Console.WriteLine("命令格式错误,正确格式: /setname 你的用户名");
}
break;
case "/SEND":
if (args.Length >= 1)
{
string message = string.Join(" ", args);
SendMessage(MessageTypeEnum.Text, message);
}
else
{
Console.WriteLine("命令格式错误,正确格式: /send 消息内容");
}
break;
case "/TEXT":
if (args.Length >= 1)
{
string message = string.Join(" ", args);
SendMessage(MessageTypeEnum.Text, message);
}
else
{
Console.WriteLine("命令格式错误,正确格式: /text 消息内容");
}
break;
case "/BROADCAST":
if (args.Length >= 1)
{
// 解析广播类型(第一个参数如果是枚举值,则识别为类型,否则默认UserMessage)
BroadcastMessageType broadcastType = BroadcastMessageType.UserMessage;
string messageContent = string.Empty;
// 检查第一个参数是否为有效的广播类型
if (Enum.TryParse<BroadcastMessageType>(args[0], true, out var parsedType))
{
broadcastType = parsedType;
// 消息内容为剩余参数拼接
messageContent = args.Length > 1 ? string.Join(" ", args.Skip(1)) : string.Empty;
}
else
{
// 未指定类型,全部参数作为消息内容
messageContent = string.Join(" ", args);
}
if (string.IsNullOrWhiteSpace(messageContent))
{
Console.WriteLine("广播消息内容不能为空!");
break;
}
BroadcastMessageModel broadcastModel = new BroadcastMessageModel(
message: messageContent,
senderClientId: _clientId,
messageType: broadcastType
);
// 发送消息
SendMessage(MessageTypeEnum.Broadcast, JsonConvert.SerializeObject(broadcastModel));
Console.WriteLine($"[已发送{broadcastType}类型广播] {messageContent}");
}
else
{
Console.WriteLine("命令格式错误,正确格式:");
Console.WriteLine(" /broadcast [消息类型] 消息内容 (例:/broadcast Warning 服务器即将重启)");
Console.WriteLine(" /broadcast 消息内容 (默认:UserMessage类型)");
Console.WriteLine(" 支持的类型:UserMessage/Warning/Error/StatusUpdate");
}
break;
case "/HEARTBEAT":
if (args.Length >= 1)
{
string message = string.Join(" ", args);
SendMessage(MessageTypeEnum.Heartbeat, message);
Console.WriteLine($"[已发送心跳] {message}");
}
else
{
// 如果没有参数,发送默认心跳消息
SendMessage(MessageTypeEnum.Heartbeat, "online");
Console.WriteLine("[已发送心跳] online");
}
break;
case "/CUSTOM":
if (args.Length >= 1)
{
string message = string.Join(" ", args);
SendMessage(MessageTypeEnum.Unknown, message);
Console.WriteLine($"[已发送自定义消息] {message}");
}
else
{
Console.WriteLine("命令格式错误,正确格式: /custom 消息内容");
}
break;
case "/MSGTO":
if (args.Length >= 2)
{
string targetClientId = args[0];
string message = string.Join(" ", args.Skip(1));
SendPrivateMsg(targetClientId, message);
Console.WriteLine($"[私聊给 {targetClientId}] {message}");
}
else
{
Console.WriteLine("命令格式错误,正确格式: /msgto 目标客户端ID 消息内容");
}
break;
case "/LIST":
// 发送在线列表请求
SendMessage(MessageTypeEnum.ListRequest, "request_online_list");
Console.WriteLine("[提示] 正在请求在线用户列表...");
break;
case "/HELP":
Console.WriteLine("\n===== 客户端命令帮助 =====");
DefaultConsoleLine();
Console.WriteLine("===========================\n");
break;
default:
// 如果不是以/开头的命令,直接作为文本消息发送
if (!input.StartsWith("/"))
{
SendMessage(MessageTypeEnum.Text, input);
}
else
{
Console.WriteLine("未知命令,输入 /help 查看可用命令");
}
break;
}
}
catch (Exception ex)
{
Console.WriteLine($"[错误] 处理输入失败: {ex.Message}");
}
}
}
/// <summary>
/// 默认的控制台提示信息
/// </summary>
private static void DefaultConsoleLine()
{
Console.WriteLine(" 直接输入 - 发送文本消息");
Console.WriteLine(" /myid - 查看自己的客户端ID");
Console.WriteLine(" /getname - 获取自己的名字");
Console.WriteLine(" /setname 用户名 - 设置自己的名字");
Console.WriteLine(" /send 消息内容 - 发送消息到服务器");
Console.WriteLine(" /text 消息内容 - 发送文本消息(默认类型)");
Console.WriteLine(" /broadcast 消息内容 - 发送广播消息(所有客户端可见,包括自己)");
Console.WriteLine(" /heartbeat 消息内容 - 发送心跳消息");
Console.WriteLine(" /custom 消息内容 - 发送自定义类型消息");
Console.WriteLine(" /msgto 目标ID 消息 - 发送私聊消息到指定客户端");
Console.WriteLine(" /list - 查看当前在线用户列表");
Console.WriteLine(" /help - 查看帮助");
Console.WriteLine(" /exit - 退出客户端");
}
更多推荐



所有评论(0)