前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用MFC写一个聊天室程序 - 学习笔记

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

作者头像
Sindsun
发布2018-04-28 10:47:02
3.1K0
发布2018-04-28 10:47:02
举报
文章被收录于专栏:狂码一生狂码一生

下面的服务器端与客户端的程序与步骤是我在学习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 [若此文确切存在侵权,请联系本站管理员进行删除!]

本文参与?腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-12-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体同步曝光计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com