专栏首页狂码一生用MFC写一个聊天室程序 - 学习笔记

用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();

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Linux系统备份与恢复

    1、如果系统未安装tar工具,时行安装     >yum -y install tar 2、新建一个要备份的文件夹     >mkdir -p /backup...

    Sindsun
  • windows环境下用c++实现socket编程

        socket即套接字,用于描述地址和端口,是一个通信链的句柄。应用程序通过socket向网络发出请求或者回应。

    Sindsun
  • 安装telnet与xinetd

    版权声明: 此文为本站源创文章[或由本站编辑从网络整理改编], 转载请备注出处:[ 狂码一生] http://www.sindsun.com/...

    Sindsun
  • LeetCode 1234. 替换子串得到平衡字符串(滑动窗口)

    有一个只含有 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符,且长度为 n 的字符串。

    Michael阿明
  • 实现去哪儿来回机票选择的view

    最近有个控件是实现和去哪儿和阿里旅行的app的选择日历效果,反编译没有效果的情况下我自己实现了个,大致的原理是: 上面是产品需要实现的效果,我看了下不就是一个L...

    xiangzhihong
  • 案例:python玩21点

    py3study
  • 如何在新公司建立良好形象

    如何在新公司建立良好形象 1、着装要适当   穿着不一定要名贵,但一定要合体、干净、整洁,而且颜色和图案的搭配一定要协调。鞋子应该是舒服而又引人注目的。对于男士...

    Java帮帮
  • 笔记59 | Android管理音频焦点的学习

    项勇
  • Python-OpenCV(2)

    这次咱们写个有点意思的东西,上个博客在最后写了画线、画矩形之类的,涉及到取颜色(r g b值),这次咱们就写个图形化的调色板。 具体就是: 三个滑动条分...

    GavinZhou
  • python开发_platform_获取操作系统详细信息工具

    =======================================================

    Hongten

扫码关注云+社区

领取腾讯云代金券