winsock
winsock是WIN32平台上提供的网络编程接口且与具体的网络协议无关(也就是说支持多种网络协议),它借鉴了Unix平台上的Berkeley(BSD)套接字方案(Unix socket 编程和 Winsock 编程是很相似的)。虽然winsock与协议无关,但是在编程时我们还是要了解一下网络协议的一些特征,例如:面向消息、面向连接与无连接、路由选择等。
面向消息
这里根据网络协议的不同,可以将他们分为有消息边界和无消息边界保护的,通常把无消息边界保护的协议称为“基于流的协议”。假设发送端发出数条不同大小的消息如:128字节、256字节,而接受端将会两次进行读取每次读取一个消息的数据包,即使这些数据包已经全部搜到并存于网络堆栈中也是如此。我们熟悉的UDP协议发送的数据包,就是有消息边界保护的(同样IP的数据报也是如此)。如果是在基于流的协议里,由于消息没有保护边界,系统可能将大的消息分割或者将小的组合然后尽可能返回更多的数据。我们熟知的TCP协议就是基于流的。
面向连接或无连接
一个面向连接的协议在数据交换之前会建立一个虚拟的路径,而且面向连接的协议通常还提供一定的可靠性保障服务(代价是维护连接需要开销)。无连接的协议只负责把数据直接投递出去其他就不关了,比较像邮政的信件寄送,虽然缺乏可靠性但是其速度快。总所周知tcp协议是面向连接的,而UDP则是无连接协议。可以根据不同的场景和需求选择使用哪种协议进行通讯。
路由
路由器会将受到的非路由协议数据包全部丢掉(例如:NETBEUI协议)。如果我们设计的程序存在不同的网络(当然这些网络经由路由器连接着)我们就需要选择可路由的协议,首推的当然是TCP/IP了。
WSAStartup 函数
在使用winsock编程前需要先加载winSock库,也就是调用WSAStartup函数(这是与Unix socket的最主要区别)。int WSAStartup(WORD wVersionRequested , LPWSADATA lpWSAData),该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节 指明副版本、低位字节指明主版本(可以利用宏MAKEWODE(2,0)获得 wVersionRequested);操作系统利用第二个参数返回请求的Socket的版本信息(有用的只有 wVersion和wHighVersion 这两个而已)。当一个应用程序调用WSAStartup函数时,操作系统根 据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的 其它Socket函数了,该函数执行成功后返回0。
1
wVersionRequested
=
MAKEWORD(
2
,
2
);
2
err
=
WSAStartup( wVersionRequested,
&
wsaData );
我们来看看在.NET C#语言下是怎么调用WSAStartup函数的,当然我们在使用C#是不需要显示打开winsock库,.net帮我们封装好了。用Reflector找到System.Net.Socket类(我的是.net3.5)。查看public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) 在里面发现了这么两句话:
1
InitializeSockets();
2
this
.m_Handle
=
SafeCloseSocket.CreateWSASocket(addressFamily, socketType, protocolType);
3
其中.m_Handle是一个SafeCloseSocket类(其实祖先是 SafeHandle 类,另附:安全句柄和紧急终结),再看看InitializeSockets()方法发现了如下语句:
if
(UnsafeNclNativeMethods.OSSOCK.WSAStartup(
0x202
,
out
lpWSAData)
!=
SocketError.Success)
{
throw
new
SocketException();
}
继续跟踪就可以发现:
[DllImport(
"
ws2_32.dll
"
, CharSet
=
CharSet.Ansi, SetLastError
=
true
)]
internal
static
extern
SocketError WSAStartup([In]
short
wVersionRequested,
out
WSAData lpWSAData);
其中ws2_32.dll便是Windows Sockets应用程序接口的动态链接库文件,用DllImport的方式就可以使用这个WIN32 api了。所以当我们使用Socket类生产一个Socket实例时系统便会帮我们调用WSAStartup。
WSACleanup
每次调用了WSAStartup都需要调用WSACleanup来释放资源。
WSAEnumProtocols函数
如果想知道系统里安装了哪些协议和这些协议的特性,可以调用int WSAEnumProtocols(lpdwProtocols,lpProtocolBuffer,lpdwBufferLength),通常需要两次调用WSAEnumProtocols()函数以获取特定的协议信息,第一次调用时指定lpdwProtocols为 NULL,调用肯定是失败的,但参数lpdwBufferLength包含了所有协议信息需要的缓冲区长度。我们知道了这个准确长度就可以进行第二次调用了。
WSASocket
winsock API 是建立在套接字上的,套接字其实就是一个句柄(C#层面上我们可看作是一个抽象的对象)。SOCKET WSASocket(int af,int type,int protocols,LPWSAProtocol_INFO lpprotocolInfo,GROUP g,DOWRD dwFlags) 使用这个函数我们就可以创建套接字了
- af:地址族描述。如果想建立UDP或TCP的套接字,可以使用 AF_INET来指定互联网协议
- type:新套接口的类型描述。它可以是:SOCKSTREAM、SOCKDGRAM、SOCKSEQPACKET、SOCKRAW和SOCKRDM。
- protocol:套接口使用的特定协议,如果调用者不愿指定协议则定为0,需要注意的是protocol 和 type是由一定对应关系的
- lpProtocolInfo:一个指向PROTOCOL_INFO结构的指针,该结构定义所创建套接口的特性。如果本参数非零,则前三个参数(af, type, protocol)被忽略。
- g:套接口组的描述字。
- iFlags:套接口属性描述。
还记得在.NET C#语言下是怎么调用WSAStartup函数的吗? 在 SafeCloseSocket 里找到了InnerSafeCloseSocket类
internal
class
InnerSafeCloseSocket : SafeHandleMinusOneIsInvalid
{
//
Fields
private
bool
m_Blockable;
private
static
readonly
byte
[] tempBuffer;
//
Methods
static
InnerSafeCloseSocket();
protected
InnerSafeCloseSocket();
internal
static
SafeCloseSocket.InnerSafeCloseSocket Accept(SafeCloseSocket socketHandle,
byte
[] socketAddress,
ref
int
socketAddressSize);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
internal
void
BlockingRelease();
internal
static
unsafe
SafeCloseSocket.InnerSafeCloseSocket CreateWSASocket(
byte
*
pinnedBuffer);
internal
static
SafeCloseSocket.InnerSafeCloseSocket CreateWSASocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType);
protected
override
bool
ReleaseHandle();
//
Properties
public
override
bool
IsInvalid { [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
get
; }
}
我们看看 SafeCloseSocket.InnerSafeCloseSocket CreateWSASocket() 方法是如何实现的:
internal
static
SafeCloseSocket.InnerSafeCloseSocket CreateWSASocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
{
SafeCloseSocket.InnerSafeCloseSocket socket
=
UnsafeNclNativeMethods.OSSOCK.WSASocket(addressFamily, socketType, protocolType, IntPtr.Zero,
0
, SocketConstructorFlags.WSA_FLAG_OVERLAPPED);
if
(socket.IsInvalid)
{
socket.SetHandleAsInvalid();
}
return
socket;
}
可看到在OSSock 类里有以下引用:
代码
[DllImport(
"
ws2_32.dll
"
, CharSet
=
CharSet.Auto, SetLastError
=
true
)]
internal
static
extern
SafeCloseSocket.InnerSafeCloseSocket WSASocket([In] AddressFamily addressFamily, [In] SocketType socketType, [In] ProtocolType protocolType, [In] IntPtr protocolInfo, [In]
uint
group, [In] SocketConstructorFlags flags);
不过令我意外的是 这个OSSOCKET 竟然是在UnsafeNclNativeMethods 下的,这个难道是不安全的吗? 能找到的UnsafeNclNativeMethods的资料不多(哪位大牛知道希望指点指点),我对.net的 GC机制也只是知道其意思,而不知道其具体是怎么实现的。