转自:https://www.sxzhongrui.com/NNNNNNNNNNNNY/article/details/45341983

本例程是在VS2010环境下,使用MFC做的是一个简单的串口通信程序。欢迎交流。

程序源码(含一个虚拟串口软件):http://www.sxzhongrui.com/detail/nnnnnnnnnnnny/8643049?

1.首先,在VS2010环境下创建基于对话框的MFC应用程序。

a.选择菜单项 文件—>新建—>项目,弹出新建项目对话框。

b.左侧面板中选择Visual C++下的MFC,中间窗口中选择MFC应用程序,然后在下面的名称编辑框中键入工程名称,本例取名SerialTest,在位置编辑框中设置工程的保存路径。点击确定。

c.在弹出的MFC应用程序向导中,点下一步到应用程序类型界面,在应用程序类型下选择基于对话框,其他使用默认设置,点击完成。

2.接下来为对话框添加所需要的控件

a.在资源视图的资源树中双击Dialog下的IDD_SERIALTEST_DIALOG,出现如下对话框编辑窗口(默认新建项目也是出现此界面,所以此步无需操作,仅作说明)

b.选中控件按Delete键删除自动生成的静态文本框TODO:在此放置对话框控件、按钮控件确定和取消。

c.从右侧工具箱中添加两个Button按钮,两个Edit Control编辑框,一个StaticText静态文本框和一个Combo Box组合框。(提示:在工具箱中鼠标左键点下所需控件不放开,并拖到IDD_ADDITION_DIALOG对话框模板上,模板上会出现一个虚线框,我们找到合适的位置松开鼠标左键放下它,即可成功添加所需控件)。

d.适当改变控件大小,并合理布局控件,使之如下图所示(提示:1.用鼠标左键选中控件后周围出现虚线框,然后鼠标移到虚线框上几个黑点的位置会变成双向箭头的形状,此时就可以按下鼠标左键并拖动来改变控件大小了。当然,整个对话框模板也可以用这种方法改变大小。2.几个控件的对齐等操作可按Ctrl键同时选中几个控件,然后选择软件左上侧工具条中的几个按钮进行操作)

e.更改控件属性(选中控件,鼠标移至右侧属性,即出现该控件的属性面板):更改Button1的Caption为清除接收框、ID为IDC_BUTTON_CLEANUP;?Button2的Caption为发送数据、ID为IDC_BUTTON_SEND;下面的编辑框用于显示发送数据,ID设为IDC_EDIT_TXDATA; 下面的编辑框用于显示接收到的数据,ID设为IDC_EDIT_RXDATA;静态文本框的Caption为选择COM口:;ComboBox的?ID设为IDC_COMBO_COM,Type属性设为DropList(即下拉列表式组合框,其编辑框是不能编辑的,只能由用户在下拉列表框中选择了某项后,在编辑框中显示其文本),Sort属性设为False,以取消自动排序显示。

f.添加串口通信控件。首先把串口通信控件加入到工具箱中,因为默认的工具箱是不带 MS 串口通信控件的。方法如下:

在工具箱界面点击鼠标右键出现如下界面:

选择选择项出现如下界面,然后选择COM组件属性页,勾选下图所示项,点击确定。

这样串口控件就已经添加至工具箱了,

将其拖入对话框里,如图所示(串口控件在运行时是看不到的)

至此控件添加基本上就完成了。

3.编写消息处理函数

a. 给Combo Box组合框添加列表项。选中ComboBox控件,右键->添加变量,设置变量名为m_comboCom,如图所示,点击完成。

在左侧类视图面板中,选择CSerialTestDlg类,双击下方的OnInitDialog()函数进入CSerialTestDlg::OnInitDialog()函数

在//TODO:在此添加额外的初始化代码添加如下代码:

// TODO: 在此添加额外的初始化代码

m_comboCom.AddString(_T(

"COM1"));

// 为组合框控件的列表框添加列表项

m_comboCom.AddString(_T(

"COM2"));

m_comboCom.AddString(_T(

"COM3"));

m_comboCom.AddString(_T(

"COM4"));

m_comboCom.AddString(_T(

"COM5"));

m_comboCom.AddString(_T(

"COM6"));

m_comboCom.AddString(_T(

"COM7"));

m_comboCom.AddString(_T(

"COM8"));

m_comboCom.AddString(_T(

"COM9"));

m_comboCom.AddString(_T(

"COM10"));

b.我们希望在组合框中选中的列表项改变时,以打开相应串口并设置串口参数,那么这就要用到CBN_SELCHANGE通知消息。选中ComboBox,右键->添加事件处理程序,出现如图所示界面,点击添加编辑。

在CSerialTestDlg::OnCbnSelchangeComboCom()函数中添加代码之前我们需要给串口控件添加控制变量m_ctrlComm(方法见3.->a.),如图

并在CSerialTestDlg类中添加变量m_setOk(用于标记串口是否设置好),方法:在类视图面板中选中CSerialTestDlg,右键->添加->添加变量,

在弹出的界面中,选择访问:private,变量类型:bool,变量名:m_setOk。这里看到SerialTestDlg.h中已经添加了此变量,并在构造函数中已进行了初始化:

现在我们回到CSerialTestDlg::OnCbnSelchangeComboCom()中,添加如下代码:

void CSerialTestDlg::OnCbnSelchangeComboCom()

{

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

int nSel;

nSel = m_comboCom.GetCurSel();

//获取组合框控件的列表框中选中项的索引

m_ctrlComm.put_CommPort(nSel+

1);

//选择串口号(这里因为列表框的索引号是从0开始,所以(nSel+1)对应的才是我们所选的串口号)

m_ctrlComm.put_PortOpen(TRUE);

//打开串口

m_ctrlComm.put_RThreshold(

2);

//收到两个字节引发OnComm事件

m_ctrlComm.put_InputMode(

1);

//输入模式选为二进制

m_ctrlComm.put_Settings(_T(

"9600,n,8,1"));

//设置串口参数,波特率,无奇偶校验,位停止位,位数据位

m_ctrlComm.put_InputMode(

1);

// 以二进制方式检取数据

m_ctrlComm.put_RThreshold(

1);

//参数1表示每当串口接收缓冲区中有多于或等于1个字符时将引发一个接收数据的OnComm事件

m_ctrlComm.put_InputLen(

0);

//设置当前接收区数据长度为0

m_ctrlComm.get_Input();

//先预读缓冲区以清除残留数据

m_setOk =

true;

//标记串口设置OK

}

c.添加串口事件消息处理函数。在此步之前,我们先给IDC_EDIT_TXDATA编辑框添加CString变量m_strTXData和IDC_EDIT_TXDATA编辑框添加CString变量m_strRXData(方法见3.->a.,注意变量类型改为CString,类别改为Value)

然后我们添加串口消息事件处理函数(方法见3.->b.),并将函数改名为OnComm

在CSerialTestDlg::OnComm()函数中添加如下代码:

void CSerialTestDlg::OnComm()

{

// TODO: 在此处添加消息处理程序代码

VARIANT variant_inp;

//Variant 是一种特殊的数据类型,除了定长String数据及用户定义类型外,可以包含任何种类的数据。

COleSafeArray safearray_inp;

LONG len,k;

BYTE rxdata[

2048];

//设置BYTE数组 An 8-bit integer that is not signed.

CString strtemp;

if(m_ctrlComm.get_CommEvent() ==

2)

//事件值为2表示接收缓冲区内有字符

{

以下你可以根据自己的通信协议加入处理代码

variant_inp=m_ctrlComm.get_Input();

//读缓冲区

safearray_inp=variant_inp;

//VARIANT型变量转换为ColeSafeArray型变量

len=safearray_inp.GetOneDimSize();

//得到有效数据长度

for(k=

0;k

safearray_inp.GetElement(&k,rxdata+k);

//转换为BYTE型数组

for(k=

0;k

//将数组转换为Cstring型变量

{

BYTE bt=*(

char*)(rxdata+k);

//字符型

strtemp.Format(_T(

"%c"),bt);

//将字符送入临时变量strtemp存放

m_strRXData+=strtemp;

//加入接收编辑框对应字符串

}

}

UpdateData(FALSE);

//更新编辑框内容

}

d.添加发送数据按钮的BN_CLICKED消息的处理函数(方法见3.->b.),

在CSerialTestDlg::OnBnClickedButtonSend()中添加如下代码:

void CSerialTestDlg::OnBnClickedButtonSend()

{

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

if (m_setOk ==

true)

//判断是否打开并初始化串口

{

UpdateData(TRUE);

//读取编辑框内容

m_ctrlComm.put_Output(COleVariant(m_strTXData));

//发送数据

}

else

{

MessageBox(_T(

"请先选择COM口"));

}

}

e.同理,添加清楚接收框按钮的BN_CLICKED消息的处理函数(方法见3.->b.),

在CSerialTestDlg::OnBnClickedButtonCleanup()中添加如下代码:

void CSerialTestDlg::OnBnClickedButtonCleanup()

{

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

m_strRXData=

"";

UpdateData(FALSE);

//更新编辑框内容

}

4.生成可执行代码

a.点击启动调试按钮或摁F5,编译成功

b.点击菜单栏生成->配置管理器,

在活动解决方案配置下拉菜单中选择Release,点击关闭。

再次编译,即可看到在目录中生成了的Release文件夹下生成了.exe文件。

5.检测软件是否能正常工作,

a.这里首先要下一个虚拟串口。下载地址:http://www.sxzhongrui.com/soft/21406.html(也可在文首提供的连接处下载)

安装完成后,打开软件

虚拟串口COM3和COM5:在右侧端口一、二分别改为COM3和COM5,点击添加端口。

b.然后打开两个刚才生成的.exe文件,分别选择COM3和COM5。

分别在发送数据的编辑框里随便输入几个字符,点击发送数据

成功。

【vs2010基于MFC对话框】实现客户–服务器模式下网络程序的Shocket双向通信 编译环境采用Visual Studio 2010,此工程为学校课堂练习,要求实现基于MFC对话框的两端通信。两端分别为客户端和服务器端。下面是实现客户端的工程过程: 1.建立工程 建立一个新的基于MFC对话框的项目,项目名称为ChatClient。然后在高级功能中勾上wIndows套接字项,点击完成创立工程,如图: 2.添加类 创建好工程后,首先要创建类名为CClientSocket的客户Socket对象,给工程添加MFC类,基类为CAsyncSocket,类名为CClientSocket:

点击类视图可以查看是否添加成功,出现CClientSocket类为成功。 3.添加头文件声明 要使客户端程序能够创建和控制本地的Socket,在客户端工程界面控制模块的头文件ChatClientDlg.h中添加如下两行代码:

#include "ClientSocket.h" //使主界面程序能够访问Socket类的代码文件

CClientSocket m_ClientSocket;//为了后面与服务器通信而定义的Socket成员变量

在Socket源文件ClientSocket.cpp中添加头文件声明: #include "ChatClientDlg.h"

4.设计布局界面和关联变量 接下来是给客户端设计一个简洁的界面: 在界面上添加一个IP地址控件和列表框,文本框两个,四个按钮,同时修改好Caption。 下一步,为IP地址控件添加变量ServerIP,为上面的文本框控件添加value类别int型变量sPort,为下面的文本框控件添加value类别CString型变量sWords为列表框关联变量m_ListWords。(可点击类向导查看添加的变量) 5.添加客户端源码 接下来为工程添加灵魂,给每个按钮加入响应代码。 连接服务器按钮代码:

//连接服务器

BYTE nFild[4];

CString sIP;

UpdateData();

ServerIP.GetAddress(nFild[0],nFild[1],nFild[2],nFild[3]);

sIP.Format("%d.%d.%d.%d",nFild[0],nFild[1],nFild[2],nFild[3]);

m_ClientSocket.Create(); //创建客户端Socket

m_ClientSocket.Connect(sIP,sPort); //发起连接请求

断开按钮代码:

//断开与服务器的连接

m_ClientSocket.Close(); //关闭客户端Socket

m_ListWords.AddString("从服务器断开");

发送消息按钮代码:

//向服务器发信息

UpdateData();

m_ClientSocket.Send(m_sWords,m_sWords.GetLength()); //发信息

m_ListWords.AddString("发送:" + m_sWords);

m_ListWords.SetTopIndex(m_ListWords.GetCount() - 1);

6.重写类函数 在类视图中选中CClientSocket,在属性窗口中单击“重写”按钮,就可以为该Socket类编写被动响应网络事件的函数 选择OnConnect()函数,为其添加代码,系统将该函数的代码自动置于ClientSocket.cpp文件中,OnConnect()代码如下:

//确认客户端是否成功连接到服务器

if(nErrorCode)

{

AfxMessageBox("连接失败,请您重试!");

return;

}

((CChatClientDlg*)(AfxGetApp()->m_pMainWnd))->m_ListWords.AddString("连接服务器成功!");

((CChatClientDlg*)(AfxGetApp()->m_pMainWnd))->m_ListWords.SetTopIndex(

((CChatClientDlg*)(AfxGetApp()->m_pMainWnd))->m_ListWords.GetCount()- 1);

到此,两端中的客户端已完成,再建立一个服务器端工程,即可实现两端通信连接。

VS2010 基于MFC 串口通信上位机,实现串口发送、接收数据及接收数据保存。 提示:不清楚的函数可在MSDN 内查询

打开VS2010 创建 MFC应用程序,选择基于对话框类 ,勾选最大化框、最小化框后 选择下一步完成。 将对话框上原有的控件删除,添加所需的控件。 发送部分:Radio Button、Combo Box 接收部分:Edit Control 选择串口、波特率:Combo Box 按键:打开串口、发送数据、清除数据、保存数据、退出 图标显示:指示串口打开、关闭状态 Activex 串口控件:Microsoft Communications Control,Version 6.0 提示:VS2010 内找不到此Activex 控件的解决方法 :https://www.sxzhongrui.com/madao1234/article/details/82047796

3.为各控件添加控制程序 ① 打开串口按钮 ,显示指示图标 提示:双击打开按钮 会直接跳转到 void C串口Dlg::OnBnClickedButtonOpen() 函数

void C串口Dlg::OnBnClickedButtonOpen()

{

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

CString str,str1,n; //定义3个CString变量

GetDlgItemText(IDC_BUTTON_OPEN,str); //获取打开串口按键的Caption,IDC_BUTTON_OPEN:打开串口按键的ID

CWnd *h1; //定义句柄类的指针

h1 = GetDlgItem(IDC_BUTTON_OPEN); // h1 赋为 指向IDC_BUTTON_OPEN 按键的句柄

if(!m_mscom.get_PortOpen()) //如果串口未打开

{

//m_comb3:波特率选择组合框的 成员变量

m_comb3.GetLBText(m_comb3.GetCurSel(),str1); //取得波特率口当前的选择值,存放在str1里

str1 = str1 + _T(",n,8,1");

m_mscom.put_Settings(str1);// 设置波特率、无校验,8数据位,1个停止位

m_mscom.put_InBufferSize(1024); //接收缓冲区大小设置

m_mscom.put_OutBufferSize(1024);//发送缓冲区大小设置

m_mscom.put_CommPort((m_comb1.GetCurSel()+1)); //选择串口,m_comb1:串口选择组合框的 成员变量

m_mscom.put_InputMode(1); //设置输入方式 二进制方式

m_mscom.put_InputLen(0);// 设置当前接收区数据长度为0,表示全部读取

m_mscom.put_RThreshold(2); // 缓冲区有2个字符引发OnComm事件

m_mscom.put_PortOpen(true); //打开串口

m_mscom.get_Input(); //先预读缓冲区以清除残留数据

if(m_mscom.get_PortOpen()) //如果串口打开

{

str = _T("关闭串口"); // str打开按钮的Caption 赋为 “关闭串口”

UpdateData(TRUE);//用于将屏幕上控件中的数据交换到变量中

h1->SetWindowText(str); //打开串口按键 文本将被修改

m_Icon.SetIcon(m_hIcon_Open); //显示打开图标

}

}

else

{

m_mscom.put_PortOpen(false); //关闭串口

if(str != _T("打开串口"))

{

str = _T("打开串口");

UpdateData(TRUE);

h1->SetWindowText(str);

m_Icon.SetIcon(m_hIcon_Close); //显示关闭图标

}

}

}

② 串口打开、关闭 图标显示 首先,准备好2个Icon 图标, 在资源视图内右键—>添加资源—>导入 ,选择2个Icon。 导入后自动分配了ID: IDI_ICON1\ IDI_ICON2 . 在BOOL C串口Dlg::OnInitDialog() 内添加如下代码:

//m_hIcon_Open:串口打开图标的HICON 类型的成员变量

//m_hIcon_Close :串口关闭图标的HICON 类型的成员变量

//LoadIcon从与应用程序实例相关联的可执行文件(.EXE)文件中加载指定的图标资源

m_hIcon_Open = LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON1));

m_hIcon_Close = LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON2));

m_Icon.SetIcon(m_hIcon_Close); //设置初始显示为关闭图标

③选择串口 与 波特率 的 Combo Box 设置 在BOOL C串口Dlg::OnInitDialog() 内添加如下代码:

// 串口组合框:显示com1~com15

CString str;

for(i=0;i<15;i++)

{

str.Format(_T("com %d"),i+1);

m_comb1.InsertString(i,str);

}

m_comb1.SetCurSel(0);

//波特率组合框

CString str1[]={_T("4800"),_T("9600"),_T("19200"),_T("38400"),_T("43000"),_T("56000"),_T("57600"),_T("115200")};

for(int i = 0; i<8 ; i++)

{

int judge_tf=m_comb3.AddString(str1[i]);

if((judge_tf==CB_ERR)|(judge_tf==CB_ERRSPACE))

MessageBox(_T("设置波特率错误"));

}

m_comb3.SetCurSel(7);

//运行次数选项框

int i;

CString str2[]={_T("1"),_T("2"),_T("3"),_T("4"),_T("5"),_T("6"),_T("7"),_T("8"),_T("9")};

for(int i = 0; i<9 ; i++)

{

int judge_times=m_Runtime.AddString(str2[i]);

if((judge_times==CB_ERR)|(judge_times==CB_ERRSPACE))

MessageBox(_T("未设置运行次数"));

}

m_Runtime.SetCurSel(0);

④ Radio选择框设置,发送数据按钮 多个Radio Button 将第一个的Group改为True 按照Tab顺序会将其他Radio Button 分为一组,返回值分别为0,1,2 … 双击发送数据按钮,添加控制程序:

void C串口Dlg::OnBnClickedButtonSend()

{

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

CString str,str1,SendData;

UpdateData(TRUE); //将控件的状态传给其关联的变量

str.Format(_T("%d"),m_MMSet); //将 单选按钮的 int变量值强制转换成CString类型

m_Runtime.GetLBText(m_Runtime.GetCurSel(),str1);//运行次数选择框返回值

SendData = str + str1; //发送的数据

//UpdateData(TRUE);

m_mscom.put_Output(COleVariant(SendData)); //数据发送

}

⑤清除数据按钮

void C串口Dlg::OnBnClickedButtonClean()

{

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

m_Edit1_Rev1 = _T(""); //给接收编辑框发送空格符

m_Edit1_Rev2 = _T("");

m_Edit1_Rev3 = _T("");

m_Edit1_Rev4 = _T("");

m_Edit1_Rev5 = _T("");

UpdateData(FALSE);//将数据在屏幕中对应控件中显示出来

}

⑥ 关闭按钮

void C串口Dlg::OnBnClickedButtonClose()

{

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

if(m_mscom.get_PortOpen())

m_mscom.put_PortOpen(FALSE);

CDialogEx::OnCancel();

}

⑦ 串口控件程序:点击Activex 控件,右键 为控件添加处理程序 功能 :多个接收编辑框分别显示接收数据

void C串口Dlg::OnCommMscomm1()

{

// TODO: 在此处添加消息处理程序代码

CString strtemp;

if(m_mscom.get_CommEvent()==2) //事件值为2 表示接收缓存区内有字符

{

BYTE str[2048]={0};

long len,k;

COleSafeArray fs;

int flag,i=0;

VARIANT InputData = m_mscom.get_Input(); //读缓冲区

fs = InputData; //Variant类型变量转换成 COleSafeArray型变量

len = fs.GetOneDimSize(); //得到有效数据长度

for(k = 0;k < len;k++)

fs.GetElement(&k,str+k); //转换成BYTE型数组

for(k = 0;k < len;k++) //将数组转换为CString型变量

{

BYTE bt=*(char*)(str+k); //字符型

strtemp.Format(_T("%c"),bt); //将字符输入临时变量strtemp

//输出为多个显示框,显示位置标志

if(strtemp == 'a')

{

flag = 0;

}

else if(strtemp == 'b')

{

flag = 1;

}

else if(strtemp == 'c')

{

flag = 2;

}

.....

//显示数据写入

switch(flag)

{

case 0:

m_Edit1_Rev1 = _T(""); //清空显示

for(k=1;k

{

bt=*(char*)(str+k); //字符型

strtemp.Format(_T("%c"),bt); //将字符输入临时变量strtemp

m_Edit1_Rev1 += strtemp;

}

break;

case 1:

m_Edit1_Rev2 = _T("");

for(k=1;k

{

bt=*(char*)(str+k); //字符型

strtemp.Format(_T("%c"),bt); //将字符输入临时变量strtemp

m_Edit1_Rev2 += strtemp;

}

break;

case 2:

m_Edit1_Rev3 = _T(""); //清空显示

for(k=1;k

{

bt=*(char*)(str+k); //字符型

strtemp.Format(_T("%c"),bt); //将字符输入临时变量strtemp

m_Edit1_Rev3 += strtemp;

}

break;

}

}

UpdateData(FALSE); // 更新编辑框内容

}

}

⑧ 数据保存按键 将数据保存为csv格式

void C串口Dlg::OnBnClickedButtonSave()

{

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

CString str,str1,str2,str3,str4;

//构建文件保存为对话框,默认扩展名为.csv,初始文件名为空...

CFileDialog fg(false,_T("*.csv"),NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

_T("Text File(*.txt)|*.txt|Excel CSV File(*.csv)|*.csv|All Flie(*.*)|*.*||"),NULL);

str1 = _T("文本,");//文本为行标题,换单元格

str2 = _T("文本,");

str3 = _T("文本,");

str4 = _T("文本,");

str8 = _T(",文本,文本,文本\n"); // 文本为列标题

if(fg.DoModal()==IDOK) //创建对话框成功

{

CString filename = fg.GetPathName(); //文件的完整路径

CStdioFile mFile(filename,CFile::modeCreate|CFile::modeWrite); //指定文件路径、创建只写模式

//写入第1个 Radio Button控制的数据

switch (m_MMSet)

{

case 0:

mFile.Write(str1,str1.GetLength());

GetDlgItemText(IDC_RADIO1,str);

mFile.WriteString(str+"\n");

break;

case 1:

mFile.Write(str1,str1.GetLength());

GetDlgItemText(IDC_RADIO2,str);

mFile.WriteString(str+"\n");

break;

case 2:

mFile.Write(str1,str1.GetLength());

GetDlgItemText(IDC_RADIO3,str);

mFile.WriteString(str+"\n");

break;

default:

break;

}

//写入第2个文本 的数据

mFile.Write(str2,str2.GetLength());

GetDlgItemText(IDC_COMBO1,str);

mFile.WriteString(str+"\n");

mFile.Write(str8,str8.GetLength()); //写列标题

//写入接收框1的数据

mFile.Write(str3,str3.GetLength());

GetDlgItemText(IDC_EDIT1_Rev1,str); //写入接收框数据

mFile.WriteString(str);//指定一个指向包含空终止文本字符串的缓冲区的指针。

GetDlgItemText(IDC_EDIT2_Rev1,str);

mFile.WriteString(",");

mFile.WriteString(str);

GetDlgItemText(IDC_EDIT3_Rev1,str);

mFile.WriteString(",");

mFile.WriteString(str+"\n");

//写入接收框2的数据

mFile.Write(str4,str4.GetLength());

GetDlgItemText(IDC_EDIT1_Rev2,str);

mFile.WriteString(str);

GetDlgItemText(IDC_EDIT2_Rev2,str);

mFile.WriteString(",");

mFile.WriteString(str);

GetDlgItemText(IDC_EDIT3_Rev2,str);

mFile.WriteString(",");

mFile.WriteString(str+"\n");

}

}

使用虚拟串口测试,可以实现 数据发送、接收、保存 。