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 );
2 err = WSAStartup( wVersionRequested, &wsaData );
1 InitializeSockets();
2 this.m_Handle = SafeCloseSocket.CreateWSASocket(addressFamily, socketType, protocolType);
3
2 this.m_Handle = SafeCloseSocket.CreateWSASocket(addressFamily, socketType, protocolType);
3
if (UnsafeNclNativeMethods.OSSOCK.WSAStartup(0x202, out lpWSAData) != SocketError.Success)
{
throw new SocketException();
}
{
throw new SocketException();
}
[DllImport("ws2_32.dll", CharSet=CharSet.Ansi, SetLastError=true)]
internal static extern SocketError WSAStartup([In] short wVersionRequested, out WSAData lpWSAData);
internal static extern SocketError WSAStartup([In] short wVersionRequested, out WSAData lpWSAData);
每次调用了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:套接口属性描述。
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; }
}
{
// 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; }
}
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;
}
{
SafeCloseSocket.InnerSafeCloseSocket socket = UnsafeNclNativeMethods.OSSOCK.WSASocket(addressFamily, socketType, protocolType, IntPtr.Zero, 0, SocketConstructorFlags.WSA_FLAG_OVERLAPPED);
if (socket.IsInvalid)
{
socket.SetHandleAsInvalid();
}
return socket;
}
代码
[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);
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机制也只是知道其意思,而不知道其具体是怎么实现的。