Loading AI tools
来自维基百科,自由的百科全书
Windows Sockets API (WSA), 简短记为Winsock, 是Windows的TCP/IP网络编程接口(API)。其函数名称兼容于Berkeley套接字API。实际上,Winsock的实现库(winsock.dll)使用的是长名字。
此條目需要补充更多来源。 (2016年9月9日) |
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)并且版权属于这五人。
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编程时,可选择下述编程模型之一:
Windows Sockets API规范包含两种接口:
Windows Sockets的代码与设计是基于Berkeley sockets,此外还提供了额外的功能使得API遵从Windows编程模式(如重叠I/O). API函数名字都以前缀WSA开始, 例如WSASend().
为了便于从Unix向Windows移植网络程序的源代码,Winsock提供了很多便利. 例如,Unix应用程序能使用errno码记录网络错误与C运行时错误。Windows Sockets引入了专门的函数WSAGetLastError()以获取错误信息. 但很多TCP/IP应用程序使用了Unix的特性, 如伪终端与fork系统调用。这使得源代码移植非常困难。
当Microsoft TCP栈接收到一个数据包时,会启动一个200毫秒的计时器。当ACK确认数据包发出之后,计时器会复位。接收到下一个数据包时,会再次启动200毫秒的计时器。这称为TCP确认延迟机制(TCP Delayed acknowledge),作用是接收到数据后延迟ACK的发送,使得TCP协议栈有机会合并多个ACK以提高性能。Microsoft TCP栈使用了下面的策略来决定在接收到数据包后什么时候发送ACK确认数据包:
为了避免小数据包拥塞网络,Microsoft TCP栈默认启用了納格算法,这个算法能够将应用程序多次调用Send发送的小尺寸的数据拼接起来,一块封包发送。Nagle算法规定的特殊情况:
为了在应用层优化性能,Winsock把应用程序调用Send发送的数据从应用程序的缓冲区复制到Winsock内核缓冲区。Microsoft TCP栈利用类似Nagle算法的方法,决定什么时候才实际地把数据投递到网络。内核缓冲区的默认大小是8K,使用SO_SNDBUF选项,可以改变Winsock内核缓冲区的大小。如果有必要的话,Winsock能缓冲大于SO_SNDBUF缓冲区大小的数据。在绝大多数情况下,应用程序完成Send调用仅仅表明数据 被复制到了Winsock内核缓冲区,并不能说明数据就实际地被投递到了网络上。例外情况:通过设置SO_SNDBUT为0禁用了Winsock内核缓冲区。
Winsock使用下面的规则来向应用程序表明一个Send调用的完成:
Winsock1.1 API,需要声明#include <winsock.h>并链接wsock32.lib,使用wsock32.dll。
Winsock2 API,需要声明#include <winsock2.h>,链接ws2_32.lib,在Winsock2_32.dll中实现。 其下是两种服务提供者接口(Service Provider Interface):
可通过ws2spi.h中的两对API函数来安装/卸载服务提供者接口:WSCInstallProvider、WSCDeinstallProvider、WSCInstallNameSpace、WSCUnInstallNameSpace。
SPOrder.dll中提供了重排序服务提供者:WSCWriteNameSpaceOrder、WSCWriteProviderOrder。
大部分Winsock2 API函数被映射到SPI函数,由当前安装的服务提供者实现其功能。一条简单规则是根据提供者链顺序从WSA*函数名映射为WSP*函数名(Winsock Service Provider, 用于传输服务提供者函数)。下述函数不在SPI中实现:
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, 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连接的关闭过程有两种:
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 下有以下取值:
确切地说,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报文后,行为如下:
收到RST报文的情况下,再做任何read write都是毫无意义。
调用调用close(),根据参数设置不同,会出现如下两种情况:
当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总是会立即返回的。
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.