C# 即时通讯工具实现
C#即时通讯工具,包含客户端和服务器端,支持TCP/UDP双协议、群聊、私聊、文件传输等功能。
项目结构
InstantMessenger/
├── Server/
│ ├── Program.cs (服务器主程序)
│ ├── ServerCore.cs (服务器核心逻辑)
│ └── Models/ (数据模型)
├── Client/
│ ├── Program.cs (客户端主程序)
│ ├── ClientCore.cs (客户端核心逻辑)
│ └── UI/ (用户界面)
└── Shared/
└── Protocol.cs (通信协议定义)
1. 共享协议定义 (Shared/Protocol.cs)
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
namespace InstantMessenger.Shared
{
// 消息类型枚举
public enum MessageType
{
Login, // 登录
Logout, // 登出
TextMessage, // 文本消息
FileMessage, // 文件消息
UserList, // 用户列表
UserStatus, // 用户状态
PrivateMessage, // 私聊消息
GroupMessage, // 群聊消息
VoiceMessage, // 语音消息
VideoMessage, // 视频消息
Error, // 错误
Heartbeat, // 心跳
Ack // 确认
}
// 用户状态
public enum UserStatus
{
Offline,
Online,
Away,
Busy,
Invisible
}
// 基础消息类
[Serializable]
public class Message
{
public MessageType Type { get; set; }
public string SenderId { get; set; }
public string SenderName { get; set; }
public string ReceiverId { get; set; }
public string ReceiverName { get; set; }
public DateTime Timestamp { get; set; }
public byte[] Data { get; set; }
public string Content { get; set; }
public Dictionary<string, object> Metadata { get; set; }
public Message()
{
Timestamp = DateTime.Now;
Metadata = new Dictionary<string, object>();
}
// 序列化消息
public byte[] Serialize()
{
BinaryFormatter formatter = new BinaryFormatter();
using (var stream = new System.IO.MemoryStream())
{
formatter.Serialize(stream, this);
return stream.ToArray();
}
}
// 反序列化消息
public static Message Deserialize(byte[] data)
{
BinaryFormatter formatter = new BinaryFormatter();
using (var stream = new System.IO.MemoryStream(data))
{
return (Message)formatter.Deserialize(stream);
}
}
}
// 用户信息
[Serializable]
public class UserInfo
{
public string UserId { get; set; }
public string UserName { get; set; }
public UserStatus Status { get; set; }
public string IPAddress { get; set; }
public int Port { get; set; }
public DateTime LastActive { get; set; }
public List<string> Groups { get; set; }
public UserInfo()
{
Groups = new List<string>();
LastActive = DateTime.Now;
}
}
// 文件传输信息
[Serializable]
public class FileTransferInfo
{
public string FileName { get; set; }
public long FileSize { get; set; }
public string FileId { get; set; }
public string SenderId { get; set; }
public string ReceiverId { get; set; }
public DateTime TransferTime { get; set; }
public int ChunkSize { get; set; } = 8192;
}
// 群组信息
[Serializable]
public class GroupInfo
{
public string GroupId { get; set; }
public string GroupName { get; set; }
public string CreatorId { get; set; }
public DateTime CreatedTime { get; set; }
public List<string> Members { get; set; }
public string Description { get; set; }
public GroupInfo()
{
Members = new List<string>();
CreatedTime = DateTime.Now;
}
}
}
2. 服务器端实现
Server/ServerCore.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using InstantMessenger.Shared;
namespace InstantMessenger.Server
{
public class ServerCore
{
// 服务器配置
private readonly IPAddress _ipAddress;
private readonly int _port;
private readonly TcpListener _tcpListener;
private readonly UdpClient _udpListener;
private bool _isRunning;
// 客户端管理
private readonly Dictionary<string, TcpClient> _connectedClients;
private readonly Dictionary<string, UserInfo> _userDatabase;
private readonly Dictionary<string, GroupInfo> _groups;
// 线程安全锁
private readonly object _lock = new object();
// 服务器统计
private int _totalMessages;
private DateTime _startTime;
public event EventHandler<string> LogMessage;
public event EventHandler<string> UserConnected;
public event EventHandler<string> UserDisconnected;
public ServerCore(IPAddress ipAddress, int port)
{
_ipAddress = ipAddress;
_port = port;
_tcpListener = new TcpListener(ipAddress, port);
_udpListener = new UdpClient(port);
_connectedClients = new Dictionary<string, TcpClient>();
_userDatabase = new Dictionary<string, UserInfo>();
_groups = new Dictionary<string, GroupInfo>();
// 创建默认群组
CreateDefaultGroups();
}
private void CreateDefaultGroups()
{
var publicGroup = new GroupInfo
{
GroupId = "public",
GroupName = "公共聊天室",
CreatorId = "system",
Description = "所有人都可以在这里聊天"
};
_groups.Add(publicGroup.GroupId, publicGroup);
}
public void Start()
{
try
{
_isRunning = true;
_startTime = DateTime.Now;
_tcpListener.Start();
Log("服务器启动成功,监听端口: " + _port);
// 启动TCP监听线程
Thread tcpListenerThread = new Thread(ListenForTcpClients);
tcpListenerThread.IsBackground = true;
tcpListenerThread.Start();
// 启动UDP监听线程
Thread udpListenerThread = new Thread(ListenForUdpMessages);
udpListenerThread.IsBackground = true;
udpListenerThread.Start();
// 启动心跳检查线程
Thread heartbeatThread = new Thread(CheckHeartbeats);
heartbeatThread.IsBackground = true;
heartbeatThread.Start();
Log("服务器准备就绪,等待客户端连接...");
}
catch (Exception ex)
{
Log("服务器启动失败: " + ex.Message);
throw;
}
}
public void Stop()
{
_isRunning = false;
// 断开所有客户端连接
lock (_lock)
{
foreach (var client in _connectedClients.Values)
{
try
{
client.Close();
}
catch { }
}
_connectedClients.Clear();
}
_tcpListener.Stop();
_udpListener.Close();
Log("服务器已停止");
}
private void ListenForTcpClients()
{
while (_isRunning)
{
try
{
TcpClient client = _tcpListener.AcceptTcpClient();
Thread clientThread = new Thread(() => HandleClient(client));
clientThread.IsBackground = true;
clientThread.Start();
}
catch (SocketException) when (!_isRunning)
{
break;
}
catch (Exception ex)
{
Log("接受客户端连接时出错: " + ex.Message);
}
}
}
private void ListenForUdpMessages()
{
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
while (_isRunning)
{
try
{
byte[] data = _udpListener.Receive(ref remoteEndPoint);
Task.Run(() => ProcessUdpMessage(data, remoteEndPoint));
}
catch (SocketException) when (!_isRunning)
{
break;
}
catch (Exception ex)
{
Log("接收UDP消息时出错: " + ex.Message);
}
}
}
private void HandleClient(TcpClient client)
{
string clientId = null;
try
{
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[4096];
while (_isRunning && client.Connected)
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0) break;
byte[] messageData = new byte[bytesRead];
Array.Copy(buffer, messageData, bytesRead);
Message message = Message.Deserialize(messageData);
ProcessMessage(message, client);
if (message.Type == MessageType.Login && clientId == null)
{
clientId = message.SenderId;
}
}
}
catch (Exception ex)
{
Log($"处理客户端 {clientId} 时出错: {ex.Message}");
}
finally
{
if (clientId != null)
{
HandleUserDisconnection(clientId);
}
client.Close();
}
}
private void ProcessMessage(Message message, TcpClient client)
{
_totalMessages++;
switch (message.Type)
{
case MessageType.Login:
HandleLogin(message, client);
break;
case MessageType.Logout:
HandleLogout(message);
break;
case MessageType.TextMessage:
HandleTextMessage(message);
break;
case MessageType.PrivateMessage:
HandlePrivateMessage(message);
break;
case MessageType.GroupMessage:
HandleGroupMessage(message);
break;
case MessageType.FileMessage:
HandleFileMessage(message, client);
break;
case MessageType.Heartbeat:
HandleHeartbeat(message);
break;
case MessageType.UserList:
SendUserList(message.SenderId);
break;
}
}
private void HandleLogin(Message message, TcpClient client)
{
lock (_lock)
{
string userId = message.SenderId;
if (_connectedClients.ContainsKey(userId))
{
// 用户已登录,发送错误消息
SendErrorMessage(userId, "该用户已在线");
return;
}
// 添加用户到数据库(如果不存在)
if (!_userDatabase.ContainsKey(userId))
{
var userInfo = new UserInfo
{
UserId = userId,
UserName = message.SenderName,
Status = UserStatus.Online,
IPAddress = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString(),
Port = ((IPEndPoint)client.Client.RemoteEndPoint).Port
};
_userDatabase[userId] = userInfo;
}
else
{
_userDatabase[userId].Status = UserStatus.Online;
_userDatabase[userId].LastActive = DateTime.Now;
}
// 保存客户端连接
_connectedClients[userId] = client;
// 添加到公共群组
if (_groups.ContainsKey("public") && !_groups["public"].Members.Contains(userId))
{
_groups["public"].Members.Add(userId);
}
// 通知所有用户有新用户上线
BroadcastUserStatusChange(userId, UserStatus.Online);
Log($"用户登录: {message.SenderName} ({userId})");
UserConnected?.Invoke(this, message.SenderName);
// 发送登录成功消息
SendLoginSuccess(userId);
// 发送当前在线用户列表
SendUserList(userId);
}
}
private void HandleLogout(Message message)
{
string userId = message.SenderId;
HandleUserDisconnection(userId);
}
private void HandleUserDisconnection(string userId)
{
lock (_lock)
{
if (_connectedClients.ContainsKey(userId))
{
_connectedClients.Remove(userId);
if (_userDatabase.ContainsKey(userId))
{
_userDatabase[userId].Status = UserStatus.Offline;
_userDatabase[userId].LastActive = DateTime.Now;
}
// 通知所有用户有用户下线
BroadcastUserStatusChange(userId, UserStatus.Offline);
Log($"用户断开连接: {userId}");
UserDisconnected?.Invoke(this, userId);
}
}
}
private void HandleTextMessage(Message message)
{
// 广播消息到所有用户
BroadcastMessage(message);
}
private void HandlePrivateMessage(Message message)
{
// 发送私聊消息到指定用户
SendToUser(message.ReceiverId, message);
}
private void HandleGroupMessage(Message message)
{
string groupId = message.ReceiverId;
if (_groups.ContainsKey(groupId))
{
// 发送消息给群组所有成员
foreach (var memberId in _groups[groupId].Members)
{
if (memberId != message.SenderId) // 不发送给自己
{
SendToUser(memberId, message);
}
}
}
}
private void HandleFileMessage(Message message, TcpClient senderClient)
{
// 这里实现文件传输逻辑
// 由于文件可能很大,需要分块传输
SendToUser(message.ReceiverId, message);
}
private void HandleHeartbeat(Message message)
{
string userId = message.SenderId;
lock (_lock)
{
if (_userDatabase.ContainsKey(userId))
{
_userDatabase[userId].LastActive = DateTime.Now;
}
}
}
private void ProcessUdpMessage(byte[] data, IPEndPoint remoteEndPoint)
{
// 处理UDP消息(用于音视频通话等实时性要求高的场景)
try
{
Message message = Message.Deserialize(data);
switch (message.Type)
{
case MessageType.VoiceMessage:
case MessageType.VideoMessage:
// 转发实时媒体流
ForwardMediaMessage(message);
break;
}
}
catch (Exception ex)
{
Log($"处理UDP消息时出错: {ex.Message}");
}
}
private void ForwardMediaMessage(Message message)
{
// 转发音视频消息
if (!string.IsNullOrEmpty(message.ReceiverId))
{
// 发送给指定用户
SendUdpToUser(message.ReceiverId, message);
}
else if (!string.IsNullOrEmpty(message.Metadata?.ContainsKey("GroupId")?.ToString()))
{
// 发送给群组
string groupId = message.Metadata["GroupId"].ToString();
if (_groups.ContainsKey(groupId))
{
foreach (var memberId in _groups[groupId].Members)
{
if (memberId != message.SenderId)
{
SendUdpToUser(memberId, message);
}
}
}
}
}
private void SendLoginSuccess(string userId)
{
var response = new Message
{
Type = MessageType.Ack,
Content = "登录成功",
ReceiverId = userId,
Timestamp = DateTime.Now
};
SendToUser(userId, response);
}
private void SendErrorMessage(string userId, string errorMessage)
{
var error = new Message
{
Type = MessageType.Error,
Content = errorMessage,
ReceiverId = userId,
Timestamp = DateTime.Now
};
SendToUser(userId, error);
}
private void SendUserList(string userId)
{
lock (_lock)
{
var userList = _userDatabase.Values
.Where(u => u.Status != UserStatus.Offline)
.Select(u => new { u.UserId, u.UserName, u.Status })
.ToList();
var message = new Message
{
Type = MessageType.UserList,
ReceiverId = userId,
Data = System.Text.Encoding.UTF8.GetBytes(
Newtonsoft.Json.JsonConvert.SerializeObject(userList)),
Timestamp = DateTime.Now
};
SendToUser(userId, message);
}
}
private void BroadcastMessage(Message message)
{
lock (_lock)
{
foreach (var clientPair in _connectedClients)
{
if (clientPair.Key != message.SenderId) // 不发送给自己
{
try
{
NetworkStream stream = clientPair.Value.GetStream();
byte[] data = message.Serialize();
stream.Write(data, 0, data.Length);
}
catch
{
// 发送失败,可能客户端已断开
}
}
}
}
}
private void BroadcastUserStatusChange(string userId, UserStatus status)
{
var statusMessage = new Message
{
Type = MessageType.UserStatus,
SenderId = userId,
Content = status.ToString(),
Timestamp = DateTime.Now
};
BroadcastMessage(statusMessage);
}
private void SendToUser(string userId, Message message)
{
lock (_lock)
{
if (_connectedClients.TryGetValue(userId, out TcpClient client))
{
try
{
NetworkStream stream = client.GetStream();
byte[] data = message.Serialize();
stream.Write(data, 0, data.Length);
}
catch
{
// 发送失败
}
}
}
}
private void SendUdpToUser(string userId, Message message)
{
lock (_lock)
{
if (_userDatabase.TryGetValue(userId, out UserInfo userInfo))
{
try
{
using (UdpClient udpClient = new UdpClient())
{
byte[] data = message.Serialize();
udpClient.Send(data, data.Length, userInfo.IPAddress, userInfo.Port + 1); // UDP端口通常是TCP端口+1
}
}
catch
{
// 发送失败
}
}
}
}
private void CheckHeartbeats()
{
while (_isRunning)
{
Thread.Sleep(30000); // 30秒检查一次
lock (_lock)
{
var inactiveUsers = _userDatabase.Values
.Where(u => u.Status == UserStatus.Online &&
(DateTime.Now - u.LastActive).TotalMinutes > 2)
.ToList();
foreach (var user in inactiveUsers)
{
Log($"用户 {user.UserName} 心跳超时,断开连接");
HandleUserDisconnection(user.UserId);
}
}
}
}
public Dictionary<string, UserInfo> GetOnlineUsers()
{
lock (_lock)
{
return _userDatabase
.Where(u => u.Value.Status != UserStatus.Offline)
.ToDictionary(k => k.Key, v => v.Value);
}
}
public Dictionary<string, GroupInfo> GetGroups()
{
lock (_lock)
{
return new Dictionary<string, GroupInfo>(_groups);
}
}
public ServerStatistics GetStatistics()
{
lock (_lock)
{
return new ServerStatistics
{
Uptime = DateTime.Now - _startTime,
TotalMessages = _totalMessages,
OnlineUsers = _connectedClients.Count,
TotalUsers = _userDatabase.Count,
TotalGroups = _groups.Count
};
}
}
private void Log(string message)
{
string logMessage = $"[{DateTime.Now:HH:mm:ss}] {message}";
LogMessage?.Invoke(this, logMessage);
Console.WriteLine(logMessage);
}
}
public class ServerStatistics
{
public TimeSpan Uptime { get; set; }
public int TotalMessages { get; set; }
public int OnlineUsers { get; set; }
public int TotalUsers { get; set; }
public int TotalGroups { get; set; }
}
}
Server/Program.cs
using System;
using System.Net;
using System.Threading;
using InstantMessenger.Server;
namespace InstantMessenger.Server
{
class Program
{
static ServerCore server;
static bool isRunning = true;
static void Main(string[] args)
{
Console.Title = "即时通讯服务器";
Console.WriteLine("╔════════════════════════════════════════╗");
Console.WriteLine("║ 即时通讯服务器 v1.0 ║");
Console.WriteLine("╚════════════════════════════════════════╝");
Console.WriteLine();
// 配置服务器参数
IPAddress ipAddress = IPAddress.Any;
int port = 8888;
if (args.Length >= 2)
{
if (IPAddress.TryParse(args[0], out IPAddress parsedIp))
ipAddress = parsedIp;
if (int.TryParse(args[1], out int parsedPort))
port = parsedPort;
}
try
{
// 创建并启动服务器
server = new ServerCore(ipAddress, port);
server.LogMessage += OnLogMessage;
server.UserConnected += OnUserConnected;
server.UserDisconnected += OnUserDisconnected;
server.Start();
// 启动控制台命令处理线程
Thread commandThread = new Thread(ProcessCommands);
commandThread.IsBackground = true;
commandThread.Start();
// 等待退出
while (isRunning)
{
Thread.Sleep(100);
}
}
catch (Exception ex)
{
Console.WriteLine($"服务器错误: {ex.Message}");
}
finally
{
server?.Stop();
Console.WriteLine("按任意键退出...");
Console.ReadKey();
}
}
static void OnLogMessage(object sender, string message)
{
Console.WriteLine(message);
}
static void OnUserConnected(object sender, string username)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 用户上线: {username}");
Console.ResetColor();
}
static void OnUserDisconnected(object sender, string username)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 用户下线: {username}");
Console.ResetColor();
}
static void ProcessCommands()
{
while (isRunning)
{
Console.Write("服务器> ");
string command = Console.ReadLine()?.Trim().ToLower();
if (string.IsNullOrEmpty(command))
continue;
switch (command)
{
case "help":
ShowHelp();
break;
case "users":
ShowOnlineUsers();
break;
case "stats":
ShowStatistics();
break;
case "groups":
ShowGroups();
break;
case "clear":
Console.Clear();
break;
case "exit":
case "quit":
isRunning = false;
break;
default:
Console.WriteLine($"未知命令: {command}");
break;
}
}
}
static void ShowHelp()
{
Console.WriteLine("可用命令:");
Console.WriteLine(" help - 显示帮助信息");
Console.WriteLine(" users - 显示在线用户");
Console.WriteLine(" stats - 显示服务器统计");
Console.WriteLine(" groups - 显示群组列表");
Console.WriteLine(" clear - 清屏");
Console.WriteLine(" exit/quit - 退出服务器");
}
static void ShowOnlineUsers()
{
var users = server.GetOnlineUsers();
Console.WriteLine($"在线用户 ({users.Count}):");
Console.WriteLine("┌──────┬────────────┬────────┬──────────────┐");
Console.WriteLine("│ ID │ 用户名 │ 状态 │ 最后活动 │");
Console.WriteLine("├──────┼────────────┼────────┼──────────────┤");
foreach (var user in users.Values)
{
Console.WriteLine($"│ {user.UserId,-4} │ {user.UserName,-10} │ {user.Status,-6} │ {user.LastActive:HH:mm:ss} │");
}
Console.WriteLine("└──────┴────────────┴────────┴──────────────┘");
}
static void ShowStatistics()
{
var stats = server.GetStatistics();
Console.WriteLine("服务器统计:");
Console.WriteLine($"运行时间: {stats.Uptime:hh\\:mm\\:ss}");
Console.WriteLine($"总消息数: {stats.TotalMessages}");
Console.WriteLine($"在线用户: {stats.OnlineUsers}");
Console.WriteLine($"注册用户: {stats.TotalUsers}");
Console.WriteLine($"群组数量: {stats.TotalGroups}");
}
static void ShowGroups()
{
var groups = server.GetGroups();
Console.WriteLine($"群组列表 ({groups.Count}):");
foreach (var group in groups.Values)
{
Console.WriteLine($" [{group.GroupId}] {group.GroupName}");
Console.WriteLine($" 创建者: {group.CreatorId}, 成员: {group.Members.Count}");
Console.WriteLine($" 描述: {group.Description}");
Console.WriteLine();
}
}
}
}
3. 客户端实现
Client/ClientCore.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using InstantMessenger.Shared;
namespace InstantMessenger.Client
{
public class ClientCore
{
// 网络组件
private TcpClient _tcpClient;
private UdpClient _udpClient;
private NetworkStream _networkStream;
// 配置
private string _serverIp;
private int _serverPort;
private int _localUdpPort;
// 状态
private bool _isConnected;
private bool _isRunning;
private string _userId;
private string _userName;
private UserStatus _status;
// 数据存储
private Dictionary<string, UserInfo> _onlineUsers;
private Dictionary<string, List<Message>> _chatHistory;
private Dictionary<string, FileTransferInfo> _fileTransfers;
// 事件
public event EventHandler<Message> MessageReceived;
public event EventHandler<string> StatusChanged;
public event EventHandler<List<UserInfo>> UserListUpdated;
public event EventHandler<FileTransferInfo> FileTransferRequested;
public event EventHandler<string> ConnectionStatusChanged;
// 线程
private Thread _receiveThread;
private Thread _heartbeatThread;
private Thread _udpReceiveThread;
// 锁
private readonly object _lock = new object();
public ClientCore()
{
_onlineUsers = new Dictionary<string, UserInfo>();
_chatHistory = new Dictionary<string, List<Message>>();
_fileTransfers = new Dictionary<string, FileTransferInfo>();
_status = UserStatus.Offline;
}
public bool Connect(string serverIp, int serverPort, string userId, string userName)
{
try
{
_serverIp = serverIp;
_serverPort = serverPort;
_userId = userId;
_userName = userName;
// 连接TCP服务器
_tcpClient = new TcpClient();
_tcpClient.Connect(serverIp, serverPort);
_networkStream = _tcpClient.GetStream();
// 初始化UDP客户端
_localUdpPort = serverPort + 1;
_udpClient = new UdpClient(_localUdpPort);
// 发送登录消息
var loginMessage = new Message
{
Type = MessageType.Login,
SenderId = _userId,
SenderName = _userName,
Timestamp = DateTime.Now
};
SendMessage(loginMessage);
_isConnected = true;
_isRunning = true;
_status = UserStatus.Online;
// 启动接收线程
_receiveThread = new Thread(ReceiveMessages);
_receiveThread.IsBackground = true;
_receiveThread.Start();
// 启动UDP接收线程
_udpReceiveThread = new Thread(ReceiveUdpMessages);
_udpReceiveThread.IsBackground = true;
_udpReceiveThread.Start();
// 启动心跳线程
_heartbeatThread = new Thread(SendHeartbeats);
_heartbeatThread.IsBackground = true;
_heartbeatThread.Start();
ConnectionStatusChanged?.Invoke(this, "连接成功");
StatusChanged?.Invoke(this, _status.ToString());
return true;
}
catch (Exception ex)
{
ConnectionStatusChanged?.Invoke(this, $"连接失败: {ex.Message}");
return false;
}
}
public void Disconnect()
{
try
{
_isRunning = false;
// 发送退出消息
if (_isConnected)
{
var logoutMessage = new Message
{
Type = MessageType.Logout,
SenderId = _userId,
Timestamp = DateTime.Now
};
SendMessage(logoutMessage);
}
// 关闭连接
_networkStream?.Close();
_tcpClient?.Close();
_udpClient?.Close();
_isConnected = false;
_status = UserStatus.Offline;
StatusChanged?.Invoke(this, _status.ToString());
ConnectionStatusChanged?.Invoke(this, "已断开连接");
}
catch (Exception ex)
{
ConnectionStatusChanged?.Invoke(this, $"断开连接时出错: {ex.Message}");
}
}
public void SendTextMessage(string receiverId, string receiverName, string content, bool isPrivate = true)
{
if (!_isConnected) return;
var message = new Message
{
Type = isPrivate ? MessageType.PrivateMessage : MessageType.GroupMessage,
SenderId = _userId,
SenderName = _userName,
ReceiverId = receiverId,
ReceiverName = receiverName,
Content = content,
Timestamp = DateTime.Now
};
SendMessage(message);
StoreMessage(message);
}
public void SendFile(string receiverId, string filePath)
{
if (!_isConnected || !File.Exists(filePath)) return;
var fileInfo = new FileInfo(filePath);
var transferInfo = new FileTransferInfo
{
FileName = fileInfo.Name,
FileSize = fileInfo.Length,
FileId = Guid.NewGuid().ToString(),
SenderId = _userId,
ReceiverId = receiverId,
TransferTime = DateTime.Now
};
_fileTransfers[transferInfo.FileId] = transferInfo;
// 发送文件传输请求
var requestMessage = new Message
{
Type = MessageType.FileMessage,
SenderId = _userId,
ReceiverId = receiverId,
Content = "文件传输请求",
Data = System.Text.Encoding.UTF8.GetBytes(
Newtonsoft.Json.JsonConvert.SerializeObject(transferInfo)),
Timestamp = DateTime.Now
};
SendMessage(requestMessage);
}
public void AcceptFileTransfer(string fileId, string savePath)
{
if (_fileTransfers.TryGetValue(fileId, out FileTransferInfo transferInfo))
{
Task.Run(() => ReceiveFile(transferInfo, savePath));
}
}
public void RejectFileTransfer(string fileId)
{
_fileTransfers.Remove(fileId);
}
private async Task ReceiveFile(FileTransferInfo transferInfo, string savePath)
{
// 实现文件接收逻辑
// 这里需要实现分块接收和重组
}
public void ChangeStatus(UserStatus newStatus)
{
_status = newStatus;
StatusChanged?.Invoke(this, newStatus.ToString());
}
public void RequestUserList()
{
if (!_isConnected) return;
var request = new Message
{
Type = MessageType.UserList,
SenderId = _userId,
Timestamp = DateTime.Now
};
SendMessage(request);
}
private void SendMessage(Message message)
{
try
{
byte[] data = message.Serialize();
_networkStream.Write(data, 0, data.Length);
_networkStream.Flush();
}
catch (Exception ex)
{
ConnectionStatusChanged?.Invoke(this, $"发送消息失败: {ex.Message}");
}
}
private void SendUdpMessage(Message message, string ipAddress, int port)
{
try
{
byte[] data = message.Serialize();
_udpClient.Send(data, data.Length, ipAddress, port);
}
catch (Exception ex)
{
ConnectionStatusChanged?.Invoke(this, $"发送UDP消息失败: {ex.Message}");
}
}
private void ReceiveMessages()
{
byte[] buffer = new byte[4096];
while (_isRunning && _isConnected)
{
try
{
int bytesRead = _networkStream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
// 连接断开
Disconnect();
break;
}
byte[] messageData = new byte[bytesRead];
Array.Copy(buffer, messageData, bytesRead);
Message message = Message.Deserialize(messageData);
ProcessReceivedMessage(message);
}
catch (IOException)
{
Disconnect();
break;
}
catch (Exception ex)
{
ConnectionStatusChanged?.Invoke(this, $"接收消息时出错: {ex.Message}");
}
}
}
private void ReceiveUdpMessages()
{
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
while (_isRunning && _isConnected)
{
try
{
byte[] data = _udpClient.Receive(ref remoteEndPoint);
Message message = Message.Deserialize(data);
ProcessUdpMessage(message);
}
catch (SocketException) when (!_isRunning)
{
break;
}
catch (Exception ex)
{
ConnectionStatusChanged?.Invoke(this, $"接收UDP消息时出错: {ex.Message}");
}
}
}
private void ProcessReceivedMessage(Message message)
{
switch (message.Type)
{
case MessageType.Ack:
// 确认消息
break;
case MessageType.TextMessage:
case MessageType.PrivateMessage:
case MessageType.GroupMessage:
StoreMessage(message);
MessageReceived?.Invoke(this, message);
break;
case MessageType.UserList:
UpdateUserList(message);
break;
case MessageType.UserStatus:
UpdateUserStatus(message);
break;
case MessageType.FileMessage:
HandleFileMessage(message);
break;
case MessageType.Error:
ConnectionStatusChanged?.Invoke(this, $"服务器错误: {message.Content}");
break;
}
}
private void ProcessUdpMessage(Message message)
{
switch (message.Type)
{
case MessageType.VoiceMessage:
case MessageType.VideoMessage:
// 处理实时音视频流
MessageReceived?.Invoke(this, message);
break;
}
}
private void StoreMessage(Message message)
{
string chatId = message.Type == MessageType.GroupMessage
? message.ReceiverId
: message.SenderId == _userId ? message.ReceiverId : message.SenderId;
lock (_lock)
{
if (!_chatHistory.ContainsKey(chatId))
{
_chatHistory[chatId] = new List<Message>();
}
_chatHistory[chatId].Add(message);
}
}
private void UpdateUserList(Message message)
{
try
{
string json = System.Text.Encoding.UTF8.GetString(message.Data);
var users = Newtonsoft.Json.JsonConvert.DeserializeObject<List<UserInfo>>(json);
lock (_lock)
{
_onlineUsers.Clear();
foreach (var user in users)
{
_onlineUsers[user.UserId] = user;
}
}
UserListUpdated?.Invoke(this, users);
}
catch (Exception ex)
{
ConnectionStatusChanged?.Invoke(this, $"更新用户列表失败: {ex.Message}");
}
}
private void UpdateUserStatus(Message message)
{
string userId = message.SenderId;
if (Enum.TryParse<UserStatus>(message.Content, out UserStatus newStatus))
{
lock (_lock)
{
if (_onlineUsers.ContainsKey(userId))
{
_onlineUsers[userId].Status = newStatus;
_onlineUsers[userId].LastActive = DateTime.Now;
}
}
}
}
private void HandleFileMessage(Message message)
{
try
{
var transferInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<FileTransferInfo>(
System.Text.Encoding.UTF8.GetString(message.Data));
_fileTransfers[transferInfo.FileId] = transferInfo;
FileTransferRequested?.Invoke(this, transferInfo);
}
catch (Exception ex)
{
ConnectionStatusChanged?.Invoke(this, $"处理文件消息失败: {ex.Message}");
}
}
private void SendHeartbeats()
{
while (_isRunning && _isConnected)
{
try
{
Thread.Sleep(15000); // 15秒发送一次心跳
var heartbeat = new Message
{
Type = MessageType.Heartbeat,
SenderId = _userId,
Timestamp = DateTime.Now
};
SendMessage(heartbeat);
}
catch
{
// 心跳发送失败
}
}
}
public List<Message> GetChatHistory(string chatId)
{
lock (_lock)
{
return _chatHistory.ContainsKey(chatId)
? new List<Message>(_chatHistory[chatId])
: new List<Message>();
}
}
public List<UserInfo> GetOnlineUsers()
{
lock (_lock)
{
return _onlineUsers.Values.ToList();
}
}
public bool IsConnected => _isConnected;
public string UserId => _userId;
public string UserName => _userName;
public UserStatus Status => _status;
}
}
Client/UI/MainForm.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using InstantMessenger.Client;
using InstantMessenger.Shared;
namespace InstantMessenger.Client.UI
{
public partial class MainForm : Form
{
private ClientCore _client;
private string _currentChatId;
private Dictionary<string, TreeNode> _userNodes;
public MainForm()
{
InitializeComponent();
InitializeClient();
}
private void InitializeClient()
{
_client = new ClientCore();
_client.MessageReceived += OnMessageReceived;
_client.StatusChanged += OnStatusChanged;
_client.UserListUpdated += OnUserListUpdated;
_client.FileTransferRequested += OnFileTransferRequested;
_client.ConnectionStatusChanged += OnConnectionStatusChanged;
_userNodes = new Dictionary<string, TreeNode>();
}
private void MainForm_Load(object sender, EventArgs e)
{
ShowLoginDialog();
}
private void ShowLoginDialog()
{
using (var loginForm = new LoginForm())
{
if (loginForm.ShowDialog() == DialogResult.OK)
{
ConnectToServer(loginForm.ServerIp, loginForm.ServerPort,
loginForm.UserId, loginForm.UserName);
}
else
{
Close();
}
}
}
private void ConnectToServer(string serverIp, int serverPort, string userId, string userName)
{
Text = $"即时通讯 - {userName}";
if (_client.Connect(serverIp, serverPort, userId, userName))
{
// 创建公共群组节点
var publicGroupNode = new TreeNode("公共聊天室")
{
Tag = "public",
ImageKey = "group",
SelectedImageKey = "group"
};
treeViewUsers.Nodes.Add(publicGroupNode);
// 创建在线用户节点
var onlineNode = new TreeNode("在线用户")
{
ImageKey = "users",
SelectedImageKey = "users"
};
treeViewUsers.Nodes.Add(onlineNode);
// 请求用户列表
_client.RequestUserList();
}
else
{
MessageBox.Show("连接服务器失败", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
ShowLoginDialog();
}
}
private void OnConnectionStatusChanged(object sender, string status)
{
Invoke(new Action(() =>
{
toolStripStatusLabel.Text = status;
}));
}
private void OnStatusChanged(object sender, string status)
{
Invoke(new Action(() =>
{
lblStatus.Text = $"状态: {status}";
}));
}
private void OnUserListUpdated(object sender, List<UserInfo> users)
{
Invoke(new Action(() =>
{
UpdateUserTree(users);
}));
}
private void UpdateUserTree(List<UserInfo> users)
{
var onlineNode = treeViewUsers.Nodes["在线用户"];
if (onlineNode == null) return;
onlineNode.Nodes.Clear();
_userNodes.Clear();
foreach (var user in users)
{
if (user.UserId == _client.UserId) continue;
var userNode = new TreeNode($"{user.UserName} ({user.UserId})")
{
Tag = user.UserId,
ImageKey = GetStatusImageKey(user.Status),
SelectedImageKey = GetStatusImageKey(user.Status),
ToolTipText = $"状态: {user.Status}\n最后活动: {user.LastActive:HH:mm:ss}"
};
onlineNode.Nodes.Add(userNode);
_userNodes[user.UserId] = userNode;
}
onlineNode.Expand();
}
private string GetStatusImageKey(UserStatus status)
{
return status switch
{
UserStatus.Online => "online",
UserStatus.Away => "away",
UserStatus.Busy => "busy",
UserStatus.Invisible => "invisible",
_ => "offline"
};
}
private void OnMessageReceived(object sender, Message message)
{
Invoke(new Action(() =>
{
DisplayMessage(message);
}));
}
private void DisplayMessage(Message message)
{
string chatId = message.Type == MessageType.GroupMessage
? message.ReceiverId
: message.SenderId == _client.UserId ? message.ReceiverId : message.SenderId;
string displayText;
if (message.Type == MessageType.GroupMessage)
{
displayText = $"[{message.Timestamp:HH:mm:ss}] [{message.SenderName}] {message.Content}";
}
else
{
displayText = $"[{message.Timestamp:HH:mm:ss}] {message.Content}";
}
// 添加到聊天记录
if (_currentChatId == chatId)
{
txtChat.AppendText(displayText + Environment.NewLine);
txtChat.ScrollToCaret();
}
}
private void OnFileTransferRequested(object sender, FileTransferInfo transferInfo)
{
Invoke(new Action(() =>
{
using (var dialog = new FileTransferDialog(transferInfo))
{
if (dialog.ShowDialog() == DialogResult.OK)
{
_client.AcceptFileTransfer(transferInfo.FileId, dialog.SavePath);
}
else
{
_client.RejectFileTransfer(transferInfo.FileId);
}
}
}));
}
private void treeViewUsers_AfterSelect(object sender, TreeViewEventArgs e)
{
if (e.Node.Tag == null) return;
_currentChatId = e.Node.Tag.ToString();
txtChat.Clear();
// 加载聊天历史
var history = _client.GetChatHistory(_currentChatId);
foreach (var message in history)
{
DisplayMessage(message);
}
// 更新UI状态
bool isGroup = e.Node.ImageKey == "group";
txtMessage.Enabled = true;
btnSend.Enabled = true;
btnSendFile.Enabled = !isGroup; // 群聊暂不支持文件传输
lblCurrentChat.Text = isGroup
? $"群聊: {e.Node.Text}"
: $"私聊: {e.Node.Text}";
}
private void btnSend_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtMessage.Text) ||
string.IsNullOrEmpty(_currentChatId)) return;
string message = txtMessage.Text;
bool isGroup = _currentChatId == "public";
if (isGroup)
{
_client.SendTextMessage(_currentChatId, "公共聊天室", message, false);
}
else
{
var selectedNode = treeViewUsers.SelectedNode;
string receiverName = selectedNode?.Text.Split('(')[0].Trim();
_client.SendTextMessage(_currentChatId, receiverName, message, true);
}
txtMessage.Clear();
txtMessage.Focus();
}
private void btnSendFile_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(_currentChatId) || _currentChatId == "public") return;
using (OpenFileDialog dialog = new OpenFileDialog())
{
dialog.Title = "选择要发送的文件";
dialog.Filter = "所有文件 (*.*)|*.*";
if (dialog.ShowDialog() == DialogResult.OK)
{
_client.SendFile(_currentChatId, dialog.FileName);
}
}
}
private void txtMessage_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)Keys.Enter && !ModifierKeys.HasFlag(Keys.Shift))
{
e.Handled = true;
btnSend_Click(sender, e);
}
}
private void 连接CToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_client.IsConnected)
{
_client.Disconnect();
}
ShowLoginDialog();
}
private void 退出XToolStripMenuItem_Click(object sender, EventArgs e)
{
Close();
}
private void 在线OToolStripMenuItem_Click(object sender, EventArgs e)
{
_client.ChangeStatus(UserStatus.Online);
}
private void 忙碌BToolStripMenuItem_Click(object sender, EventArgs e)
{
_client.ChangeStatus(UserStatus.Busy);
}
private void 离开AToolStripMenuItem_Click(object sender, EventArgs e)
{
_client.ChangeStatus(UserStatus.Away);
}
private void 隐身IToolStripMenuItem_Click(object sender, EventArgs e)
{
_client.ChangeStatus(UserStatus.Invisible);
}
private void 关于AToolStripMenuItem_Click(object sender, EventArgs e)
{
MessageBox.Show("即时通讯工具 v1.0\n\n功能:\n- 实时文字聊天\n- 私聊和群聊\n- 文件传输\n- 用户状态管理\n\n作者:AI助手",
"关于", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
_client?.Disconnect();
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.文件FToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.连接CToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
this.退出XToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.状态SToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.在线OToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.忙碌BToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.离开AToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.隐身IToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.帮助HToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.关于AToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
this.treeViewUsers = new System.Windows.Forms.TreeView();
this.splitContainer2 = new System.Windows.Forms.SplitContainer();
this.txtChat = new System.Windows.Forms.TextBox();
this.panel1 = new System.Windows.Forms.Panel();
this.txtMessage = new System.Windows.Forms.TextBox();
this.btnSendFile = new System.Windows.Forms.Button();
this.btnSend = new System.Windows.Forms.Button();
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.toolStripStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
this.lblStatus = new System.Windows.Forms.Label();
this.lblCurrentChat = new System.Windows.Forms.Label();
this.imageList1 = new System.Windows.Forms.ImageList();
this.menuStrip1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
this.splitContainer1.Panel1.SuspendLayout();
this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit();
this.splitContainer2.Panel1.SuspendLayout();
this.splitContainer2.Panel2.SuspendLayout();
this.splitContainer2.SuspendLayout();
this.panel1.SuspendLayout();
this.statusStrip1.SuspendLayout();
this.SuspendLayout();
// menuStrip1
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.文件FToolStripMenuItem,
this.状态SToolStripMenuItem,
this.帮助HToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(800, 24);
this.menuStrip1.TabIndex = 0;
this.menuStrip1.Text = "menuStrip1";
// 文件FToolStripMenuItem
this.文件FToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.连接CToolStripMenuItem,
this.toolStripSeparator1,
this.退出XToolStripMenuItem});
this.文件FToolStripMenuItem.Name = "文件FToolStripMenuItem";
this.文件FToolStripMenuItem.Size = new System.Drawing.Size(57, 20);
this.文件FToolStripMenuItem.Text = "文件(&F)";
// 连接CToolStripMenuItem
this.连接CToolStripMenuItem.Name = "连接CToolStripMenuItem";
this.连接CToolStripMenuItem.Size = new System.Drawing.Size(120, 22);
this.连接CToolStripMenuItem.Text = "连接(&C)";
// toolStripSeparator1
this.toolStripSeparator1.Name = "toolStripSeparator1";
this.toolStripSeparator1.Size = new System.Drawing.Size(117, 6);
// 退出XToolStripMenuItem
this.退出XToolStripMenuItem.Name = "退出XToolStripMenuItem";
this.退出XToolStripMenuItem.Size = new System.Drawing.Size(120, 22);
this.退出XToolStripMenuItem.Text = "退出(&X)";
// 状态SToolStripMenuItem
this.状态SToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.在线OToolStripMenuItem,
this.忙碌BToolStripMenuItem,
this.离开AToolStripMenuItem,
this.隐身IToolStripMenuItem});
this.状态SToolStripMenuItem.Name = "状态SToolStripMenuItem";
this.状态SToolStripMenuItem.Size = new System.Drawing.Size(58, 20);
this.状态SToolStripMenuItem.Text = "状态(&S)";
// 在线OToolStripMenuItem
this.在线OToolStripMenuItem.Name = "在线OToolStripMenuItem";
this.在线OToolStripMenuItem.Size = new System.Drawing.Size(116, 22);
this.在线OToolStripMenuItem.Text = "在线(&O)";
// 忙碌BToolStripMenuItem
this.忙碌BToolStripMenuItem.Name = "忙碌BToolStripMenuItem";
this.忙碌BToolStripMenuItem.Size = new System.Drawing.Size(116, 22);
this.忙碌BToolStripMenuItem.Text = "忙碌(&B)";
// 离开AToolStripMenuItem
this.离开AToolStripMenuItem.Name = "离开AToolStripMenuItem";
this.离开AToolStripMenuItem.Size = new System.Drawing.Size(116, 22);
this.离开AToolStripMenuItem.Text = "离开(&A)";
// 隐身IToolStripMenuItem
this.隐身IToolStripMenuItem.Name = "隐身IToolStripMenuItem";
this.隐身IToolStripMenuItem.Size = new System.Drawing.Size(116, 22);
this.隐身IToolStripMenuItem.Text = "隐身(&I)";
// 帮助HToolStripMenuItem
this.帮助HToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.关于AToolStripMenuItem});
this.帮助HToolStripMenuItem.Name = "帮助HToolStripMenuItem";
this.帮助HToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
this.帮助HToolStripMenuItem.Text = "帮助(&H)";
// 关于AToolStripMenuItem
this.关于AToolStripMenuItem.Name = "关于AToolStripMenuItem";
this.关于AToolStripMenuItem.Size = new System.Drawing.Size(110, 22);
this.关于AToolStripMenuItem.Text = "关于(&A)";
// splitContainer1
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer1.Location = new System.Drawing.Point(0, 24);
this.splitContainer1.Name = "splitContainer1";
// splitContainer1.Panel1
this.splitContainer1.Panel1.Controls.Add(this.treeViewUsers);
// splitContainer1.Panel2
this.splitContainer1.Panel2.Controls.Add(this.splitContainer2);
this.splitContainer1.Size = new System.Drawing.Size(800, 426);
this.splitContainer1.SplitterDistance = 200;
this.splitContainer1.TabIndex = 1;
// treeViewUsers
this.treeViewUsers.Dock = System.Windows.Forms.DockStyle.Fill;
this.treeViewUsers.Location = new System.Drawing.Point(0, 0);
this.treeViewUsers.Name = "treeViewUsers";
this.treeViewUsers.Size = new System.Drawing.Size(200, 426);
this.treeViewUsers.TabIndex = 0;
// splitContainer2
this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer2.Location = new System.Drawing.Point(0, 0);
this.splitContainer2.Name = "splitContainer2";
this.splitContainer2.Orientation = System.Windows.Forms.Orientation.Horizontal;
// splitContainer2.Panel1
this.splitContainer2.Panel1.Controls.Add(this.txtChat);
this.splitContainer2.Panel1.Controls.Add(this.lblCurrentChat);
// splitContainer2.Panel2
this.splitContainer2.Panel2.Controls.Add(this.panel1);
this.splitContainer2.Size = new System.Drawing.Size(596, 426);
this.splitContainer2.SplitterDistance = 350;
this.splitContainer2.TabIndex = 0;
// txtChat
this.txtChat.Dock = System.Windows.Forms.DockStyle.Fill;
this.txtChat.Location = new System.Drawing.Point(0, 20);
this.txtChat.Multiline = true;
this.txtChat.Name = "txtChat";
this.txtChat.ReadOnly = true;
this.txtChat.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtChat.Size = new System.Drawing.Size(596, 330);
this.txtChat.TabIndex = 0;
// panel1
this.panel1.Controls.Add(this.lblStatus);
this.panel1.Controls.Add(this.txtMessage);
this.panel1.Controls.Add(this.btnSendFile);
this.panel1.Controls.Add(this.btnSend);
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(596, 72);
this.panel1.TabIndex = 0;
// txtMessage
this.txtMessage.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.txtMessage.Location = new System.Drawing.Point(3, 3);
this.txtMessage.Multiline = true;
this.txtMessage.Name = "txtMessage";
this.txtMessage.Size = new System.Drawing.Size(490, 66);
this.txtMessage.TabIndex = 0;
// btnSendFile
this.btnSendFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnSendFile.Enabled = false;
this.btnSendFile.Location = new System.Drawing.Point(499, 32);
this.btnSendFile.Name = "btnSendFile";
this.btnSendFile.Size = new System.Drawing.Size(94, 37);
this.btnSendFile.TabIndex = 2;
this.btnSendFile.Text = "发送文件";
this.btnSendFile.UseVisualStyleBackColor = true;
// btnSend
this.btnSend.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnSend.Enabled = false;
this.btnSend.Location = new System.Drawing.Point(499, 3);
this.btnSend.Name = "btnSend";
this.btnSend.Size = new System.Drawing.Size(94, 23);
this.btnSend.TabIndex = 1;
this.btnSend.Text = "发送";
this.btnSend.UseVisualStyleBackColor = true;
// statusStrip1
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripStatusLabel});
this.statusStrip1.Location = new System.Drawing.Point(0, 450);
this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(800, 22);
this.statusStrip1.TabIndex = 2;
this.statusStrip1.Text = "statusStrip1";
// toolStripStatusLabel
this.toolStripStatusLabel.Name = "toolStripStatusLabel";
this.toolStripStatusLabel.Size = new System.Drawing.Size(59, 17);
this.toolStripStatusLabel.Text = "未连接";
// lblStatus
this.lblStatus.AutoSize = true;
this.lblStatus.Location = new System.Drawing.Point(3, 50);
this.lblStatus.Name = "lblStatus";
this.lblStatus.Size = new System.Drawing.Size(47, 12);
this.lblStatus.TabIndex = 3;
this.lblStatus.Text = "状态: ";
// lblCurrentChat
this.lblCurrentChat.Dock = System.Windows.Forms.DockStyle.Top;
this.lblCurrentChat.Location = new System.Drawing.Point(0, 0);
this.lblCurrentChat.Name = "lblCurrentChat";
this.lblCurrentChat.Size = new System.Drawing.Size(596, 20);
this.lblCurrentChat.TabIndex = 1;
this.lblCurrentChat.Text = "未选择聊天";
this.lblCurrentChat.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
// imageList1
this.imageList1.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit;
this.imageList1.ImageSize = new System.Drawing.Size(16, 16);
this.imageList1.TransparentColor = System.Drawing.Color.Transparent;
// MainForm
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 472);
this.Controls.Add(this.splitContainer1);
this.Controls.Add(this.statusStrip1);
this.Controls.Add(this.menuStrip1);
this.MainMenuStrip = this.menuStrip1;
this.Name = "MainForm";
this.Text = "即时通讯工具";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing);
this.Load += new System.EventHandler(this.MainForm_Load);
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
this.splitContainer1.Panel1.ResumeLayout(false);
this.splitContainer1.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
this.splitContainer1.ResumeLayout(false);
this.splitContainer2.Panel1.ResumeLayout(false);
this.splitContainer2.Panel1.PerformLayout();
this.splitContainer2.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit();
this.splitContainer2.ResumeLayout(false);
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
this.statusStrip1.ResumeLayout(false);
this.statusStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.MenuStrip menuStrip1;
private System.Windows.Forms.ToolStripMenuItem 文件FToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem 连接CToolStripMenuItem;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
private System.Windows.Forms.ToolStripMenuItem 退出XToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem 状态SToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem 在线OToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem 忙碌BToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem 离开AToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem 隐身IToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem 帮助HToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem 关于AToolStripMenuItem;
private System.Windows.Forms.SplitContainer splitContainer1;
private System.Windows.Forms.TreeView treeViewUsers;
private System.Windows.Forms.SplitContainer splitContainer2;
private System.Windows.Forms.TextBox txtChat;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.TextBox txtMessage;
private System.Windows.Forms.Button btnSendFile;
private System.Windows.Forms.Button btnSend;
private System.Windows.Forms.StatusStrip statusStrip1;
private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel;
private System.Windows.Forms.Label lblStatus;
private System.Windows.Forms.Label lblCurrentChat;
private System.Windows.Forms.ImageList imageList1;
}
}
4. 辅助窗体
Client/UI/LoginForm.cs
using System;
using System.Windows.Forms;
namespace InstantMessenger.Client.UI
{
public partial class LoginForm : Form
{
public string ServerIp { get; private set; }
public int ServerPort { get; private set; }
public string UserId { get; private set; }
public string UserName { get; private set; }
public LoginForm()
{
InitializeComponent();
}
private void btnConnect_Click(object sender, EventArgs e)
{
if (ValidateInput())
{
ServerIp = txtServerIp.Text;
ServerPort = int.Parse(txtServerPort.Text);
UserId = txtUserId.Text;
UserName = txtUserName.Text;
DialogResult = DialogResult.OK;
Close();
}
}
private bool ValidateInput()
{
if (string.IsNullOrWhiteSpace(txtServerIp.Text))
{
MessageBox.Show("请输入服务器IP地址", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if (!int.TryParse(txtServerPort.Text, out int port) || port < 1 || port > 65535)
{
MessageBox.Show("请输入有效的端口号", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if (string.IsNullOrWhiteSpace(txtUserId.Text))
{
MessageBox.Show("请输入用户ID", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if (string.IsNullOrWhiteSpace(txtUserName.Text))
{
MessageBox.Show("请输入用户名", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
return true;
}
private void btnCancel_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
Close();
}
private void InitializeComponent()
{
this.lblServerIp = new System.Windows.Forms.Label();
this.txtServerIp = new System.Windows.Forms.TextBox();
this.lblServerPort = new System.Windows.Forms.Label();
this.txtServerPort = new System.Windows.Forms.TextBox();
this.lblUserId = new System.Windows.Forms.Label();
this.txtUserId = new System.Windows.Forms.TextBox();
this.lblUserName = new System.Windows.Forms.Label();
this.txtUserName = new System.Windows.Forms.TextBox();
this.btnConnect = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.SuspendLayout();
// lblServerIp
this.lblServerIp.AutoSize = true;
this.lblServerIp.Location = new System.Drawing.Point(12, 15);
this.lblServerIp.Name = "lblServerIp";
this.lblServerIp.Size = new System.Drawing.Size(65, 12);
this.lblServerIp.TabIndex = 0;
this.lblServerIp.Text = "服务器IP:";
// txtServerIp
this.txtServerIp.Location = new System.Drawing.Point(83, 12);
this.txtServerIp.Name = "txtServerIp";
this.txtServerIp.Size = new System.Drawing.Size(150, 21);
this.txtServerIp.TabIndex = 1;
this.txtServerIp.Text = "127.0.0.1";
// lblServerPort
this.lblServerPort.AutoSize = true;
this.lblServerPort.Location = new System.Drawing.Point(12, 42);
this.lblServerPort.Name = "lblServerPort";
this.lblServerPort.Size = new System.Drawing.Size(65, 12);
this.lblServerPort.TabIndex = 2;
this.lblServerPort.Text = "服务器端口:";
// txtServerPort
this.txtServerPort.Location = new System.Drawing.Point(83, 39);
this.txtServerPort.Name = "txtServerPort";
this.txtServerPort.Size = new System.Drawing.Size(150, 21);
this.txtServerPort.TabIndex = 3;
this.txtServerPort.Text = "8888";
// lblUserId
this.lblUserId.AutoSize = true;
this.lblUserId.Location = new System.Drawing.Point(12, 69);
this.lblUserId.Name = "lblUserId";
this.lblUserId.Size = new System.Drawing.Size(53, 12);
this.lblUserId.TabIndex = 4;
this.lblUserId.Text = "用户ID:";
// txtUserId
this.txtUserId.Location = new System.Drawing.Point(83, 66);
this.txtUserId.Name = "txtUserId";
this.txtUserId.Size = new System.Drawing.Size(150, 21);
this.txtUserId.TabIndex = 5;
// lblUserName
this.lblUserName.AutoSize = true;
this.lblUserName.Location = new System.Drawing.Point(12, 96);
this.lblUserName.Name = "lblUserName";
this.lblUserName.Size = new System.Drawing.Size(53, 12);
this.lblUserName.TabIndex = 6;
this.lblUserName.Text = "用户名:";
// txtUserName
this.txtUserName.Location = new System.Drawing.Point(83, 93);
this.txtUserName.Name = "txtUserName";
this.txtUserName.Size = new System.Drawing.Size(150, 21);
this.txtUserName.TabIndex = 7;
// btnConnect
this.btnConnect.Location = new System.Drawing.Point(77, 130);
this.btnConnect.Name = "btnConnect";
this.btnConnect.Size = new System.Drawing.Size(75, 23);
this.btnConnect.TabIndex = 8;
this.btnConnect.Text = "连接";
this.btnConnect.UseVisualStyleBackColor = true;
// btnCancel
this.btnCancel.Location = new System.Drawing.Point(158, 130);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(75, 23);
this.btnCancel.TabIndex = 9;
this.btnCancel.Text = "取消";
this.btnCancel.UseVisualStyleBackColor = true;
// LoginForm
this.AcceptButton = this.btnConnect;
this.CancelButton = this.btnCancel;
this.ClientSize = new System.Drawing.Size(250, 165);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.btnConnect);
this.Controls.Add(this.txtUserName);
this.Controls.Add(this.lblUserName);
this.Controls.Add(this.txtUserId);
this.Controls.Add(this.lblUserId);
this.Controls.Add(this.txtServerPort);
this.Controls.Add(this.lblServerPort);
this.Controls.Add(this.txtServerIp);
this.Controls.Add(this.lblServerIp);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "LoginForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "连接到服务器";
this.ResumeLayout(false);
this.PerformLayout();
}
private System.Windows.Forms.Label lblServerIp;
private System.Windows.Forms.TextBox txtServerIp;
private System.Windows.Forms.Label lblServerPort;
private System.Windows.Forms.TextBox txtServerPort;
private System.Windows.Forms.Label lblUserId;
private System.Windows.Forms.TextBox txtUserId;
private System.Windows.Forms.Label lblUserName;
private System.Windows.Forms.TextBox txtUserName;
private System.Windows.Forms.Button btnConnect;
private System.Windows.Forms.Button btnCancel;
}
}
参考代码 c#即时通讯工具 www.youwenfan.com/contentcnp/112225.html
5. 项目配置和运行说明
创建项目步骤:
-
创建解决方案和项目:
InstantMessenger/ ├── InstantMessenger.sln ├── Server/ (控制台应用) ├── Client/ (Windows窗体应用) └── Shared/ (类库) -
添加引用:
- Client项目引用Shared项目
- Server项目引用Shared项目
-
添加NuGet包:
- Newtonsoft.Json (用于JSON序列化)
运行步骤:
-
启动服务器:
cd Server/bin/Debug InstantMessenger.Server.exe # 或带参数运行 InstantMessenger.Server.exe 0.0.0.0 8888 -
启动客户端:
- 运行Client项目
- 在登录界面输入:
- 服务器IP: 127.0.0.1
- 端口: 8888
- 用户ID和用户名
功能特点:
-
双协议支持:
- TCP:可靠的消息传输(文字、文件)
- UDP:实时音视频流传输
-
多种聊天模式:
- 私聊
- 群聊(公共聊天室)
- 文件传输
-
用户状态管理:
- 在线、离开、忙碌、隐身、离线
-
心跳机制:
- 自动检测连接状态
- 清理不活跃用户
-
可扩展架构:
- 模块化设计
- 易于添加新功能
扩展建议:
-
数据库支持:
- 使用SQLite或SQL Server存储用户信息和聊天记录
-
加密通信:
- 使用SSL/TLS加密TCP连接
- 端到端加密消息内容
-
多媒体功能:
- 语音通话
- 视频通话
- 屏幕共享
-
高级功能:
- 消息撤回
- 已读回执
- 离线消息
- 群组管理
浙公网安备 33010602011771号