你将收获

通过浅显易懂的代码与讲解,让你的C++网络编程能力得到质的飞跃,具备开发高并发服务器的能力! 同时跟大家推荐几本网络编程的圣经《Windows网络编程》《TCPIP协议详解(全卷)》 《UNIX网络编程》!

适用人群

所有人

课程介绍

C/S模型在企业级的软件开发中非常常见,高并发网络通讯更是很多项目的核心模块,学好socket 网络编程显得尤为重要,本课程分为TCP/IP 协议、socket基础与进阶的异步通讯模型三个部分,通过浅显易懂的代码与讲解,让你的C++网络编程能力得到质的飞跃,具备开发高并发服务器的能力!

课程目录

学员评价

5.0
  • 100%
  • 0%
  • 0%
  • 0%
  • 0%
  • TM1695648164 2018-05-10 20:55

    5.0分

    老师想问你的代码在哪里下载啊?

  • kiss1987823 2018-01-13 20:20

    5.0分

    老师,可以把每节课的代码,上传给我们么?

没有更多了

同学笔记

  • weixin_38793855 2020-03-06 09:27:37

    来源:IO模型之完成端口completion port 查看详情

    完成端口IOCP模型

    一个完成端口实际上就是一个通知队列,操作系统吧已经完成的重叠IO请求通知发到队列中,完成端口会充分利用Windows最复杂的内核对象来进行IO的调度,属于异步IO,适用于CS通信模式中性能最好的网络通信模型。

    完成端口IOCP模型的原理

    完成端口创建几个线程,等到用户请求的时候,就把这些请求都加入到一个公共消息队列中去,然后这几个线程就排队从消息队列中取出消息并加以处理,这种方式就很优雅的实现了异步通信和负载均衡的问题,因为它提供了一种机制来使用几个线程“公平的”处理来自多个客户端的输入输出,并且线程如果没事干的时候会被系统挂起,不会占用CPU周期,这个关键的作为交换的消息队列,它就是完成端口。

    使用完成端口的基本流程

    1、调用CreateIoCompletionPort函数创建一个完成端口,第四个参数设为0,让完成端口上每次处理一次只允许执行的线程

    2、根据系统中CPU核心的数量建立对应的Worker线程,

    3、一个用于监听的Socket,在指定的端口上监听连接请求

    4、将接受的套接字绑定到完成端口

    5、使用重叠IO,在套接字上投递一个或者多个WSARecv或者WSASend请求

     

    在新开辟的线程中

    1、在Worker线程使用GetQueuedCompletionStatus函数,它让Worker线程进入不占用CPU的睡眠状态,直到完成端口上出现了需要大量处理的网络操作或者超出了等待的时间限制为止

    2、使用重叠IO,在套接字上投递一个或者多个WSARecv或者WSASend请求

    3、重复1~2步骤。

     

    CreateIoCompletionPort函数

    CreateIoCompletionPort(

    HANDLE FileHandle,

    HANDLE ExistingCompletionPort,

    ULONG_PTR CompletionKey,

    DWORD NumberOfConcurrentThreads

    );

    参数:

    第一个参数:有效的文件句柄或者INVALID_HANDLE_VALUE

    第二个参数:已经存在的完成端口,为NULL则新建一个IOCP

    第三个参数:传送给处理函数的参数

    第四个参数:有多个线程在访问这个消息队列,当参数WxistingCompletionPort不为0的时候,系统忽略该参数,当该参数为0表示允许同时相等数据处理器个数的线程访问该消息队列。

    返回值:

    成功返回一个IOCP的句柄,失败返回NULL ;

    GetQueuedCompletionStatus函数

    GetQueuedCompletionStatus(

    HANDLE CompletionPort,

    LPDWORD lpNumberOfBytesTransferred,

    PULONG_PTR lpCompletionKey,

    LPOVERLAPPED* lpOverlapped,

    DWORD dwMilliseconds

    );

    参数:

    第一个参数:完成端口

    第二个参数:接收一个IO操作中完成的字节数

    第三个参数:自定义结构体参数

    第四个参数:完成IO操作时指定的重叠结构的地址

    第五个参数:等待完成包出现在完成端口的毫秒数,INFINITE(一直等待)

    返回值:

    成功返回TRUE , 失败FALSE,调用WSAGetLastError得到扩展的错误

     

    服务端代码

    #include <iostream>
    #include<WinSock2.h>
    #include<cstdlib>
    #pragma comment(lib,"ws2_32.lib")
    using namespace std;
    DWORD WINAPI WorkerThread(LPVOID CompletionPortId);
    typedef struct _MY_WSAOVERLAPPED
    {
        WSAOVERLAPPED overlap;
        WSABUF Buffer;
        DWORD NumberOfBytesRecvd;
        DWORD Flags;
        SOCKET socket;
        _MY_WSAOVERLAPPED()
        {
            Buffer.buf = new char[64]{ '\0' };
            Buffer.len = 64;
            Flags = 0;
            overlap.hEvent = NULL;
        }
        ~_MY_WSAOVERLAPPED()
        {
            delete[]Buffer.buf;
            Buffer.buf = NULL;
            Buffer.len = 0;
        }
    }MY_WSAOVERLAPPED, * PMY_WSAOVERLAPPED;
    int main()
    {
        WSADATA wd;
        if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
        {
            cout << "wsastartup error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        //1 调用CreateIoCompletionPort函数创建一个完成端口,第四个参数设为0,让完成端口上每次处理一次只允许执行的线程
        HANDLE completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
        if (completionPort == NULL)
        {
            cout << "CreateIoCompletionPort " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        //1 根据系统中CPU核心的数量建立对应的Worker线程,
        SYSTEM_INFO sysInfo;
        GetSystemInfo(&sysInfo);
        for (int i = 0; i < (int)sysInfo.dwNumberOfProcessors; i++)
        {
            HANDLE h = CreateThread(NULL, 0, WorkerThread, completionPort, 0, NULL);
            CloseHandle(h);
        }
        cout << "创建了" << sysInfo.dwNumberOfProcessors << "个工作线程" << endl;
        SOCKET s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
        if (s == INVALID_SOCKET)
        {
            cout << "socket error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        sockaddr_in addr;
        addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
        addr.sin_family = AF_INET;
        addr.sin_port = htons(8000);

        int bindRet = bind(s, (sockaddr*)&addr, sizeof sockaddr);
        if (bindRet == SOCKET_ERROR)
        {
            cout << "bind error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        int listenRet = listen(s, 5);
        if (listenRet == SOCKET_ERROR)
        {
            cout << "listen error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        while (true)
        {
            // 3 一个用于监听的Socket,在指定的端口上监听连接请求
            SOCKET sClient = WSAAccept(s, NULL, NULL, NULL, NULL);
            if (sClient == INVALID_SOCKET)
            {
                cout << "accept error " << WSAGetLastError() << endl;
                continue;
            }
            char temp[64];
            sprintf_s(temp, "欢迎%d进入客户端", sClient);

            int sendRet = send(sClient, temp, strlen(temp), 0);
            if (sendRet == SOCKET_ERROR)
            {
                closesocket(sClient);
            }
            cout << sClient << "进入客户端" << endl;
            //4 将接受的套接字绑定到完成端口
            CreateIoCompletionPort((HANDLE)sClient, completionPort, (ULONG_PTR)sClient, 0);
            PMY_WSAOVERLAPPED pOver = new MY_WSAOVERLAPPED;
            //5 使用重叠IO,在套接字上投递一个或者多个WSARecv或者WSASend请求
            int ret = WSARecv(sClient, &pOver->Buffer, 1, &pOver->NumberOfBytesRecvd, &pOver->Flags, &pOver->overlap, NULL);
            if (ret == SOCKET_ERROR)
            {
                int err = WSAGetLastError();
                if (err != WSA_IO_PENDING)
                {
                    cout << "wsarecv error " << WSAGetLastError() << endl;
                    closesocket(sClient);
                    delete pOver;
                }
            }
        }
        CloseHandle(completionPort);
        closesocket(s);
        if (WSACleanup() == SOCKET_ERROR)
        {
            cout << "wsacleanu 出错" << endl;
        }
    }
    DWORD WINAPI WorkerThread(LPVOID CompletionPortId)
    {
        HANDLE completionPort = (HANDLE)CompletionPortId;
        DWORD dwByteTransferred;
        SOCKET sClient;
        PMY_WSAOVERLAPPED pOver = NULL;
        while (true)
        {
            //1 在Worker线程使用GetQueuedCompletionStatus函数,它让Worker线程进入不占用CPU的睡眠状态,直到完成端口上出现了需要大量处理的网络操作或者超出了等待的时间限制为止
            bool b = GetQueuedCompletionStatus(completionPort, &dwByteTransferred, (PULONG_PTR)&sClient, (LPOVERLAPPED*)&pOver, INFINITE);
            if (sClient == NULL)
                continue;
            if (b && dwByteTransferred > 0)
            {
                cout << sClient << " 说:" << pOver->Buffer.buf << endl;
                ZeroMemory(pOver->Buffer.buf, 64);
                // 2 使用重叠IO,在套接字上投递一个或者多个WSARecv或者WSASend请求
                int ret = WSARecv(sClient, &pOver->Buffer, 1, &pOver->NumberOfBytesRecvd, &pOver->Flags, &pOver->overlap, NULL);
                if (ret == SOCKET_ERROR)
                {
                    int err = WSAGetLastError();
                    if (err != WSA_IO_PENDING)
                    {
                        closesocket(sClient);
                        delete pOver;
                        cout << "wsarecv error" << err << endl;
                    }
                }
            }
            else
            {
                cout << sClient << "离开了" << endl;
                closesocket(sClient);
                delete pOver;
            }
        }
    }

  • weixin_38793855 2020-03-05 19:54:06

    来源:IO模型之重叠Overlapped IO(基于完成例程) 查看详情

    完成例程(completion routines)

    完成例程回调函数原型

    void CALLBACK CompletionRoutineFunc(DWORD dwError,DWROD cbTransferred,LPWSAOVERLAPPED lpOverlapped,DWORD dwFlags)

    参数:

    第一个参数:表名一个重叠操作的完成的状态

    第二个参数:指明了重叠操作期间,实际传输字节数

    第三个参数:传输到最初的IO调用内的重叠结构

    第四个参数:返回操作结束时可能用到的标志

    SleepEx函数:

    SleepEx(

    DWORD dwMilliseconds,

    BOOL bAlertable

    );

    参数:

    第一个参数:等待的超时时间,如果设置为INFINITE就会一直等待下去。

    第二个参数:是否置于警觉状态,如果为FALSE,则一定要等待超时时间完毕后才返回,我们这里希望重叠操作,一完成就能返回,要设置为TRUE ;

    返回值:

    如果指定的时间间隔过期,返回值为0

    如果返回的函数是由于一个或多个IO完成回调函数而返回的函数,则返回值是WAIT_IO_COMPLETION,只有在bAlertable是正确的情况下,才会发生这种情况,如果调用SleepEx函数的线程与调用扩展IO函数的线程相同。

    基于完成例程的重叠IO编程步骤

    1、创建套接字在指定端口监听,并接受客户端连接请求。

    2、为接受的套接字新建一个WSAOVERLAPPED结构

    3、在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构,同时提供一个完成例程函数,注意函数通常会以失败告终,返回SOCKET_ERROR错误状态WSA_IO_PENDINIG(IO操作尚未完成)

    4、使用伪事件数组,将调用WSAWaitForMultipleEvents函数,fAlertable设置为true,等待重叠IO请求完成,重叠请求完成以后,完成例程会自动执行,而且WSAWaitForMultipleEvent函数的返回WAIT_IO_COMPLETION说明一个或多个完成例程已经排队等待执行。

    5、在完成例程内,可用一个完成例程投递另一个重叠WSARecv请求。

    6、如果要想知道完成例程调用后的返回结果,调用WSAWaitForMultipleEvents函数(返回值WAIT_IO_COMPLETION)或者SleepEx函数等待重叠操作返回的结果。

    7、重复5~6.

     

    服务端代码

    #include <iostream>
    #include<WinSock2.h>
    #include<cstdlib>
    #pragma comment(lib,"ws2_32.lib")
    using namespace std;
    void CALLBACK CompletionRoutineFunc(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags);
    typedef struct _MY_WSAOVERLAPPED
    {
        WSAOVERLAPPED overlap;
        WSABUF Buffer;
        DWORD NumberOfBytesRecvd;
        DWORD Flags;
        SOCKET socket;
        _MY_WSAOVERLAPPED()
        {
            Buffer.buf = new char[64]{ '\0' };
            Buffer.len = 64;
            Flags = 0;
        }
        ~_MY_WSAOVERLAPPED()
        {
            delete[]Buffer.buf;
            Buffer.buf = NULL;
            Buffer.len = 0;
        }
    }MY_WSAOVERLAPPED, * PMY_WSAOVERLAPPED;
    int main()
    {
        WSADATA wd;
        if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
        {
            cout << "wsastartup error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (s == INVALID_SOCKET)
        {
            cout << "socket error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        sockaddr_in addr;
        addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
        addr.sin_family = AF_INET;
        addr.sin_port = htons(8000);

        int bindRet = bind(s, (sockaddr*)&addr, sizeof sockaddr);
        if (bindRet == SOCKET_ERROR)
        {
            cout << "bind error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        int listenRet = listen(s, 0);
        if (listenRet == SOCKET_ERROR)
        {
            cout << "listen error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        while (true)
        {
            SOCKET sClient = accept(s, NULL, NULL);
            if (sClient == INVALID_SOCKET)
            {
                cout << "accept error" << WSAGetLastError() << endl;
                continue;
            }
            cout << sClient << "进入聊天室" << endl;
            char temp[64];
            sprintf_s(temp,"欢迎%d进入客户端",sClient);
            send(sClient,temp,strlen(temp),0);
            PMY_WSAOVERLAPPED pOver = new MY_WSAOVERLAPPED;
            pOver->socket = sClient;
            int ret = WSARecv(pOver->socket, &pOver->Buffer, 1, &pOver->NumberOfBytesRecvd, &pOver->Flags, &pOver->overlap, CompletionRoutineFunc);
            if (ret == SOCKET_ERROR)
            {
                int err = WSAGetLastError();
                if (err != WSA_IO_PENDING)
                {
                    cout << "wsarecv error " << WSAGetLastError << endl;
                    closesocket(sClient);
                    continue;
                }
            }
        }
    }
    void CALLBACK CompletionRoutineFunc(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
    {
        PMY_WSAOVERLAPPED pover = (PMY_WSAOVERLAPPED)lpOverlapped;
        if (dwError == 0 && cbTransferred > 0)
        {
            cout << pover->socket << " 说:" << pover->Buffer.buf << endl;
            ZeroMemory(pover->Buffer.buf, 64);
            int ret = WSARecv(pover->socket, &pover->Buffer, 1, &pover->NumberOfBytesRecvd, &pover->Flags, &pover->overlap, CompletionRoutineFunc);
            if (ret == SOCKET_ERROR)
            {
                int err = WSAGetLastError();
                if (err != WSA_IO_PENDING)
                {
                    cout << "wsarecv error " << WSAGetLastError() << endl;
                    closesocket(pover->socket);
                    delete[]pover->Buffer.buf;
                }
            }
        }
        else
        {
            cout << pover->socket << "离开了" << endl;
            closesocket(pover->socket);
            delete[]pover->Buffer.buf;
        }
    }

  • weixin_38793855 2020-03-04 10:54:00

    来源:IO模型之重叠Overlapped IO(基于事件通知) 查看详情

    重叠Overlapped IO模型

    重叠模型让应用程序使用重叠数据(WSAOVERLAPPED) , OVERLAPPED 是WIN32的一项技术,你可以要求操作系统为你传送数据, 并且在传送完毕时通知你,这项技术使你的程序在IO进行过程中仍然能够继续处理事务,支持重叠IO操作的系统对象处理文件,还有管道,串口,SOCKET

    重叠Overlapped IO模型的优点

    比起select WSAAsyncSelect以及WSAEventSelect等模型,重叠IO模型使应用程序能达到更加的系统性能,因为使用重叠模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序投递了一个10kb大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接拷贝到投递的缓冲区,而这4中模型需要调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区,差别就体现出来了。

    重叠IO请求的通知的方式

    1、事件对象通知(event object notification)

    2、完成例程(completion routines)

    重叠IO基于事件通知的流程

    将windows事件对象与WSAOVERLAPPED结构关联在一起,(WSAOVERLAPPED结构中专门有对应的参数),使用重叠结构,我们常用的send,sendto,recv,recvfrom都要被WSASend,WSAWendto,WSARecv,WSARecvFrom替换,它们的参数中都有一个Overlapped参数,我们可以假设把我们的WSARecv这样的操作“绑定”到这个重叠结构上,提交一个请求,其他的事情就交个重叠结构去操作,而其中重叠结构又要与windows的事件对象(最多64个事件)“绑定”在一起,这样我们调用完WSARecv以后,就可以坐享其成,邓傲重叠结构操作完成以后,自然就有与之对应的事件来通知我们操作完成,然后我们就可以根据重叠操作的结构去的我们想要的数据。

    WSARecv(

    SOCKET s,

    LPWSABUF lpBuffers,

    DWORD dwBufferCount,

    LPDWORD lpNumberOfBytesRecvd,

    LPDWORD lpFlags,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

    );

    参数:

    第一个参数:投递这个操作的套接字

    第二个参数:接收缓冲区,是WSABUF结构数组

    第三个参数:数组中WSABUG结构的数量

    第四个参数:如果接收操作立即完成,返回函数所收到的字节数

    第五个参数:设置为0即可

    第六个参数:“绑定重叠结构”

    第七个参数:完成例程中的参数,我们这里设置为NULL

    返回值:

    成功返回0 , 完成例程将在调用线程处于alertable状态时被调用

    失败返回SOCKET_ERROR的值,并通过WSAGetLastError错误代码,错误代码WSA_IO_PENDING表示重叠操作已经成功启动,并将在稍后的时间内表示完成,任何其他错误代码表名重叠操作为成功启动。

    WSASend(

    SOCKET s,

    LPWSABUF lpBuffers,

    DWORD dwBufferCount,

    LPDWORD lpNumberOfBytesSent,

    DWORD dwFlags,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

    );

    参数:

    第一个参数:标识一个已经连接套接字的描述字

    第二个参数:一个指向WSABUF结构数组的指针

    第三个参数:lpBuffers数组中WSABUF结构的数组

    第四个参数:如果发送操作立即完成,则为一个指向所发送数据字节的指针

    第五个参数:标志位

    第六个参数:指向WSAOERLAPPED结构的指针,对于非重叠接口则忽略。

    第七个参数:一个指向发送操作完成后调用的完成例程的指针(对于非重叠则忽略)

    返回值:

    成功返回0 , 完成例程将在调用线程处于alertable状态时被调用

    否则返回SOCKET_ERROR的值,并通过调用WSAGetLastError()来检索特定的错误代码,错误代码WSA_IO_PENDING表名重叠操作已经成功启动,并将在稍后的事件内表示完成,任何其他的错误代码表名重叠操作为成功启动,也不会出现指示。

    typedef struct _WSABUF {

    ULONG len; /* the length of the buffer */

    _Field_size_bytes_(len) CHAR FAR *buf; /* the pointer to the buffer */

    } WSABUF, FAR * LPWSABUF;

     

    WSAGetOverlappedResult函数:

    WSAGetOverlappedResult(

    SOCKET s,

    LPWSAOVERLAPPED lpOverlapped,

    LPDWORD lpcbTransfer,

    BOOL fWait,

    LPDWORD lpdwFlags

    );

    参数:

    第一个参数:套接字s

    第二个参数想要查询结构的重叠结构的指针

    第三个参数:本次重叠操作的实际接收(或发送)的字节数

    第五个参数:设置为TURE,除非重叠操作完成,否则函数不会返回,设置为Fasle,而且操作仍处于挂起状态,那么函数就会返回false,错误为WSA_IO_INCOMPLETE,应为我们是等待事件来通知我们操作完成。

    第六个参数:接收结果标志

    范返回值:

    成功则返回true,重叠操作已经成功完成,并更新lpcbTransfer所指向的值,失败返回false,通过WSAGetLastError来获取错误代码

    基于事件通知的重叠IO编程步骤

    1、创建套接字在指定端口监听(socket会默认设置WSA_FLAG_OOVERLAPPED标志),并接受客户端连接请求

    2、为接受的套接字为新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄,同时将该事件对象句柄分配给一个事件数组,以便稍后有WSAWaitForMultipleEvent函数使用

    3、在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构,让WSAOVERLAPPED结构来替我们请求IO请求,注意函数通常会以失败告终,返回SOCKET_ERROR错误状态WSA_IO_PENDING(IO操作尚未完成)

    4、使用步骤2的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入“已传信”状态,根据WSAWaitForMultipleEvents函数的返回值来确定究竟事件数组中的哪一个事件被触发了。

    5、WSAWaitForMultipleEvents函数返回后,针对“已传信”状态的事件,调用WSAResetEvent函数,事件已经被触发了之后,它对于我们来讲已经没有利用价值了,所以要将它重置准备下一次使用。

    6、使用WSAGetOverlappedResult函数取的重叠的返回状态,需要检查堆放的Socket连接是否关闭,如果操作完成,WSABUF结构里面就存有我们WSARecv来的数据。

    7、在套接字上继续投递WSARecv请求,即重复步骤3·6

    服务端代码

    #include<stdlib.h>
    #include<WinSock2.h>
    #include <iostream>
    #pragma comment(lib,"ws2_32.lib")
    using namespace std;
    SOCKET socketArray[WSA_MAXIMUM_WAIT_EVENTS];
    WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
    WSAOVERLAPPED* overArray[WSA_MAXIMUM_WAIT_EVENTS];
    WSABUF bufArray[WSA_MAXIMUM_WAIT_EVENTS];
    int N = 0;
    DWORD WINAPI ThreadProc(LPVOID lpRarameter);
    int main()
    {
        WSADATA wd;
        if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
        {
            cout << "WSAStartup error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        //
        SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (s == INVALID_SOCKET)
        {
            cout << "socket error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        sockaddr_in addr;
        addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
        addr.sin_family = AF_INET;
        addr.sin_port = htons(8000);
        int bindRet = bind(s, (sockaddr*)&addr, sizeof sockaddr);
        if (bindRet == SOCKET_ERROR)
        {
            cout << " bind error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }
        int listenRet = listen(s, 0);
        if (listenRet == SOCKET_ERROR)
        {
            cout << "listen error " << WSAGetLastError() << endl;
            exit(EXIT_FAILURE);
        }

        HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
        CloseHandle(hThread);
        while (true)
        {
            SOCKET sClient = accept(s, NULL, NULL);
            if (sClient == INVALID_SOCKET)
            {
                cout << "accept error" << WSAGetLastError() << endl;
                continue;
            }
            cout << sClient << " 进入聊天室" << endl;
            char temp[30] = {'\0'};
            sprintf_s(temp, "欢迎%d进入聊天室", sClient);
            send(sClient, temp, strlen(temp), 0);
            WSAEVENT event = WSACreateEvent();
            WSAOVERLAPPED* pOver = new WSAOVERLAPPED;
            pOver->hEvent = event;
            WSABUF wsabuf;
            wsabuf.buf = new char[100]{ '\0' };
            wsabuf.len = 100;
            socketArray[N] = sClient;
            eventArray[N] = event;
            bufArray[N] = wsabuf;
            overArray[N++] = pOver;
            DWORD flags = 0;
            DWORD numberOfByRecvd = 0;
            int wsaRecvRet = WSARecv(sClient, &wsabuf, 1, &numberOfByRecvd, &flags, pOver, NULL);
            if (wsaRecvRet == SOCKET_ERROR)
            {
                int err = WSAGetLastError();
                if (err != WSA_IO_PENDING)
                {
                    cout << "WSARecv 失败" << endl;
                }
            }
        }
    }
    DWORD WINAPI ThreadProc(LPVOID lpRarameter)
    {
        while (true)
        {
            int waitRet = WSAWaitForMultipleEvents(N, eventArray, false, 1000, false);
            if (waitRet == WSA_WAIT_FAILED)
            {
                continue;
            }
            int index = waitRet - WSA_WAIT_EVENT_0;

            WSAResetEvent(eventArray[index]);
            WSAOVERLAPPED* pOver = overArray[index];
            DWORD lpcbTransfer = 0;
            DWORD lpdwFlags = 0;
            int ret2 = WSAGetOverlappedResult(socketArray[index], pOver, &lpcbTransfer, true, &lpdwFlags);
            if (ret2 && lpcbTransfer > 0)
            {
                cout << socketArray[index] << "说:" << bufArray[index].buf << endl;
                ZeroMemory(bufArray[index].buf, 100);
                DWORD numberOfRecv = 0;
                DWORD flags = 0;
                WSARecv(socketArray[index], &bufArray[index], 1, &numberOfRecv, &flags, pOver, NULL);
            }
            else
            {
                cout << socketArray[index] << "离开了" << endl;
                closesocket(socketArray[index]);
                WSACloseEvent(eventArray[index]);
                delete overArray[index];
                delete[]bufArray[index].buf;
                for (int i = index; i < N - 1; i++)
                {
                    socketArray[i] = socketArray[i + 1];
                    eventArray[i] = eventArray[i + 1];
                    overArray[i] = overArray[i + 1];
                    bufArray[i] = bufArray[i + 1];
                }
            }
        }
    }

     

没有更多了