一、序
之前做WINCE的项目,涉及到PC与PDA通信的时候,采用的是ActiveSync的通信方式,在PC上采用RAPI函数对PDA中的数据或文件进行控制,这种方式是单项的,与PDA中的程序基本无联系,在扩展性和功能性方面存在一些局限性。
采用Socket通信是一种不错的选择,但由于应用在特殊行业,不能使用WIFI模块和3G模块,PDA上没有分配IP地址,好像并不具备Socket通信的条件。
在查找了一些资料后发现ActiveSync通信其实是基于TCP的连接方式,既然是TCP,那么一定有地址和端口。
在
PDA
程序中运行:
Dns.GetHostEntry(Dns.GetHostName()).AddressList[
0
].ToString();
发现了“
192.168.55.101
”这个地址。
运行:
Dns.GetHostEntry(“PPP_PEER”).AddressList[
0
].ToString();
发现了“
192.168.55.100
”这个地址。
二、基于ActiveSync的Socket通信
当PDA与PC通过ActiveSync的方式连接后,PDA会得分配到192.168.55.101的IP地址,PC会分配到192.168.55.100的IP地址,值得注意的是PC上的这个IP地址是无法通过Ipconfig指令查找到的,也无法ping通,无法Bind,不算是一个真正意义上的IP地址。而且任何一台PDA通过ActiveSync连接后,地址都相同(PC 192.168.55.100,PDA 192.168.55.101)。
初步确定采用
PC
作为
Socket Server
端,
PDA
作为
Socket Client
端进行数据通信这种方式后(注
1
),就着手开始编写代码。
2.1 Socket Server端(PC服务器)
代码与普通
Socket Server
代码没什么两样,值得注意的是
Bind
的
IP
地址不能为
192.168.55.100
,也不能为该计算机的网络
IP
地址,而需要绑定“
127.0.0.1
”。
IPEndPoint iep
=
new
IPEndPoint(IPAddress.Parse(“
127.0
.
0.1
”),
10000
);
m_Socket
=
new
Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
m_Socket.Bind(iep);
2.2Socket Client端(PDA端 WINCE)
代码与普通
Socket Client
代码差不多,连接的地址设置为“
192.168.55.100
”,可采用这样的方式编写:
string
m_ServerIp
=
""
;
try
{
m_ServerIp
=
Dns.GetHostEntry(“PPP_PEER”).AddressList[
0
].ToString();
}
catch
{
m_ServerIp
=
"
192.168.55.100
"
;
}
IPAddress serverIp
=
IPAddress.Parse(m_ServerIp);
int
serverPort
=
10000
;
IPEndPoint iep
=
new
IPEndPoint(serverIp, serverPort);
m_SocketConnection
=
new
Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
m_SocketConnection.Connect(iep);
另外值得注意的是
Socket
的
Connected
属性,在
PC
端的
Socket Server
服务关闭(
Close
)的情况下,
PDA
只要
Connect
后
Connected
属性都会变成
true
。因此不能仅仅通过
Connected
属性来判断
Socket
是否连接正常,需要单独开一个线程,采用心跳包的方式进行检测。最好在
Socket.Connect
后执行一下
Socket.Receive
,如果成功则说明网络正常(注
2
),不成功说明网络断开。为防止
Receive
阻塞,
PC
服务端程序在与
PDA
连接后应立即发送一个短字节给客户端,让客户端接收。
三、Socket中涉及到的结构字节数组转换
Socket通信中涉及到很多结构与字节数组的转换,在WINDOW上运行正常的程序在WINCE中会报”不支持….”的异常。代码如下:
[StructLayout(LayoutKind.Sequential, CharSet
=
CharSet.Auto)]
public
struct
SocketMsg
{
public
SocketMsgType MsgId;
//
枚举类型
[MarshalAs(UnmanagedType.ByValTStr, SizeConst
=
20
)]
public
string
MsgDatetime;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst
=
100
)]
public
string
MsgContent;
}
SocketMsg msg
=
new
SocketMsg();
Msg.MsgId
=
0
;
Msg.MsgDatetime
=
DateTime.Now.ToString(
"
yyyy-MM-dd HH:mm:ss
"
);
Msg.MsgContent
=
""
;
private
byte
[] MsgStructToByte(SocketMsg msg)
{
int
size
=
Marshal.SizeOf(msg);
System.IntPtr ptr
=
Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(msg, ptr,
false
);
byte
[] ba
=
new
byte
[size];
Marshal.Copy(ptr, ba,
0
, ba.Length);
return
ba;
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
以上代码在
WINDOW
下运行正常,但是在
WINCE
运行到
StructureToPtr
时报异常,异常显示不支持
ByValTStr
这种方式。为解决该问题,需要改变该结构定义。
[StructLayout(LayoutKind.Sequential, CharSet
=
CharSet.Auto)]
public
struct
SocketMsg
{
public
SocketMsgType MsgId;
//
枚举类型
[MarshalAs(UnmanagedType. ByValArray, SizeConst
=
20
)]
public
byte
[] MsgDatetime;
//
注3
[MarshalAs(UnmanagedType. ByValArray, SizeConst
=
100
)]
public
byte
[] MsgContent;
}
SocketMsg msg
=
new
SocketMsg();
msg.MsgId
=
0
;
msg.MsgDatetime
=
Encoding.ASCII.GetBytes(DateTime.Now.ToString(
"
yyyy-MM-dd HH:mm:ss
"
)
+
"
"
);
msg.MsgContent
=
new
byte
[
100
];
需要说明的是
+" "
,因为MsgDatetime数组定义为
20
字节
,
而
”2011-03-31 -15:30:30”
是
19
位,因此需要补一个空格凑足
20
位,
StructureToPtr
时就不会报异常了,同样道理在给
MsgContent
赋值的时候也应该注意该问题。
四、另外一些补充说明
OpenNETCF
是一个很好的开发库,但是在WINCE中,由于一些DLL不存在,因此需要有选择的使用。例如因为没有Cellcore.dll这个文件(Windows mobile支持),在WINCE中无法使用
OpenNETCF.Net Namespace
中的
ConnectionManager Class
来管理网络连接状态。
附:
以上代码的环境为:
PC端:WIN2003,ActiveSync4.5,F2.0
PDA端:WINCE5.0,CF2.0
注1:Google里资料说PDA作为Socket Server ,PC作为Socket Client的好像无法实现,因此就没有进行测试,有兴趣的朋友可测试一下。
注2:不能用Socket.Send,测试发现在Socket Server服务关闭的情况下,Connect后Send有时也会成功
注3:在WINCE中 char默认为unicode16位,因此在定义结构体时最好采用byte。