目前使用最广泛的Windows Socket2.2版本,所需的一些文件如下(以安装了VC6为例说明其物理位置):
l 头文件winsock2.h,通常处于C:"Program Files"Microsoft Visual Studio"VC98"INCLUDE;查看该头文件可知其中又包含了windows.h和pshpack4.h头文件,因此在windows中的一些常用API都可以使用;
l 库文件Ws2_32.lib,通常处于C:"Program Files"Microsoft Visual Studio"VC98"Lib;
l DLL文件Ws2_32.dll,通常处于C:"WINDOWS"system32。
2、UDP收发程序
#include
#pragma comment(lib, "ws2_32")
int WebCamera::UDPinitializer(SOCKET *Socket,unsigned short Port)
{
int error;
SOCKADDR_IN addrSrv;
// SOCKADDR_IN AddrTo;
unsigned long flag=1;
int nRecvBuf=1024*1024*256;//设置为256M
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD( 2, 2 );
if ( WSAStartup( wVersionRequested, &wsaData ) != 0 )
{
return 0;
}
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
{
WSACleanup( );
return 0;
}
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(Port);
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
if(*Socket)
{
closesocket(*Socket);
*Socket=NULL;
}
*Socket=socket( AF_INET , SOCK_DGRAM , 0 );
error = bind(*Socket,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
if(SOCKET_ERROR == error)
return 0;
else
{
setsockopt(*Socket,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
// setsockopt(m_hSocketImage,SOL_SOCKET,SO_REUSEADDR,(const char*)&nRecvBuf,sizeof(int));
if (ioctlsocket(*Socket,FIONBIO,&flag)!=0)
{
closesocket(*Socket);
return 0;
}
}
return 1;
}
SOCKET是socket套接字类型,在WINSOCK2.H中有如下定义:
typedef unsigned int u_int;
typedef u_int SOCKET;
可知套接字实际上就是一个无符号整型,它将被Socket环境管理和使用。套接字将被创建、设置、用来发送和接收数据,最后会被关闭。
WORD类型是一个16位的无符号整型,在WTYPES.H中被定义为:
typedef unsigned short WORD;
其目的是提供两个字节的存储,在Socket中这两个字节可以表示主版本号和副版本号。使用MAKEWORD宏可以给一个WORD类型赋值。例如要表示主版本号2,副版本号0,可以使用以下代码:
WORD wVersionRequested;
wVersionRequested = MAKEWORD( 2, 0 );
注意低位内存存储主版本号2,高位内存存储副版本号0,其值为0x0002。使用宏LOBYTE可以读取WORD的低位字节,HIBYTE可以读取高位字节。
WSADATA类型是一个结构,描述了Socket库的一些相关信息,其结构定义如下:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
} WSADATA;
typedef WSADATA FAR *LPWSADATA;
值得注意的就是wVersion字段,存储了Socket的版本类型。LPWSADATA是WSADATA的指针类型。它们不用程序员手动填写,而是通过Socket的初始化函数WSAStartup读取出来。
WSAStartup函数被用来初始化Socket环境,它的定义如下:
int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);
其返回值为整型,调用方式为PASCAL(即标准类型,PASCAL等于__stdcall),参数有两个,第一个参数为WORD类型,指明了Socket的版本号,第二个参数为WSADATA类型的指针。
若返回值为0,则初始化成功,若不为0则失败。
这是Socket环境的退出函数。返回值为0表示成功,SOCKET_ERROR表示失败。
socket的创建函数,其定义为:
SOCKET PASCAL FAR socket (int af, int type, int protocol);
第一个参数为int af,代表网络地址族,目前只有一种取值是有效的,即AF_INET,代表internet地址族;
第二个参数为int type,代表网络协议类型,SOCK_DGRAM代表UDP协议,SOCK_STREAM代表TCP协议;
第三个参数为int protocol,指定网络地址族的特殊协议,目前无用,赋值0即可。
返回值为SOCKET,若返回INVALID_SOCKET则失败。
这个函数用来设置Socket的属性,若不能正确设置socket属性,则数据的发送和接收会失败。定义如下:
int PASCAL FAR setsockopt (SOCKET s, int level, int optname, const char FAR * optval, int optlen);
其返回值为int类型,0代表成功,SOCKET_ERROR代表有错误发生。
第一个参数SOCKET s,代表要设置的套接字;
第二个参数int level,代表要设置的属性所处的层次,层次包含以下取值:SOL_SOCKET代表套接字层次;IPPROTO_TCP代表TCP协议层次,IPPROTO_IP代表IP协议层次(后面两个我都没有用过);
第三个参数int optname,代表设置参数的名称,SO_BROADCAST代表允许发送广播数据的属性,其它属性可参考MSDN;
第四个参数const char FAR * optval,代表指向存储参数数值的指针,注意这里可能要使用reinterpret_cast类型转换;
第五个参数int optlen,代表存储参数数值变量的长度。
sockaddr_in定义了socket发送和接收数据包的地址,定义:
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
其中in_addr的定义如下:
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
首先阐述in_addr的含义,很显然它是一个存储ip地址的联合体,有三种表达方式:
第一种用四个字节来表示IP地址的四个数字;
第二种用两个双字节来表示IP地址;
第三种用一个长整型来表示IP地址。
给in_addr赋值的一种最简单方法是使用inet_addr函数,它可以把一个代表IP地址的字符串赋值转换为in_addr类型,如
addrto.sin_addr.s_addr=inet_addr("192.168.0.2");
本例子中由于是广播地址,所以没有使用这个函数。其反函数是inet_ntoa,可以把一个in_addr类型转换为一个字符串。
sockaddr_in的含义比in_addr的含义要广泛,其各个字段的含义和取值如下:
第一个字段short sin_family,代表网络地址族,如前所述,只能取值AF_INET;
第二个字段u_short sin_port,代表IP地址端口,由程序员指定;
第三个字段struct in_addr sin_addr,代表IP地址;
第四个字段char sin_zero[8],很搞笑,是为了保证sockaddr_in与SOCKADDR类型的长度相等而填充进来的字段。
以下代表指明了广播地址,端口号为7861的一个地址:
sockaddr_in addrto; //发往的地址
memset(&addrto,0,sizeof(addrto));
addrto.sin_family = AF_INET; //地址类型为internetwork
addrto.sin_addr.s_addr = INADDR_BROADCAST; //设置ip为广播地址
addrto.sin_port = htons(7861); //端口号为7861
sockaddr类型是用来表示Socket地址的类型,同上面的sockaddr_in类型相比,sockaddr的适用范围更广,因为sockaddr_in只适用于TCP/IP地址。Sockaddr的定义如下:
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
可知sockaddr有16个字节,而sockaddr_in也有16个字节,所以sockaddr_in是可以强制类型转换为sockaddr的。事实上也往往使用这种方法。
线程挂起函数,表示线程挂起一段时间。Sleep(1000)表示挂起一秒。定义于WINBASE.H头文件中。WINBASE.H又被包含于WINDOWS.H中,然后WINDOWS.H被WINSOCK2.H包含。所以在本例中使用Sleep函数不需要包含其它头文件。
在Socket中有两套发送和接收函数,一是sendto和recvfrom;二是send和recv。前一套在函数参数中要指明地址;而后一套需要先将套接字和一个地址绑定,然后直接发送和接收,不需绑定地址。sendto的定义如下:
int PASCAL FAR sendto (SOCKET s, const char FAR * buf, int len, int flags, const struct sockaddr FAR *to, int tolen);
第一个参数就是套接字;
第二个参数是要传送的数据指针;
第三个参数是要传送的数据长度(字节数);
第四个参数是传送方式的标识,如果不需要特殊要求则可以设置为0,其它值请参考MSDN;
第14、 WSAGetLastError函数
该函数用来在Socket相关API失败后读取错误码,根据这些错误码可以对照查出错误原因。
关闭套接字,其参数为SOCKET类型。成功返回0,失败返回SOCKET_ERROR。
写一个UDP发送程序的步骤如下:
1)用WSAStartup函数初始化Socket环境;
2)用socket函数创建一个套接字;
3)用setsockopt函数设置套接字的属性,例如设置为广播类型;很多时候该步骤可以省略;
4)创建一个sockaddr_in,并指定其IP地址和端口号;
5)用sendto函数向指定地址发送数据,这里的目标地址就是广播地址;注意这里不需要绑定,即使绑定了,其地址也会被sendto中的参数覆盖;若使用send函数则会出错,因为send是面向连接的,而UDP是非连接的,只能使用sendto发送数据;
6)用closesocket函数关闭套接字;
7)用WSACleanup函数关闭Socket环境。
数据发送代码:
void CDlgCommunicationNet::DataSend(CString DataSendBuf)
{
//CString 转成 char *
int len = DataSendBuf.GetLength();
char *pDataSendBuf = new char[len+1];
memset(pDataSendBuf,0,len+1);
WideCharToMultiByte(CP_OEMCP, NULL, (LPCWSTR)DataSendBuf, -1,NULL, 0, NULL, FALSE);
WideCharToMultiByte(CP_OEMCP, NULL, (LPCWSTR)DataSendBuf, -1,(LPSTR)pDataSendBuf, len, NULL, FALSE);
pDataSendBuf[len]='\0';
//send
sendto(m_Socket,pDataSendBuf,len,0,(SOCKADDR*)&(m_IPAddress),sizeof(SOCKADDR));
delete[] pDataSendBuf;
}
与之类似,一个UDP接收程序的步骤如下,注意接收方一定要bind套接字:
1)用WSAStartup函数初始化Socket环境;
2)用socket函数创建一个套接字;
3)用setsockopt函数设置套接字的属性,例如设置为广播类型;
4) 创建一个sockaddr_in,并指定其IP地址和端口号;
5)用bind函数将套接字与接收的地址绑定起来,然后调用recvfrom函数或者recv接收数据; 注意这里一定要绑定,因为接收报文的套接字必须在网络上有一个绑定的名称才能保证正确接收数据;
6)用closesocket函数关闭套接字;
7)用WSACleanup函数关闭Socket环境。
新建一个线程做数据接收:DWORD WINAPI CDlgTriggerNet::DataReceiveThread(LPVOID lpData)
{
CDlgTriggerNet *pThis = reinterpret_cast
while(1)
{
pThis->DataReceive();
}
return TRUE;
}
数据接收代码:
BOOL CDlgTriggerNet::DataReceive()
{
char* pDataReceiveBuf;
pDataReceiveBuf = new char[10];
memset(pDataReceiveBuf,0,sizeof(char)*10);
int AddrLength=sizeof(sockaddr_in);
recvfrom(m_Socket,pDataReceiveBuf,10,0,(sockaddr*)&m_IPAddress,(int *)&AddrLength); //阻塞接收方式
//CString String;
//String.Format(_T("%s"),pDataReceiveBuf);
USES_CONVERSION;
CString String = A2T(pDataReceiveBuf);
m_bTriggerNetFlag = FALSE;
if ( String == m_CStringTrigger )
{
m_bTriggerNetFlag = TRUE;
}
delete[] pDataReceiveBuf;
return m_bTriggerNetFlag;
}