赞
踩
在学习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功能分块等,后续随缘更新,感谢观看。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。