客户端代码
技术点
- 传输方式: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 → 接收线程退出 → 进程结束。
代码
初始化 & 连接
private static Socket _clientSocket;
private static string _serverIp = "127.0.0.1";
private static int _serverPort = 23841;
private static string _clientId;
private static string _clientUserName;
private static void Main(string[] args)
{
Console.Title = "Socket客户端";
RegisterApplicationExitEvent();
if (InitializeClient() == true)
{
HandleConsoleInput();
}
Console.WriteLine("按任意键退出...");
Console.ReadKey();
}
private static bool InitializeClient()
{
try
{
_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;
}
}
private static void ReceiveServerMessages()
{
byte[] tempBuffer = new byte[1024];
byte[] lengthBuffer = new byte[4];
try
{
using (MemoryStream messageBuffer = new MemoryStream())
{
while (true)
{
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);
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;
}
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);
}
}
消息接收
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)
{
Console.WriteLine($"\n[服务器] 收到非标准JSON消息: {message}");
Console.WriteLine($"[反序列化错误] {jsonEx.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"\n[服务器] 处理消息时发生错误: {ex.Message}");
Console.WriteLine($"[原始消息] {message}");
}
}
```
```csharp
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;
}
}
处理欢迎消息
private static void HandleWelcomeMessage(MessageStyle serverMessage)
{
try
{
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}");
}
}
处理私信消息
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}");
}
}
处理设置用户名相关消息
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}");
}
}
处理服务器返回的在线列表消息
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}");
}
}
发送消息
private static void SendMessageToServer(string message)
{
if (_clientSocket == null || !_clientSocket.Connected)
{
Console.WriteLine("未连接到服务器,无法发送消息");
return;
}
try
{
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
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:
Console.WriteLine("[错误] 请使用 SendPrivateMsg 方法发送私聊消息");
break;
default:
Console.WriteLine($"[错误] 不支持的消息类型: {messageType}");
break;
}
}
发送私聊消息
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));
}
控制台命令
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)
{
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}");
}
}
}
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 - 退出客户端");
}