在 `Program.cs` 文件中,修改了获取发送者完整端点的代码。原来的代码使用 `socket.RemoteEndPoint?.ToString() ?? "Unknown"`,而现在修改为 `socket.RemoteEndPoint?.ToString() ?? "Unknown:0"`。这个变化确保在没有可用的远程端点时,返回的字符串包含端口信息(默认为0),以便于后续的验证逻辑。
342 lines
19 KiB
C#
342 lines
19 KiB
C#
using System.Net.Sockets;
|
||
using System.Net.Http;
|
||
using log4net;
|
||
using log4net.Config;
|
||
using System.Net;
|
||
using System.Data.SQLite;
|
||
using chatserver.Data;
|
||
using System.Text.Json;
|
||
using System.Reflection;
|
||
using static log4net.Appender.FileAppender;
|
||
|
||
[assembly: XmlConfigurator(ConfigFile = "config/log4net.config", Watch = true)]
|
||
namespace chatserver
|
||
{
|
||
internal class ChatServer
|
||
{
|
||
private static readonly ILog log = LogManager.GetLogger(typeof(ChatServer));
|
||
private static readonly object Client_lock = new object();
|
||
private static List<Socket> Client = new();
|
||
private static List<User> LoginUser = new();
|
||
private static Socket? Server;
|
||
private static SQLiteConnection? User_db;
|
||
|
||
static void Main(string[] args)
|
||
{
|
||
//XmlConfigurator.Configure();
|
||
log.Info("Hello World!");
|
||
Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||
Server.Bind(new IPEndPoint(IPAddress.Any, 52006));
|
||
Server.Listen(20);
|
||
OpenUser_db();
|
||
log.Info("服务器以在52006端口监听");
|
||
while (true)
|
||
{
|
||
try
|
||
{
|
||
Socket client = Server.Accept();
|
||
lock (Client_lock) { Client.Add(client); }
|
||
log.Info($"用户 {client.RemoteEndPoint} 连接");
|
||
Thread thread = new Thread(() => HandleClient(client));
|
||
thread.Start();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
log.Error("Error accepting client connection: " + ex.Message);
|
||
}
|
||
}
|
||
}
|
||
public static void OpenUser_db()
|
||
{
|
||
log.Info("正在打开数据库连接...");
|
||
User_db = new SQLiteConnection("Data Source=ServerUser.db;Version=3;"); //没有数据库则自动创建
|
||
User_db.Open();
|
||
EnsureUsersTableExists(); // 确保users表存在
|
||
log.Info("数据库连接已打开");
|
||
}
|
||
static void HandleClient(Socket socket)
|
||
{
|
||
try
|
||
{
|
||
while (true)
|
||
|
||
{
|
||
byte[] buffer = new byte[1024];
|
||
int received = socket.Receive(buffer);
|
||
if (received == 0) break; // 客户端断开连接
|
||
string message = System.Text.Encoding.UTF8.GetString(buffer, 0, received);
|
||
log.Info("Received message: " + message);
|
||
var Type = JsonSerializer.Deserialize<TypeData>(message);
|
||
if (Type != null)
|
||
{
|
||
if (Type.type == "register")
|
||
{
|
||
var sginuser = JsonSerializer.Deserialize<SignData>(message);
|
||
if (sginuser != null && sginuser.username != null && sginuser.password != null)
|
||
{
|
||
log.Info($"用户 {sginuser.username} 正在注册");
|
||
if (string.IsNullOrEmpty(sginuser.username) || string.IsNullOrEmpty(sginuser.password))
|
||
{
|
||
log.Warn("用户名或密码不能为空");
|
||
var emptyResult = new SignResultData { type = "register", status = "error_-1", message = "用户名或密码不能为空" };
|
||
socket.Send(JsonSerializer.SerializeToUtf8Bytes(emptyResult));
|
||
continue; // 如果用户名或密码为空,则跳过注册流程
|
||
}
|
||
if (sginuser.username.Length < 2 || sginuser.username.Length > 20)
|
||
{
|
||
log.Warn($"用户注册时 {sginuser.username} 用户名长度不符合要求");
|
||
var lengthResult = new SignResultData { type = "register", status = "error_2", message = "用户名长度必须在2到20个字符之间" };
|
||
socket.Send(JsonSerializer.SerializeToUtf8Bytes(lengthResult));
|
||
continue; // 如果用户名长度不符合要求,则跳过注册流程
|
||
}
|
||
if (sginuser.password.Length < 4 || sginuser.password.Length > 20)
|
||
{
|
||
log.Warn($"用户注册时 {sginuser.username} 密码长度不符合要求");
|
||
var weakPwdResult = new SignResultData { type = "register", status = "error_1", message = "密码长度必须在4到20个字符之间" };
|
||
socket.Send(JsonSerializer.SerializeToUtf8Bytes(weakPwdResult));
|
||
continue; // 如果密码过弱,则跳过注册流程
|
||
}
|
||
if (UserExists(sginuser.username))
|
||
{
|
||
log.Warn($"用户 {sginuser.username} 已存在");
|
||
var Result = new SignResultData { type = "register", status = "error_0", message = "用户名已存在" };
|
||
socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(Result)));
|
||
continue;// 如果用户名已存在,则跳过注册流程
|
||
}
|
||
var cmd = new SQLiteCommand("INSERT INTO users (userid, username, password) VALUES (@userid, @username, @password)", User_db);
|
||
var timedUlid = Ulid.NewUlid(DateTimeOffset.UtcNow);
|
||
cmd.Parameters.AddWithValue("@userid", timedUlid);
|
||
cmd.Parameters.AddWithValue("@username", sginuser.username);
|
||
cmd.Parameters.AddWithValue("@password", sginuser.password);
|
||
cmd.ExecuteNonQuery();
|
||
log.Info($"用户 {sginuser.username} 注册成功(id:{timedUlid})");
|
||
var result = new SignResultData { type = "register", status = "succeed", message = "注册成功" };
|
||
socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(result)));
|
||
}
|
||
}
|
||
else if (Type.type == "login")
|
||
{
|
||
var loginData = JsonSerializer.Deserialize<LoginData>(message);
|
||
if (loginData != null)
|
||
{
|
||
if (string.IsNullOrEmpty(loginData.username) || string.IsNullOrEmpty(loginData.password))
|
||
{
|
||
log.Warn("用户名或密码不能为空");
|
||
var emptyResult = new LoginResultData { type = "login", status = "error_-1", message = "用户名或密码不能为空" };
|
||
socket.Send(JsonSerializer.SerializeToUtf8Bytes(emptyResult));
|
||
continue;
|
||
}
|
||
if (loginData.username.Length < 2 || loginData.username.Length > 20)
|
||
{
|
||
log.Warn($"用户登录时 {loginData.username} 用户名长度不符合要求");
|
||
var lengthResult = new LoginResultData { type = "login", status = "error_2", message = "用户名长度必须在2到20个字符之间" };
|
||
socket.Send(JsonSerializer.SerializeToUtf8Bytes(lengthResult));
|
||
continue;
|
||
}
|
||
if (loginData.password.Length > 20)
|
||
{
|
||
log.Warn($"用户登录时 {loginData.username} 密码长度不符合要求");
|
||
var weakPwdResult = new LoginResultData { type = "login", status = "error_1", message = "密码长度不能超过20个字符" };
|
||
socket.Send(JsonSerializer.SerializeToUtf8Bytes(weakPwdResult));
|
||
continue;
|
||
}
|
||
var Authentication = UserAuthentication(loginData.username, loginData.password);
|
||
if (Authentication != "")
|
||
{
|
||
log.Info($"用户 {loginData.username} 登录成功 (id:{Authentication})");
|
||
var timedUlid = Ulid.NewUlid(DateTimeOffset.UtcNow);
|
||
lock (Client_lock)
|
||
{
|
||
LoginUser.Add(new User
|
||
{
|
||
UserId = Authentication,
|
||
LoginIP = socket.RemoteEndPoint?.ToString() ?? "Unknown:0",
|
||
Avatar = null,
|
||
token = timedUlid.ToString()
|
||
});
|
||
}
|
||
var result = new LoginResultData
|
||
{
|
||
type = "login",
|
||
status = "succeed",
|
||
message = "登录成功",
|
||
token = timedUlid.ToString(),
|
||
username = loginData.username,
|
||
userid = Authentication
|
||
};
|
||
socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(result)));
|
||
}
|
||
else
|
||
{
|
||
log.Warn($"用户 {loginData.username} 登录失败,用户名或密码错误");
|
||
var result = new LoginResultData { type = "login", status = "error_0", message = "用户名或密码错误" };
|
||
socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(result)));
|
||
}
|
||
}
|
||
}
|
||
else if (Type.type == "chat")
|
||
{
|
||
var chatData = JsonSerializer.Deserialize<ChatData>(message);
|
||
if (chatData != null && chatData.message != null)
|
||
{
|
||
log.Info($"接收到聊天消息: {chatData.message}");
|
||
if (Client.Count == 0)
|
||
{
|
||
log.Warn("没有客户端连接,取消发送消息。");
|
||
return;
|
||
}
|
||
// 获取发送者完整端点(IP: 端口)
|
||
string senderEndpoint = socket.RemoteEndPoint?.ToString() ?? "Unknown:0";
|
||
// ==== 修改:使用完整端点验证(非IP部分)====
|
||
var loginUser = LoginUser.FirstOrDefault(u => u.LoginIP == senderEndpoint && u.UserId == chatData.userid);
|
||
if (loginUser == null || loginUser.token != chatData.token)
|
||
{
|
||
log.Warn($"聊天消息验证失败:端点 {senderEndpoint} 未登录或 token 不匹配");
|
||
var errorData = new ChatRegisterData
|
||
{
|
||
type = "chat",
|
||
userid = chatData.userid ?? "Unid",
|
||
status = "error_0",
|
||
message = "未登录或token无效,无法发送消息",
|
||
msgtype = MessageType.Text,
|
||
timestamp = DateTime.Now
|
||
};
|
||
socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(errorData)));
|
||
continue;
|
||
}
|
||
|
||
List<Socket> clientsCopy;
|
||
List<User> usersCopy;
|
||
// 1. 获取集合快照
|
||
lock (Client_lock)
|
||
{
|
||
clientsCopy = new List<Socket>(Client); // 创建客户端列表副本
|
||
usersCopy = new List<User>(LoginUser); // 创建用户列表副本
|
||
}
|
||
var chatRegisterData = new ChatRegisterData
|
||
{
|
||
type = "chat",
|
||
userid = chatData.userid ?? "Unid",
|
||
user = GetUsernameByUserId(chatData.userid!),
|
||
message = chatData.message,
|
||
msgtype = chatData.msgtype ?? MessageType.Text,
|
||
status = "succeed",
|
||
timestamp = DateTime.Now
|
||
};
|
||
foreach (var user in usersCopy)
|
||
{
|
||
// 查找匹配的客户端
|
||
var targetClient = clientsCopy.FirstOrDefault(c =>
|
||
c.RemoteEndPoint?.ToString() == user.LoginIP);
|
||
|
||
if (targetClient != null)
|
||
{
|
||
try
|
||
{
|
||
// ==== 修改:添加发送异常处理 ====
|
||
targetClient.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(chatRegisterData)));
|
||
}
|
||
catch (SocketException ex)
|
||
{
|
||
log.Error($"发送消息到 {user.LoginIP} 失败: {ex.SocketErrorCode}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
log.Error($"发送消息异常: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
log.Warn("接收到无效的聊天消息");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
log.Warn("未知的请求类型: " + Type.type);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (SocketException ex)
|
||
{
|
||
log.Error("Socket error: " + ex.Message);
|
||
}
|
||
catch (JsonException ex)
|
||
{
|
||
log.Error("JSON parsing error: " + ex.Message);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
log.Error("Error handling client: " + ex.Message);
|
||
}
|
||
finally
|
||
{
|
||
lock (Client_lock)
|
||
{
|
||
log.Info($"用户 {socket.RemoteEndPoint} 已断开连接");
|
||
var disconnectedIp = socket.RemoteEndPoint?.ToString();
|
||
if (!string.IsNullOrEmpty(disconnectedIp))
|
||
{
|
||
LoginUser.RemoveAll(u => u.LoginIP.StartsWith(disconnectedIp));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 查询User_db是否有相同用户名
|
||
/// </summary>
|
||
/// <param name="username"></param>
|
||
/// <returns></returns>
|
||
static bool UserExists(string username)
|
||
{
|
||
using var cmd = new SQLiteCommand("SELECT COUNT(*) FROM users WHERE username = @username", User_db);
|
||
cmd.Parameters.AddWithValue("@username", username);
|
||
var count = Convert.ToInt32(cmd.ExecuteScalar());
|
||
return count > 0;
|
||
}
|
||
/// <summary>
|
||
/// 验证用户登录信息并返回userid结果
|
||
/// </summary>
|
||
/// <param name="username"></param>
|
||
/// <param name="password"></param>
|
||
/// <returns></returns>
|
||
static string UserAuthentication(string username, string password)
|
||
{
|
||
using var cmd = new SQLiteCommand("SELECT userid FROM users WHERE username = @username AND password = @password", User_db);
|
||
cmd.Parameters.AddWithValue("@username", username);
|
||
cmd.Parameters.AddWithValue("@password", password);
|
||
var result = cmd.ExecuteScalar();
|
||
return result != null ? result.ToString()! : string.Empty;
|
||
}
|
||
// 在ChatServer类中添加一个方法用于初始化users表
|
||
private static void EnsureUsersTableExists()
|
||
{
|
||
using var cmd = new SQLiteCommand(@"
|
||
CREATE TABLE IF NOT EXISTS users (
|
||
userid TEXT PRIMARY KEY,
|
||
username TEXT NOT NULL UNIQUE,
|
||
password TEXT NOT NULL
|
||
)", User_db);
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
/// <summary>
|
||
/// 根据userid查询对应的用户名
|
||
/// </summary>
|
||
/// <param name="userid"></param>
|
||
/// <returns>用户名,如果不存在则返回</returns>
|
||
static string GetUsernameByUserId(string userid)
|
||
{
|
||
if (string.IsNullOrEmpty(userid) || userid == "Unid")
|
||
{
|
||
return "Unnamed"; // 如果userid为空或为"Unid",返回默认用户名
|
||
}
|
||
using var cmd = new SQLiteCommand("SELECT username FROM users WHERE userid = @userid", User_db);
|
||
cmd.Parameters.AddWithValue("@userid", userid);
|
||
var result = cmd.ExecuteScalar();
|
||
return result != null ? result.ToString()! : "Unnamed";
|
||
}
|
||
|
||
}
|
||
} |