ChatX/chatserver/Program.cs
XuShanQiXun e21a07db59 修复发送者端点获取逻辑
在 `Program.cs` 文件中,修改了获取发送者完整端点的代码。原来的代码使用 `socket.RemoteEndPoint?.ToString() ?? "Unknown"`,而现在修改为 `socket.RemoteEndPoint?.ToString() ?? "Unknown:0"`。这个变化确保在没有可用的远程端点时,返回的字符串包含端口信息(默认为0),以便于后续的验证逻辑。
2025-06-21 09:47:55 +08:00

342 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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";
}
}
}