用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 条评论
登录 后参与评论

相关文章

来自专栏我是攻城师

ElasticSearch之Java Api聚合分组实战

4736
来自专栏Android 研究

APK安装流程详解14——PMS中的新安装流程上(拷贝)补充

mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACK...

3721
来自专栏JAVA技术站

Sharding-Jdbc之读写分离导读 原

      Sharding-JDBC是一个开源的分布式数据库中间件,它无需额外部署和依赖,完全兼容JDBC和各种ORM框架。Sharding-JDBC作为面向...

6133
来自专栏一“技”之长

使用iOS原生sqlite3框架对sqlite数据库进行操作

      sqlite数据库是一种小型数据库,由于其小巧与简洁,在移动开发领域应用深广,sqlite数据库有一套完备的sqlite语句进行管理操作,一些常用的...

921
来自专栏Seebug漏洞平台

Memcached 命令执行漏洞(CVE-2016-8704、CVE-2016-8705、CVE-2016-8706)简析

Author: p0wd3r, dawu (知道创宇404安全实验室) Date: 2016-11-01 0x00 漏洞概述 1.漏洞简介 Memcached...

3916
来自专栏linux驱动个人学习

高通Audio中ASOC的codec驱动(二)

继上一篇文章:高通Audio中ASOC的machine驱动(一) ASOC的出现是为了让codec独立于CPU,减少和CPU之间的耦合,这样同一个codec驱动...

1K6
来自专栏ios 技术积累

iOS AFNetworking 源码阅读一

大名鼎鼎的AFNetWorking,做iOS开发的人都知道吧。 AFNetWorking一款轻量级网络请求开源框架,基于iOS和mac os 网络进行扩展的高...

1823
来自专栏BinarySec

一些pwn题目的解题思路[pwnable.kr] II

目录 以下是solution的目录 #mistake #shellshock #coin1 #blackjack #lotto #cmd1 Other 一些pw...

3985
来自专栏Java 源码分析

SpringBoot 笔记(十一):Servlet容器

2622
来自专栏JAVA后端开发

解决spring boot无法捕捉404异常问题

但是在使用过程中,发现404时,根本没办法进入到该异常处理。经查,是spring mvc 在异常时,没有抛出404异常。 处理办法如下:

7342

扫码关注云+社区

领取腾讯云代金券