下面的服务器端与客户端的程序与步骤是我在学习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--