CSocket是MFC封装的一个网络通信套接字,旨在为快速开发提供便利。
CSocket是在CAsyncSocket的基础上再次进行了封装,更见的便于使用。并且CAsyncSocket继承于CObject,这样可以使用CObject的一些特性。
首先看一下这个类是如何使用。
在使用mfc类的时候,要继承这个类,然后重写自己需要的功能,这是正常的步骤。
我将实现一个服务器程序,和一个客户端程序,服务器程序用于转发所有的客户端发送来的消息,这类似于一个群聊的服务。
下面是客户端的设计过程:
1.连接服务器必须要指定端口和ip地址,并且还有一个按钮用于连接。
2.需要一个展示消息的控件。
3.需要一个输入消息的控件和一个发送按钮。
基本的形状就如下所示了:
既然要通信,那么我们写一个自定义的类,或者从vs添加一个类,
#pragma once
#include <afxsock.h>
class ClientSocket :
public CSocket
{
public:
virtual void OnReceive(int nErrorCode); // 客户端将服务器转发来的消息在窗口中显示,
};
这段代码很简单,因为对于简单的通信,我们发送消息是主动的,可以直接使用CSocket的方法即可,而接收消息是被动的,所以我这里重写了这个虚函数,这样当服务器转发过来消息时,这个函数就会触发。下面写一下当有消息时,进行读取的操作。
#include "pch.h"
#include "ClientSocket.h"
#include "ClientDlg.h"
void ClientSocket::OnReceive(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
CString str;
int len = Receive(str.GetBuffer(1024), 1024);
if (len) {
((CClientDlg*)AfxGetApp()->m_pMainWnd)->m_listbox.InsertString(0, str);
}
CSocket::OnReceive(nErrorCode);
}
当有消息时,我们读取到CString中,并且将这个消息显示在窗口的消息显示控件中。
至此,这个类就设计完成了。
我们来看一下,如何与服务器连接:
为了方便理解,我将窗口下的控件绑定的变量贴出来。
public:
ClientSocket m_sock;
CIPAddressCtrl m_address;
int m_port;
CListBox m_listbox;
CString m_msg;
afx_msg void OnConnect();
afx_msg void OnSendmsg();
1.套接字对象,就是我们上面创建的类对象。2.ip地址控件。3.端口控件。4.消息显示控件。5.将要发送的消息控件。6.连接服务器的按钮处理函数。7.发送消息的按钮处理函数。
连接服务器:
void CClientDlg::OnConnect()
{
// TODO: 在此添加控件通知处理程序代码
CString addr;
BYTE _1 = 0, _2 = 0, _3 = 0, _4 = 0;
m_address.GetAddress(_1, _2, _3, _4);
addr.Format(TEXT("%d.%d.%d.%d"), _1, _2, _3, _4);
BOOL s = m_sock.Connect(addr, m_port); // 连接到服务器,在连接前,应该先创建套接字Create,我在初始化中已创建
if (s) {
MessageBox(TEXT("连接服务器成功"));
GetDlgItem(IDC_BUTTON1)->EnableWindow(false);
GetDlgItem(IDC_BUTTON1)->SetWindowTextW(L"服务器已连接");
}
else {
MessageBox(TEXT("连接服务器失败"));
}
}
这里呢,就是获取控件的地址和端口,使用CSocke的Connect方法进行连接服务器。如果连上了,我这里将按钮置灰,并且改变按钮的文字。
那么在连接上服务器之后,我们就可以进行数据的发送了:
void CClientDlg::OnSendmsg()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData();
m_sock.Send(m_msg, m_msg.GetLength()*2); // 发送数据,发送数据的长度是字节数。
}
至此,客户端的简单通信已经写完了,主要阐述了,如何发送和接收数据。
编写服务器端程序
编写服务器端,主要实现的是,将某一个客户端发来的消息转发给所有已经连接服务器的客户端。
服务器套接字是由两个套接字对象共同来实现的,一个套接字用于监听,当监听到套接字连接时,保存这个套接字用于通信,所以这里要设计两个套接字,一个用于监听的,一个用于通信的。
那么我们先来看一下通信套接字,就是当客户端请求连接时,与客户端通信的套接字。
因为我们的服务器是转发消息,所以,这里只需要接收客户端发来的消息即可。因为发送消息的动作是主动的,所以我们可以在监听套接字的返回套接字中发送信息。
/**
* @brief 这个套接字是用于和客户端一对一通信的,
*/
class ClientSocket : public CSocket {
public:
/**
* @brief 如果客户端发来信息,我们接收后,使用主socket遍历所有已经连接的客户端,并发送消息。
* @param nErrorCode
*/
virtual void OnReceive(int nErrorCode);
};
当客户端有消息发来的时候,触发这个函数执行:
void ClientSocket::OnReceive(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
TCHAR buf[1024] = {};
int len = Receive(buf, 1024); // 最大一次读取1024个字节,这个buf必须初始化,否则读取到的数据填充后会乱码。
if (len != 0) {
theApp.serversocket.SendAll(buf,1024); // 在读取到数据后,我们需要给所有已经连接到服务器的客户端进行发送相同的消息
}
CSocket::OnReceive(nErrorCode);
}
接收数据,然后使用监听套接字进行转发,这个转发函数,可以稍后看一下。
至此,专门用于和客户端通信的套接字也完成了,然后就是设计监听套接字,
监听套接字需要实现的功能:
监听客户端的连接,将这些连接保存在链表中,用于群发时遍历发送。
最后需要做一个清理工作,就是删除链表中的套接字资源
设计如下:
#include <afxsock.h>
/**
* @brief 这个套接字用于主套接字,用来监听的,
*/
class CServerSocket : public CSocket
{
public:
CPtrList m_socketlist;
void DellList();
void SendAll(void* buf, int len); // 遍历所有的客户端,进行发消息,这个函数在通信套接字中使用,
virtual void OnAccept(int nErrorCode); // 如果客户端有连接,我们将创建一个用于和此客户端通信的套接字,并且加入到客户端链表中
};
首先重写OnAccept函数,当有客户端连接时,触发该函数。
void CServerSocket::OnAccept(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
ClientSocket* client = new ClientSocket;
BOOL s = Accept(*client);
if (s != 0) {
m_socketlist.AddTail(client); // 如果有连接的客户端我们就把他加入到链表中
}
CSocket::OnAccept(nErrorCode);
}
将连接的套接字添加到链表中。
发送消息:
void CServerSocket::SendAll(void* buf, int len)
{
auto pos = m_socketlist.GetHeadPosition();
while (pos) {
ClientSocket* msocket = (ClientSocket*)m_socketlist.GetNext(pos);
if (msocket) {
BOOL S = msocket->Send(buf, len); // 遍历客户端进行发送
GetLastError();
}
}
}
遍历链表中的连接,进行群发。
关闭时,删除资源:
void CServerSocket::DellList()
{
auto pos = m_socketlist.GetHeadPosition();
while (pos) {
auto p = m_socketlist.GetNext(pos);
if (p != NULL) {
delete p;
}
}
m_socketlist.RemoveAll();
}
至此,服务器端设计完成,监听套接字的sendall函数是由与客户端通信的套接字来调用的,因为客户端套接字需要获取所有的已连接的套接字,而这个状态是监听套接字完成的。
最后,我们创建一个监听套接字在APP类里面就可以了。
下面是项目的代码:仅供参考,可以不需要阅读
Server.h
// Server.h: PROJECT_NAME 应用程序的主头文件
//
#pragma once
#ifndef __AFXWIN_H__
#error "在包含此文件之前包含 'pch.h' 以生成 PCH"
#endif
#include "resource.h" // 主符号
#include "CServerSocket.h"
// CServerApp:
// 有关此类的实现,请参阅 Server.cpp
//
class CServerApp : public CWinApp
{
public:
CServerApp();
CServerSocket serversocket;
// 重写
public:
virtual BOOL InitInstance();
// 实现
DECLARE_MESSAGE_MAP()
};
extern CServerApp theApp;
Server.cpp
// Server.cpp: 定义应用程序的类行为。
//
#include "pch.h"
#include "framework.h"
#include "Server.h"
#include "ServerDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CServerApp
BEGIN_MESSAGE_MAP(CServerApp, CWinApp)
ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()
// CServerApp 构造
CServerApp::CServerApp()
{
// 支持重新启动管理器
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
// TODO: 在此处添加构造代码,
// 将所有重要的初始化放置在 InitInstance 中
}
// 唯一的 CServerApp 对象
CServerApp theApp;
// CServerApp 初始化
BOOL CServerApp::InitInstance()
{
// 如果一个运行在 Windows XP 上的应用程序清单指定要
// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
//则需要 InitCommonControlsEx()。 否则,将无法创建窗口。
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// 将它设置为包括所有要在应用程序中使用的
// 公共控件类。
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinApp::InitInstance();
if (!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
// 创建 shell 管理器,以防对话框包含
// 任何 shell 树视图控件或 shell 列表视图控件。
CShellManager *pShellManager = new CShellManager;
// 激活“Windows Native”视觉管理器,以便在 MFC 控件中启用主题
CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
// 标准初始化
// 如果未使用这些功能并希望减小
// 最终可执行文件的大小,则应移除下列
// 不需要的特定初始化例程
// 更改用于存储设置的注册表项
// TODO: 应适当修改该字符串,
// 例如修改为公司或组织名
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
CServerDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: 在此放置处理何时用
// “确定”来关闭对话框的代码
}
else if (nResponse == IDCANCEL)
{
// TODO: 在此放置处理何时用
// “取消”来关闭对话框的代码
}
else if (nResponse == -1)
{
TRACE(traceAppMsg, 0, "警告: 对话框创建失败,应用程序将意外终止。\n");
TRACE(traceAppMsg, 0, "警告: 如果您在对话框上使用 MFC 控件,则无法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。\n");
}
// 删除上面创建的 shell 管理器。
if (pShellManager != nullptr)
{
delete pShellManager;
}
#if !defined(_AFXDLL) && !defined(_AFX_NO_MFC_CONTROLS_IN_DIALOGS)
ControlBarCleanUp();
#endif
// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
// 而不是启动应用程序的消息泵。
return FALSE;
}
ServerDlg.h
// ServerDlg.h: 头文件
//
#pragma once
// CServerDlg 对话框
class CServerDlg : public CDialogEx
{
// 构造
public:
CServerDlg(CWnd* pParent = nullptr); // 标准构造函数
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_SERVER_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:
int m_port;
CIPAddressCtrl m_address;
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnStartServer();
};
ServerDlg.cpp
// ServerDlg.cpp: 实现文件
//
#include "pch.h"
#include "framework.h"
#include "Server.h"
#include "ServerDlg.h"
#include "afxdialogex.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()
// CServerDlg 对话框
CServerDlg::CServerDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_SERVER_DIALOG, pParent)
, m_port(8081)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CServerDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT1, m_port);
DDV_MinMaxInt(pDX, m_port, 1025, 65535);
DDX_Control(pDX, IDC_IPADDRESS1, m_address);
}
BEGIN_MESSAGE_MAP(CServerDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_CREATE()
ON_BN_CLICKED(IDC_BUTTON1, &CServerDlg::OnStartServer)
END_MESSAGE_MAP()
// CServerDlg 消息处理程序
BOOL CServerDlg::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_address.SetAddress(127, 0, 0, 1);
CString addr;
BYTE _1 = 0, _2 = 0, _3 = 0, _4 = 0;
m_address.GetAddress(_1, _2, _3, _4);
addr.Format(TEXT("%d.%d.%d.%d"), _1, _2, _3, _4);
theApp.serversocket.Create(m_port, SOCK_STREAM, addr); // 为了方便测试,我直接获取的初始化的控件信息,按理来说他不应该这样,你可以将他放置在下面的启动服务器函数中,这样就可以动态使用用户输入的地址进行创建了。
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CServerDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CServerDlg::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 CServerDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
int CServerDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialogEx::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: 在此添加您专用的创建代码
return 0;
}
void CServerDlg::OnStartServer()
{
// TODO: 在此添加控件通知处理程序代码
bool s = theApp.serversocket.Listen(); // 在启动服务的时候,监听,在此之前,应该先Create,我在初始化中创建的。
if (s) {
AfxMessageBox(TEXT("监听成功"));
}
else {
AfxMessageBox(TEXT("监听失败!"));
}
}
CServerSocket.h
#pragma once
#include <afxsock.h>
/**
* @brief 这个套接字用于主套接字,用来监听的,
*/
class CServerSocket : public CSocket
{
public:
CPtrList m_socketlist;
void DellList();
void SendAll(void* buf, int len); // 遍历所有的客户端,进行发消息,这个函数在通信套接字中使用,
virtual void OnAccept(int nErrorCode); // 如果客户端有连接,我们将创建一个用于和此客户端通信的套接字,并且加入到客户端链表中
};
/**
* @brief 这个套接字是用于和客户端一对一通信的,
*/
class ClientSocket : public CSocket {
public:
/**
* @brief 如果客户端发来信息,我们接收后,使用主socket遍历所有已经连接的客户端,并发送消息。
* @param nErrorCode
*/
virtual void OnReceive(int nErrorCode);
};
CServerSocket.cpp
#include "pch.h"
#include "CServerSocket.h"
#include "Server.h"
void ClientSocket::OnReceive(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
TCHAR buf[1024] = {};
int len = Receive(buf, 1024); // 最大一次读取1024个字节,这个buf必须初始化,否则读取到的数据填充后会乱码。
if (len != 0) {
theApp.serversocket.SendAll(buf,1024); // 在读取到数据后,我们需要给所有已经连接到服务器的客户端进行发送相同的消息
}
CSocket::OnReceive(nErrorCode);
}
void CServerSocket::DellList()
{
auto pos = m_socketlist.GetHeadPosition();
while (pos) {
auto p = m_socketlist.GetNext(pos);
if (p != NULL) {
delete p;
}
}
m_socketlist.RemoveAll();
}
void CServerSocket::SendAll(void* buf, int len)
{
auto pos = m_socketlist.GetHeadPosition();
while (pos) {
ClientSocket* msocket = (ClientSocket*)m_socketlist.GetNext(pos);
if (msocket) {
BOOL S = msocket->Send(buf, len); // 遍历客户端进行发送
GetLastError();
}
}
}
void CServerSocket::OnAccept(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
ClientSocket* client = new ClientSocket;
BOOL s = Accept(*client);
if (s != 0) {
m_socketlist.AddTail(client); // 如果有连接的客户端我们就把他加入到链表中
}
CSocket::OnAccept(nErrorCode);
}
Client.h
// Client.h: PROJECT_NAME 应用程序的主头文件
//
#pragma once
#ifndef __AFXWIN_H__
#error "在包含此文件之前包含 'pch.h' 以生成 PCH"
#endif
#include "resource.h" // 主符号
// CClientApp:
// 有关此类的实现,请参阅 Client.cpp
//
class CClientApp : public CWinApp
{
public:
CClientApp();
// 重写
public:
virtual BOOL InitInstance();
// 实现
DECLARE_MESSAGE_MAP()
};
extern CClientApp theApp;
Client.cpp
// Client.cpp: 定义应用程序的类行为。
//
#include "pch.h"
#include "framework.h"
#include "Client.h"
#include "ClientDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CClientApp
BEGIN_MESSAGE_MAP(CClientApp, CWinApp)
ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()
// CClientApp 构造
CClientApp::CClientApp()
{
// 支持重新启动管理器
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
// TODO: 在此处添加构造代码,
// 将所有重要的初始化放置在 InitInstance 中
}
// 唯一的 CClientApp 对象
CClientApp theApp;
// CClientApp 初始化
BOOL CClientApp::InitInstance()
{
// 如果一个运行在 Windows XP 上的应用程序清单指定要
// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
//则需要 InitCommonControlsEx()。 否则,将无法创建窗口。
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// 将它设置为包括所有要在应用程序中使用的
// 公共控件类。
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinApp::InitInstance();
if (!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
// 创建 shell 管理器,以防对话框包含
// 任何 shell 树视图控件或 shell 列表视图控件。
CShellManager *pShellManager = new CShellManager;
// 激活“Windows Native”视觉管理器,以便在 MFC 控件中启用主题
CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
// 标准初始化
// 如果未使用这些功能并希望减小
// 最终可执行文件的大小,则应移除下列
// 不需要的特定初始化例程
// 更改用于存储设置的注册表项
// TODO: 应适当修改该字符串,
// 例如修改为公司或组织名
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
CClientDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: 在此放置处理何时用
// “确定”来关闭对话框的代码
}
else if (nResponse == IDCANCEL)
{
// TODO: 在此放置处理何时用
// “取消”来关闭对话框的代码
}
else if (nResponse == -1)
{
TRACE(traceAppMsg, 0, "警告: 对话框创建失败,应用程序将意外终止。\n");
TRACE(traceAppMsg, 0, "警告: 如果您在对话框上使用 MFC 控件,则无法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。\n");
}
// 删除上面创建的 shell 管理器。
if (pShellManager != nullptr)
{
delete pShellManager;
}
#if !defined(_AFXDLL) && !defined(_AFX_NO_MFC_CONTROLS_IN_DIALOGS)
ControlBarCleanUp();
#endif
// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
// 而不是启动应用程序的消息泵。
return FALSE;
}
ClientDlg.h
// ClientDlg.h: 头文件
//
#pragma once
#include "ClientSocket.h"
// CClientDlg 对话框
class CClientDlg : public CDialogEx
{
// 构造
public:
CClientDlg(CWnd* pParent = nullptr); // 标准构造函数
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_CLIENT_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:
ClientSocket m_sock;
CIPAddressCtrl m_address;
int m_port;
afx_msg void OnConnect();
CString m_msg;
afx_msg void OnSendmsg();
CListBox m_listbox;
};
CalientDlg.cpp
// ClientDlg.cpp: 实现文件
//
#include "pch.h"
#include "framework.h"
#include "Client.h"
#include "ClientDlg.h"
#include "afxdialogex.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()
// CClientDlg 对话框
CClientDlg::CClientDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_CLIENT_DIALOG, pParent)
, m_port(8081)
, m_msg(_T(""))
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CClientDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_IPADDRESS1, m_address);
DDX_Text(pDX, IDC_EDIT1, m_port);
DDV_MinMaxInt(pDX, m_port, 1025, 65535);
DDX_Text(pDX, IDC_EDIT2, m_msg);
DDX_Control(pDX, IDC_LIST1, m_listbox);
}
BEGIN_MESSAGE_MAP(CClientDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON1, &CClientDlg::OnConnect)
ON_BN_CLICKED(IDC_BUTTON2, &CClientDlg::OnSendmsg)
END_MESSAGE_MAP()
// CClientDlg 消息处理程序
BOOL CClientDlg::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_address.SetAddress(127, 0, 0, 1);
m_sock.Create();
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CClientDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CClientDlg::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 CClientDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CClientDlg::OnConnect()
{
// TODO: 在此添加控件通知处理程序代码
CString addr;
BYTE _1 = 0, _2 = 0, _3 = 0, _4 = 0;
m_address.GetAddress(_1, _2, _3, _4);
addr.Format(TEXT("%d.%d.%d.%d"), _1, _2, _3, _4);
BOOL s = m_sock.Connect(addr, m_port); // 连接到服务器,在连接前,应该先创建套接字Create,我在初始化中已创建
if (s) {
MessageBox(TEXT("连接服务器成功"));
GetDlgItem(IDC_BUTTON1)->EnableWindow(false);
GetDlgItem(IDC_BUTTON1)->SetWindowTextW(L"服务器已连接");
}
else {
MessageBox(TEXT("连接服务器失败"));
}
}
void CClientDlg::OnSendmsg()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData();
m_sock.Send(m_msg, m_msg.GetLength()*2); // 发送数据,发送数据的长度是字节数。
}
ClientSocket.h
#pragma once
#include <afxsock.h>
class ClientSocket :
public CSocket
{
public:
virtual void OnReceive(int nErrorCode); // 客户端将服务器转发来的消息在窗口中显示,
};
ClientSocket.cpp
#include "pch.h"
#include "ClientSocket.h"
#include "ClientDlg.h"
void ClientSocket::OnReceive(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
CString str;
int len = Receive(str.GetBuffer(1024), 1024);
if (len) {
((CClientDlg*)AfxGetApp()->m_pMainWnd)->m_listbox.InsertString(0, str);
}
CSocket::OnReceive(nErrorCode);
}