Windows Sockets API (WSA), 簡短記為Winsock, 是WindowsTCP/IP網絡編程接口(API)。其函數名稱兼容於Berkeley套接字API。實際上,Winsock的實現庫(winsock.dll)使用的是長名字。

Winsock
開發者微軟
首次發佈1992年6月,​32年前​(1992-06
當前版本2.2.2(1997年8月,​27年前​(1997-08
作業系統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. ^ Trumpet Winsock v5.0. [2016-09-09]. (原始內容存檔於2021-08-08). 

外部連結

編輯