基于MFC的简易TCP/IP调试助手开发

在学习TCP/IP通讯过程中,打算参考网上的教程写一个简易的调试助手,服务器与客户端分别参考以下两位代码完成,效果图如下。

1、如何基于TCP/IP协议进行MFC Socket网络通讯编程_Ezreal_zh的博客-CSDN博客_mfc tcp通信

2、使用MFC制作简易TCP客户端_哔哩哔哩_bilibili

 

        首先我们要了解windows套接字的编程流程是什么,分为服务器和客户端:

服务器:

1、加载套接字库 
2、创建套接字(socket)。 
3、将套接字绑定到一个本地地址和端口上(bind)。 
4、将套接字设为监听模式,准备接收客户请求(listen)。 
5、等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。 
6、用返回的套接字和客户端进行通信(send/recv)。 
7、返回,等待另一客户请求。 
8、关闭套接字

客户端程序:

1、加载套接字库 
2、创建套接字(socket)。 
3、向服务器发出连接请求(connect)。 
4、和服务器端进行通信(send/recv)。 
5、关闭套接字。

原文链接:https://blog.csdn.net/qq_32171677/article/details/60959137

        首先新建一个项目,选中基于对话框,勾选windows(套接字),选择完成即可。

        要完成标签页的切换效果需要用到MFC 的Tab Control控件,在主窗口添加 Tab Control控件,然后需要插入两个DIALOG资源,

将其ID分别命名为IDC_DIALOG_SERVER,和IDC_DIALOG_CLIENT。

        由于服务端和客户端是作为子级页面存在与标签页中,所以需要将其属性中的

Stlye设置为:Child(子级),同时将其Border属性设置为:none(无)

分别为server、client标签页添加类,基类为CDialogEX

双击Tab Control控件,生成OnTcnSelchangeTab1消息函数,同时选中控件右键 添加变量 :m_tab,

在头文件中,声明server,client 的对象用于绑定切换    CClient client; CServer    server;需要include Server 和Client


// TCP_IPDlg.h : 头文件
//

#pragma once
#include "afxcmn.h"
#include "Client.h"
#include "Server.h"

// CTCP_IPDlg 对话框
class CTCP_IPDlg : public CDialogEx
{
.
.
.
.
.
.
.
.
.
public:
	CTabCtrl m_tab;
	afx_msg void OnTcnSelchangeTab1(NMHDR *pNMHDR, LRESULT *pResult);

public:
    CClient client;
	CServer	server;

}

在OnInitDialog函数中初始化标签页的大小和绑定关系。

BOOL CTCP_IPDlg::OnInitDialog()
{
.
.
.
.
.
.
.
// TODO: 在此添加额外的初始化代码

	m_tab.InsertItem(0, _T("Server"));		//插入Server标签
	m_tab.InsertItem(1, _T("Client"));		//插入Client标签

	server.Create(MAKEINTRESOURCE(IDD_DIALOG_SERVER), &m_tab);	//创建server标签页
	client.Create(MAKEINTRESOURCE(IDD_DIALOG_CLIENT), &m_tab);	//创建client标签页

	CRect tabRect = { 0x00 }, tabWinRect = { 0x00 }; //标签控件客户区的Rect
													 //设置客户区Rect
	m_tab.GetClientRect(&tabRect);


	tabRect.left += 2;
	tabRect.right -= 2;
	tabRect.top += 23;
	tabRect.bottom -= 2;


	//根据调整好的tabRect放置server,client对话框,并设为显示
	server.MoveWindow(&tabRect);
	client.MoveWindow(&tabRect);
	server.ShowWindow(TRUE);

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

}

在OnTcnSelchangeTab1设置切换的显示关系

void CTCP_IPDlg::OnTcnSelchangeTab1(NMHDR *pNMHDR, LRESULT *pResult)
{
	*pResult = 0;
	// TODO: 在此添加控件通知处理程序代码
	CRect tabRect, tabWinRect;
	m_tab.GetClientRect(&tabRect);
	tabRect.left += 5;
	tabRect.right -= 5;
	tabRect.top += 23;
	tabRect.bottom -= 5;


	switch (m_tab.GetCurSel()) {//返回组合框中列表框中当前选中的项的下标,如果没有选中项返回CB_ERR
								//如果当前选中为标签1 server
	case 0:
		server.ShowWindow(TRUE);
		client.ShowWindow(FALSE);
		break;
		//如果当前选中为标签2 client
	case 1:
		server.ShowWindow(FALSE);
		client.ShowWindow(TRUE);
		break;
	default:
		server.ShowWindow(TRUE);
		break;
	}
}

         主界面的显示功能便完成,可进行server,client标签页的切换,接下来先进行client客户端的开发。

        在客户端界面,我们需要添加五个Edit Control控件用于输入IP地址、PORT端口号,显示日志文件,接收显示服务器消息以及输入发送服务端的内容;添加两个Button控件用于连接/断开连接和发送消息,具体布局如下,控件ID可分别设置为:

IDC_EDIT_IP

IDC_EDIT_PORT

IDC_EDIT_LOG

IDC_EDIT_RECEIVE

IDC_EDIT_SEND

IDC_BUTTON_CONNECT

IDC_BUTTON_SEND

为IDC_EDIT_LOG控件添加变量m_log,类别为control用于日志内容显示

为IDC_EDIT_SEND控件添加变量m_send,类别为value,用于保存控件输入发送的内容

双击IDC_BUTTON_CONNECT、IDC_BUTTON_SEND生成消息函数

        参照原文使用的接收服务器消息的实现方法为,添加一个ClientJin类,基类为CSocket,添加消息函数OnReceive具体操作为:在类视图中找到CTCP_IPDlg类(你的主窗口类),右键打开“类向导”,点击“添加类”

ClientJin.h代码如下:

#pragma once
#include "afxsock.h"

class CClient;

class CClientJin :
	public CSocket
{
public:
	CClientJin(CClient* pDlg);
	virtual ~CClientJin();
	virtual void OnReceive(int nErrorCode);

private:
	CClient* m_dlg;
};

 ClientJin.cpp代码如下:

#include "stdafx.h"
#include "ClientJin.h"
#include "Client.h"
#include "Resource.h"
#include "TCP_IP.h"
#include "TCP_IPDlg.h"

CClientJin::CClientJin(CClient* pDlg) : m_dlg(pDlg)
{
}


CClientJin::~CClientJin()
{
}


void CClientJin::OnReceive(int nErrorCode)
{
	// TODO: 在此添加专用代码和/或调用基类
	char* pData = NULL;
	pData = new char[1024];
	memset(pData, 0, sizeof(char) * 1024);
	CString str;
	Receive(pData, 1024);
	str = pData;

	CTCP_IPDlg* mainDlg = (CTCP_IPDlg*)theApp.GetMainWnd();
	if (mainDlg != NULL)
	{
		mainDlg->client.SetDlgItemText(IDC_EDIT_RECEIVE, str);
	}
	//if (m_dlg != NULL)
	//{
	//	m_dlg->SetDlgItemTextW(IDC_EDIT_RECEIVE, str);
	//}

	delete pData;
	pData = NULL;
	CSocket::OnReceive(nErrorCode);
}

回到Client类,在头文件添加以下声明,并在cpp文件实现

//系统自动生成
public:
	CEdit m_log;
	CString m_send;
	afx_msg void OnBnClickedButtonConnect();
	afx_msg void OnBnClickedButtonSend();


//自己手动添加
public:
	CClientJin sock;
	bool m_connect = false;//用于判断服务器:连接/断开连接
	void Log(CString str);
	

连接功能实现:

void CClient::OnBnClickedButtonConnect()
{
	// TODO: 在此添加控件通知处理程序代码
	if (m_connect == true) {//m_connect初始定义为false

		m_connect = false;
		sock.Close();
		SetDlgItemText(IDC_BUTTON_CONNECT, _T("连接服务器"));
		Log(_T("断开连接成功"));
		return;
	}
	if (!sock.Create()) {
		MessageBox((_T("创建端口失败")));
		return;
	}
	CString sIP;
	GetDlgItemText(IDC_EDIT_IP, sIP);
	UINT PORT = GetDlgItemInt(IDC_EDIT_PORT);
	if (!sock.Connect(sIP, PORT)) {
		MessageBox((_T("连接失败")));
		sock.Close();
		return;
	}
	m_connect = true;
	SetDlgItemText(IDC_BUTTON_CONNECT, _T("断开连接"));
	Log(_T("连接成功"));
}

发送功能实现:

void CClient::OnBnClickedButtonSend()
{
	// TODO: 在此添加控件通知处理程序代码
	UpdateData(true);
	if (m_send != "") {
		m_send += "\r\n";
		int nlen = m_send.GetLength() + 1;
		char* pBuff = new char[nlen];
		memset(pBuff, 0, nlen);
		WideCharToMultiByte(CP_OEMCP, 0, m_send.GetBuffer(1), -1, pBuff, nlen, 0, 0);
		if (sock.Send(pBuff, nlen) == SOCKET_ERROR) {
			MessageBox((_T("发送失败")));
			return;
		}
		CString str;
		str = pBuff;
		str = _T("你发送的内容为:") + str;
		Log(str);

		delete pBuff;
	}
}

日志显示功能实现:

void CClient::Log(CString str)
{
	str += "\r\n";
	m_log.SetSel(-1, -1);//定位光标
	m_log.ReplaceSel(str);
}

        客户端功能便已完成,可以通过网络调试助手测试客户端通讯功能,我用的是NetAssist V5.0.2,下载连接:NetAssist网络调试助手下载_NetAssist5.0.2官方最新版下载 - 系统之家

        接下来便是服务器的开发,添加ListBox Control存放日志,添加一个Edit Control 用于输入发送的消息,两个Button控件用启动/断开服务器和发送消息,具体布局如下,控件ID可分别设置为:

IDC_LIST_SSEND

IDC_EDIT_SSEND

IDC_BUTTON_START

IDC_BUTTON_SSEND

为IDC_LIST_SSEND控件添加变量m_listwords,类别为control

 为IDC_EDIT_SSEND控件添加变量send_edit,类别为control

双击IDC_BUTTON_START和IDC_BUTTON_SSEND生成消息函数

在类向导中添加虚函数OnInitDialog()用于初始化

Serve.h代码如下:

服务器的启动我们以线程的方式启动,需要在头文件声明一个全局的IP和sock listen_sock和线程函数;然后在cpp文件中定义,为防止重复定义,需使用extern关键字

// CServer 对话框
extern CString IP;
extern SOCKET listen_sock;
extern SOCKET sock;
UINT server_thd(LPVOID p);

class CServer : public CDialogEx
{
.
.
.
.
.
.
.
.

//系统生成
public:
	CListBox m_listwords;
	virtual BOOL OnInitDialog();
	afx_msg void OnBnClickedButtonSsend();
	afx_msg void OnBnClickedButtonStart();

private:
	CEdit send_edit;


//手动生成
public:
	bool m_connect = false;
	void update(CString s);


}

在cpp文件中进行定义

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

#include "stdafx.h"
#include "TCP_IP.h"
#include "Server.h"
#include "afxdialogex.h"
#include "Resource.h"
#include <afxwin.h>

CString IP;
SOCKET listen_sock;
SOCKET sock;

// CServer 对话框

线程函数实现:

UINT server_thd(LPVOID p)
{
	CServer* dlg = (CServer*)  p;
	SOCKADDR_IN local_addr,client_addr;
	int iaddrSize = sizeof(SOCKADDR_IN);

	int res;
	char msg[1024];
	//CServer* dlg = (CServer *)AfxGetApp()->GetMainWnd();
	//char ch_ip[20];
	//CString2Char(IP, ch_ip);

	CString strFormat;

	//获取本地IP地址
	//local_addr.sin_addr.s_addr = inet_addr(ch_ip);
	local_addr.sin_addr.s_addr = INADDR_ANY;
	local_addr.sin_family = AF_INET;
	local_addr.sin_port = htons(8888);
	//创建套接字
	if ((listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {
		dlg->update(_T("创建监听失败"));
		return -1;
	}
	//绑定套接字
	if (bind(listen_sock, (struct sockaddr*)&local_addr, sizeof(SOCKADDR_IN))) {
		strFormat.Format(_T("绑定错误:%d"), WSAGetLastError());
		dlg->update(strFormat);
		closesocket(listen_sock);
		return -1;
	}
    //监听端口
	if (listen(listen_sock, 1) != 0) {
		strFormat.Format(_T("listen错误:%d"), WSAGetLastError());
		dlg->update(strFormat);
		closesocket(listen_sock);
		return -1;
	}
	//接收套接字
	if ((sock = accept(listen_sock, (struct sockaddr*)&client_addr, &iaddrSize)) == INVALID_SOCKET) {
		dlg->update(_T("accept失败"));
		return -1;
	}else {
		CString port;
		port.Format(_T("%d"), int(ntohs(client_addr.sin_port)));
		dlg->update(_T("已连接客户端:") + CString(inet_ntoa(client_addr.sin_addr)) + _T("端口:") + port);
	}
	//接收数据
	while (1)
	{
		if ((res = recv(sock, msg, 1024, 0)) == -1) {
			dlg->update(_T("失去客户端连接"));
			break;
		}
		else {
			msg[res] = '\0';
			CString port;
			port.Format(_T("%d"), int(ntohs(client_addr.sin_port)));
			dlg->update(_T("\n") + CString(inet_ntoa(client_addr.sin_addr)) + _T("端口:") + port+ CString(msg));
		}
	}
	return 0;
}

OnInitDialog()函数如下 :

/ TODO:  在此添加额外的初始化
	send_edit.GetDlgItem(IDC_EDIT_SSEND);
	send_edit.SetFocus();

	WSADATA wsaData;
	WORD wVersion;
	wVersion = MAKEWORD(2, 2);
	WSAStartup(wVersion, &wsaData);

	return FALSE;  // return TRUE unless you set the focus to a control
				  // 异常: OCX 属性页应返回 FALSE

update()函数如下:

void CServer::update(CString s)
{
	m_listwords.AddString(s);
}

启动/断开服务功能如下:

// TODO: 在此添加控件通知处理程序代码
	if (m_connect)
	{
		m_connect = false;
		closesocket(listen_sock);
		SetDlgItemText(IDC_BUTTON_START, _T("启动服务器"));
		update(_T("服务器已关闭"));
		return;
	}
	if (m_connect !=true)
	{
		char name[128];
		hostent* pHost;
		gethostname(name, 128);//获得主机名
		pHost = gethostbyname(name);//获得主机结构
		IP = inet_ntoa(*(in_addr*)pHost->h_addr);
		update(_T("本服务器IP地址为:") + IP);
		AfxBeginThread(server_thd, this);//创建线程

		m_connect = true;
		SetDlgItemText(IDC_BUTTON_START, _T("关闭服务器"));
		return;
	}

发送消息内容如下:

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

	CString s;
	char msg[1024];
	send_edit.GetWindowTextW(s);
	CString2Char(s, msg);

	if (send(sock, msg, strlen(msg), 0) == SOCKET_ERROR) {
		m_listwords.SetWindowTextW(_T("发送失败"));
	}else if (s==" ")
	{
		MessageBox(_T("请输入信息"));
	}
	else
	{
		//s = msg;
		//m_listwords.AddString(_T("server:") + s);
		update(_T("server:") + s);
		send_edit.SetWindowTextW(_T(""));
		m_listwords.SetFocus();
		
	}

至此所有功能均已完成,可用网络调试助手进行测试通讯,以上的代码为记录我的学习过程而制作的简易TCP/IP通讯助手,代码还存在很大的优化空间,如线程的同步问题,服务器的日志区,发送区分离,accept功能分块等,后续随缘更新,感谢观看。

  • 4
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值