zoukankan      html  css  js  c++  java
  • 网络基础编程_5.4聊天室-IOCP服务器

    聊天室-IOCP服务器

    main

    1. 创建完成端口内核对象(CreateIoCompletionPort)

    2. 获取核心数并创建线程(GetSystemInfo + CreateThread)

    3. 创建套接字并绑定接听(socket + bind + listen)

    4. 接收客户端并绑定IOCP(accept + CreateIoCompletionPort)

    5. 将客户端套接字添加到队列(vector.pushback)

    6. 投递一个客户端套接字的IO请求(WSARecv)

    thread

    1. 从完成队列中获取完成的消息(GetQueuedCompletionStatus)

    2. 判断是否获取成功,如果成功就转发消息,投递一个新的IO请求

    3. 如果失败就将自己从客户端容器中删除

    #include <stdio.h>
    
    // 1. 包含必要的头文件和库, 必须位于 windows之前
    #include <WinSock2.h>
    #pragma comment(lib, "ws2_32.lib")
    
    #include <windows.h>
    #include <ws2tcpip.h>
    
    // 动态数组
    #include <vector>
    using namespace std;
    
    // 用于保存所有连接的客户端
    vector<SOCKET> ClientList;
    
    // 创建一个自定义的 OVERLAPPED 的结构,附加一些数据
    struct MyOverLapped
    {
        // 保留原始的重叠结构
        OVERLAPPED Overlapped;
    
        // 自己添加新的数据
        WSABUF WsaBuf;
    };
    
    // 工具函数,用于判断是否执行成功
    VOID CheckResult(BOOL Value, LPCWSTR ErrMsg)
    {
        // 如果 Value 非空,就表示执行成功
        if (Value == FALSE)
        {
            printf("ErrMsg: %ls
    ", ErrMsg);
            system("pause"); ExitProcess(0);
        }
    }
    
    
    // 用于执行接收数据的线程
    DWORD WINAPI ThreadRoutine(LPVOID lpThreadParameter)
    {
        // 保存函数的执行结果
        BOOL Result = FALSE;
    
        // 保存实际的操作数量
        DWORD RealOper = 0;
    
        // 定义完成键,作用是区分IO请求是谁发送的,在绑定的时候设置
        ULONG_PTR CompletKey = 0;
    
        // 重叠 IO 结构
        MyOverLapped* pOverLapped = nullptr;
    
        // 获取 IOCP 内核对象
        HANDLE IoHandle = (HANDLE)lpThreadParameter;
    
    
        // 2. 在循环中不断的接收数据,如果返回值为 > 0 表示成功
        while (TRUE)
        {
            // 当有 IO 请求完成时,从中获取完成的信息,没有就休眠
            Result = GetQueuedCompletionStatus(
                IoHandle,                        // IOCP 内核对象
                &RealOper,                         // 实际操作数量
                &CompletKey,                    // 完成键
                (LPOVERLAPPED*)& pOverLapped,    // 存放重叠结构
                INFINITE);                        // 等待时长
    
            // 3. 将当前的套接字关闭并从列表移除
            if (Result == FALSE && RealOper == 0)
            {
                for (int i = 0; i < ClientList.size(); ++i)
                {
                    // 找到当前对应的套接字
                    if (ClientList[i] == (SOCKET)CompletKey)
                    {
                        // 收尾工作
                        closesocket((SOCKET)CompletKey);
                        printf("%08X 退出了聊天室
    ", (SOCKET)CompletKey);
                        ClientList.erase(ClientList.begin() + i);
                        break;
                    }
                }
            }
            else
            {
                // 转发给当前在线的除自己以外的所有客户端
                for (size_t i = 0; i < ClientList.size(); ++i)
                {
                    // 排除掉自己,不会发消息给自己
                    if (ClientList[i] == (SOCKET)CompletKey)
                        continue;
    
                    // 直接转发消息
                    send(ClientList[i], pOverLapped->WsaBuf.buf, pOverLapped->WsaBuf.len, 0);
                }
    
                // 释放堆空间的数据
                delete[] pOverLapped->WsaBuf.buf;
                delete pOverLapped;
    
                // 每一个异步 IO 操作都需要重叠结构
                MyOverLapped* OverLapped = new MyOverLapped{ 0 };
                OverLapped->WsaBuf.len = 0x100;
                OverLapped->WsaBuf.buf = new char[0x100]{ 0 };
    
                // 先投递一个接收的消息
                DWORD RealRecv = 0, Flags = 0;
                WSARecv(
                    (SOCKET)CompletKey,            // 客户端套接字
                    &OverLapped->WsaBuf,    // 保存缓冲区和缓冲区的大小
                    1,                        // 缓冲区的个数
                    &RealRecv,                // 实际操作数
                    &Flags,                    // 标志
                    (OVERLAPPED*)OverLapped,// 重叠结构
                    NULL);                    // 回调函数
            }
        }
    
        return 0;
    }
    
    int main()
    {
        // [1] 创建一个完成端口内核对象,固定写法
        HANDLE IoHanlde = CreateIoCompletionPort(
            INVALID_HANDLE_VALUE, NULL, 0, 0);
    
        // [2] 获取当前 CPU 的数量
        SYSTEM_INFO SystemInfo = { 0 };
        GetSystemInfo(&SystemInfo);
    
        // [3] 根据获取的 CPU 数量,创建线程
        for (int i = 0; i < SystemInfo.dwNumberOfProcessors; ++i)
            CreateThread(NULL, NULL, ThreadRoutine, (LPVOID)IoHanlde, NULL, NULL);
    
        // 2. 初始化网络环境并判断是否成功[ 搜索信号(2G?3G?4G?) ]
        WSAData WsaData = { 0 };
        if (!WSAStartup(MAKEWORD(2, 2), &WsaData))
            CheckResult(WsaData.wVersion == 0x0202, L"初始化网络环境失败");
    
        // 3. 创建套接字(IP+PORT) [ 买手机 ]
        SOCKET ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        CheckResult(ServerSocket != INVALID_SOCKET, L"套接字创建失败");
    
        // 4. 绑定套接字,提供IP和端口 (办手机卡)
        sockaddr_in ServerAddr = { 0 };
        ServerAddr.sin_port = htons(0x1515);        // 端口
        ServerAddr.sin_family = AF_INET;            // 协议类型
        inet_pton(AF_INET, "127.0.0.1", &ServerAddr.sin_addr.S_un);
        BOOL Result = bind(ServerSocket,    // 要绑定的套接字
            (SOCKADDR*)& ServerAddr,        // 服务器的地址和IP对应的结构体
            sizeof(sockaddr_in));            // 必须要指定大小
        CheckResult(Result != SOCKET_ERROR, L"套接字绑定失败");
    
        // 5. 监听套接字 (开机,等待连接)
        // -  监听谁,最多等待多少个客户端的链接
        Result = listen(ServerSocket, SOMAXCONN);
    
        // 6. 循环等待客户端的连接(接电话)
        while (true)
        {
            // 接收客户端
            int dwSize = sizeof(sockaddr_in);
            sockaddr_in ClientAddr = { 0 };        // 接收的客户端ip和端口
            SOCKET ClientSocket = accept(ServerSocket,
                (SOCKADDR*)&ClientAddr, &dwSize);
    
            // 添加到容器中
            printf("%08X 进入了聊天室
    ", ClientSocket);
            ClientList.push_back(ClientSocket);
    
            // [4] 绑定套接字对象到 IOCP 句柄
            CreateIoCompletionPort((HANDLE)ClientSocket,
                // (完成键)会在接收到消息时,传入到线程函数中
                IoHanlde, (ULONG_PTR)ClientSocket, 0);
    
            // [5] 每一个异步 IO 操作都需要重叠结构
            MyOverLapped* OverLapped = new MyOverLapped{ 0 };
            OverLapped->WsaBuf.len = 0x100;
            OverLapped->WsaBuf.buf = new char[0x100]{ 0 };
    
            // [6] 先投递一个接收的消息
            DWORD RealRecv = 0, Flags = 0;
            WSARecv(
                ClientSocket,            // 客户端套接字
                &OverLapped->WsaBuf,    // 保存缓冲区和缓冲区的大小
                1,                        // 缓冲区的个数
                &RealRecv,                // 实际操作数
                &Flags,                    // 标志
                (OVERLAPPED*)OverLapped,// 重叠结构
                NULL);                    // 回调函数
        }
    
        // 7. 关闭套接字执行清理工作
        closesocket(ServerSocket);
    
        // 8. 清理网络环境
        WSACleanup();
    
        system("pause");
        return 0;
    }
    
    
  • 相关阅读:
    爬虫之JSON
    爬虫bs4案例
    爬虫bs4
    爬虫之Xpath案例
    爬虫之xpath
    监控 Kubernetes 集群应用
    手动部署k8s-prometheus
    ingress之tls和path使用
    ingress安装配置
    kube-dns和coreDNS的使用
  • 原文地址:https://www.cnblogs.com/ltyandy/p/10946024.html
Copyright © 2011-2022 走看看