Kernel

1. 构造函数与析构函数

cpp 复制代码
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include"TcpServer.h"

// 构造函数:初始化中介者指针
TcpServer::TcpServer(INetMediator* pTcpS) {
    m_pMediator = pTcpS;
}

// 析构函数:默认空实现
TcpServer::~TcpServer() {

}

2. 初始化网络(initNet)

功能:加载 Winsock 库、创建 TCP 套接字、绑定 IP 端口、监听连接、创建接受连接线程

cpp 复制代码
// 初始化网络(加载库,创建套接字,绑定IP端口,监听,创建接受链接的线程)
bool TcpServer::initNet() {
    // 1. 加载Winsock库(版本2.2)
    WORD version = MAKEWORD(2, 2);
    WSADATA data = {};
    int err = WSAStartup(version, &data);
    if (0 != err) {
        cout << "WSAStartup fail" << endl;
        return false;
    }
    // 验证版本是否匹配
    if (2 != HIBYTE(data.wVersion) || 2 != LOBYTE(data.wVersion)) {
        cout << "WSAStartup version fail" << endl;
        return false;
    }
    cout << "WSAStartup success" << endl;

    // 2. 创建TCP套接字
    m_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (INVALID_SOCKET == m_sock) {
        cout << "socket error:" << WSAGetLastError << endl;
        return false;
    }
    cout << "socket success" << endl;

    // 3. 绑定IP和端口
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(TCP_PORT);  // TCP_PORT为宏定义的端口号
    addr.sin_addr.S_un.S_addr = INADDR_ANY;  // 绑定所有网卡IP
    err = bind(m_sock, (sockaddr*)&addr, sizeof(addr));
    if (SOCKET_ERROR == err) {
        cout << "bind error:" << WSAGetLastError() << endl;
        return false;
    }
    cout << "bind success" << endl;

    // 4. 开始监听连接(监听队列长度由LISTEN_QUEUE_LENGTH宏定义)
    err = listen(m_sock, LISTEN_QUEUE_LENGTH);
    if (SOCKET_ERROR == err) {
        cout << "listen error:" << WSAGetLastError() << endl;
        return false;
    }
    cout << "listen success" << endl;

    // 5. 创建接受连接的线程(阻塞的accept放在线程中执行)
    m_handle = (HANDLE)_beginthreadex(
        0,                      // 线程安全级别(默认)
        0,                      // 堆栈大小(默认1M)
        &acceptThread,          // 线程函数地址
        this,                   // 线程参数(当前类实例指针)
        0,                      // 创建后立即运行
        nullptr                 // 不返回线程ID
    );

    return true;
}

3. 接受连接线程函数(acceptThread)

功能:循环接受客户端连接,为每个客户端创建独立的数据接收线程

cpp 复制代码
// 接收连接的线程函数(静态成员函数)
unsigned __stdcall TcpServer::acceptThread(void* IpVoid) {
    TcpServer* pThis = (TcpServer*)IpVoid;  // 转换为当前类实例指针
    SOCKET sock = INVALID_SOCKET;           // 客户端套接字
    sockaddr_in addrClient = {};            // 客户端地址信息
    int size = sizeof(addrClient);          // 地址结构体大小
    HANDLE handle = nullptr;                // 接收数据线程句柄
    unsigned int threadID = 0;              // 接收数据线程ID

    // 循环接受连接(m_bRunning控制线程退出)
    while (pThis->m_bRunning) {
        // 阻塞等待客户端连接
        sock = accept(pThis->m_sock, (sockaddr*)&addrClient, &size);
        if (INVALID_SOCKET == sock) {
            cout << "accept error:" << WSAGetLastError() << endl;
            continue;
        }

        // 连接成功,打印客户端IP
        cout << "accept success:" << inet_ntoa(addrClient.sin_addr) << endl;

        // 为该客户端创建独立的接收数据线程
        handle = (HANDLE)_beginthreadex(
            0,                      // 线程安全级别(默认)
            0,                      // 堆栈大小(默认1M)
            &TcpServer::recvThread, // 接收数据线程函数地址
            pThis,                  // 线程参数(当前类实例指针)
            0,                      // 创建后立即运行
            &threadID               // 返回线程ID
        );

        // 保存线程ID与客户端套接字的映射关系
        pThis->m_mapThreadIdToSocket[threadID] = sock;
        // 保存线程句柄(用于后续回收)
        if (handle) {
            pThis->m_listHandle.push_back(handle);
        }
    }
    return 1;
}

4. 接收数据线程函数(recvThread)

功能:线程入口函数,调用 recvData 处理实际数据接收

cpp 复制代码
// 接收数据线程函数(静态成员函数)
unsigned __stdcall TcpServer::recvThread(void* IpVoid) {
    TcpServer* pThis = (TcpServer*)IpVoid;  // 转换为当前类实例指针
    pThis->recvData();                      // 调用成员函数接收数据
    return 1;
}

5. 接收数据(recvData)

功能:从客户端套接字接收数据,处理粘包问题,通过中介者转发数据

cpp 复制代码
// 接收数据核心函数
void TcpServer::recvData() {
    Sleep(5);  // 短暂休眠,确保接收连接线程已保存套接字映射

    // 获取当前线程ID,通过映射表找到对应的客户端套接字
    unsigned int threadID = GetCurrentThreadId();
    SOCKET sock = INVALID_SOCKET;
    if (m_mapThreadIdToSocket.count(threadID) > 0) {
        sock = m_mapThreadIdToSocket[threadID];
    } else {
        cout << "TcpServer::recvData socket error, threadID:" << threadID << endl;
        return;
    }

    int offset = 0;         // 数据接收偏移量(处理粘包)
    int nRecvNum = 0;       // 单次接收数据长度
    int packSize = 0;       // 数据包总长度(先接收长度,再接收数据)

    // 循环接收数据(m_bRunning控制线程退出)
    while (m_bRunning) {
        offset = 0;
        // 1. 先接收数据包长度(解决粘包问题)
        nRecvNum = recv(sock, (char*)&packSize, sizeof(int), 0);
        if (nRecvNum <= 0) {
            cout << "TcpServer::recvData get packSize error:" << WSAGetLastError() << endl;
            break;
        }

        // 2. 申请内存存储数据包
        char* pack = new char[packSize];
        // 3. 循环接收完整数据(处理单次接收不完整的情况)
        while (packSize > 0) {
            nRecvNum = recv(sock, pack + offset, packSize, 0);
            if (nRecvNum > 0) {
                packSize -= nRecvNum;  // 剩余未接收长度
                offset += nRecvNum;    // 已接收数据偏移
            } else {
                cout << "TcpServer::recv data error:" << WSAGetLastError() << endl;
                delete[] pack;  // 接收失败,释放内存
                pack = nullptr;
                goto END_RECV;  // 退出循环
            }
        }

        // 4. 通过中介者转发接收的数据(sock作为客户端标识)
        m_pMediator->transmitData(pack, offset, sock);
        delete[] pack;  // 释放数据包内存
        pack = nullptr;

    END_RECV:
        break;
    }
}

6. 发送数据(sendData)

功能:向指定客户端发送数据,先发送长度再发送数据,避免粘包

cpp 复制代码
// 发送数据(TCP协议)
// data: 要发送的数据缓冲区
// len: 数据长度
// to: 目标客户端套接字(TCP用套接字标识目标)
bool TcpServer::sendData(char* data, int len, long to) {
    // 1. 校验参数合法性
    if (data == nullptr || len <= 0) {
        cout << "TcpClient::sendData paramter error:" << endl;
        return false;
    }

    SOCKET destSock = (SOCKET)to;  // 转换为套接字类型

    // 2. 先发数据长度(4字节),解决粘包问题
    int nSendNum = send(destSock, (char*)&len, sizeof(len), 0);
    if (nSendNum == SOCKET_ERROR) {
        cout << "TcpClient::sendData send len error:" << WSAGetLastError() << endl;
        return false;
    }

    // 3. 再发送实际数据
    nSendNum = send(destSock, data, len, 0);
    if (nSendNum == SOCKET_ERROR) {
        cout << "TcpClient::send data error:" << WSAGetLastError() << endl;
        return false;
    }

    return true;
}

7. 关闭网络(unInitNet)

功能:回收线程资源、关闭所有套接字、卸载 Winsock 库n

1. 核心初始化函数(函数指针数组初始化)

cpp

运行

cpp 复制代码
void Kernel::setTypeFunArr()
{
	cout << "__func__" << endl;
	// 初始化函数指针数组,将业务处理函数与协议类型绑定
	memset(m_typeFunArr, 0, sizeof(m_typeFunArr));
	m_typeFunArr[DEF_PROT_REGISTER_RQ - DEF_BASE] = &Kernel::dealRegisterRq;
	m_typeFunArr[DEF_PROT_LOGIN_RQ - DEF_BASE] = &Kernel::dealLoginRq;
	m_typeFunArr[DEF_PROT_FRIEND_OFFLINE - DEF_BASE] = &Kernel::dealOfflineRq;
	m_typeFunArr[DEF_PROT_CHAT_INFO_RQ - DEF_BASE] = &Kernel::dealChatRq;
	m_typeFunArr[DEF_PROT_PROT_ADD_FRIEND_RQ - DEF_BASE] = &Kernel::dealAddFriendRq;
	m_typeFunArr[DEF_PROT_PROT_ADD_FRIEND_RS - DEF_BASE] = &Kernel::dealAddFriendRs;
}

2. 数据分发核心函数(协议解析与函数回调)

cpp 复制代码
void Kernel::dealData(char* data, int len, long from)
{
	cout << "__func__" << endl;
	// 解析协议类型
	prot_type type = *(prot_type*)data;
	// 计算函数指针数组下标
	int index = type - DEF_BASE;

	// 合法性校验
	if (index >= 0 && index < TYPE_FUN_LEN)
	{
		deal_fun pFun = m_typeFunArr[index];
		if (pFun)
		{
			// 回调对应业务处理函数(类成员函数指针调用关键点)
			(this->*pFun)(data, len, from);
		}
		else
		{
			cout << "type1" << type << endl;
		}
	}
	else
	{
		cout << "type2" << type << endl;
	}
}

3. 注册业务处理函数(数据库校验 + 结果响应)

cpp 复制代码
void Kernel::dealRegisterRq(char* data, int len, long from)
{
	cout << "__func__" << endl;
	// 拆包
	PROT_REGISTER_RQ* rq = (PROT_REGISTER_RQ*)data;
	list<string>lstStr;
	char szSql[1024] = "";
	PROT_REGISTER_RS rs;

	// 校验昵称是否重复
	sprintf_s(szSql, "select name from t_user where name='%s';", rq->nick);
	if (!m_sql.SelectMySql(szSql, 1, lstStr)) {
		cout << "查询数据库失败" << szSql << endl;
		return;
	}
	if (lstStr.size() != 0) {
		rs.result = REGISTER_NAME_EXISTS;
	}
	else {
		// 校验手机号是否重复
		lstStr.clear();
		sprintf_s(szSql, "select tel from t_user where tel='%s';", rq->tel);
		if (!m_sql.SelectMySql(szSql, 1, lstStr)) {
			cout << "查询数据库失败" << szSql << endl;
			return;
		}
		if (lstStr.size() != 0) {
			rs.result = REGISTER_TEL_EXISTS;
		}
		else {
			// 插入注册数据
			sprintf_s(szSql,
				"insert into t_user (name,tel,password,feeling,iconid) values ('%s','%s','%s','写代码太难了',9);",
				rq->nick, rq->tel, rq->pass);
			if (m_sql.UpdateMySql(szSql)) {
				rs.result = REGISTER_SUCC;
			}
		}
	}

	// 响应客户端
	m_pMediator->sendData((char*)&rs, sizeof(rs), from);
}

4. 登录业务处理函数(数据库校验 + 在线状态维护)

cpp 复制代码
void Kernel::dealLoginRq(char* data, int len, long from)
{
	cout << "__func__" << endl;
	PROT_LOGIN_RQ* rq = (PROT_LOGIN_RQ*)data;
	list<string>lstStr;
	char szSql[1024] = "";
	PROT_LOGIN_RS rs;

	// 查询手机号对应的密码和ID
	sprintf_s(szSql, "select password, id from t_user where tel='%s';", rq->tel);
	if (!m_sql.SelectMySql(szSql, 2, lstStr)) {
		cout << "查询数据库失败" << szSql << endl;
		return;
	}

	if (lstStr.size() == 0) {
		rs.result = LOGIN_NOTEXIST;
	}
	else {
		// 提取密码和ID
		string pass = lstStr.front();
		lstStr.pop_front();
		int id = stoi(lstStr.front());

		// 密码校验
		if (pass == rq->pass) {
			rs.result = LOGIN_SUC;
			rs.id = id;
			// 维护用户ID与Socket映射(在线状态)
			m_mapIdToSocket[id] = from;
			// 推送用户及好友信息
			getUserAndFriendInfo(id);
			m_pMediator->sendData((char*)&rs, sizeof(rs), from);
			return;
		}
		else {
			rs.result = LOGIN_PASSERROR;
		}
	}

	m_pMediator->sendData((char*)&rs, sizeof(rs), from);
}

5. 聊天业务处理函数(在线判断 + 消息转发)

cpp 复制代码
void Kernel::dealChatRq(char* data, int len, long from)
{
	cout << "__func__" << endl;
	PROT_CHAT_INFO_RQ* rq = (PROT_CHAT_INFO_RQ*)data;

	// 检查接收方是否在线(核心:map查找在线状态)
	if (m_mapIdToSocket.count(rq->friendid)) {
		// 在线则转发消息
		m_pMediator->sendData(data, len, m_mapIdToSocket[rq->friendid]);
	}
	else {
		// 不在线返回失败响应
		PROT_CHAT_INFO_RS rs;
		rs.friendid = rq->myid;
		rs.myid = rq->friendid;
		rs.result = CHAT_RESULT_FAIL;
		m_pMediator->sendData((char*)&rs, sizeof(rs), from);
	}
}

6. 添加好友回复处理函数(数据库双向绑定 + 列表更新)

cpp 复制代码
void Kernel::dealAddFriendRs(char* data, int len, long from)
{
	cout << "__func__" << endl;
	PROT_ADD_FRIEND_RS* rs = (PROT_ADD_FRIEND_RS*)data;

	// 同意添加则写入双向好友关系
	if (ADD_FRIEND_ACCEPT == rs->result)
	{
		char szSql[1024] = "";
		// A->B 好友关系
		sprintf_s(szSql, "insert into t_friend values (%d,%d);", rs->destid, rs->myid);
		if (!m_sql.UpdateMySql(szSql)) {
			cout << "插入数据库失败" << szSql << endl;
			return;
		}
		// B->A 好友关系(双向绑定)
		sprintf_s(szSql, "insert into t_friend values (%d,%d);", rs->myid, rs->destid);
		if (!m_sql.UpdateMySql(szSql)) {
			cout << "插入数据库失败" << szSql << endl;
			return;
		}

		// 更新双方好友列表
		getUserAndFriendInfo(rs->destid);
	}

	// 响应请求方
	if (m_mapIdToSocket.count(rs->destid) > 0)
	{
		m_pMediator->sendData(data, len, m_mapIdToSocket[rs->destid]);
	}
}
相关推荐
hardmenstudent2 小时前
Python字典--第1关:元组使用:这份菜单能修改吗?
开发语言·python
John_Rey2 小时前
Rust底层深度探究:自定义分配器(Allocators)——控制内存分配的精妙艺术
开发语言·后端·rust
逻极2 小时前
VS Code之Java 开发完全指南:从环境搭建到实战优化
java·开发语言
月月玩代码2 小时前
SLF4J,简单门面Java日志框架
java·开发语言
moeyui7052 小时前
Python文件编码读取和处理整理知识点
开发语言·前端·python
·心猿意码·2 小时前
C# 垃圾回收机制深度解析
开发语言·c#
bin91532 小时前
PHP文档保卫战:AI自动生成下的创意守护与反制指南
开发语言·人工智能·php·工具·ai工具
歪歪1003 小时前
解决多 Linux 客户端向 Windows 服务端的文件上传、持久化与生命周期管理问题
linux·运维·服务器·开发语言·前端·数据库·windows
Ryan ZX3 小时前
【Go语言基础】序列化和反序列化
开发语言·后端·golang