完成端口学习之简易聊天室

来源:互联网 发布:激活软件的危害 编辑:程序博客网 时间:2024/06/05 10:18

严格的来说吧,这个算是我学习C++后第一个写的不成熟的小程序,现在还没有毕业,一切的东西都算是学习中的小小动手玩乐。
这个是基于SOCKET完成端口而实现的C/S简易聊天工具,没有前端界面,就代码而言应该来说是很冗余和漏洞百出的。贴出来的目的在于将来有所成长后,回头看看曾经写的所谓“学生式的代码”。

这份代码也是具体学了小猪前辈的代码而有所得的东西。
原文:http://blog.csdn.net/piggyxp/article/details/6922277
具体的介绍就不多说了,毕竟这是写给以后的自己看的。
大体思路为:
1、构建IO操作结构体以及对应的数组
2、构建Socket操作的结构体以及对应的数组
3、用AcceptEx指针来获取相应的客户端信息


1.对于结构体的定义

 在IO操作结构体当中,定义重叠结构体,然后再后面接上每个网络操作对应的Socket以及缓冲区等信息。
typedef struct _PER_IO_CONTEXT{    OVERLAPPED          m_Overlapped;               //每个socket有一个重叠结构    SOCKET              m_SocketAccept;             //这个网络操作对应的socket    WSABUF              m_wsaBuf;                   //缓冲区    char                m_szBuffer[MAX_BUFFER_LEN]; //WSABUF存放字符的缓冲区    OPERATION_TPYE      m_OpType;                   //枚举对象,标识网络操作的类型    //初始化资源    _PER_IO_CONTEXT()    {        ZeroMemory(&m_Overlapped, sizeof(m_Overlapped));        ZeroMemory(m_szBuffer, MAX_BUFFER_LEN);        m_SocketAccept = INVALID_SOCKET;        m_wsaBuf.buf = m_szBuffer;        m_wsaBuf.len = MAX_BUFFER_LEN;        m_OpType = NONE;    }    //释放socket资源    ~_PER_IO_CONTEXT()    {        if (m_SocketAccept != INVALID_SOCKET)        {            ZeroMemory(&m_Overlapped, sizeof(m_Overlapped));            ZeroMemory(&m_szBuffer, MAX_BUFFER_LEN);            m_wsaBuf.buf = m_szBuffer;            m_wsaBuf.len = MAX_BUFFER_LEN;            m_OpType = NONE;        }    }    // 重置缓冲区内容    void ResetBuffer()    {        ZeroMemory(m_szBuffer, MAX_BUFFER_LEN);    }}PER_IO_CONTEXT;
然后就是Socket对应的结构体了
typedef struct _PER_SOCKET_CONTEXT{    SOCKET          m_Socket;               //每个客户端连接的socket    SOCKADDR_IN     m_ClientAddr;           //每个客户端的地址    char            m_username[40];         //存放用户名    //初始化    _PER_SOCKET_CONTEXT() {        m_Socket = INVALID_SOCKET;        //将m_ClientAddr这个位置后面的m_ClientAddr长度个字节用0填上        //即对m_ClientAddr进行清零        memset(&m_ClientAddr, 0, sizeof(m_ClientAddr));    }    //释放资源    ~_PER_SOCKET_CONTEXT()     {        if (m_Socket != INVALID_SOCKET)        {            closesocket(m_Socket);            m_Socket = INVALID_SOCKET;        }    }}PER_SOCKET_CONTEXT;
当然,每个结构体也有对应的操作,因为对于代码编程理解有限,所以采用比较笨拙的方式定义相应的方法
class PER_IO_CONTEXT_ARR{private:        //创建io结构体数组,用来存放每个socket对应的io操作    PER_IO_CONTEXT *IO_CONTEXT_ARR[2048];public:    int num=0;  //计数    //获取编号    PER_IO_CONTEXT * GetARR(int i)    {        return IO_CONTEXT_ARR[i];    }    //循环遍历IO操作数组,通过遍历所有位置,如果为0.则表示可以存放新的IO操作,后面只需要将数组全部填0,就可以初始化数组,并且在移除IO操作后,可以在原来位置上放心的IO操作。    PER_IO_CONTEXT* GetNewIoContext()    {        for (int i = 0; i < 2048; i++)        {            //如果某一个IO_CONTEXT_ARRAY[i]为0,表示哪一个位可以放入PER_IO_CONTEXT              if (IO_CONTEXT_ARR[i] == 0)            {                IO_CONTEXT_ARR[i] = new PER_IO_CONTEXT();                num++;                return IO_CONTEXT_ARR[i];            }        }    }    //如果IO操作数组中某个io操作完成,那么移除该IO操作,并且用0替换该位置的值,以方便新的IO操作存放    //新增了一个IO操作,NUM++ 所以只需要循环遍历前num个成员就可以了    void RemoveContext(PER_IO_CONTEXT * RContext)    {        for (int i = 0; i < num; i++)        {            if (IO_CONTEXT_ARR[i] == RContext)            {                IO_CONTEXT_ARR[i]->~_PER_IO_CONTEXT();                IO_CONTEXT_ARR[i] = 0;                //移除一个IO操作,计数-1                num--;                break;            }        }        return;    }};
class PER_SOCKET_CONTEXT_ARR{private:    //创建socket结构体数组    PER_SOCKET_CONTEXT * SOCKET_CONTEXT_ARR[2048];public:    int num = 0; //计数    //循环判定socket结构体数组中那个位置为0,表示该位置可以存放新的socket信息,同IO操作数组一样,初始化后,就可以从0位置开始存放,并且因为和操作是一对一关系,因此socket结构体数组和io操作数组下标应该是对应关系,这样在后续相关操作过程中,通过下标查询相应的数组,就可以得到例如客户端的地址,端口,和相对应的socket。    PER_SOCKET_CONTEXT* GetNewSocketContext(SOCKADDR_IN* addr, char *u)    {        for (int i = 0; i < 2048; i++)        {            //如果某个位置上的值为0,表示该位置可以存放新的socket结构体信息            if (SOCKET_CONTEXT_ARR[i] == 0)             {                SOCKET_CONTEXT_ARR[num] = new PER_SOCKET_CONTEXT();                //该位置存放新的socket结构体信息,同时我们也把客户端的相应信息写入进去                //将传入进来的addr拷贝进SOCKET_CONTEXT_ARR指针指向的位置,长度为地址长度                memcpy(&(SOCKET_CONTEXT_ARR[num]->m_ClientAddr),addr,sizeof(SOCKADDR_IN));                //将传入方法的名字字符串拷贝进去                strcpy(SOCKET_CONTEXT_ARR[num]->m_username,u);                //存入一个socket结构体数组信息,计数+1                num++;                //返回值应该为当前socket结构体数组位置,而计数+1是为了新的socket结构体数组信息,                return SOCKET_CONTEXT_ARR[num - 1];            }        }    }    PER_SOCKET_CONTEXT * getARR(int i)    {        //获取当前socket结构体数组位置        return  SOCKET_CONTEXT_ARR[i];    }    //这里要新建一个方法,虽然有了判断位置是否可以存放数据,但是我们缺少一个通过计数值向SOCKET_CONTEXT_ARR中存放socket,addr,name的操作    void AddSocketArray(SOCKET s, SOCKADDR_IN *addr, char *u)    {        SOCKET_CONTEXT_ARR[num] = new PER_SOCKET_CONTEXT();        SOCKET_CONTEXT_ARR[num]->m_Socket = s;        memcpy(&(SOCKET_CONTEXT_ARR[num]->m_ClientAddr),addr,sizeof(SOCKADDR_IN));        strcpy(SOCKET_CONTEXT_ARR[num]->m_username, u);        num++;    }    //通IO操作一样,如果一个SOCKET结构体数组使用完毕,需要退出数组,那么直接关闭相应的Socket就可以了    void RemoveContext(PER_SOCKET_CONTEXT* S)    {        for (int i = 0; i < num; i++)        {            if (SOCKET_CONTEXT_ARR[i] == S)            {                //关闭相应的socket                closesocket(SOCKET_CONTEXT_ARR[i]->m_Socket);                num--;                break;            }        }    }};
结构体以及相应的方法便定义完成。

2.一些相关申明

#include "stdafx.h"#include "stdio.h"#include "winsock2.h" #include "ws2tcpip.h" #include "mswsock.h"#pragma comment(lib,"ws2_32.lib") #pragma warning(disable: 4996)#define MAX_BUFFER_LEN 4096#define MAX_POST_ACCEPT 6HANDLE                      mIoCompletionPort;              //完成端口接收对象PER_IO_CONTEXT_ARR          ArrayIoContext;                 //建立的io操作结构体对象PER_SOCKET_CONTEXT_ARR      ArraySocketContext;             //socket结构体结构体对象组DWORD WINAPI                workThread(LPVOID lpParam);     //申明线程PER_SOCKET_CONTEXT          * ListenContext;                //创建监听socketLPFN_ACCEPTEX               mAcceptEx;                      //AcceptEx函数指针GUID GuidAcceptEx   =       WSAID_ACCEPTEX;                 //指针的GUID,这个是识别AcceptEx函数必须的LPFN_GETACCEPTEXSOCKADDRS   mAcceptExSockAddrs;             //AcceptEx指针GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS; //同上 GUIDbool _PostSend(PER_IO_CONTEXT * pIoContext);bool _PostRecv(PER_IO_CONTEXT * pIoContext);//申明接收连接操作bool _PostAccept(PER_IO_CONTEXT * pAcceptContext);

3.main
在主线程中,不用做太多的事情,主要把acceptex这行东西搞定就行了,有啥事都扔给工作线程,让他去做。

int main(){    //绑定WSA    WSADATA wsaData;    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR)    {        printf("初始化失败!=%d \n", GetLastError());        return 1;    }    //建立完成端口    mIoCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);    if (mIoCompletionPort == NULL)    {        printf("建立完成端口失败! =%d \n",WSAGetLastError());        return 2;    }    //创建线程    SYSTEM_INFO si;    GetSystemInfo(&si);    int m_nThreads = si.dwNumberOfProcessors * 2 + 2;    //初始化线程句柄    HANDLE * m_phWorkThreads = new HANDLE[m_nThreads];    for (int i = 0; i < m_nThreads; i++)    {        m_phWorkThreads[i] = CreateThread(0,0,workThread,NULL,0,NULL);    }    printf("线程创立成功!\n");    //服务器地址    struct sockaddr_in ServerAddress;    //socke    ListenContext = new PER_SOCKET_CONTEXT;    //通过WSASocket来建立Socekt,这样才能将socket和IO重叠结构绑定    ListenContext->m_Socket = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);    if (ListenContext->m_Socket == INVALID_SOCKET)    {        printf("初始化SOCKET失败! =%d \n",WSAGetLastError());        return 3;    }    //填充服务器地址信息    ZeroMemory(&ServerAddress,sizeof(ServerAddress));    ServerAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY);    ServerAddress.sin_family = AF_INET;    ServerAddress.sin_port = htons(8000);    //绑定地址和端口    if (bind(ListenContext->m_Socket,(SOCKADDR*)&ServerAddress,sizeof(ServerAddress))==SOCKET_ERROR)    {        printf("绑定端口失败! =%d",GetLastError());        return 4;    }    //开始对端口进行监听    if (listen(ListenContext->m_Socket, SOMAXCONN) == SOCKET_ERROR)    {        printf("监听失败! =%d",GetLastError());        return 5;    }    //将监听socket放入完成端口中    if ((CreateIoCompletionPort((HANDLE)ListenContext->m_Socket, mIoCompletionPort, (DWORD)ListenContext, 0) == NULL))    {        printf("将服务端ListenContext结构体放入完成端口失败! =%d",GetLastError());        if (ListenContext->m_Socket == INVALID_SOCKET)        {            closesocket(ListenContext->m_Socket);            ListenContext->m_Socket = INVALID_SOCKET;        }        return 3;    }    //使用AcceptEx指针,博文上说这个指针时微软的,直接调用就好了,在和其他人交流后知道,通过这个指针,以及相应的函数操作,可以很方便的将客户端的各种信息以及对应的socke传入到其他的方法中    DWORD dwBytes = 0;    if (SOCKET_ERROR == WSAIoctl(        ListenContext->m_Socket,        SIO_GET_EXTENSION_FUNCTION_POINTER,        &GuidAcceptEx,        sizeof(GuidAcceptEx),        &mAcceptEx,        sizeof(mAcceptEx),        &dwBytes,        NULL,        NULL))    {        printf("WSAIoctl 未能获取AcceptEx函数指针。= %d\n", WSAGetLastError());        return 6;    }    //同理getAccetExSocket那个指针一样的进行操作,两个指针一起用就可以达到很好的调用效果    if (SOCKET_ERROR == WSAIoctl(        ListenContext->m_Socket,        SIO_GET_EXTENSION_FUNCTION_POINTER,        &GuidGetAcceptExSockAddrs,        sizeof(GuidGetAcceptExSockAddrs),        &mAcceptExSockAddrs,        sizeof(mAcceptExSockAddrs),        &dwBytes,        NULL,        NULL))    {        printf("WSAIoctl 未能获取GuidGetAcceptExSockAddrs函数指针。= %d\n", WSAGetLastError());        return 7;    }    //投递accept io请求    for (size_t i = 0; i < MAX_POST_ACCEPT; i++)    {        //通过结构体对象中的新建IO操作方法绑定一个Accept        PER_IO_CONTEXT * newAcceptIoContext = ArrayIoContext.GetNewIoContext();        //既然绑定了,那就判断一下连接接收操作        //如果连接都失败了,那就不用进行其他操作了,直接将之从ArrayIoContext队列中删除就行了。        if (_PostAccept(newAcceptIoContext) == false)        {            ArrayIoContext.RemoveContext(newAcceptIoContext);            return false;        }    }    //printf("投递了%d个Accept请求 \n",MAX_POST_ACCEPT);    printf("服务初始化完成……\n等待客户端……\n");    //退出指令    //默认状态是正常运行    bool run = true;    while (true)    {        char string[20];        gets_s(string);        //如果输入字符串等于“exit”,返回值为false        if (!strcmp("exit",string))        {            run = false;        }    }    WSACleanup();    return 0;}

4.WorkThread

DWORD WINAPI workThread(LPVOID lpParam){    //每个socket对应的重叠结构    OVERLAPPED *            pOverlapped= NULL;    //新的socket结构体    PER_SOCKET_CONTEXT      *pListenContext = NULL;    //接收的字符串    DWORD                   dwBytesTransfered = 0;    //这里通过循环来处理客户端发送的请求    while (true)    {        //我们先需要去询问队列状态        BOOL bReturn = GetQueuedCompletionStatus(            mIoCompletionPort,          //建立的那个完成端口            &dwBytesTransfered,         //操作完成后返回的字节数            (PULONG_PTR)&pListenContext,        //绑定的那个结构体参数            &pOverlapped,               //连接进来的时候建立的那个重叠结构体            INFINITE);                  //设置为一直等待        //前面我们投递AcceptEx参数的时候,就投递了一个重叠结构体,这个重叠结构体参数里面包含了我们需要的数据        //取出数据        PER_IO_CONTEXT * pIoContext = CONTAINING_RECORD(pOverlapped,PER_IO_CONTEXT,m_Overlapped);        //判断客户端是否断开        if (!bReturn)        {            DWORD dwErr = GetLastError();            if (dwErr == 64)            {                printf("有客户端异常退出!\n");            }            else            {                printf("客户端异常! %d\n",dwErr);            }            continue;        }        else        {            switch (pIoContext->m_OpType)            {            case ACCEPT:            {                //1.如果得到IO操作结构体中的状态符号为ACCEPT,那么久应该取出相对应的客户端的地址信息,做登录比对                SOCKADDR_IN *ClientAddr = NULL, *LocalAddr = NULL;                int Len = sizeof(SOCKADDR_IN);                mAcceptExSockAddrs(                    pIoContext->m_wsaBuf.buf,                    pIoContext->m_wsaBuf.len-((sizeof(SOCKADDR)+16)*2),                    sizeof(SOCKADDR) + 16,                    sizeof(SOCKADDR) + 16,                    (LPSOCKADDR*)&LocalAddr,                    &Len,                    (LPSOCKADDR*)&ClientAddr,                    &Len);                printf("客户端 %s:%d 连入.\n", inet_ntoa(ClientAddr->sin_addr), ntohs(ClientAddr->sin_port));                //接收的用户名                char *input_username = new char[40];                //接收的密码                char *input_password = new char[40];                input_username = strtok(pIoContext->m_wsaBuf.buf, "#");                input_password = strtok(NULL, "");                char *user = new char[40];                strcpy(user, input_username);                //是否登陆成功                bool enter = false;                if (input_username != NULL && input_password != NULL)                {                    //查找账号是否存在                    for (int i = 0; i < sizeof(username) / sizeof(username[0]); i++) {                        int j = 0;                        for (j = 0; username[i][j] == input_username[j] && input_username[j]; j++);                        if (username[i][j] == input_username[j] && input_username[j] == 0)                        {                            //账号存在查找密码是否正确                            int k;                            for (k = 0; password[i][k] == input_password[k] && input_password[k]; k++);                            if (password[i][k] == input_password[k] && input_password[k] == 0)                            {                                enter = true;                            }                            break;                        }                    }                }                if (enter)                {                    printf("客户端 %s:%d 登陆成功! \n",                         inet_ntoa(ClientAddr->sin_addr),                        ntohs(ClientAddr->sin_port));                    strcpy(pIoContext->m_wsaBuf.buf, "登陆成功!\n");                }                else {                    printf("客户端 %s:%d 登陆失败!\n", inet_ntoa(ClientAddr->sin_addr), ntohs(ClientAddr->sin_port));                    strcpy(pIoContext->m_wsaBuf.buf, "登陆失败!\n");                }                //客户端登录成功后,把客户端的信息填入到相应的数组中(GetNewSocketContext)                PER_SOCKET_CONTEXT* newSocketContext = ArraySocketContext.GetNewSocketContext(ClientAddr, user);                newSocketContext->m_Socket = pIoContext->m_SocketAccept;                memcpy(&(newSocketContext->m_ClientAddr), ClientAddr, sizeof(SOCKADDR_IN));                 //绑定完成端口                HANDLE hTemp = CreateIoCompletionPort((HANDLE)newSocketContext->m_Socket, mIoCompletionPort, (DWORD)newSocketContext, 0);                if (hTemp == NULL)                {                    printf("创建完成端口失败! =%d \n",GetLastError());                    break;                }                //给客户端SocketContext绑定一个Recv                PER_IO_CONTEXT* pNewSendIoContext = ArrayIoContext.GetNewIoContext();                memcpy(&(pNewSendIoContext->m_wsaBuf.buf), &pIoContext->m_wsaBuf.buf, sizeof(pIoContext->m_wsaBuf.len));                pNewSendIoContext->m_SocketAccept = newSocketContext->m_Socket;                //send出去                _PostSend(pNewSendIoContext);                //查看是否登录成功                if (enter) {                    //给这个新建的socket结构体定一个PostRecv                    PER_IO_CONTEXT* pNewRecvIoContext = ArrayIoContext.GetNewIoContext();                    pNewRecvIoContext->m_SocketAccept = newSocketContext->m_Socket;                    if (!_PostRecv(pNewRecvIoContext))                    {                        ArrayIoContext.RemoveContext(pNewRecvIoContext);                    }                }                //给这个服务端的SocketContext绑定Accept                pIoContext->ResetBuffer();                _PostAccept(pIoContext);            }                break;            case RECV:            {                //执行recv后,进行接收数据的处理,发给别的客户端,并再recv                if (dwBytesTransfered > 1) {                    char *Senddata = new char[MAX_BUFFER_LEN];                    ZeroMemory(Senddata, MAX_BUFFER_LEN);                    char *temp = new char[MAX_BUFFER_LEN];                    ZeroMemory(temp, MAX_BUFFER_LEN);                    char *sendname = new char[40];                    ZeroMemory(sendname, 40);                    printf_s("客户端 %s(%s:%d) 发送:%s\n",                        pListenContext->m_username,                        inet_ntoa(pListenContext->m_ClientAddr.sin_addr),                        ntohs(pListenContext->m_ClientAddr.sin_port),                        pIoContext->m_szBuffer);                    sprintf_s(Senddata, MAX_BUFFER_LEN, "%s(%s:%d)说:%s\n", pListenContext->m_username, inet_ntoa(pListenContext->m_ClientAddr.sin_addr), ntohs(pListenContext->m_ClientAddr.sin_port), pIoContext->m_szBuffer);                    for (int i = 0; i < ArraySocketContext.num; i++)                    {                        PER_SOCKET_CONTEXT* cSocketContext = ArraySocketContext.getARR(i);                        if (cSocketContext->m_Socket == pListenContext->m_Socket) {                            continue;                        }                        //判断是否不是单对单消息,且消息有长度                        if (strlen(sendname) == 0 && strlen(Senddata) > 0) {                            // 给这个客户端SocketContext绑定一个Recv的计划                            PER_IO_CONTEXT* pNewSendIoContext = ArrayIoContext.GetNewIoContext();                            memcpy(&(pNewSendIoContext->m_wsaBuf.buf), &Senddata, sizeof(Senddata));                            pNewSendIoContext->m_SocketAccept = cSocketContext->m_Socket;                            // Send投递出去                            _PostSend(pNewSendIoContext);                        }                    }                }                pIoContext->ResetBuffer();                _PostRecv(pIoContext);            }            break;            case SEND:                ArrayIoContext.RemoveContext(pIoContext);                break;            default:                printf("_WorkThread中的 pIoContext->m_OpType 参数异常.\n");                break;            }        }    }    printf("线程退出!");    return 0;}

5.相关操作

//投递发送数据请求bool _PostSend(PER_IO_CONTEXT * pIoContext){    //初始化变量    DWORD dwFlags = 0;    DWORD dwBytes = 0;    pIoContext->m_OpType = SEND;    WSABUF *p_wbuf = &pIoContext->m_wsaBuf;    OVERLAPPED *p_ol = &pIoContext->m_Overlapped;    pIoContext->ResetBuffer();    if ((WSASend(pIoContext->m_SocketAccept, p_wbuf, 1, &dwBytes, dwFlags, p_ol,        NULL) == SOCKET_ERROR) && (WSAGetLastError() != WSA_IO_PENDING))    {        ArrayIoContext.RemoveContext(pIoContext);        return false;    }    return true;}//投递接收数据请求bool _PostRecv(PER_IO_CONTEXT * pIoContext){    // 初始化变量    DWORD dwFlags = 0;    DWORD dwBytes = 0;    pIoContext->m_OpType = RECV;    WSABUF *p_wbuf = &pIoContext->m_wsaBuf;    OVERLAPPED *p_ol = &pIoContext->m_Overlapped;    pIoContext->ResetBuffer();    int nBytesRecv = WSARecv(pIoContext->m_SocketAccept, p_wbuf, 1, &dwBytes, &dwFlags, p_ol, NULL);    // 如果返回值错误,并且错误的代码并非是Pending的话,那就说明这个重叠请求失败了    if (nBytesRecv == SOCKET_ERROR && (WSAGetLastError() != WSA_IO_PENDING))    {        if (WSAGetLastError() != 10054) {            printf("投递一个WSARecv失败!%d \n", WSAGetLastError());        }        return false;    }    return true;}//投递操作请求bool _PostAccept(PER_IO_CONTEXT * pAcceptContext){    //投递Accept请求,准备参数    DWORD dwBytes = 0;    pAcceptContext->m_OpType = ACCEPT;    WSABUF *p_wbuf = &pAcceptContext->m_wsaBuf;    OVERLAPPED *p_ol = &pAcceptContext->m_Overlapped;    //为以后新连接进来的客户端准备好socket    pAcceptContext->m_SocketAccept = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);    if (pAcceptContext->m_SocketAccept == INVALID_SOCKET)    {        printf("新建socket失败! =%d \n", WSAGetLastError());        return false;    }    //投递AcceptEx参数    if (mAcceptEx(ListenContext->m_Socket,        pAcceptContext->m_SocketAccept,        p_wbuf->buf,        p_wbuf->len - ((sizeof(SOCKADDR_IN) + 16) * 2),        sizeof(SOCKADDR_IN) + 16,        sizeof(SOCKADDR_IN) + 16,        &dwBytes, p_ol) == FALSE)    {        if (WSAGetLastError() != ERROR_IO_PENDING)        {            printf("投递Accept操作失败! =%d\n",WSAGetLastError());            return false;        }    }    return true;}

大致代码就是这样的,就思路上是可以实现我的聊天目的,但是就代码编程来说,很冗余和多此一举的。
提醒下自己,以后有所提升就回来重新修改我的第一份C++小程序!

原创粉丝点击