用MFC写一个聊天室程序 - 学习笔记

下面的服务器端与客户端的程序与步骤是我在学习MFC网络编程写一个聊天室程序所写的程序,在这里作一个笔记,也希望能帮到一部分刚刚学习的朋友,一起共勉,一起努力历进,如果有错误的或者不懂的地方,可以注册为本站会员,在下面的留言区进行留言讨论!

服务器端:

Step 1:

         新建>项目>C++>MFC应用程序

Step 2:

         在程序文件.h中引入socket库:

         #include <WinSock2.h>

#pragma comment(lib,"ws2_32.lib")

Step 3:

         在构造函数或者初始化函数中初始化套接字库:

         WSADATA wsa;

    WORD mw = MAKEWORD(2, 2);

    int wsaStart = WSAStartup(mw, &wsa);

    if (wsaStart != 0) {

        AfxMessageBox(_T("初始化套接字失败"));

        exit(-1);

    }

Step 4:

创建一个线程,用来接收客户端发过来的请求

m_ListenThread = CreateThread(NULL, 0, ListenThreadFunc, this, 0, NULL);

         线程函数声明定义:

         DWORD WINAPI ListenThreadFunc(LPVOID pParam);

Step 5:

    创建套接字:

    m_ListenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (pChatRoom->m_ListenSock == INVALID_SOCKET) {

        AfxMessageBox(_T("创建套接字失败"));

        goto _Error_End;

    }

Step 6:

    绑定端口号:

    UINT uPort = pChatRoom->GetDlgItemInt(IDC_EDIT_LISTEN_PORT);

    if (uPort < 1 || uPort > 65535) {

        AfxMessageBox(_T("请输入正确的端口号:1-65535"));

        goto _Error_End;

    }

Step 7:

    绑定服务端地址:

    sockaddr_in service;

    service.sin_family = AF_INET;

    service.sin_addr.S_un.S_addr = INADDR_ANY;

    service.sin_port = htons(uPort);

    if (bind(pChatRoom->m_ListenSock, (sockaddr*)&service, sizeof(sockaddr_in)) == SOCKET_ERROR) {

        AfxMessageBox(_T("绑定端口失败"));

        goto _Error_End;

    }

Step 8:

    监听端口:

    if (listen(pChatRoom->m_ListenSock,5) == SOCKET_ERROR){

        AfxMessageBox(_T("监听端口失败"));

        goto _Error_End;

    }

Step 9:

    循环监听处理客户端的请求:

    while (TRUE)

    {

        // SOCKET_Select是对Select异步模型进行封装的一个函数

        if (SOCKET_Select(pChatRoom->m_ListenSock,100,TRUE)) {

            //创建一个地址对象,用来存储客户端的地址信息

            sockaddr_in clientAddr;

            int iLen = sizeof(sockaddr_in);

            //接受客户端的连接请求

            SOCKET accSock = accept(pChatRoom->m_ListenSock, (struct sockaddr*)&clientAddr, &iLen);

            if (accSock == INVALID_SOCKET) {

                continue;

            }

            //下面的ClientItem为一个类,它的结构是一个链表,用来存储所有连接进来的客户端信息,在后面将会写出这个类的原型

            ClientItem tItem;

            tItem.m_Socket = accSock;

            //在下面使用inet_ntop的时候,需要引入#include <WS2tcpip.h>头文件

            char sendBuf[20] = { '\0' };

            tItem.m_StrIp = inet_ntop(AF_INET, (void*)&clientAddr.sin_addr, sendBuf, 16);

           

            //将当前连接进来的客户端信息压入我们创建的链表对象中,m_Client是创建的集合对象:CArray <ClientItem, ClientItem> m_ClientArray;

            tItem.m_pMainWnd = pChatRoom;

            INT_PTR idx = pChatRoom->m_ClientArray.Add(tItem);

            //为当前连接进来的用户创建一个单独的线程,让服务器与客户端进行通信,得到线程句柄,ClientThreadProc是客户端线程函数,&(pChatRoom->m_ClientArray.GetAt(idx))是客户端线程参数

            tItem.hThread = CreateThread(NULL, 0, ClientThreadProc, &(pChatRoom->m_ClientArray.GetAt(idx)),CREATE_SUSPENDED, NULL);

            pChatRoom->m_ClientArray.GetAt(idx).hThread = tItem.hThread;

            //线程挂起,待系统分配执行

            ResumeThread(tItem.hThread);

            Sleep(100);

        }

    }

Step 10:

    封装select异步模型函数:

    BOOL SOCKET_Select(SOCKET hSocket, int nTimeOut, BOOL bRead) {

    fd_set fdset;

    timeval tv;

    FD_ZERO(&fdset);

    FD_SET(hSocket, &fdset);

    nTimeOut = nTimeOut > 1000 ? 1000 : nTimeOut;

    tv.tv_sec = 0;

    tv.tv_usec = nTimeOut;

 

    int iRet = 0;

    if (bRead) {

        iRet = select(0, &fdset, NULL, NULL, &tv);

    }

    else {

        iRet = select(0, NULL, &fdset, NULL, &tv);

    }

    if (iRet <= 0) {

        return FALSE;

    }

    else if (FD_ISSET(hSocket, &fdset)) {

        return TRUE;

    }

    else {

        return FALSE;

    }

}

Step 11:

    创建处理客户端数据的线程函数:

    DWORD WINAPI ClientThreadProc(LPVOID pParam) {

    CString strMsg;

    ClientItem m_ClientItem = *(ClientItem*)pParam;

    while (TRUE && !(m_ClientItem.m_pMainWnd->bShutDown))

    {

        if (SOCKET_Select(m_ClientItem.m_Socket,100,TRUE)) {

            TCHAR szBuf[MAX_BUF_SIZE] = {0};

            //接收并处理客户端发过来的数据

            int iRet = recv(m_ClientItem.m_Socket,(char *)szBuf,MAX_BUF_SIZE,0);

            if (iRet > 0) {

                strMsg.Format(_T("%s"),szBuf);

                strMsg = _T("客户端:") + m_ClientItem.m_StrIp + _T(">") + strMsg;

                m_ClientItem.m_pMainWnd->ShowMsg(strMsg);

                m_ClientItem.m_pMainWnd->SendClientsMsg(strMsg,&m_ClientItem);

            }

            else {

                strMsg = _T("客户端:") + m_ClientItem.m_StrIp + _T("离开了聊天室!");

                m_ClientItem.m_pMainWnd->ShowMsg(strMsg);

                //如果客户下线,将客户端信息从链表中移除

                m_ClientItem.m_pMainWnd->RemoveClientFromArray(m_ClientItem);

                break;

            }

        }

    }

    return TRUE;

}

Step 12:

    向客户端发送数据;

    CString strMsg;

    GetDlgItemText(IDC_EDIT_INPUT_MSG,strMsg);

    if (bIsServer == TRUE) {

        strMsg = _T("服务器:>")+strMsg;

        ShowMsg(strMsg);

        //下面的函数是用来向所有链接的客户端进行群发的函数,将在后面的步骤会写出来

        SendClientsMsg(strMsg);

    }

    else if (bIsServer == FALSE) {

        CString strTmp = _T("本地客户端:>")+strMsg;

        ShowMsg(strTmp);

        //向客户端发送数据

        int iSend = send(m_ConnectSock,(char*)strMsg.GetBuffer(),strMsg.GetLength()*sizeof(TCHAR),0);

        //释放缓冲区

        strMsg.ReleaseBuffer();

    }

    else {

        AfxMessageBox(_T("请您先进入聊天室"));

    }

    SetDlgItemText(IDC_EDIT_INPUT_MSG,_T(""));

Step 13:

    封装向所有连接的客户端进行消息 群发函数:

    SendClientsMsg(CString strMsg, ClientItem * pNotSend)

{

    TCHAR szBuf[MAX_BUF_SIZE] = {0};

    _tcscpy_s(szBuf,MAX_BUF_SIZE,strMsg);

    for (INT_PTR idx = 0; idx < m_ClientArray.GetCount();idx++) {

        if (!pNotSend || pNotSend->m_Socket != m_ClientArray.GetAt(idx).m_Socket || pNotSend->hThread != m_ClientArray.GetAt(idx).hThread

            || pNotSend->m_StrIp != m_ClientArray.GetAt(idx).m_StrIp) {

            send(m_ClientArray.GetAt(idx).m_Socket,(char*)szBuf,_tcslen(szBuf)*sizeof(TCHAR),0);

        }

    }

}

Step 14:

    装一个函数用来停止服务器,释放所有的资源

    void CChatRoomsDlg::StopServer()

{

    UINT nCount = m_ClientArray.GetCount();

    HANDLE *m_pHandles = new HANDLE[nCount+1];

    m_pHandles[0] = m_ListenThread;

    for (int idx = 0; idx < nCount;idx++) {

        m_pHandles[idx + 1] = m_ClientArray.GetAt(idx).hThread;

    }

    bShutDown = TRUE;

    DWORD dwRet = WaitForMultipleObjects(nCount+1,m_pHandles,TRUE,1000);

    if (dwRet != WAIT_OBJECT_0) {

        for (INT_PTR i = 0; i < m_ClientArray.GetCount();i++) {

            TerminateThread(m_ClientArray.GetAt(i).hThread, -1);

            closesocket(m_ClientArray.GetAt(i).m_Socket);

        }

        TerminateThread(m_ListenThread,1);

        closesocket(m_ListenSock);

    }

    delete[] m_pHandles;

    m_ListenThread = NULL;

    m_ListenSock = INVALID_SOCKET;

    bIsServer = -1;

    bShutDown = FALSE;

}

Step 15:

    在程序主窗口退出程序的消息函数中释放套接字,将执行上一步中的停止服务器函数:

    WSACleanup();

 

 

客户端:

Step 1:

         新建>项目>C++>MFC应用程序

Step 2:

         在程序文件.h中引入socket库:

         #include <WinSock2.h>

#pragma comment(lib,"ws2_32.lib")

Step 3:

         在构造函数或者初始化函数中初始化套接字库:

         WSADATA wsa;

    WORD mw = MAKEWORD(2, 2);

    int wsaStart = WSAStartup(mw, &wsa);

    if (wsaStart != 0) {

        AfxMessageBox(_T("初始化套接字失败"));

        exit(-1);

    }

Step 4:

创建一个线程,用来接收客户端发过来的请求

m_ListenThread = CreateThread(NULL, 0, ConnectThreadFunc, this, 0, NULL);

         线程函数声明定义:

         DWORD WINAPI ConnectThreadFunc(LPVOID pParam)

Step 5:

    创建套接字:

    m_ConnectSock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

    if (pChatRoom->m_ConnectSock == INVALID_SOCKET) {

        AfxMessageBox(_T("新建SOCKET失败"));

        return FALSE;

    }

Step 6:

    设置服务器端口号:

    int iPort = pChatRoom->GetDlgItemInt(IDC_EDIT_SERVER_PORT);

    if (iPort < 1 || iPort > 65535) {

        AfxMessageBox(_T("请输入正确的端口号:1-65535"));

        goto _ErrorEnd;

    }

Step 7:

    连接服务器:

    char szIpAddr[16] = {0};

    USES_CONVERSION;

    strcpy_s(szIpAddr,16,T2A(strSevIp));

 

    sockaddr_in server;

    server.sin_family = AF_INET;

    server.sin_port = htons(iPort);

    inet_pton(AF_INET, szIpAddr,(void*)&server.sin_addr.S_un.S_addr);

    //server.sin_addr.S_un.S_addr = inet_addr(szIpAddr);

    int connectResult = connect(pChatRoom->m_ConnectSock,(sockaddr*)&server,sizeof(struct sockaddr));

    if (connectResult == INVALID_SOCKET) {

        AfxMessageBox(_T("连接失败,请重试 !"));

        goto _ErrorEnd;

    }

Step 8:

    循环监测与服务器的连接状态,并准备随时接收服务器返回的数据:

    while (TRUE && !(pChatRoom->bShutDown))

    {

        if (SOCKET_Select(pChatRoom->m_ConnectSock)) {

            TCHAR szBuf[MAX_BUF_SIZE] = {0};

            int iRet = recv(pChatRoom->m_ConnectSock,(char*)szBuf,MAX_BUF_SIZE,0);

            if (iRet > 0) {

                pChatRoom->ShowMsg(szBuf);

            }

            else {

                pChatRoom->ShowMsg(_T("聊天室服务器已经停止,请重新进行连接"));

                break;

            }

        }

        Sleep(100);

    }

Step 9:

    封装select异步模型函数:

    BOOL SOCKET_Select(SOCKET hSocket, int nTimeOut, BOOL bRead) {

    fd_set fdset;

    timeval tv;

    FD_ZERO(&fdset);

    FD_SET(hSocket, &fdset);

    nTimeOut = nTimeOut > 1000 ? 1000 : nTimeOut;

    tv.tv_sec = 0;

    tv.tv_usec = nTimeOut;

 

    int iRet = 0;

    if (bRead) {

        iRet = select(0, &fdset, NULL, NULL, &tv);

    }

    else {

        iRet = select(0, NULL, &fdset, NULL, &tv);

    }

    if (iRet <= 0) {

        return FALSE;

    }

    else if (FD_ISSET(hSocket, &fdset)) {

        return TRUE;

    }

    else {

        return FALSE;

    }

}

Step 10:

    向服务器发送请求数据;

    // TODO: 在此添加控件通知处理程序代码

    CString strMsg;

    GetDlgItemText(IDC_EDIT_INPUT_MSG,strMsg);

    if (bIsServer == TRUE) {

        strMsg = _T("服务器:>")+strMsg;

        ShowMsg(strMsg);

        SendClientsMsg(strMsg);

    }

    else if (bIsServer == FALSE) {

        CString strTmp = _T("本地客户端:>")+strMsg;

        ShowMsg(strTmp);

        int iSend = send(m_ConnectSock,(char*)strMsg.GetBuffer(),strMsg.GetLength()*sizeof(TCHAR),0);

        strMsg.ReleaseBuffer();

    }

    else {

        AfxMessageBox(_T("请您先进入聊天室"));

    }

    SetDlgItemText(IDC_EDIT_INPUT_MSG,_T(""));

Step 11:

    装一个函数用来停止客户器,释放所有的资源

    void CChatRoomsDlg::StopClient()

{

    bShutDown = TRUE;

    DWORD dwRet = WaitForSingleObject(m_ConnectThread,1000);

    if (dwRet != WAIT_OBJECT_0) {

        TerminateThread(m_ConnectThread,-1);

        closesocket(m_ConnectSock);

    }

    m_ConnectThread = NULL;

    m_ConnectSock = INVALID_SOCKET;

    bIsServer = -1;

    bShutDown = FALSE;

}

Step 12:

    在程序主窗口退出程序的消息函数中释放套接字,将执行上一步中的停止服务器函数:

    WSACleanup();

 

 


版权声明: 此文为本站源创文章[或由本站编辑从网络整理改编],
转载请备注出处:
[狂码一生] https://www.sindsun.com/articles/16/31
[若此文确切存在侵权,请联系本站管理员进行删除!]


--THE END--