CSocket使用详解-MFC网络编程

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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值