用Winsock控件实现点对点通信
中科院电子所 中科院研究生院(北京 100039) 郭旭
摘 要: Winsock控件是一个专门用于Windows网络编程,与Sockets完全兼容的ActiveX控件,它提供了访问 TCP 和 UDP 网络服务的方便途径。网络编程中最常用的是客户机/服务器模型,但是点对点通信模式编程更简单,适合传输数据流少的场合,如控制测量等方面。本文提出了在VC++中用Winsock控件实现Internet中按照二进制数据格式点对点通信的一种常见的方案。
关键词: Winsock,点对点通信,网络编程
一 引言
随着计算机和Internet网络的发展,原先在测量、控制、消费等领域不同设备/仪器之间,大多采用RS232/485或现场总线组建网络等进行数据传输的情况正在改变,特别在要求数据传输速度和可靠性方面的应用产品,转向基于Internet网络的远程传输和应用。网络编程大多基于TCP/IP协议,实现方法有多种,复杂但编程适应性广的方法是调用底层的Winsock API函数或MFC基本类等;简单方便的方法可以采用如Winsock控件。在网络编程中最常用的方案便是客户机/服务器模型,客户应用程序向服务器程序请求服务。另外一种模式是点对点通信,通信双方对等,既有客户机功能又有服务器的功能,编程简单,适合传输数据流少的场合,而且采用简单的UDP协议,易于用微控制器等嵌入式系统实现,在测量控制方面有许多应用。接下将介绍一些网络编程和Winsock控件知识,用VC++实现点对点通信。
二 Winsock介绍
Windows Sockets接口是TCP/IP网络最为通用API,已成为Windows网络编程的事实上的标准。它以Unix中流行的Socket接口为范例定义了一套Microsoft Windows下网络编程接口函数库。它不仅包含了人们所熟悉的Berkeley Socket风格的库函数;也包含了一组针对Windows的扩展库函数,以使程序员能充分地利用Windows消息驱动机制进行编程。Socket实际在计算机中提供了一个通信端口,应用程序在网络上传输,接收的信息都通过这个Socket接口来实现。在应用开发中就像使用文件句柄一样,可以对Socket句柄进行读,写操作。
MicroSoft在Sockets API库的基础上创建了WinSock控件,专门用于Windows接口,与Sockets完全兼容。,利用 WinSock 控件可以与远程计算机建立连接,Winsock包含有用户数据文报协议 (UDP)和传输控制协议 (TCP)。Winsock控件对用户来说是不可见的,它提供了访问 TCP 和 UDP 网络服务的方便途径。Winsock控件封装了烦琐的技术细节,编写网络应用程序时,不必了解 TCP/IP的细节或调用低级的 Winsock APIs。通过设置控件的属性并调用其方法就可轻易连接到一台远程机器上去,并且还可双向交换数据。
下面列出有关Winsock控件的属性,方法和事件。
属性:
LocalIP:本地的IP地址。
LocalPort:所用到的本地端口;
Protocol:TCP或UDP协议。
RemoteHostIP:对方IP地址。
RemotePort:对方数据通信的端口。
方法:
Listen:用于服务器程序,等待客户访问。
Bind:绑定IP地址和端口。
Connect:向远程主机发出连接请求。
Accept:接受一个连接请求。
Senddata:发送数据。
Getdata:取得接收到的数据。
Close:关闭当前连接。
事件:
DataArrival:新数据到达。
ConnectionRequest:对方请求连接时接受连接请求。
Error:后台处理中出现错误。.
Close :对方关闭连接时出现。
三 网络通信协议基础
Winsock控件支持两种协议,TCP协议和UDP协议,都属于用TCP/IP协议。TCP(Transfer Control Protocol)是传输控制协议的简称,是基于连接的协议,在数据传输之前必须先建立连接,通信双方是基于客户/服务器模型,必须分别建立客户应用程序和服务器应用程序。UDP(用户数据文报协议)协议是一种无连接协议,通信双方之间的传输类似于传递邮件:消息从一方发送到另一方,但是两者之间没有明确的连接,通信双方是对等的,单次传输的最大数据量取决于具体的网络。
利用Winsock控件创建双方的通信过程如下:
基于TCP协议通信,需要分别建立客户应用程序和服务器应用程序。创建客户应用程序,就必须知道服务器的名或 IP 地址(RemoteHost 属性)和进行“侦听”的端口(RemotePort 属性),然后调用 Connect 方法。 创建服务器应用程序,就应设置一个收听端口(LocalPort 属性)并调用 Listen 方法。当客户机要连接时会发生 ConnectionRequest 事件。为了完成连接,可调用ConnectionRequest 事件内的 Accept 方法。 建立连接后,任何一方都可以收发数据。为了发送数据,可调用 SendData 方法。当接收数据时会发生 DataArrival 事件。调用 DataArrival 事件内的 GetData 方法就可获取数据。
基于UDP协议通信相对简单,为了传输数据,首先要设置双方的端口(RemotePort和LocalPort)属性,一般双方端口号一样,同时设置对方IP(RemoteHostIP)地址。这样通过调用SendData方法就可以发送信息,有数据到来触发DataArrival 事件,调用GetData方法接收已送来的信息。
四 二进制数据点对点通信的实现
基于网络的远程控制和测量应用中,一般数据传输采用二进制格式,双机之间的连接方式是主丛式,构建一个测控网络,从机之间如果要交换数据也得通过主机。在网络测量控制等领域,通信双方需要传输的数据流量少,下位机一般是微控制器等嵌入式系统,数据处理能力较慢,通信双方也不必保持紧密联系,因此大多采用UDP协议,基于点对点的方式,双方通信的数据可靠性可以通过定义数据表示格式来保证。为了描述如何应用,我们在这里建立一个基于对话框的简单的点对点通信例子,说明在VC++中如何用Winsock控件实现二进制数据点对点通信。
首先启动VC++,新建基于对话框项目工程,在控件箱中添加Winsock控件。在窗体上放置一个Winsock控件和其他控件(如按钮控件和文本框控件等),如图所示。程序代码如下,其中程序中应用VARIANT和SAFEARRAY数据类型提供一个统一安全的机制来实现数据传递。
首先,声明一些全局公共变量,代码如下所示:
BYTE send_data_buf[100]; // 发送缓冲区
BYTE recv_data_buf[500]; // 接收缓冲区
int send_data_len; //发送数据长度
int recv_data_len; //接收数据长度
m_slave1是Winsock控件。
动态初始化设置Winsock控件的一些属性,准备通信。
void CWinsock_exampleDlg::OnConnect()
{
VARIANT LocalPort_VT,LocalIP_VT;
m_slave1.Close(); // 关闭当前Winsocket
m_slave1.SetLocalPort(m_port); //本机端口号
m_slave1.SetRemotePort(m_port); //远程端口号
m_slave1.SetRemoteHost(m_udp_ip_port); //对方IP地址
m_slave1.SetProtocol(1); // 选择UDP协议,1=UDP, 0=TCP
LocalPort_VT.vt=VT_I2;//数据为整数
LocalPort_VT.lVal= m_port;
LocalIP_VT.vt=VT_BSTR;//数据为字符串
LocalIP_VT.bstrVal=m_slave1.GetLocalIP().AllocSysString();
m_slave1.Bind(LocalPort_VT,LocalIP_VT); // 绑定本机IP地址和端口号
VARIANT comm_vt;
CString temp="Hello";
comm_vt.vt=VT_BSTR;
comm_vt.bstrVal=temp.AllocSysString();
m_slave1.SendData(comm_vt);//发送连接信息
}
连接操作完成,控件初始化完成,即可发送数据到对方。因此在Winsock控件的Send事件中加入以下代码:
void CWinsock_exampleDlg::OnSend()
{
SAFEARRAY * psa;//安全数组
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = send_data_len; // SAFEARRAY长度
psa = SafeArrayCreate(VT_UI1, 1, rgsabound);//创建类型为字节的数组
long len;
for(len=0;len<send_data_len;len++)
SafeArrayPutElement(psa, &len, &send_data_buf[len]);//缓冲区数据入安全数组
VARIANT send_var;
send_var.vt=VT_ARRAY|VT_UI1;
send_var.parray=psa;
m_slave1.SendData(send_var);//数据发送
}
系统收到网络数据包,如果数据包IP地址、端口、协议类型与Winsock控件网络参数吻合,将触发消息,通知控件进行处理,即触发Winsock控件的DataArrival事件。本例中将获取的二进制数据转化成ASCII,因此在Winsock控件的DataArrival事件加入以下代码:
void CWinsock_exampleDlg::OnDataArrivalWinsockSlave1(long bytesTotal)
{
SAFEARRAY * psa_recv;
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = bytesTotal;// 设置数组的长度
psa_recv = SafeArrayCreate(VT_UI1, 1, rgsabound);
VARIANT recv_var;
recv_var.vt=VT_ARRAY|VT_UI1;//设置数据类型为字节型数组
recv_var.parray=psa_recv;
VARIANT Vtype;
Vtype.vt=VT_ERROR;
VARIANT Maxlen;
Maxlen.vt=VT_I4;
Maxlen.lVal=bytesTotal;
m_slave1.GetData(&recv_var,Vtype,Maxlen);//接收数据包
psa_recv=recv_var.parray;
long len;
for(len=0;len< bytesTotal;len++)
SafeArrayGetElement(psa_recv, &len, &recv_data_buf[len]);//数据存入接收缓冲区
BYTE temp;
long ls_len=0;
m_edit_input_cs=m_edit_input_cs+"\r\n Receive Packer: ";
ls_len=m_edit_input_cs.GetLength();
for(len=0;len< bytesTotal;len++)// 二进制字节数据转换为ASCII码显示。
{
temp=' ';
m_edit_input_cs.Insert(ls_len,temp);
ls_len++;
temp=recv_data_buf[len]>>4;
temp=temp&0x0f;
if(temp>=0 && temp<=9) temp=temp+0x30;// 数字
else temp=temp+0x37;//字母
m_edit_input_cs.Insert(ls_len,temp);
ls_len++;
temp=recv_data_buf[len];
temp=temp&0x0f;
if(temp>=0 && temp<=9) temp=temp+0x30;
else temp=temp+0x37;
m_edit_input_cs.Insert(ls_len,temp);
ls_len++;
}
UpdateData(FALSE);
}
五 结束语
本文介绍了点对点通信实现,很方便地实现点对点文件传输,交换其他信息。笔者开发一个远程医疗监护网络系统,要求一台主机控制八台从机,主机系统软件用VC++实现,采取点对点通信,实现了一台主机控制多台从机的通信。
参考文献
[1] 计算机网络(第3版),Andrew S. Tanenbaum著,熊桂喜等译,清华大学出版社,1999