基于CSocket类网络群聊服务器开发

第一次接触MFC的Socket网络编程.

 参考了:MFC网络编程——简单的服务器/客户端-CSDN论坛

这里用的编译器是:Visual Studio 2019

服务器开发:

  1. 新建一个MFC项目 项目名称:MFCServer

应用程序类型:选择基于对话框

 

2.在高级功能中,勾选Windows套接字

 

 其余的默认就行。

3.在对话框中加入3个Static Text,2个Edit Control,1个Button,下表是各个控件的参数:

注:添加变量可以右键对应的控件,点击添加变量即可

 

 

 

4.添加事件处理函数,可通过右键对话框,点击类向导

 

 

在类向导中添加一个新的CServerSocket类,基类为CSocket;在虚函数中添加三个函数,OnAccept()、OnClose()、OnReceive()

 

点击确定即可。

5.编写CServerSocket类,首先在资源管理器中打开CServerSocket.h头文件

 

  修改CServerSocket.h 头文件

#pragma once

#include <afxsock.h>

#include"MFCServerDlg.h"   //主对话框头文件

class CMFCServerDlg;        //主对话框类

class CServerSocket :public CSocket

{

public:

    CMFCServerDlg* m_pDlg; //主对话框类指针对象

    CServerSocket() {};

    virtual ~CServerSocket() {};

    virtual void OnAccept(int nErrorCode);

    virtual void OnClose(int nErrorCode);

    virtual void OnReceive(int nErrorCode);

};

 

 接着修改源文件 CServerSocket.cpp

#include "pch.h"

#include "CServerSocket.h"

void CServerSocket::OnAccept(int nErrorCode)

{

    // TODO: 在此添加专用代码和/或调用基类

    m_pDlg->AddClient();                                  //添加上线用户

    CSocket::OnAccept(nErrorCode);

}

void CServerSocket::OnClose(int nErrorCode)

{

    // TODO: 在此添加专用代码和/或调用基类

    m_pDlg->RemoveClient(this);                   // 删除下线用户

    CSocket::OnClose(nErrorCode);

}

void CServerSocket::OnReceive(int nErrorCode)

{

    // TODO: 在此添加专用代码和/或调用基类

    m_pDlg->RecvData(this);                           // 接收数据

    CSocket::OnReceive(nErrorCode);

}

6.在头文件MFCServerDlg.h 中添加服务器类CServerSocket.h 头文件,以及CServerSocket类声明

#include"CServerSocket.h"

class CServerSocket;

7.在CMFCServerDlg类中添加函数声明与变量声明

CServerSocket* listenSocket;            // 用于打开服务器

CPtrList m_clientList;                  // 链表用于存储用户

bool m_connect;                         // 用于标记服务器状态

void AddClient();                       // 增加用户,响应用户请求

void RemoveClient(CServerSocket* pSocket);    // 移除下线的用户

void RecvData(CServerSocket* pSocket);         // 获取数据

void UpdateEvent(CString str);          // 更新事件日志

void SendMSG(CString str);              // 发送消息给各个客户端

 

8.在源文件MFCServerDlg.cpp 中添加函数实现

先在资源视图中打开

 

 

资源视图可以从顶部菜单栏——视图中打开

 

 

双击按钮自动生成响应函数

 

 

9.编写OnBnClickedStartserver()函数实现

void CMFCServerDlg::OnBnClickedStartserver()

{

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

    if (m_connect)

    {

       delete listenSocket;

       listenSocket = NULL;

       m_connect = false;

       SetDlgItemText(IDC_StartServer, _T("打开服务器"));

       UpdateEvent(_T("系统关闭服务器."));

       return;

    }

    listenSocket = new CServerSocket();

    listenSocket->m_pDlg = this;

    // 指定对话框为主对话框,不能少了这句

    UpdateData(true);

    if (!listenSocket->Create(m_port))// 创建服务器的套接字,IP地址默认本机IP

    {

       AfxMessageBox(_T("创建套接字错误!"));

       listenSocket->Close();

       return;

    }

    if (!listenSocket->Listen())

    {

       AfxMessageBox(_T("监听失败!"));

       listenSocket->Close();

       return;

    }

    m_connect = true;

    SetDlgItemText(IDC_StartServer, _T("关闭服务器"));

    UpdateEvent(_T("系统打开服务器."));

}

说明:本函数用于打开或关闭服务器,主要用到Create函数和Listen函数用于创建服务器和监听客户端。其中端口号从编辑框获取,应用程序的可用端口范围是1024-65535。

10.编写AddClient() 函数,用于增加用户,响应用户请求

void CMFCServerDlg::AddClient()

{

    CServerSocket* pSocket = new CServerSocket;

    pSocket->m_pDlg = this;

    listenSocket->Accept(*pSocket);

    pSocket->AsyncSelect(FD_READ | FD_WRITE | FD_CLOSE);

    m_clientList.AddTail(pSocket);

    m_userCount = m_clientList.GetCount();

    UpdateData(false);

    UpdateEvent(_T("用户连接服务器."));

    SendMSG(_T("有一位用户加入"));

}

说明:本函数在CServerSocket类中的OnAccept消息中调用,用于响应用户连接服务器的请求,主要函数为Accept,当连接成功后,通过链表m_clientList保存新用户,更新日志,向所有用户发送提醒“有一位用户加入”。

11.编写RemoveClient函数,用于移除下线的用户

 void CMFCServerDlg::RemoveClient(CServerSocket* pSocket)

{

    POSITION nPos = m_clientList.GetHeadPosition();

    POSITION nTmpPos = nPos;

    UpdateData();

    if (m_userCount == 0)//如果所有用户退出,将聊天记录清空

    {

       m_chatData = _T("");

    }

    while (nPos)

    {

       CServerSocket* pSockItem = (CServerSocket*)m_clientList.GetNext(nPos);

       if (pSockItem->m_hSocket == pSocket->m_hSocket)

       {

           pSockItem->Close();

           delete pSockItem;

           m_clientList.RemoveAt(nTmpPos);

           m_userCount = m_clientList.GetCount();

           UpdateData(false);

           UpdateEvent(_T("用户离开."));

           return;

       }

       nTmpPos = nPos;

    }

}

说明:本函数在CServerSocket类中的OnClose消息中调用,用到POSITION结构,查找存储用户中哪位用户下线了,将下线用户释放,从链表中删除,并更新日志。以及判断用户是否全部下线,如果全部用户下线则将m_chatData聊天数据清除。

12.编写RecvData函数,用于接收来着客户端的数据,并立刻将数据发送给全部客户端

void CMFCServerDlg::RecvData(CServerSocket* pSocket)

{

    char* pData = NULL;

    pData = new char[1024];

    memset(pData, 0, sizeof(char) * 1024);

    CString str;

    if (pSocket->Receive(pData, 1024, 0) != SOCKET_ERROR)

    {

       str.Format(_T("%s"), pData);

       m_chatData += str + _T("\r\n"); //将收到的数据加到m_chatData,并换行

       SendMSG(m_chatData); // 将m_chatData转发给所有用户,包括发送数据的用户    }

    delete[]pData;

    pData = NULL;

}

说明:本函数在CServerSocket类中的OnReceive消息中调用,用于处理接收到的数据并将数据转发给所有用户,通过CSocket类的GetPeerName函数可以获取用户的IP和端口号。

13.编写UpdateEvent函数,用于更新事件日志

void CMFCServerDlg::UpdateEvent(CString str)

{

    CString string;

    CTime time = CTime::GetCurrentTime();// 获取系统当前时间

    str += _T("\r\n");                                  // 用于换行显示日志

    string = time.Format(_T("%Y/%m/%d %H:%M:%S  ")) + str;// 格式化当前时间

    int lastLine = m_eventLog.LineIndex(m_eventLog.GetLineCount() - 1);//获取编辑框最后一行索引

    m_eventLog.SetSel(lastLine + 1, lastLine + 2, 0);   //选择编辑框最后一行

    m_eventLog.ReplaceSel(string);                     //替换所选那一行的内容

}

14.编写SendMSG函数,用于发送数据给客户端

void CMFCServerDlg::SendMSG(CString str)

{

    char* pSend = (char*)str.GetBuffer(0);

    int nlen = str.GetLength();

    POSITION nPos = m_clientList.GetHeadPosition();

    while (nPos)

    {

       CServerSocket*pTemp(CServerSocket*)m_clientList.GetNext(nPos);

       pTemp->Send(pSend, nlen * 2);//长度一定要乘2,不然发送中文数据可能会乱码

    }

}

最后可以在初始化的时候设置一个默认的端口号,这里默认设置6000

 

 

最后的成品

 

客户端开发:

  1. 新建MFC项目,项目名称为:MFCClient
  2. 勾选Windows套接字
  3. 加入控件

 

 

客户端的控件会多一点

需要用到5个编辑框,2个按钮,3个静态文本

ID

描述文字

变量名

变量类型

备注

编辑框1

IDC_IPAddress

m_address

CString

编辑框2

IDC_UserName

m_userName

CString

编辑框3

IDC_PORT

m_port

UINT

将数字(Number)设为True

编辑框4

IDC_DataReceive

将多行,垂直滚动设为True

编辑框5

IDC_DataSend

m_DataSend

CString

按钮1

IDC_connect

连接服务器

m_ConPC

CButton

按钮2

IDC_Send

发送

4.类向导添加新的类CClientSocket   添加虚函数OnReceive();与写服务器时的步骤差不多

  1. 编写 CClientSocket.h 头文件

#pragma once

#include <afxsock.h>

class CClientSocket : public CSocket

{

public:

    CClientSocket();

    virtual ~CClientSocket();

    virtual void OnReceive(int nErrorCode);

};

 

2.编写源文件 CClientSocket.cpp 实现OnReceive()函数

#include "pch.h"

#include "CClientSocket.h"

#include "MFCClientDlg.h"

#include "MFCClient.h"

CClientSocket::CClientSocket() {}

CClientSocket::~CClientSocket() {}

void CClientSocket::OnReceive(int nErrorCode)

{

    // TODO:  在此添加专用代码和/或调用基类

    char* pData = NULL;

    pData = new char[1024];

    memset(pData, 0, sizeof(char) * 1024);

    UCHAR leng = 0;

    CString str;

    leng = Receive(pData, 1024, 0);

    str.Format(_T("% s"), pData);

    // 在编辑框中显示接收到的数据

    ((CMFCClientDlg*)theApp.GetMainWnd())->SetDlgItemTextW(IDC_DataReceive, str);

    delete[]pData;

    pData = NULL;

    CSocket::OnReceive(nErrorCode);

}

3.编写MFCClinentDlg.h 头文件,在里面加入头文件

#include"CClientSocket.h"

以及在类内部声明

bool m_connect;                    //连接状态

CClientSocket* pSock;               // 客户端套接字指针对象

 

接下来的代码均在CMFCClientDlg.cpp 中编写

4.编写“打开服务器”按钮的响应函数(双击按钮生成)

void CMFCClientDlg::OnBnClickedconnect()

{

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

    if (m_connect)                             // 如果已经连接,则断开服务器

    {

        m_connect = false;

        pSock->Close();

        delete pSock;

        pSock = NULL;

        m_ConPC.SetWindowTextW(_T("连接服务器"));

        GetDlgItem(IDC_IPAddress)->EnableWindow(TRUE);

        GetDlgItem(IDC_PORT)->EnableWindow(TRUE);

        GetDlgItem(IDC_Username)->EnableWindow(TRUE);

        UpdateData(false);

        return;

    }

    else                                                // 未连接,则连接服务器

    {

        pSock = new CClientSocket();

        if (!pSock->Create())         //创建套接字

        {

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

            return;

        }

    }

    UpdateData();

    if (m_address.GetLength() == 0)

    {

        AfxMessageBox(_T("IP地址必须填写!"));

        return;

    }

    if (!pSock->Connect(m_address, m_port))    //连接服务器

    {

        AfxMessageBox(_T("连接服务器失败!"));

        return;

    }

    else

    {

        m_connect = true;

        m_ConPC.SetWindowTextW(_T("断开服务器"));

//成功连接后将IP、端口、昵称 这三个编辑框控件状态设为只读

        GetDlgItem(IDC_IPAddress)->EnableWindow(FALSE);

        GetDlgItem(IDC_PORT)->EnableWindow(FALSE);

        GetDlgItem(IDC_Username)->EnableWindow(FALSE);

        UpdateData(false);

    }

}

说明:点击连接服务器按钮后,本函数通过Create和Connect与服务器建立连接,并在连接成功后,将IP、端口、昵称 这三个编辑框控件状态设为只读,再次按下该按钮时,会断开连接

5.编写OnBnClickedSend()函数

void CMFCClientDlg::OnBnClickedSend()

{

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

    if (!m_connect)return;                               //未连接服务器则不执行

    UpdateData(true);                                     //获取界面数据

    CString SendText = _T("");

    SendText += m_userName + _T(":") + m_DataSend;

    if (SendText != "")

    {

        char* pBuff = (char*)SendText.GetBuffer(0);

        int nlen = SendText.GetLength();

        pSock->Send(pBuff, nlen * 2);

    }

    GetDlgItem(IDC_DataSend)->SetWindowText(_T(""));

}

说明:该函数会将昵称+发送内容一并发给服务器

6.在初始化的时候设置IP地址的默认值,以及端口的默认参数

其实也可以不用,就是如果没有就得每次打开都得输入一次;

 

大家可以按下win+R ,输入cmd

在cmd中输入 ipconfig 查找自己的ip地址

7.防止在输入发送内容时,按下回车键导致程序关闭

可以在类向导中,添加PreTranslateMessage()函数(也是在虚函数中找)

 

 

点击确定

编写PreTranslateMessage()函数

BOOL CMFCClientDlg::PreTranslateMessage(MSG* pMsg)

{

    // TODO: 在此添加专用代码和/或调用基类

    switch (pMsg->wParam)

    {

    case WM_KEYDOWN:OnBnClickedSend();//回车按下后将消息发送出去

case VK_ESCAPE:

       return true; break;

    }

    return CDialogEx::PreTranslateMessage(pMsg);

}

说明:回车发送这个功能还不太完善,按下回车后会发送两次,原因应该是按下回车会触发一次,松开回车会触发一次。暂时还没有比较好的解决方法。

最后最后,这个程序只能在同一局域网才能连接,如果是在两台或者多台电脑上运行,起服务器的那台电脑需要关闭Windows防火墙,不然客户端是连接不到服务器的

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值