fbpx
维基百科

Winsock

Windows Sockets API (WSA), 简短记为Winsock, 是WindowsTCP/IP网络编程接口(API)。其函数名称兼容于Berkeley套接字API。实际上,Winsock的实现库(winsock.dll)使用的是长名字。

Winsock
開發者微软
首次发布1992年6月,​30年前​(1992-06
操作系统Microsoft Windows
类型API

Winsock是一种能使Windows程序通过任意网络传输协议发送数据的API。Winsock中有几个只支持TCP/IP协议的函数(例如gethostbyaddr()),但是在Winsock 2中新增了所有这些函数的通用版本,以允许开发者使用其它的传输协议。

历史

MS-DOS与早期版本的Microsoft Windows使用的网络协议是NetBIOS. 因此,各方提供了各自的MS-DOS上的TCP/IP实现。由于各种解决方案的API函数名并不统一,使得软件开发者难以下决心转到TCP/IP协议上。

1991年10月,以Martin Hall, Mark Towfiq, Geoff Arnold, Henry Sanders为首在CompuServe BBS上讨论形成了Windows Sockets API规范(specification)并且版权属于这五人。

规范版本

  • Version 1.0 (1992年6月) 虽然不限于TCP/IP, 但TCP与UDP是仅有被明确提到的网络协议。
  • Version 1.1 (1993年1月) 很多小的修正与澄清。Winsock以Windows消息的形式发送通知,使程序无需担心并发性。
  • Winsock 2 向后兼容Winsock 1.1,还增加了协议独立的名字解析,异步操作(基于事件通知与完成过程(completion routine)), 多播, QoS. 明文支持了多协议,包括IPX/SPXDECnet英语DECnet. 增加了服务提供者接口英语Service Provider Interface(SPI)与分层协议实现.
    • Versions 2.0.x (1994年5月)草案没有正式公开发布。
    • Version 2.1.0 (1996年1月)是Winsock 2首次公开的规范。定义了两种接口:一是应用程序接口(API),这种接口将开发者和底层隔离开;二是服务提供接口(SPI),这种接口允许对Winsock协议栈的扩展。对多种传输协议的官方支持。Winsock 2的规范中写入了对OSI、Novell IPX/SPX和Digital DECNet的官方支持。也添加了对一些诸如服务质量(QoS)、广播等技术创新的支持。重叠I/O机制下工作。
    • Version 2.2.0 (1996年5月)包含很多小的矫正与澄清,并且不再支持16位Windows应用程序.
    • Version 2.2.1 (1997年5月)与Version 2.2.2 (1997年8月)增加小的新功能。

Windows 95 OSR2以后版本的Windows操作系统均支持Windows Sockets version 2.2。此外,Windows 95 with the Windows Socket 2 Update也支持WinSock 2.2。

Windows 95、Windows NT 3.51及更早版本的Windows操作系统,最高支持Windows Sockets version 1.1。

技术

WinSock编程时,可选择下述编程模型之一:

  • 同步阻塞模型:直接使用recv()与send()收发数据,是最直观与最基本的IO模型;
  • select模型,可以阻塞/非阻塞/超时返回等方式查询socket的状态;
  • WSAAsyncSelect函数把socket绑定到一个窗口句柄,socket的若干事件绑定到一个窗口消息(如WM_SOCKET_NOTIFY),来异步响应的模型;
  • WSAEventSelect函数把socket与其若干事件绑定到一个操作系统的同步event上,来异步响应的模型;配套的API函数有:WSACreateEvent、WSAResetEvent、WSAWaitForMultiEvents、WSAEnumNewWorkEvents
  • Overlapped I/O事件通知模型:使用WSA_FLAG_OVERLAPPED标志通过WSASocket创建一个套接字;使用WSASend、WSASendTo、WSARecv、WSARecvFrom、WSAIoctl、AcceptEx、TransmitFile等函数带一个WSAOVERLAPPED结构参数来发起重叠IO操作。WSACreateEvent函数创建所需要的事件对象。WSAWaitForMultipleEvents函数来等待该事件对象。再调用WSAGetOverlappedResult函数判断重叠IO操作是否成功完成。
  • Overlapped I/O完成例程模型,使用完成例程来通知socket事件;使用ReadFileEx和WriteFileEx,或WSARecv/WSASend,指定完成例程。
  • 完成端口模型:首先使用CreateCompletionPort创建一个完成端口对象,然后把任意数量的socket或IO句柄绑定到该完成端口对象(通过CreateCompletionPort函数)。然后创建一批工作者线程,每个线程通过GetQueuedCompletionStatus就把自己绑定到完成端口对象。IO完成包在完成端口对象先进先出,挂在完成端口对象上的工作者线程按照后进先出调度执行以减少线程切换的代价。

Windows Sockets API规范包含两种接口:

  • API,用于应用程序开发者。
  • 服务提供者接口英语Service Provider Interface(SPI), 用于增加新的协议模块到操作系统。实际上,现在Windows上实现TCP/IP以外的网络协议并没有什么用途.

Windows Sockets的代码与设计是基于Berkeley sockets,此外还提供了额外的功能使得API遵从Windows编程模式(如重叠I/O). API函数名字都以前缀WSA开始, 例如WSASend().

为了便于从Unix向Windows移植网络程序的源代码,Winsock提供了很多便利. 例如,Unix应用程序能使用errno码英语errno.h记录网络错误与C运行时错误。Windows Sockets引入了专门的函数WSAGetLastError()以获取错误信息. 但很多TCP/IP应用程序使用了Unix的特性, 如伪终端英语pseudo terminalfork系统调用英语Fork (operating system)。这使得源代码移植非常困难。

回复Ack的延时

当Microsoft TCP栈接收到一个数据包时,会启动一个200毫秒的计时器。当ACK确认数据包发出之后,计时器会复位。接收到下一个数据包时,会再次启动200毫秒的计时器。这称为TCP确认延迟机制(TCP Delayed acknowledge),作用是接收到数据后延迟ACK的发送,使得TCP协议栈有机会合并多个ACK以提高性能。Microsoft TCP栈使用了下面的策略来决定在接收到数据包后什么时候发送ACK确认数据包:

  1. 如果在200毫秒的计时器超时之前,接收到下一个数据包,则立即发送ACK确认数据包。
  2. 如果当前恰好有数据包需要发给ACK确认信息的接收端,则把ACK确认信息附带在数据包上立即发送。
  3. 当计时器超时,ACK确认信息立即发送。

发送小尺寸数据时默认采用Nagle算法拼接为大数据包

为了避免小数据包拥塞网络,Microsoft TCP栈默认启用了納格算法,这个算法能够将应用程序多次调用Send发送的小尺寸的数据拼接起来,一块封包发送。Nagle算法规定的特殊情况:

  1. 如果Microsoft TCP栈拼接起来的数据超过了MSS值,这个数据会立即发送,而不等待前一个数据包的ACK确认信息。以太网MTU(Maximum Transmission Unit)是1460字节。
  2. 如果以前发送的数据都收到了回复的Ack,那么当前的数据会立即发送,而没有延迟。
  3. 如果等待超时(例如200ms)仍然没有拼成一个MSS的数据包,当前的数据会被发送。
  4. 如果设置了TCP_NODELAY选项,就会禁用Nagle算法,应用程序调用Send发送的数据包会立即被投递到网络,而没有延迟。

Winsock的内核发送缓冲区与send函数的实现

为了在应用层优化性能,Winsock把应用程序调用Send发送的数据从应用程序的缓冲区复制到Winsock内核缓冲区。Microsoft TCP栈利用类似Nagle算法的方法,决定什么时候才实际地把数据投递到网络。内核缓冲区的默认大小是8K,使用SO_SNDBUF选项,可以改变Winsock内核缓冲区的大小。如果有必要的话,Winsock能缓冲大于SO_SNDBUF缓冲区大小的数据。在绝大多数情况下,应用程序完成Send调用仅仅表明数据 被复制到了Winsock内核缓冲区,并不能说明数据就实际地被投递到了网络上。例外情况:通过设置SO_SNDBUT为0禁用了Winsock内核缓冲区。

Winsock使用下面的规则来向应用程序表明一个Send调用的完成:

  1. 如果socket仍然在SO_SNDBUF限额内,Winsock复制应用程序要发送的数据到内核缓冲区,完成Send调用。
  2. 如果Socket超过了SO_SNDBUF限额并且先前只有一个被缓冲的发送数据在内核缓冲区,Winsock复制要发送的数据到内核缓冲区,完成Send调用。
  3. 如果Socket超过了SO_SNDBUF限额并且内核缓冲区有不只一个被缓冲的发送数据,Winsock复制要发送的数据到内核缓冲区,然后投递数据到网络,直到Socket降到SO_SNDBUF限额内或者只剩余一个要发送的数据,才完成Send调用。

实现

Microsoft实现

  • Microsoft未提供Winsock 1.0实现.
  • Version 1.1作为插件包(称为Wolverine)用于Windows for Workgroups (代号Snowball). 它是Windows 95与Windows NT 3.5版以后的组成部分。
  • Version 2.1是Windows 95的插件包. 它是Windows 98, Windows NT 4.0及其以后版本的组成部分。 Microsoft不提供Windows 3.x或Windows NT 3.x的Winsock 2实现。

Winsock1.1 API,需要声明#include <winsock.h>并链接wsock32.lib,使用wsock32.dll。

Winsock2 API,需要声明#include <winsock2.h>,链接ws2_32.lib,在Winsock2_32.dll中实现。 其下是两种服务提供者接口(Service Provider Interface):

  • Transport SPI: 提供建立连接、数据传输、QoS、错误处理等功能。rsvpsp.dll实现了RSVP QoS;mswsock.dll实现了Winsock core。
  • NameSpace SPI:名字解析,如getXXXbyYYY等函数。支持TCP/IP、NT DS、NLA等命名空间。

可通过ws2spi.h中的两对API函数来安装/卸载服务提供者接口:WSCInstallProvider、WSCDeinstallProvider、WSCInstallNameSpace、WSCUnInstallNameSpace。

SPOrder.dll中提供了重排序服务提供者:WSCWriteNameSpaceOrder、WSCWriteProviderOrder。

大部分Winsock2 API函数被映射到SPI函数,由当前安装的服务提供者实现其功能。一条简单规则是根据提供者链顺序从WSA*函数名映射为WSP*函数名(Winsock Service Provider, 用于传输服务提供者函数)。下述函数不在SPI中实现:

  • 事件处理函数与等待函数,直接映射到Windows API:
    • WSACreateEvent,
    • WSACloseEvent,
    • WSASetEvent,
    • WSAResetEvent
    • WSAWaitForMultipleEvents
  • 名字服务函数:
    • 特定IP名字转换函数与名字解析函数:Winsock 1.1的GetXXXByYYY以及WSAAsyncGetXXXbyYYY, WSACancelAsyncRequest, gethostname等。
    • 支持(support)函数: ntohs, ntohl, htonl, htons
    • IP转换函数:inet_XtoY, inet_addr, ...
  • 错误处理函数:WSAGetLastError, WSASetLastError
  • 其他函数
    • WSAEnumProtocols
    • WSAIsBlocking,
    • WSASetBlockingHook,
    • WSAUnhookBlockingHook

WSP前缀(Winsock Service Provider)的函数是传输服务提供者函数。例如WSPStartup函数用于初始化分层服务提供者。

WPU前缀(Winsock Provider Upcall)的函数是被服务提供者调用的ws2_32.dll中的函数,

WSC前缀(WinSock Configuration)的函数名是LSP安装程序调用的ws2_32.dll中的函数,如:WSCInstallProvider, WSCWriteProviderOrder。

NSP前缀(NameSpace Provider)的函数名用于命名空间提供者。


service provider interface (SPI)是各种功能的具体实施者。允许插入第三方厂商写的 service providers 而无需改变Winsock 2 的API与DLL(ws2_32.dll);从而应用程序开发人员写的基于Winsock的代码也无需改变 。

Winsock 2 SPI 有两种类型的 service providers —— transport 和 namespace。Transport providers(通常称之为协议栈)提供建立连接、传输数据、进行流控制和差错控制等功能。Namespace providers 提供网络协议的寻址属性、协议无关的名字解析。

SPI transport service providers细分为两类 —— base service providers 和 layered service providers。Base service providers 实现了传输协议的实际细节:建立连接、传送数据、流控制和差错控制。Layered service providers 仅实现了高层的自定义的通讯功能,而且依赖于已有的下层的 base provider 来与远程端进行实际的数据交换。也就是说,LSP是做什么的,就是做一些附件的高端的可选的功能。如在 base TCP/IP 栈的顶端实现一个带宽管理器。 而base service providers提供必需的基础的功能。

Winsock 2不允许 namespace providers 的LSP。虽然可以使用 Winsock 2 SPI 来实现一个新的 namespace provider,但是不能改变或扩展已有 namespace provider 的命名、注册和查询行为。

layered transport service provider一般由高层应用开发;而 base transport providers 和 namespace providers 一般由操作系统厂商和协议栈厂商开发。

编写 service provider的就是标准的DLL,其导出函数只有运输服务的WSPStartup 或名字服务的 NSPStartup。WSPStartup 和 NSPStartup通过输出参数lpProcTable作为LSP的dispatch table,提供了约30个可用的函数的地址,这些函数原型在WS2spi.h中声明。WSPStartup的另外一个参数UpcallTable为LSP提供了ws2_32.dll中的15个函数的地址表,这些函数原型也在WS2spi.h中声明。如果ws2_32.dll提供了额外的函数,就需要在WSPStartup通过GetProcAddress获取函数地址,目前仅有一个例子WPUCompleteOverlappedResult。

LSP可以形成一个链,通过调用下层LSP的WSPStartup函数,下层LSP由上层LSP装入。最上层的LSP被ws2_32.dll装入。WSPStartup函数参数lpProtocolInfo指向一个WSAPROTOCOL_INFOW结构组成的链表。链表最底层是base provider。 WSPStartup与WSPCleanup使用引用计数来加载/清除。

其他实现

遵从Winsock规范的TCP/IP与UDP/IP协议栈有:3Com, Beame & Whiteside, DEC, Distinct, FTP Software, Frontier, IBM, Microdyne, NetManage英语NetManage, Novell, Sun Microsystems, Trumpet Software International[1].

例子

列出所有服务提供者

/** * Winsock 2 API Protocol Enumerator - */ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #define WINSOCK_API_LINKAGE #include <winsock2.h> #include <ws2spi.h> #include <wtypes.h> #include <assert.h> #include <winnt.h> #include <stdlib.h> #include <stdio.h> #pragma comment(lib,"Ws2_32.lib") char *ExpandServiceFlags(DWORD serviceFlags) {  /* A little utility function to make sense of all those bit flags */  /* The following code leaks. Yeah, I know.. Go find Buffer 0v3rfl0w$ :-) */  char *serviceFlagsText = (char *)malloc(2048);  memset(serviceFlagsText, '\0', 2048);  char *strip_comma;  /* Hey - it's only for printing and demo purposes.. */  if (serviceFlags & XP1_CONNECTIONLESS)  {  strcat(serviceFlagsText, "Connectionless, ");  }  if (serviceFlags & XP1_GUARANTEED_ORDER)  {  strcat(serviceFlagsText, "Guaranteed Order, ");  }  if (serviceFlags & XP1_GUARANTEED_DELIVERY)  {  strcat(serviceFlagsText, "Message Oriented, ");  }  if (serviceFlags & XP1_CONNECT_DATA)  {  strcat(serviceFlagsText, "Connect Data, ");  }  if (serviceFlags & XP1_DISCONNECT_DATA)  {  strcat(serviceFlagsText, "Disconnect Data, ");  }  if (serviceFlags & XP1_SUPPORT_BROADCAST)  {  strcat(serviceFlagsText, "Broadcast Supported, ");  }  if (serviceFlags & XP1_EXPEDITED_DATA)  {  strcat(serviceFlagsText, "Urgent Data, ");  }  if (serviceFlags & XP1_QOS_SUPPORTED)  {  strcat(serviceFlagsText, "QoS supported, ");  }  /*  * While we're quick and dirty, let's get as dirty as possible..  */  strip_comma = strrchr(serviceFlagsText, ',');  if (strip_comma)  *strip_comma = '\0';  return (serviceFlagsText); } void PrintProtocolInfo(LPWSAPROTOCOL_INFOW prot) {  wprintf(L"Protocol Name: %s\n", prot->szProtocol); /* #%^@$! UNICODE...*/  printf("\tServiceFlags1: %d (%s)\n",  prot->dwServiceFlags1,  ExpandServiceFlags(prot->dwServiceFlags1));  printf("\tProvider Flags: %d\n", prot->dwProviderFlags);  printf("\tNetwork Byte Order: %s\n",  (prot->iNetworkByteOrder == BIGENDIAN) ? "Big Endian" : "Little Endian");  printf("\tVersion: %d\n", prot->iVersion);  printf("\tAddress Family: %d\n", prot->iAddressFamily);  printf("\tSocket Type: ");  switch (prot->iSocketType)  {  case SOCK_STREAM:  printf("STREAM\n");  break;  case SOCK_DGRAM:  printf("DGRAM\n");  break;  case SOCK_RAW:  printf("RAW\n");  break;  default:  printf(" Some other type\n");  }  printf("\tProtocol: ");  switch (prot->iProtocol)  {  case IPPROTO_TCP:  printf("TCP/IP\n");  break;  case IPPROTO_UDP:  printf("UDP/IP\n");  break;  default:  printf("some other protocol\n");  } } int _cdecl main(int argc, char** argv) {  LPWSAPROTOCOL_INFOW bufProtocolInfo = NULL;  DWORD dwSize = 0;  INT dwError;  INT iNumProt;  /*  * Enum Protocols - First, obtain size required  */  printf("Sample program to enumerate Protocols\n");  WSCEnumProtocols(NULL, // lpiProtocols  bufProtocolInfo, // lpProtocolBuffer  &dwSize, // lpdwBufferLength  &dwError); // lpErrno  bufProtocolInfo = (LPWSAPROTOCOL_INFOW)malloc(dwSize);  if (!bufProtocolInfo) {  fprintf(stderr, "SHOOT! Can't MALLOC!!\n");  exit(1);  }  /* Now, Enum */  iNumProt = WSCEnumProtocols(  NULL, // lpiProtocols  bufProtocolInfo, // lpProtocolBuffer  &dwSize, // lpdwBufferLength  &dwError);  if (SOCKET_ERROR == iNumProt)  {  fprintf(stderr, "Darn! Can't Enum!!\n");  exit(1);  }  printf("%d Protocols detected:\n", iNumProt);  for (int i = 0;  i < iNumProt;  i++)  {  PrintProtocolInfo(&bufProtocolInfo[i]);  printf("-------\n");  }  printf("Done");  return(0); } 

阻塞式程序

 int nPort =65000;//指定通信端口  WSADATA wsaData;  WSAStartup( MAKEWORD( 2, 2 ), &wsaData );  // 创建监听套接字,绑定本地端口,开始监听   SOCKET sListen = socket( AF_INET,SOCK_STREAM, 0 );  SOCKADDR_IN addr;   addr.sin_family = AF_INET;   addr.sin_port = htons( nPort );   addr.sin_addr.S_un.S_addr = INADDR_ANY;   bind( sListen, (sockaddr *)&addr, sizeof( addr ) );   listen( sListen, 5 );  SOCKADDR_IN saRemote;   int nRemoteLen = sizeof( saRemote );   SOCKET sRemote = accept( sListen, (sockaddr *)&saRemote, &nRemoteLen ); 

优雅关闭

TCP连接的关闭过程有两种:

  • 优雅关闭(graceful close)如果发送缓存中还有数据未发出则其发出去,并且收到所有数据的ACK之后,发送FIN包,开始关闭过程
  • 强制关闭(hard close或abortive close)如果缓存中还有数据,则这些数据都将被丢弃,然后发送RST包,直接重置TCP连接。
 shutdown(clntSock, SD_SEND); //数据发送完毕,断开输出流,向客户端发送FIN包   recv(clntSock, buffer, BUF_SIZE, 0); //阻塞,等待客户端接收完毕后closesocket,这将导致本方的recv从阻塞状态返回   fclose(fp); //释放资源  closesocket(clntSock);   closesocket(servSock); //也要关闭用于listen/accept的socket  WSACleanup(); 

调用 close()/closesocket() 函数意味着完全断开连接,即不能发送数据也不能接收数据,这种“生硬”的方式有时候会显得不太“优雅”。使用 shutdown() 函数和WSASendDisconnect函数可以优雅关闭连接,其函数原型为

int shutdown(SOCKET s, int howto); //Windows 

howto 在 Windows 下有以下取值:

  • SD_RECEIVE:关闭接收操作,也就是断开输入流。
  • SD_SEND:关闭发送操作,也就是断开输出流。
  • SD_BOTH:同时关闭接收和发送操作。

确切地说,close() / closesocket() 用来关闭套接字,将套接字描述符(或句柄)从内存清除,之后再也不能使用该套接字。应用程序关闭套接字后,与该套接字相关的连接和缓存也失去了意义,TCP协议会自动触发关闭连接的操作。shutdown() 用来关闭连接,而不是套接字;套接字依然存在,直到调用 close() / closesocket() 将套接字从内存清除。调用 close()/closesocket() 关闭套接字时,或调用 shutdown() 关闭输出流时,都会向对方发送 FIN 包。FIN 包表示数据传输完毕,对端收到 FIN 包就知道不会再有数据传送过来了。默认情况下,close()/closesocket() 会立即向网络中发送FIN包,不管输出缓冲区中是否还有数据,而shutdown() 会等输出缓冲区中的数据传输完毕再发送FIN包。也就意味着,调用 close()/closesocket() 将丢失输出缓冲区中的数据,而调用 shutdown() 不会。

shutdown()并不实际关闭socket,而是仅仅改变其可用性。shutdown是一种优雅地单方向或者双方向关闭socket的方法。 如果有多个进程共享一个socket,shutdown影响所有进程,而close只影响本进程。shutdown本身并不影响底层,也即此前发出的异步send/recv不会返回。在所有已发送的包被对端确认后,本方会发送FIN包给client,开始TCP四次挥手过程。 对端收到FIN报文后,并不知道server端以何种方式shutdown,甚至不知道server端是shutdown还是close。

若本方发送FIN报文后没有收到对端的FIN-ACK,会两次重传FIN报文,若一直收不到对端的FIN-ACK,则会给对端发送RST信号,关闭socket并释放资源。对端收到FIN信号后,再调用read函数会返回0;因为接收了FIN,表明以后再无数据可以接收。

对端收到RST报文后,行为如下:

  • 阻塞模型下,内核无法主动通知应用层出错,只有应用层主动调用read()或者write()这样的IO系统调用时,内核才会利用出错来通知应用层已经收到RST报文
  • 非阻塞模型下,select或者epoll会返回sockfd可读,应用层对其进行读取时,read()会报RST错误。

收到RST报文的情况下,再做任何read write都是毫无意义。

调用调用close(),根据参数设置不同,会出现如下两种情况:

  • l_onoff为非0,l_linger为0: 向对端发送一个RST报文,丢弃本地缓冲区的未读数据,关闭socket并释放相关资源,此种方式为强制关闭。
  • l_onoff 为非0,l_linger为非0:向对端发送一个FIN报文,收到对端FIN-ACK后,进入了FIN_WAIT_2阶段,参考TCP四次挥手过程,此种方式为优雅关闭。如果在l_linger的时间内仍未完成四次挥手,则强制关闭。

当l_onoff值设置为0时,closesocket会立即返回,并关闭用户socket句柄。如果此时缓冲区中有未发送数据,则系统会在后台将这些数据发送完毕后关闭TCP连接,是一个优雅关闭过程,但是这里有一个副作用就是socket的底层资源会被保留直到TCP连接关闭,这个时间用户应用程序是无法控制的。

当l_onoff值设置为非0值,而l_linger也设置为0,那么closesocket也会立即返回并关闭用户socket句柄,但是如果此时缓冲区中有未发送数据,TCP会发送RST包重置连接,所有未发数据都将丢失,这是一个强制关闭过程。

当l_onoff值设置为非0值,而l_linger也设置为非0值时,同时如果socket是阻塞式的,此时如果缓冲区中有未发送数据,如果TCP在l_linger表明的时间内将所有数据发出,则发完后关闭TCP连接,这时是优雅关闭过程;如果如果TCP在l_linger表明的时间内没有将所有数据发出,则会丢弃所有未发数据然后TCP发送RST包重置连接,此时就是一个强制关闭过程了。

另外还有一个socket选项SO_DONTLINGER,它的参数值是一个bool类型的,如果设置为true,则等价于SO_LINGER中将l_onoff设置为0。

注意SO_LINGER和SO_DONTLINGER选项只影响closesocket的行为,而与shutdown函数无关,shutdown总是会立即返回的。

参见

参考文献

  1. ^ . [2016-09-09]. (原始内容存档于2021-08-08). 

外部链接

  • MSDN - Winsock2 Reference (页面存档备份,存于互联网档案馆
  • MSDN - Winsock2 Home (页面存档备份,存于互联网档案馆
  • Sockets FAQ (页面存档备份,存于互联网档案馆) - Windows Sockets FAQ
  • - Winsock C++ Programming
  • Porting Berkley Socket programs to Winsock (页面存档备份,存于互联网档案馆
  • Windows Network Development blog (页面存档备份,存于互联网档案馆) — Microsoft developer blog covering Winsock, WSK, WinINet, Http.sys, WinHttp, QoS and System.Net, with a focus on features being introduced in Windows Vista
  • Brief History of Microsoft on the Web (页面存档备份,存于互联网档案馆
  • with descriptions
  • WinSock Development Information (页面存档备份,存于互联网档案馆
  • Winsock Programmer's FAQ (页面存档备份,存于互联网档案馆

winsock, 此條目需要补充更多来源, 2016年9月9日, 请协助補充多方面可靠来源以改善这篇条目, 无法查证的内容可能會因為异议提出而移除, 致使用者, 请搜索一下条目的标题, 来源搜索, 网页, 新闻, 书籍, 学术, 图像, 以检查网络上是否存在该主题的更多可靠来源, 判定指引, windows, sockets, 简短记为, 是windows的tcp, ip网络编程接口, 其函数名称兼容于berkeley套接字api, 实际上, 的实现库, winsock, 使用的是长名字, 開發者微软首次发布199. 此條目需要补充更多来源 2016年9月9日 请协助補充多方面可靠来源以改善这篇条目 无法查证的内容可能會因為异议提出而移除 致使用者 请搜索一下条目的标题 来源搜索 Winsock 网页 新闻 书籍 学术 图像 以检查网络上是否存在该主题的更多可靠来源 判定指引 Windows Sockets API WSA 简短记为Winsock 是Windows的TCP IP网络编程接口 API 其函数名称兼容于Berkeley套接字API 实际上 Winsock的实现库 winsock dll 使用的是长名字 Winsock開發者微软首次发布1992年6月 30年前 1992 06 操作系统Microsoft Windows类型APIWinsock是一种能使Windows程序通过任意网络传输协议发送数据的API Winsock中有几个只支持TCP IP协议的函数 例如gethostbyaddr 但是在Winsock 2中新增了所有这些函数的通用版本 以允许开发者使用其它的传输协议 目录 1 历史 1 1 规范版本 2 技术 2 1 回复Ack的延时 2 2 发送小尺寸数据时默认采用Nagle算法拼接为大数据包 2 3 Winsock的内核发送缓冲区与send函数的实现 3 实现 3 1 Microsoft实现 3 2 其他实现 4 例子 4 1 列出所有服务提供者 4 2 阻塞式程序 4 3 优雅关闭 5 参见 6 参考文献 7 外部链接历史 编辑MS DOS与早期版本的Microsoft Windows使用的网络协议是NetBIOS 因此 各方提供了各自的MS DOS上的TCP IP实现 由于各种解决方案的API函数名并不统一 使得软件开发者难以下决心转到TCP IP协议上 1991年10月 以Martin Hall Mark Towfiq Geoff Arnold Henry Sanders为首在CompuServe BBS上讨论形成了Windows Sockets API规范 specification 并且版权属于这五人 规范版本 编辑 Version 1 0 1992年6月 虽然不限于TCP IP 但TCP与UDP是仅有被明确提到的网络协议 Version 1 1 1993年1月 很多小的修正与澄清 Winsock以Windows消息的形式发送通知 使程序无需担心并发性 Winsock 2 向后兼容Winsock 1 1 还增加了协议独立的名字解析 异步操作 基于事件通知与完成过程 completion routine 多播 QoS 明文支持了多协议 包括IPX SPX与DECnet 英语 DECnet 增加了服务提供者接口 英语 Service Provider Interface SPI 与分层协议实现 Versions 2 0 x 1994年5月 草案没有正式公开发布 Version 2 1 0 1996年1月 是Winsock 2首次公开的规范 定义了两种接口 一是应用程序接口 API 这种接口将开发者和底层隔离开 二是服务提供接口 SPI 这种接口允许对Winsock协议栈的扩展 对多种传输协议的官方支持 Winsock 2的规范中写入了对OSI Novell IPX SPX和Digital DECNet的官方支持 也添加了对一些诸如服务质量 QoS 广播等技术创新的支持 重叠I O机制下工作 Version 2 2 0 1996年5月 包含很多小的矫正与澄清 并且不再支持16位Windows应用程序 Version 2 2 1 1997年5月 与Version 2 2 2 1997年8月 增加小的新功能 Windows 95 OSR2以后版本的Windows操作系统均支持Windows Sockets version 2 2 此外 Windows 95 with the Windows Socket 2 Update也支持WinSock 2 2 Windows 95 Windows NT 3 51及更早版本的Windows操作系统 最高支持Windows Sockets version 1 1 技术 编辑WinSock编程时 可选择下述编程模型之一 同步阻塞模型 直接使用recv 与send 收发数据 是最直观与最基本的IO模型 select模型 可以阻塞 非阻塞 超时返回等方式查询socket的状态 WSAAsyncSelect函数把socket绑定到一个窗口句柄 socket的若干事件绑定到一个窗口消息 如WM SOCKET NOTIFY 来异步响应的模型 WSAEventSelect函数把socket与其若干事件绑定到一个操作系统的同步event上 来异步响应的模型 配套的API函数有 WSACreateEvent WSAResetEvent WSAWaitForMultiEvents WSAEnumNewWorkEvents Overlapped I O事件通知模型 使用WSA FLAG OVERLAPPED标志通过WSASocket创建一个套接字 使用WSASend WSASendTo WSARecv WSARecvFrom WSAIoctl AcceptEx TransmitFile等函数带一个WSAOVERLAPPED结构参数来发起重叠IO操作 WSACreateEvent函数创建所需要的事件对象 WSAWaitForMultipleEvents函数来等待该事件对象 再调用WSAGetOverlappedResult函数判断重叠IO操作是否成功完成 Overlapped I O完成例程模型 使用完成例程来通知socket事件 使用ReadFileEx和WriteFileEx 或WSARecv WSASend 指定完成例程 完成端口模型 首先使用CreateCompletionPort创建一个完成端口对象 然后把任意数量的socket或IO句柄绑定到该完成端口对象 通过CreateCompletionPort函数 然后创建一批工作者线程 每个线程通过GetQueuedCompletionStatus就把自己绑定到完成端口对象 IO完成包在完成端口对象先进先出 挂在完成端口对象上的工作者线程按照后进先出调度执行以减少线程切换的代价 Windows Sockets API规范包含两种接口 API 用于应用程序开发者 服务提供者接口 英语 Service Provider Interface SPI 用于增加新的协议模块到操作系统 实际上 现在Windows上实现TCP IP以外的网络协议并没有什么用途 Windows Sockets的代码与设计是基于Berkeley sockets 此外还提供了额外的功能使得API遵从Windows编程模式 如重叠I O API函数名字都以前缀WSA开始 例如WSASend 为了便于从Unix向Windows移植网络程序的源代码 Winsock提供了很多便利 例如 Unix应用程序能使用errno码 英语 errno h 记录网络错误与C运行时错误 Windows Sockets引入了专门的函数WSAGetLastError 以获取错误信息 但很多TCP IP应用程序使用了Unix的特性 如伪终端 英语 pseudo terminal 与fork系统调用 英语 Fork operating system 这使得源代码移植非常困难 回复Ack的延时 编辑 当Microsoft TCP栈接收到一个数据包时 会启动一个200毫秒的计时器 当ACK确认数据包发出之后 计时器会复位 接收到下一个数据包时 会再次启动200毫秒的计时器 这称为TCP确认延迟机制 TCP Delayed acknowledge 作用是接收到数据后延迟ACK的发送 使得TCP协议栈有机会合并多个ACK以提高性能 Microsoft TCP栈使用了下面的策略来决定在接收到数据包后什么时候发送ACK确认数据包 如果在200毫秒的计时器超时之前 接收到下一个数据包 则立即发送ACK确认数据包 如果当前恰好有数据包需要发给ACK确认信息的接收端 则把ACK确认信息附带在数据包上立即发送 当计时器超时 ACK确认信息立即发送 发送小尺寸数据时默认采用Nagle算法拼接为大数据包 编辑 为了避免小数据包拥塞网络 Microsoft TCP栈默认启用了納格算法 这个算法能够将应用程序多次调用Send发送的小尺寸的数据拼接起来 一块封包发送 Nagle算法规定的特殊情况 如果Microsoft TCP栈拼接起来的数据超过了MSS值 这个数据会立即发送 而不等待前一个数据包的ACK确认信息 以太网MTU Maximum Transmission Unit 是1460字节 如果以前发送的数据都收到了回复的Ack 那么当前的数据会立即发送 而没有延迟 如果等待超时 例如200ms 仍然没有拼成一个MSS的数据包 当前的数据会被发送 如果设置了TCP NODELAY选项 就会禁用Nagle算法 应用程序调用Send发送的数据包会立即被投递到网络 而没有延迟 Winsock的内核发送缓冲区与send函数的实现 编辑 为了在应用层优化性能 Winsock把应用程序调用Send发送的数据从应用程序的缓冲区复制到Winsock内核缓冲区 Microsoft TCP栈利用类似Nagle算法的方法 决定什么时候才实际地把数据投递到网络 内核缓冲区的默认大小是8K 使用SO SNDBUF选项 可以改变Winsock内核缓冲区的大小 如果有必要的话 Winsock能缓冲大于SO SNDBUF缓冲区大小的数据 在绝大多数情况下 应用程序完成Send调用仅仅表明数据 被复制到了Winsock内核缓冲区 并不能说明数据就实际地被投递到了网络上 例外情况 通过设置SO SNDBUT为0禁用了Winsock内核缓冲区 Winsock使用下面的规则来向应用程序表明一个Send调用的完成 如果socket仍然在SO SNDBUF限额内 Winsock复制应用程序要发送的数据到内核缓冲区 完成Send调用 如果Socket超过了SO SNDBUF限额并且先前只有一个被缓冲的发送数据在内核缓冲区 Winsock复制要发送的数据到内核缓冲区 完成Send调用 如果Socket超过了SO SNDBUF限额并且内核缓冲区有不只一个被缓冲的发送数据 Winsock复制要发送的数据到内核缓冲区 然后投递数据到网络 直到Socket降到SO SNDBUF限额内或者只剩余一个要发送的数据 才完成Send调用 实现 编辑Microsoft实现 编辑 Microsoft未提供Winsock 1 0实现 Version 1 1作为插件包 称为Wolverine 用于Windows for Workgroups 代号Snowball 它是Windows 95与Windows NT 3 5版以后的组成部分 Version 2 1是Windows 95的插件包 它是Windows 98 Windows NT 4 0及其以后版本的组成部分 Microsoft不提供Windows 3 x或Windows NT 3 x的Winsock 2实现 Winsock1 1 API 需要声明 include lt winsock h gt 并链接wsock32 lib 使用wsock32 dll Winsock2 API 需要声明 include lt winsock2 h gt 链接ws2 32 lib 在Winsock2 32 dll中实现 其下是两种服务提供者接口 Service Provider Interface Transport SPI 提供建立连接 数据传输 QoS 错误处理等功能 rsvpsp dll实现了RSVP QoS mswsock dll实现了Winsock core NameSpace SPI 名字解析 如getXXXbyYYY等函数 支持TCP IP NT DS NLA等命名空间 可通过ws2spi h中的两对API函数来安装 卸载服务提供者接口 WSCInstallProvider WSCDeinstallProvider WSCInstallNameSpace WSCUnInstallNameSpace SPOrder dll中提供了重排序服务提供者 WSCWriteNameSpaceOrder WSCWriteProviderOrder 大部分Winsock2 API函数被映射到SPI函数 由当前安装的服务提供者实现其功能 一条简单规则是根据提供者链顺序从WSA 函数名映射为WSP 函数名 Winsock Service Provider 用于传输服务提供者函数 下述函数不在SPI中实现 事件处理函数与等待函数 直接映射到Windows API WSACreateEvent WSACloseEvent WSASetEvent WSAResetEvent WSAWaitForMultipleEvents 名字服务函数 特定IP名字转换函数与名字解析函数 Winsock 1 1的GetXXXByYYY以及WSAAsyncGetXXXbyYYY WSACancelAsyncRequest gethostname等 支持 support 函数 ntohs ntohl htonl htons IP转换函数 inet XtoY inet addr 错误处理函数 WSAGetLastError WSASetLastError 其他函数 WSAEnumProtocols WSAIsBlocking WSASetBlockingHook WSAUnhookBlockingHookWSP前缀 Winsock Service Provider 的函数是传输服务提供者函数 例如WSPStartup函数用于初始化分层服务提供者 WPU前缀 Winsock Provider Upcall 的函数是被服务提供者调用的ws2 32 dll中的函数 WSC前缀 WinSock Configuration 的函数名是LSP安装程序调用的ws2 32 dll中的函数 如 WSCInstallProvider WSCWriteProviderOrder NSP前缀 NameSpace Provider 的函数名用于命名空间提供者 service provider interface SPI 是各种功能的具体实施者 允许插入第三方厂商写的 service providers 而无需改变Winsock 2 的API与DLL ws2 32 dll 从而应用程序开发人员写的基于Winsock的代码也无需改变 Winsock 2 SPI 有两种类型的 service providers transport 和 namespace Transport providers 通常称之为协议栈 提供建立连接 传输数据 进行流控制和差错控制等功能 Namespace providers 提供网络协议的寻址属性 协议无关的名字解析 SPI transport service providers细分为两类 base service providers 和 layered service providers Base service providers 实现了传输协议的实际细节 建立连接 传送数据 流控制和差错控制 Layered service providers 仅实现了高层的自定义的通讯功能 而且依赖于已有的下层的 base provider 来与远程端进行实际的数据交换 也就是说 LSP是做什么的 就是做一些附件的高端的可选的功能 如在 base TCP IP 栈的顶端实现一个带宽管理器 而base service providers提供必需的基础的功能 Winsock 2不允许 namespace providers 的LSP 虽然可以使用 Winsock 2 SPI 来实现一个新的 namespace provider 但是不能改变或扩展已有 namespace provider 的命名 注册和查询行为 layered transport service provider一般由高层应用开发 而 base transport providers 和 namespace providers 一般由操作系统厂商和协议栈厂商开发 编写 service provider的就是标准的DLL 其导出函数只有运输服务的WSPStartup 或名字服务的 NSPStartup WSPStartup 和 NSPStartup通过输出参数lpProcTable作为LSP的dispatch table 提供了约30个可用的函数的地址 这些函数原型在WS2spi h中声明 WSPStartup的另外一个参数UpcallTable为LSP提供了ws2 32 dll中的15个函数的地址表 这些函数原型也在WS2spi h中声明 如果ws2 32 dll提供了额外的函数 就需要在WSPStartup通过GetProcAddress获取函数地址 目前仅有一个例子WPUCompleteOverlappedResult LSP可以形成一个链 通过调用下层LSP的WSPStartup函数 下层LSP由上层LSP装入 最上层的LSP被ws2 32 dll装入 WSPStartup函数参数lpProtocolInfo指向一个WSAPROTOCOL INFOW结构组成的链表 链表最底层是base provider WSPStartup与WSPCleanup使用引用计数来加载 清除 其他实现 编辑 遵从Winsock规范的TCP IP与UDP IP协议栈有 3Com Beame amp Whiteside DEC Distinct FTP Software Frontier IBM Microdyne NetManage 英语 NetManage Novell Sun Microsystems Trumpet Software International 1 例子 编辑列出所有服务提供者 编辑 Winsock 2 API Protocol Enumerator ifndef WIN32 LEAN AND MEAN define WIN32 LEAN AND MEAN endif define WINSOCK API LINKAGE include lt winsock2 h gt include lt ws2spi h gt include lt wtypes h gt include lt assert h gt include lt winnt h gt include lt stdlib h gt include lt stdio h gt pragma comment lib Ws2 32 lib char ExpandServiceFlags DWORD serviceFlags A little utility function to make sense of all those bit flags The following code leaks Yeah I know Go find Buffer 0v3rfl0w char serviceFlagsText char malloc 2048 memset serviceFlagsText 0 2048 char strip comma Hey it s only for printing and demo purposes if serviceFlags amp XP1 CONNECTIONLESS strcat serviceFlagsText Connectionless if serviceFlags amp XP1 GUARANTEED ORDER strcat serviceFlagsText Guaranteed Order if serviceFlags amp XP1 GUARANTEED DELIVERY strcat serviceFlagsText Message Oriented if serviceFlags amp XP1 CONNECT DATA strcat serviceFlagsText Connect Data if serviceFlags amp XP1 DISCONNECT DATA strcat serviceFlagsText Disconnect Data if serviceFlags amp XP1 SUPPORT BROADCAST strcat serviceFlagsText Broadcast Supported if serviceFlags amp XP1 EXPEDITED DATA strcat serviceFlagsText Urgent Data if serviceFlags amp XP1 QOS SUPPORTED strcat serviceFlagsText QoS supported While we re quick and dirty let s get as dirty as possible strip comma strrchr serviceFlagsText if strip comma strip comma 0 return serviceFlagsText void PrintProtocolInfo LPWSAPROTOCOL INFOW prot wprintf L Protocol Name s n prot gt szProtocol UNICODE printf t ServiceFlags1 d s n prot gt dwServiceFlags1 ExpandServiceFlags prot gt dwServiceFlags1 printf t Provider Flags d n prot gt dwProviderFlags printf t Network Byte Order s n prot gt iNetworkByteOrder BIGENDIAN Big Endian Little Endian printf t Version d n prot gt iVersion printf t Address Family d n prot gt iAddressFamily printf t Socket Type switch prot gt iSocketType case SOCK STREAM printf STREAM n break case SOCK DGRAM printf DGRAM n break case SOCK RAW printf RAW n break default printf Some other type n printf t Protocol switch prot gt iProtocol case IPPROTO TCP printf TCP IP n break case IPPROTO UDP printf UDP IP n break default printf some other protocol n int cdecl main int argc char argv LPWSAPROTOCOL INFOW bufProtocolInfo NULL DWORD dwSize 0 INT dwError INT iNumProt Enum Protocols First obtain size required printf Sample program to enumerate Protocols n WSCEnumProtocols NULL lpiProtocols bufProtocolInfo lpProtocolBuffer amp dwSize lpdwBufferLength amp dwError lpErrno bufProtocolInfo LPWSAPROTOCOL INFOW malloc dwSize if bufProtocolInfo fprintf stderr SHOOT Can t MALLOC n exit 1 Now Enum iNumProt WSCEnumProtocols NULL lpiProtocols bufProtocolInfo lpProtocolBuffer amp dwSize lpdwBufferLength amp dwError if SOCKET ERROR iNumProt fprintf stderr Darn Can t Enum n exit 1 printf d Protocols detected n iNumProt for int i 0 i lt iNumProt i PrintProtocolInfo amp bufProtocolInfo i printf n printf Done return 0 阻塞式程序 编辑 int nPort 65000 指定通信端口 WSADATA wsaData WSAStartup MAKEWORD 2 2 amp wsaData 创建监听套接字 绑定本地端口 开始监听 SOCKET sListen socket AF INET SOCK STREAM 0 SOCKADDR IN addr addr sin family AF INET addr sin port htons nPort addr sin addr S un S addr INADDR ANY bind sListen sockaddr amp addr sizeof addr listen sListen 5 SOCKADDR IN saRemote int nRemoteLen sizeof saRemote SOCKET sRemote accept sListen sockaddr amp saRemote amp nRemoteLen 优雅关闭 编辑 TCP连接的关闭过程有两种 优雅关闭 graceful close 如果发送缓存中还有数据未发出则其发出去 并且收到所有数据的ACK之后 发送FIN包 开始关闭过程 强制关闭 hard close或abortive close 如果缓存中还有数据 则这些数据都将被丢弃 然后发送RST包 直接重置TCP连接 shutdown clntSock SD SEND 数据发送完毕 断开输出流 向客户端发送FIN包 recv clntSock buffer BUF SIZE 0 阻塞 等待客户端接收完毕后closesocket 这将导致本方的recv从阻塞状态返回 fclose fp 释放资源 closesocket clntSock closesocket servSock 也要关闭用于listen accept的socket WSACleanup 调用 close closesocket 函数意味着完全断开连接 即不能发送数据也不能接收数据 这种 生硬 的方式有时候会显得不太 优雅 使用 shutdown 函数和WSASendDisconnect函数可以优雅关闭连接 其函数原型为 int shutdown SOCKET s int howto Windows howto 在 Windows 下有以下取值 SD RECEIVE 关闭接收操作 也就是断开输入流 SD SEND 关闭发送操作 也就是断开输出流 SD BOTH 同时关闭接收和发送操作 确切地说 close closesocket 用来关闭套接字 将套接字描述符 或句柄 从内存清除 之后再也不能使用该套接字 应用程序关闭套接字后 与该套接字相关的连接和缓存也失去了意义 TCP协议会自动触发关闭连接的操作 shutdown 用来关闭连接 而不是套接字 套接字依然存在 直到调用 close closesocket 将套接字从内存清除 调用 close closesocket 关闭套接字时 或调用 shutdown 关闭输出流时 都会向对方发送 FIN 包 FIN 包表示数据传输完毕 对端收到 FIN 包就知道不会再有数据传送过来了 默认情况下 close closesocket 会立即向网络中发送FIN包 不管输出缓冲区中是否还有数据 而shutdown 会等输出缓冲区中的数据传输完毕再发送FIN包 也就意味着 调用 close closesocket 将丢失输出缓冲区中的数据 而调用 shutdown 不会 shutdown 并不实际关闭socket 而是仅仅改变其可用性 shutdown是一种优雅地单方向或者双方向关闭socket的方法 如果有多个进程共享一个socket shutdown影响所有进程 而close只影响本进程 shutdown本身并不影响底层 也即此前发出的异步send recv不会返回 在所有已发送的包被对端确认后 本方会发送FIN包给client 开始TCP四次挥手过程 对端收到FIN报文后 并不知道server端以何种方式shutdown 甚至不知道server端是shutdown还是close 若本方发送FIN报文后没有收到对端的FIN ACK 会两次重传FIN报文 若一直收不到对端的FIN ACK 则会给对端发送RST信号 关闭socket并释放资源 对端收到FIN信号后 再调用read函数会返回0 因为接收了FIN 表明以后再无数据可以接收 对端收到RST报文后 行为如下 阻塞模型下 内核无法主动通知应用层出错 只有应用层主动调用read 或者write 这样的IO系统调用时 内核才会利用出错来通知应用层已经收到RST报文 非阻塞模型下 select或者epoll会返回sockfd可读 应用层对其进行读取时 read 会报RST错误 收到RST报文的情况下 再做任何read write都是毫无意义 调用调用close 根据参数设置不同 会出现如下两种情况 l onoff为非0 l linger为0 向对端发送一个RST报文 丢弃本地缓冲区的未读数据 关闭socket并释放相关资源 此种方式为强制关闭 l onoff 为非0 l linger为非0 向对端发送一个FIN报文 收到对端FIN ACK后 进入了FIN WAIT 2阶段 参考TCP四次挥手过程 此种方式为优雅关闭 如果在l linger的时间内仍未完成四次挥手 则强制关闭 当l onoff值设置为0时 closesocket会立即返回 并关闭用户socket句柄 如果此时缓冲区中有未发送数据 则系统会在后台将这些数据发送完毕后关闭TCP连接 是一个优雅关闭过程 但是这里有一个副作用就是socket的底层资源会被保留直到TCP连接关闭 这个时间用户应用程序是无法控制的 当l onoff值设置为非0值 而l linger也设置为0 那么closesocket也会立即返回并关闭用户socket句柄 但是如果此时缓冲区中有未发送数据 TCP会发送RST包重置连接 所有未发数据都将丢失 这是一个强制关闭过程 当l onoff值设置为非0值 而l linger也设置为非0值时 同时如果socket是阻塞式的 此时如果缓冲区中有未发送数据 如果TCP在l linger表明的时间内将所有数据发出 则发完后关闭TCP连接 这时是优雅关闭过程 如果如果TCP在l linger表明的时间内没有将所有数据发出 则会丢弃所有未发数据然后TCP发送RST包重置连接 此时就是一个强制关闭过程了 另外还有一个socket选项SO DONTLINGER 它的参数值是一个bool类型的 如果设置为true 则等价于SO LINGER中将l onoff设置为0 注意SO LINGER和SO DONTLINGER选项只影响closesocket的行为 而与shutdown函数无关 shutdown总是会立即返回的 参见 编辑Berkeley sockets 分层服务提供者 Winsock LSP 参考文献 编辑 Trumpet Winsock v5 0 2016 09 09 原始内容存档于2021 08 08 外部链接 编辑MSDN Winsock2 Reference 页面存档备份 存于互联网档案馆 MSDN Winsock2 Home 页面存档备份 存于互联网档案馆 Sockets FAQ 页面存档备份 存于互联网档案馆 Windows Sockets FAQ Client Server Programming with TCP IP Sockets Winsock C Programming Porting Berkley Socket programs to Winsock 页面存档备份 存于互联网档案馆 Windows Network Development blog 页面存档备份 存于互联网档案馆 Microsoft developer blog covering Winsock WSK WinINet Http sys WinHttp QoS and System Net with a focus on features being introduced in Windows Vista Brief History of Microsoft on the Web 页面存档备份 存于互联网档案馆 Winsock error codes list with descriptions WinSock Development Information 页面存档备份 存于互联网档案馆 Winsock Programmer s FAQ 页面存档备份 存于互联网档案馆 取自 https zh wikipedia org w index php title Winsock amp oldid 71703067, 维基百科,wiki,书籍,书籍,图书馆,

文章

,阅读,下载,免费,免费下载,mp3,视频,mp4,3gp, jpg,jpeg,gif,png,图片,音乐,歌曲,电影,书籍,游戏,游戏。