在学习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功能分块等,后续随缘更新,感谢观看。