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]);
	}
}
相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai12 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜12 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa12 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio