实验目标

采用C/S模型,分为客户端和服务器端程序。服务器可以与多个客户端建立连接,为多个客户机服务。服务器动态统计进入聊天室的客户端数量,并显示。会显示进入和退出聊天室的动态。

采用链表来管理客户机的套接字对象。

实验目的

  1. 学习如何从CSocket类派生出自己的WinSock类。
  2. 学习如何利用CSocketFile类、CArchive类和CSocket类的合作来实现网络进程之间的数据传输。
  3. 学习如何用链表管理多个动态客户机的套接字,实现服务器和所有的聊天客户机所显示信息的同步更新

实验环境

Windows10 x64,Visual Studio 2017

实验内容

分为两大块,创建客户端和创建服务器端。

服务器端:

  1. 使用MFC ApplicationWizard创建客户端应用程序框架
  2. 为对话框界面添加控件对象
  3. 为对话框中的控件对象定义相应的成员变量
  4. 创建从CSocket类继承的派生类
  5. 为对话框类添加控件对象事件的响应函数
  6. 为CTsDlg对话框类添加其他的成员函数和成员变量
  7. 创建专用于数据传输序列化处理的类CMsg
  8. 添加事件函数和成员函数的代码

客户端:

与服务器端同理

实验原理(模型)

服务器端:

  1. 创建空的服务器监听套接字对象

    CSocket sockserv;
    
  2. 创建监听套接字对象底层句柄

    CLSocket.Create(8000);
    
  3. 启动监听,准备客户端的连接请求

    sockServ.Listen();
    
  4. 接收连接,进入与客户机的会话期。动态的为客户机创建连接套接字对象(采用链表来管理套接字对象)

    CSocket sockRecv
    sockServ.Accept(sockRecv);
    
  5. 创建文件对象并关联到套接字对象

    CSocketFile* m_pFile;    //定义一个CSocketFile对象指针
    file =  new CSocketFile(&sockRecv);
    
  6. 消息的传送

    CArchive* arIn,arOut;
    arIn = CArchive(&file,CArchive::load);   //创建用于输入的CArchive对象
    arOut = CArchive(&file,CArchive::store);  //创建用于输出的CArchive对象
    //CArchive对象需要关联到文件对象
    
    arIn >> dwValue; //进行数据输入
    adOut << dwValue;  //数据输出
    
  7. 关闭套接字

    sockRecv.Close();
    sockSecv.Close();
    

创建CSocket对象分为两步:1. 调用CSocket类的构造函数,创建一个空的CSocket对象。2. 调用此CSocket对象的Create()成员函数,创建对象的底层套接字

客户端:

  1. 创建空的客户端套接字对象

    CSocket sockClient;
    
  2. 创建套接字对象底层句柄

    sockClient.Create();
    
  3. 请求连接到服务器

    sockClient.Connect(strAddr,nPort);
    
  4. 创建文件对象并关联到套接字对象

    CSocketFile* m_pFile;    //定义一个CSocketFile对象指针
    file =  new CSocketFile(&sockClient);
    
  5. 消息的传送

    CArchive* arIn,arOut;
    arIn = CArchive(&file,CArchive::load);   //创建用于输入的CArchive对象
    arOut = CArchive(&file,CArchive::store);  //创建用于输出的CArchive对象
    //CArchive对象需要关联到文件对象
    
    arIn >> dwValue; //进行数据输入
    adOut << dwValue;  //数据输出
    
  6. 关闭套接字

    sockClient.Close();
    

服务器端:创建监听套接字对象的时候,要用众所周知的保留端口号

服务器端:收到连接请求之后,需要创建一个专门用于连接的套接字对象。(即每个与服务器连接的客户端都有一个专属的用于连接和数据交换的套接字对象)

实验过程(代码分析)

👉创造程序框架 (MFC基于对话框)

👉添加控件对象

👉添加控件成员变量

CSocket类从CAsynSocket类继承了许多成员函数,封装了Windows套接字应用程序编程接口API。

应用程序类CTsApp、CTcApp对应文件

VC++自动生成

###派生类CLSocket、CCSocket对应文件

服务器端:

CLSocket,专用于监听客户端的连接请求(添加OnAccept事件处理函数)

CCSocket,专用于客户端建立连接并交换数据(添加OnReceive事件处理函数)

添加事件响应函数,用于停止服务、和监听

✏️LSocket.h

#pragma once
class CTsDlg;
class CLSocket : public CSocket
{
    DECLARE_DYNAMIC(CLSocket);
public:
    CLSocket(CTsDlg* m_pDlg);
    virtual ~CLSocket();
    CTsDlg* m_pDlg;
protected:
    virtual void OnAccept(int nErrorCode);
};

✏️LSocket.cpp

#include "pch.h"
#include "LSocket.h"
#include "tsDlg.h"

CLSocket::CLSocket(CTsDlg* pDlg)
{
    m_pDlg = pDlg;
}

CLSocket::~CLSocket()
{
    m_pDlg = NULL;
}

void CLSocket::OnAccept(int nErrorCode)
{
    CSocket::OnAccept(nErrorCode);
    m_pDlg->OnAccept();
}
IMPLEMENT_DYNAMIC(CLSocket, CSocket)

✏️CSocket.h

#pragma once

class CTsDlg;
class CMsg;
class CCSocket:public CSocket
{
    DECLARE_DYNAMIC(CCSocket);
public:
    CCSocket(CTsDlg* pDlg);
    virtual ~CCSocket();
public:
    CTsDlg* m_pDlg;
    CSocketFile* m_pFile;
    CArchive* m_pArchiveIn;
    CArchive* m_pArchiveOut;
public:
    void Initialize();
    void SendMessage(CMsg* pMsg);
    void ReceiveMessage(CMsg* pMsg);
protected:
    virtual void OnReceive(int nErrorCode);
};

✏️CSocket.cpp

#include "pch.h"
#include "CSocket.h"
#include "tsDlg.h"
#include "Msg.h"

CCSocket::CCSocket(CTsDlg* pDlg)
{
    m_pDlg = pDlg;
    m_pFile = NULL;
    m_pArchiveIn = NULL;
    m_pArchiveOut = NULL;
}

CCSocket::~CCSocket()
{
    m_pDlg = NULL;
    if (m_pArchiveOut != NULL) delete m_pArchiveOut;
    if (m_pArchiveIn != NULL) delete m_pArchiveIn;
    if (m_pFile != NULL) delete m_pFile;
}



void CCSocket::Initialize()
{
    m_pFile = new CSocketFile(this, TRUE);
    m_pArchiveIn = new CArchive(m_pFile, CArchive::load);
    m_pArchiveOut = new CArchive(m_pFile, CArchive::store);
}


void CCSocket::SendMessage(CMsg* pMsg)
{
    if (m_pArchiveOut != NULL)
    {
        pMsg->Serialize(*m_pArchiveOut);
        m_pArchiveOut->Flush();
    }
}


void CCSocket::ReceiveMessage(CMsg* pMsg)
{
    pMsg->Serialize(*m_pArchiveIn);

}

void CCSocket::OnReceive(int nErrorCode)
{
    CSocket::OnReceive(nErrorCode);
    m_pDlg->OnReceive(this);
}
IMPLEMENT_DYNAMIC(CCSocket,CSocket)

客户端:

CCSocket,专用于于服务器建立连接并交换数据(添加OnReceive事件处理函数)

✏️CSocket.h

#pragma once
class CTcDlg;
class CCSocket:public CSocket
{
    DECLARE_DYNAMIC(CCSocket);
public:
    CCSocket(CTcDlg* pDlg);
    virtual ~CCSocket();
    CTcDlg* m_pDlg;
protected:
    virtual void OnReceive(int nErrorCode);
};

✏️CSocket.cpp

#include "pch.h"
#include "CSocket.h"
#include "tcDlg.h"
IMPLEMENT_DYNAMIC(CCSocket,CSocket)

CCSocket::CCSocket(CTcDlg* pDlg)
{
    m_pDlg = pDlg;
}

CCSocket::~CCSocket()
{
    m_pDlg = NULL;
}

void CCSocket::OnReceive(int nErrorCode)
{
    CSocket::OnReceive(nErrorCode);
    if (nErrorCode == 0) m_pDlg->OnReceive();
}

对话框类CTsDlg、CTcDlg对应文件

主要是对控件变量的声明和定义。

服务器端:

✏️tsDlg.h


// tsDlg.h: 头文件
//

#pragma once
#include "CSocket.h"
#include "LSocket.h"

class CMsg;

// CTsDlg 对话框
class CTsDlg : public CDialogEx
{
// 构造
public:
    CTsDlg(CWnd* pParent = nullptr);    // 标准构造函数

// 对话框数据

#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_TS_DIALOG };
#endif
    protected:
        virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持
// 实现
protected:
    HICON m_hIcon;

    // 生成的消息映射函数
    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    CStatic m_staNum; 
    CButton m_btnListen;
    CButton m_btnClose;
    CListBox m_listMsg;
    UINT m_nPort;
    
    afx_msg void OnButtonListen();
    afx_msg void OnClose();


    // 生成的消息映射函数
    
public:
    
    CLSocket* m_pLSocket;
    CPtrList m_connList;
    void OnAccept();
    void OnReceive(CCSocket* pSocket);
    void backClients(CMsg* pMsg);
};

✏️tsDlg.cpp


// tsDlg.cpp: 实现文件
//

#include "pch.h"
#include "framework.h"
#include "ts.h"
#include "tsDlg.h"
#include "afxdialogex.h"
#include "Msg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
    CAboutDlg();

// 对话框数据
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_ABOUTBOX };
#endif

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CTsDlg 对话框



CTsDlg::CTsDlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_TS_DIALOG, pParent)
    
{
    m_nPort = 0;
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    m_pLSocket = NULL;
}

void CTsDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_STATIC_NUM, m_staNum);
    DDX_Text(pDX, IDC_EDIT_PORT, m_nPort);
    DDX_Control(pDX, IDC_BUTTON_LISTEN, m_btnListen);
    DDX_Control(pDX, IDOK, m_btnClose);
    DDX_Control(pDX, IDC_LIST_MSG, m_listMsg);
}

BEGIN_MESSAGE_MAP(CTsDlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDOK, &CTsDlg::OnClose)
    ON_BN_CLICKED(IDC_BUTTON_LISTEN, &CTsDlg::OnButtonListen)
END_MESSAGE_MAP()


// CTsDlg 消息处理程序

BOOL CTsDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // 将“关于...”菜单项添加到系统菜单中。

    // IDM_ABOUTBOX 必须在系统命令范围内。
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != nullptr)
    {
        BOOL bNameValid;
        CString strAboutMenu;
        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
        ASSERT(bNameValid);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
    //  执行此操作
    SetIcon(m_hIcon, TRUE);            // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标

    // TODO: 在此添加额外的初始化代码
    m_nPort = 8000;
    UpdateData(FALSE);
    GetDlgItem(IDOK)->EnableWindow(FALSE);

    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

void CTsDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialogEx::OnSysCommand(nID, lParam);
    }
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CTsDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // 用于绘制的设备上下文

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // 使图标在工作区矩形中居中
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // 绘制图标
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CTsDlg::OnQueryDragIcon()
{
    return (HCURSOR) m_hIcon;
}



void CTsDlg::OnClose()
{
    CMsg msg;
    msg.m_strText = "服务器终止服务!";
    delete m_pLSocket;
    m_pLSocket = NULL;
    while (!m_connList.IsEmpty()) 
    {
        CCSocket* pSocket = (CCSocket*)m_connList.RemoveHead();
        pSocket->SendMessage(&msg);
        delete pSocket;
    }
    while (m_listMsg.GetCount() != 0)
        m_listMsg.DeleteString(0);
    GetDlgItem(IDC_EDIT_PORT)->EnableWindow(TRUE);
    GetDlgItem(IDC_BUTTON_LISTEN)->EnableWindow(TRUE);
    GetDlgItem(IDOK)->EnableWindow(FALSE);

}


void CTsDlg::OnButtonListen()
{
    UpdateData(TRUE);
    m_pLSocket = new CLSocket(this);
    if (!m_pLSocket->Create(m_nPort))
    {
        delete m_pLSocket;
        m_pLSocket = NULL;
        AfxMessageBox(_T("创建监听套接字错误!"));
        return;
    }
    if (!m_pLSocket->Listen())
    {
        delete m_pLSocket;
        m_pLSocket = NULL;
        AfxMessageBox(_T("启动监听错误!"));
        return;
    }
    GetDlgItem(IDC_EDIT_PORT)->EnableWindow(FALSE);
    GetDlgItem(IDC_BUTTON_LISTEN)->EnableWindow(FALSE);
    GetDlgItem(IDOK)->EnableWindow(TRUE);

}


void CTsDlg::OnAccept()
{
    CCSocket* pSocket = new CCSocket(this);
    if (m_pLSocket->Accept(*pSocket))
    {
        pSocket->Initialize();
        m_connList.AddTail(pSocket);
        CString strTemp;
        strTemp.Format(_T("在线人数:%d", m_connList.GetCount()));
        m_staNum.SetWindowText(strTemp);
    }
    else delete pSocket;
}


void CTsDlg::OnReceive(CCSocket* pSocket)
{
    static CMsg msg;
    do {
        pSocket->ReceiveMessage(&msg);
        m_listMsg.AddString(msg.m_strText);
        backClients(&msg);
        if (msg.m_bClose)
        {
            pSocket->Close();
            POSITION pos, temp;
            for (pos = m_connList.GetHeadPosition(); pos != NULL;)
            {
                temp = pos;
                CCSocket* pSock = (CCSocket*)m_connList.GetNext(pos);
                if (pSock == pSocket)
                {
                    m_connList.RemoveAt(temp);
                    CString strTemp;
                    strTemp.Format((_T("在线人数:%d", m_connList.GetCount())));
                    m_staNum.SetWindowText(strTemp);
                    break;
                }
            }
            delete pSocket;
            break;
        }
    } while (!pSocket->m_pArchiveIn->IsBufferEmpty());
}



void CTsDlg::backClients(CMsg* pMsg)
{
    for (POSITION pos = m_connList.GetHeadPosition(); pos != NULL;)
    {
        CCSocket* pSocket = (CCSocket*)m_connList.GetNext(pos);
        pSocket->SendMessage(pMsg);
    }
}

客户端:

✏️tcDlg.h


// tcDlg.h: 头文件
//
#include "CSocket.h"
#pragma once


// CTcDlg 对话框
class CTcDlg : public CDialogEx
{
// 构造
public:
    CTcDlg(CWnd* pParent = nullptr);    // 标准构造函数

// 对话框数据
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_TC_DIALOG };
#endif

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持


// 实现
protected:
    HICON m_hIcon;

    // 生成的消息映射函数
    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnEnChangeEditCname();
    afx_msg void OnEnChangeEdit2();
    CButton m_btnClose;
    CString m_strCName;
    CString m_strSName;
    UINT m_nPort;
    CString m_strMsg;
    CButton m_btnConn;
    CButton m_Send;
    CListBox m_listMsg;
    afx_msg void OnSend();
    afx_msg void OnButtonConn();
    afx_msg void OnButtonClose();
    afx_msg void OnDestroy();
    CCSocket* m_pSocket;
    CSocketFile* m_pFile;
    CArchive* m_pArchiveIn;
    CArchive* m_pArchiveOut;
    void OnReceive();
    void ReceiveMsg();
    void SendMsg(CString& strText, bool st);
};

✏️tcDlg.cpp


// tcDlg.cpp: 实现文件
//

#include "pch.h"
#include "framework.h"
#include "tc.h"
#include "tcDlg.h"
#include "afxdialogex.h"
#include "CSocket.h"
#include "Msg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
    CAboutDlg();

// 对话框数据
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_ABOUTBOX };
#endif

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CTcDlg 对话框



CTcDlg::CTcDlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_TC_DIALOG, pParent)
{
    m_strCName=_T("");
    m_strSName=_T("");
    m_nPort = 0;
    m_strMsg=_T("");

    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    m_pSocket = NULL;
    m_pFile = NULL;
    m_pArchiveIn = NULL;
    m_pArchiveOut = NULL;
}

void CTcDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_BUTTON_CLOSE, m_btnClose);
    DDX_Text(pDX, IDC_EDIT_CNAME, m_strCName);
    DDX_Text(pDX, IDC_EDIT_SNAME, m_strSName);
    DDX_Text(pDX, IDC_EDIT_PORT, m_nPort);
    DDX_Text(pDX, IDC_EDIT_MSG, m_strMsg);
    DDX_Control(pDX, IDC_BUTTON_CONN, m_btnConn);
    DDX_Control(pDX, IDOK, m_Send);
    DDX_Control(pDX, IDC_LIST_MSG, m_listMsg);
}

BEGIN_MESSAGE_MAP(CTcDlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    //ON_EN_CHANGE(IDC_EDIT2, &CTcDlg::OnEnChangeEdit2)
    ON_BN_CLICKED(IDOK, &CTcDlg::OnSend)
    ON_BN_CLICKED(IDC_BUTTON_CONN, &CTcDlg::OnButtonConn)
    ON_BN_CLICKED(IDC_BUTTON_CLOSE, &CTcDlg::OnButtonClose)
    ON_WM_DESTROY()
END_MESSAGE_MAP()


// CTcDlg 消息处理程序

BOOL CTcDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // 将“关于...”菜单项添加到系统菜单中。

    // IDM_ABOUTBOX 必须在系统命令范围内。
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != nullptr)
    {
        BOOL bNameValid;
        CString strAboutMenu;
        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
        ASSERT(bNameValid);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
    //  执行此操作
    SetIcon(m_hIcon, TRUE);            // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标

    // TODO: 在此添加额外的初始化代码
    m_strCName = _T("客户 1");
    m_nPort = 8000;
    m_strSName = _T("localhost");
    GetDlgItem(IDC_EDIT_MSG)->EnableWindow(FALSE);
    GetDlgItem(IDOK)->EnableWindow(FALSE);
    GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(FALSE);
    UpdateData(FALSE);


    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

void CTcDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialogEx::OnSysCommand(nID, lParam);
    }
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CTcDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // 用于绘制的设备上下文

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // 使图标在工作区矩形中居中
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // 绘制图标
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CTcDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}



void CTcDlg::OnEnChangeEdit2()
{
    // TODO:  如果该控件是 RICHEDIT 控件,它将不
    // 发送此通知,除非重写 CDialogEx::OnInitDialog()
    // 函数并调用 CRichEditCtrl().SetEventMask(),
    // 同时将 ENM_CHANGE 标志“或”运算到掩码中。

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


void CTcDlg::OnSend()
{
    // TODO: 在此添加控件通知处理程序代码
    UpdateData(TRUE);
    if (!m_strMsg.IsEmpty())
    {
        this->SendMsg(m_strCName + (_T(":")) + m_strMsg, FALSE);
        m_strMsg = _T("");
        UpdateData(FALSE);
    }
}


void CTcDlg::OnButtonConn()
{
    m_pSocket = new CCSocket(this);
    if (!m_pSocket->Create())
    {
        delete m_pSocket;
        m_pSocket = NULL;
        AfxMessageBox(_T("套接字创建错误!"));
        return;
    }
    if (!m_pSocket->Connect(m_strSName, m_nPort))
    {
        delete m_pSocket;
        m_pSocket = NULL;
        AfxMessageBox(_T("无法连接服务器错误!"));
        return;

    }

    m_pFile = new CSocketFile(m_pSocket);
    m_pArchiveIn = new CArchive(m_pFile, CArchive::load);
    m_pArchiveOut = new CArchive(m_pFile, CArchive::store);
    UpdateData(TRUE);
    CString strTemp;
    strTemp = m_strCName +(_T( ":进入聊天室"));
    SendMsg(strTemp, FALSE);
    GetDlgItem(IDC_EDIT_MSG)->EnableWindow(TRUE);
    GetDlgItem(IDOK)->EnableWindow(TRUE);
    GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(TRUE);

    GetDlgItem(IDC_EDIT_CNAME)->EnableWindow(FALSE);
    GetDlgItem(IDC_EDIT_SNAME)->EnableWindow(FALSE);
    GetDlgItem(IDC_EDIT_PORT)->EnableWindow(FALSE);
    GetDlgItem(IDC_BUTTON_CONN)->EnableWindow(FALSE);
}






void CTcDlg::OnButtonClose()
{
    CString strTemp;
    strTemp = m_strCName + (_T(":离开聊天室"));
    SendMsg(strTemp, TRUE);

    delete m_pArchiveOut;
    m_pArchiveOut = NULL;
    delete m_pArchiveIn;
    m_pArchiveIn = NULL;
    delete m_pFile;
    m_pFile = NULL;
    m_pSocket->Close();
    delete m_pSocket;
    m_pSocket = NULL;

    while (m_listMsg.GetCount() != 0) m_listMsg.DeleteString(0);
    GetDlgItem(IDC_EDIT_MSG)->EnableWindow(FALSE);
    GetDlgItem(IDOK)->EnableWindow(FALSE);
    GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(FALSE);

    GetDlgItem(IDC_EDIT_CNAME)->EnableWindow(TRUE);
    GetDlgItem(IDC_EDIT_SNAME)->EnableWindow(TRUE);
    GetDlgItem(IDC_EDIT_PORT)->EnableWindow(TRUE);
    GetDlgItem(IDC_BUTTON_CONN)->EnableWindow(TRUE);

}


void CTcDlg::OnDestroy()
{
    CDialog::OnDestroy();
    if ((m_pSocket != NULL) && (m_pFile != NULL) && (m_pArchiveOut != NULL))
    {
        CMsg msg;
        CString strTemp;
        strTemp = (_T("DDDD:离开聊天室!"));
        msg.m_bClose = TRUE;
        msg.m_strBuf = m_strCName + strTemp;
        msg.Serialize(*m_pArchiveOut);
        m_pArchiveOut->Flush();
    }
    delete m_pArchiveOut;
    m_pArchiveOut = NULL;
    delete m_pArchiveIn;
    m_pArchiveIn = NULL;
    delete m_pFile;
    m_pFile = NULL;
    if (m_pSocket != NULL)
    {
        BYTE Buffer[50];
        m_pSocket->ShutDown();
        while (m_pSocket->Receive(Buffer, 50) > 0);
    }
    delete m_pSocket;
    m_pSocket = NULL;
}


void CTcDlg::OnReceive()
{
    do
    {
        ReceiveMsg();
        if (m_pSocket = NULL) return;
    } while (!m_pArchiveIn->IsBufferEmpty());
}


void CTcDlg::ReceiveMsg()
{
    CMsg msg;
    TRY
    {
        msg.Serialize(*m_pArchiveIn);
    m_listMsg.AddString(msg.m_strBuf);
    }
        CATCH(CFileException, e)
    {
        CString strTemp;
        strTemp = (_T("服务器重置连接!连接关闭!"));
        m_listMsg.AddString(strTemp);
        msg.m_bClose = TRUE;
        m_pArchiveOut->Abort();
        delete m_pArchiveIn;
        m_pArchiveIn = NULL;
        delete m_pArchiveOut;
        m_pArchiveOut = NULL;
        delete m_pFile;
        m_pFile = NULL;
        delete m_pSocket;
        m_pSocket = NULL;
    }
    END_CATCH
}


void CTcDlg::SendMsg(CString& strText, bool st)
{
    if (m_pArchiveOut != NULL)
    {
        CMsg msg;
        msg.m_strBuf = strText;
        msg.m_bClose = st;
        msg.Serialize(*m_pArchiveOut);
        m_pArchiveOut->Flush();
    }
}

数据传输序列化处理类CMsg对应文件

服务器端:

✏️Msg.h

#pragma once

// CMsg 命令目标


class CMsg : public CObject
{
    DECLARE_DYNCREATE(CMsg);
public:
    CMsg();
    virtual ~CMsg();
public:
    CString m_strText;
    BOOL m_bClose;
public:
    virtual void Serialize(CArchive&ar);
};


✏️Msg.cpp

// CMsg.cpp: 实现文件
//

#include "pch.h"
#include "ts.h"
#include "Msg.h"


// CMsg

CMsg::CMsg()
{
    m_strText = _T("");
    m_bClose = FALSE;
}

void CMsg::Serialize(CArchive & ar)
{
    if (ar.IsStoring())
    {
        ar << (WORD)m_bClose;
        ar << m_strText;
    }
    else {
        WORD wd;
        ar >> wd;
        m_bClose = (BOOL)wd;
        ar >> m_strText;
    }
}
CMsg::~CMsg()
{

}

IMPLEMENT_DYNAMIC(CMsg,CObject)

客户端:

✏️Msg.h

#pragma once
#include <afx.h>
class CMsg :
    public CObject
{
    DECLARE_DYNCREATE(CMsg);
public:
    CMsg();
    virtual ~CMsg();
    virtual void Serialize(CArchive& ar);
    CString m_strBuf;
    BOOL m_bClose;
};

✏️Msg.cpp

#include "pch.h"
#include "Msg.h"
CMsg::CMsg() 
{
    m_strBuf = _T("");
    m_bClose = FALSE;
}

void CMsg::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        ar << (WORD)m_bClose;
        ar << m_strBuf;
    }
    else {
        WORD wd;
        ar >> wd;
        m_bClose = (BOOL)wd;
        ar >> m_strBuf;
    }
}
CMsg::~CMsg()
{

}
IMPLEMENT_DYNAMIC(CMsg,CObject)

实验界面结果展示

实验过程中遇到的问题

⛔️在Msg.h中声明了析构函数,但cpp中没有定义

💚在Msg.cpp中补上析构函数的定义


⛔️ 字符问题

strTemp = m_strCName + ":进入聊天室";

💚在字符串前面,加上_T

strTemp = m_strCName + (_T(":进入聊天室"));

实验参考书

服务器端

客户端

评论