WSAAccept 函数 (winsock2.h)

WSAAccept 函数根据条件函数的返回值有条件地接受连接,提供服务质量流规范,并允许传输连接数据。

语法

SOCKET WSAAPI WSAAccept(
  [in]      SOCKET          s,
  [out]     sockaddr        *addr,
  [in, out] LPINT           addrlen,
  [in]      LPCONDITIONPROC lpfnCondition,
  [in]      DWORD_PTR       dwCallbackData
);

参数

[in] s

一个描述符,用于标识在调用 侦听 函数后侦听连接的套接字。

[out] addr

指向 sockaddr 结构的可选指针,该结构接收连接实体的地址,称为通信层。 addr 参数的确切格式由创建套接字时建立的地址系列确定。

[in, out] addrlen

指向一个整数的可选指针,该整数包含 addr 参数所指向的 sockaddr 结构的长度(以字节为单位)。

[in] lpfnCondition

可选、应用程序指定的条件函数的地址,该函数将根据作为参数传递的调用方信息做出接受/拒绝决策,并选择性地通过将适当的值分配给此函数的结果参数 g 来创建或联接套接字组。 如果此参数 NULL,则不调用条件函数。

[in] dwCallbackData

作为传递给条件函数的 dwCallbackData 参数的值传递回应用程序指定的条件函数的回调数据。 仅当 lpfnCondition 参数不 NULL时,此参数才适用。 Windows 套接字不解释此参数。

返回值

如果未发生错误,WSAAccept 将返回一个类型为 SOCKET 的值,该值是接受套接字的描述符。 否则,将返回INVALID_SOCKET值,并通过调用 WSAGetLastError来检索特定的错误代码。

addrlen 引用的整数最初包含由 加法器指向的空间量。返回时,它将包含返回的地址的实际长度(以字节为单位)。

错误代码 意义
WSAEACCES
尝试以禁止其访问权限的方式访问套接字。 如果提供的连接请求已超时或已撤回,则返回此错误。
WSAECONNREFUSED
无法建立连接,因为目标计算机主动拒绝连接。 如果在条件函数的返回值(CF_REJECT)中指示强制拒绝了连接请求,则返回此错误。
WSAECONNRESET
远程主机强行关闭了现有连接。 指示传入连接返回此错误,但在接受呼叫之前,远程对等方随后终止。
WSAEFAULT
系统在尝试在调用中使用指针参数时检测到无效的指针地址。 addrlen 参数太小或 加载项lpfnCondition 不属于用户地址空间,则返回此错误。
WSAEINTR
阻止操作被调用 WSACancelBlockingCall中断。 如果通过 WSACancelBlockingCall取消阻止 Windows 套接字 1.1 调用,则返回此错误。
WSAEINPROGRESS
阻止操作当前正在执行。 如果正在进行阻止的 Windows 套接字 1.1 调用,则返回此错误。
WSAEINVAL
提供了无效的参数。 如果在 WSAAccept之前未调用 侦听,则返回此错误,条件函数的返回值无效,或者指定套接字处于无效状态的任何情况。
WSAEMFILE
打开的套接字太多。 如果在 WSAAccept 条目时队列为 nonempty,并且没有可用的套接字描述符,则返回此错误。
WSAENETDOWN
套接字操作遇到死网。 如果网络子系统失败,则返回此错误。
WSAENOBUFS
无法对套接字执行操作,因为系统缺少足够的缓冲区空间或队列已满。 如果没有可用的缓冲区空间,则返回此错误。
WSAENOTSOCK
尝试对不是套接字的内容执行操作。 如果传入 s 参数的套接字描述符不是套接字,则返回此错误。
WSAEOPNOTSUPP
协议系列尚未配置到系统中,或者不存在任何实现。 如果引用的套接字不是支持面向连接的服务的类型,则返回此错误。
WSAEWOULDBLOCK
无法立即完成非阻塞套接字操作。 如果套接字被标记为非阻止且未接受任何连接,则返回此错误。
WSANOTINITIALIZED
应用程序未调用 WSAStartup,或 WSAStartup 失败。 此错误返回对 WSAStartup 函数 dit 的成功调用,然后再使用此函数。
WSATRY_AGAIN
这通常是主机名解析期间的临时错误,这意味着本地服务器未收到来自权威服务器的响应。 如果接受连接请求被延迟,则返回此错误,如条件函数的返回值(CF_DEFER)。

言论

WSAAccept 函数提取套接字上挂起连接队列中的第一个连接,s,并检查它是否与条件函数,前提是指定了条件函数(即,而不是 NULL)。 如果条件函数返回CF_ACCEPT,WSAAccept 创建新的套接字。 新创建的套接字的属性与套接字 相同,包括 WSAAsyncSelect 注册的异步事件,或 WSAEventSelect。 如果条件函数返回CF_REJECT,WSAAccept 拒绝连接请求。 条件函数与此函数运行在同一线程中,应尽快返回。 如果无法立即做出决策,条件函数应返回CF_DEFER以指示尚未做出任何决定,并且服务提供商不应对此连接请求采取任何操作。 当应用程序准备好对连接请求执行操作时,它将再次调用 WSAAccept,并将CF_ACCEPT或CF_REJECT作为条件函数的返回值返回。

当应用程序调用 WSAAccept 且队列上没有挂起的连接时,默认模式下的套接字(阻止)将阻止连接。

当应用程序调用 WSAAccept 且队列上没有挂起的连接时,非阻止模式(阻止)中的套接字失败,WSAEWOULDBLOCK 错误。 WSAAccept 成功并返回新的套接字句柄后,接受的套接字不能用于接受任何其他连接。 原始套接字保持打开状态,并侦听新的连接请求。

addr 参数是用连接实体的地址填充的结果参数,称为通信层。 addr 参数的确切格式由通信所在的地址系列决定。 addrlen 是值结果参数;它最初应包含 addr指向的空间量。返回时,它将包含返回的地址的实际长度(以字节为单位)。 此调用用于面向连接的套接字类型,例如SOCK_STREAM。 如果 addr 和/或 addrlen 等于 NULL,则不会返回有关接受套接字的远程地址的信息。 否则,如果成功接受连接,将填充这两个参数。

条件函数的原型在 Winsock2.h 头文件中定义为 LPCONDITIONPROC,如下所示。

int CALLBACK 
ConditionFunc( 
  IN     LPWSABUF    lpCallerId, 
  IN     LPWSABUF    lpCallerData, 
  IN OUT LPQOS       lpSQOS, 
  IN OUT LPQOS       lpGQOS,
  IN     LPWSABUF    lpCalleeId, 
  IN     LPWSABUF    lpCalleeData, 
  OUT    GROUP FAR * g, 	
  IN     DWORD_PTR   dwCallbackData
);

ConditionFunc 是应用程序指定的回调函数的占位符。 实际条件函数必须驻留在 DLL 或应用程序模块中。 它在模块定义文件中导出。

lpCallerId 参数指向包含连接实体地址的 WSABUF 结构,其中其 len 参数是缓冲区的长度(以字节为单位),其 buf 参数是指向缓冲区的指针。 lpCallerData 是包含任何用户数据的值参数。 这些参数中的信息随连接请求一起发送。 如果没有调用方标识或调用方数据可用,则相应的参数将 NULL。 许多网络协议不支持连接时调用方数据。 大多数传统的网络协议可以在连接请求时支持调用方标识符信息。 lpCaller Id 指向的 WSABUF 的 buf 部分指向 sockaddrsockaddr 结构根据其地址系列进行解释(通常通过将 sockaddr 转换为特定于地址系列的某种类型)。

lpSQOS 参数引用调用方指定的套接字 FLOWSPEC 结构,每个方向对应一个,后跟任何其他提供程序特定的参数。 对于任何单向套接字,将忽略发送或接收流规范值。 NULL 值表示没有调用方提供的服务质量,并且无法进行协商。 非NULLlpSQOS 指针表示要进行服务质量协商,或者提供商准备接受服务质量请求而不进行协商。

lpGQOS 参数保留,应 NULL。 (保留供将来与套接字组一起使用)引用调用方要创建的套接字组 FLOWSPEC 结构,每个方向各有一个,后跟任何其他提供程序特定的参数。 lpGQOSNULL 值表示没有调用方指定的组服务质量。 如果协商发生,则可以返回服务质量信息。

lpCalleeId 是包含已连接实体的本地地址的参数。 lpCallee Id 指向的 WSABUF buf 部分指向 sockaddr 结构。 sockaddr 结构根据其地址系列进行解释(通常通过将 sockaddr 转换为特定于地址系列(如结构 sockaddr_in)的某些类型)。

lpCalleeData 是条件函数用来将用户数据送回连接实体的结果参数。 lpCalleeData->len 最初包含服务提供商分配的缓冲区的长度,并由 lpCalleeData->buf指向。 零值表示不支持将用户数据传回调用方。 条件函数应最多将 lpCalleeData->len 字节的数据复制到 lpCalleeData->buf,然后更新 lpCalleeData->len,以指示传输的实际字节数。 如果未将用户数据传回调用方,则条件函数应将 lpCalleeData->len 设置为零。 所有地址和用户数据的格式都特定于套接字所属的地址系列。

g 参数在条件函数中分配,以指示以下任一操作:

  • 如果 g 是现有的套接字组标识符,则向此组添加 ,前提是满足此组设置的所有要求。
  • 如果 g = SG_UNCONSTRAINED_GROUP,请创建一个不受约束的套接字组,并将 作为第一个成员。
  • 如果 g = SG_CONSTRAINED_GROUP,请创建一个受约束的套接字组,并将 作为第一个成员。
  • 如果 g = 零,则不执行组操作。
对于不受约束的组,只要单个服务提供商支持这些套接字集,就可以将其组合在一起。 受约束套接字组只能包含面向连接的套接字,并且要求所有分组套接字上的连接都位于同一主机上的同一地址。 对于新创建的套接字组,可以使用 getsockopt 函数检索新组标识符,并将 级别 参数设置为 SOL_SOCKEToptname 参数设置为 SO_GROUP_ID。 在关闭属于此套接字组的最后一个套接字之前,套接字组及其关联的套接字组 ID 保持有效。 套接字组 ID 在给定服务提供商的所有进程中都是唯一的。 在关闭属于此套接字组的最后一个套接字之前,套接字组及其关联的标识符保持有效。 套接字组标识符在给定服务提供商的所有进程中都是唯一的。 有关套接字组的详细信息,请参阅 WSASocket 函数的备注。

传递给条件函数的 dwCallbackData 参数值 是作为原始 WSAAccept 调用中的 dwCallbackData 参数传递的值。 此值仅由 Windows 套接字版本 2 客户端解释。 这样,客户端就可以将 WSAAccept 调用站点中的一些上下文信息传递给条件函数。 这也为条件函数提供了确定是否接受连接所需的任何其他信息。 典型的用法是将(适当强制转换)指针传递给包含对此套接字关联的应用程序定义对象的引用的数据结构。

注意 为了防止使用 WSAAccept 函数免受 SYN 攻击,应用程序在报告连接请求之前必须执行完整的 TCP 握手(SYN-SYNACK-ACK)。 以这种方式防止 SYN 攻击导致SO_CONDITIONAL_ACCEPT套接字选项变得无效;条件函数仍在调用,WSAAccept 函数正常运行,但依赖客户端无法执行握手的服务器应用程序将无法正常运行。
 
注意 发出阻止的 Winsock 调用(如 WSAAccept)时,Winsock 可能需要等待网络事件,然后调用才能完成。 在这种情况下,Winsock 会执行可警报的等待,这可以通过在同一线程上计划的异步过程调用(APC)中断。 在 APC 中发出另一个阻止 Winsock 调用,中断同一线程上的持续阻止 Winsock 调用将导致未定义的行为,并且永远不会由 Winsock 客户端尝试。
 

示例代码

以下示例演示如何使用 WSAAccept 函数。
#include <winsock2.h>
#include <stdio.h>
#include <windows.h>

/* Define an example conditional function that depends on the pQos field */
int CALLBACK ConditionAcceptFunc(
    LPWSABUF lpCallerId,
    LPWSABUF lpCallerData,
    LPQOS pQos,
    LPQOS lpGQOS,
    LPWSABUF lpCalleeId,
    LPWSABUF lpCalleeData,
    GROUP FAR * g,
    DWORD_PTR dwCallbackData
    )
{

    if (pQos != NULL) {
        RtlZeroMemory(pQos, sizeof(QOS));
        return CF_ACCEPT;
    } else
        return CF_REJECT;
}

int main() {

    /* Declare and initialize variables */
    WSADATA wsaData;
    SOCKET ListenSocket, AcceptSocket;
    struct sockaddr_in saClient;
    int iClientSize = sizeof(saClient);
    u_short port = 27015;
    char* ip;
    sockaddr_in service;
    int error;

    /* Initialize Winsock */
    error = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (error) {
        printf("WSAStartup() failed with error: %d\n", error);
        return 1;
    }

    /* Create a TCP listening socket */
    ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ListenSocket == INVALID_SOCKET) {  
        printf("socket() failed with error: %d\n", WSAGetLastError() );
        WSACleanup();
        return 1;
    }

    /*-----------------------------------------  
     *  Set up the sock addr structure that the listening socket
     *  will be bound to. In this case, the structure holds the
     * local IP address and the port specified. */
    service.sin_family = AF_INET;
    service.sin_port = htons(port);
    hostent* thisHost;
    thisHost = gethostbyname("");
    ip = inet_ntoa (*(struct in_addr *)*thisHost->h_addr_list);
    service.sin_addr.s_addr = inet_addr(ip);

    /*-----------------------------------------
     *  Bind the listening socket to the IP address.
     * and port number specified by the sockaddr structure. */
    error = bind(ListenSocket, (SOCKADDR *) &service, sizeof(SOCKADDR));
    if (error == SOCKET_ERROR) {  
        printf("bind() failed with error: %d\n", WSAGetLastError() );
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
  
    /* Make the socket listen for incoming connection requests */
    error = listen(ListenSocket, 1);
    if (error == SOCKET_ERROR) {  
        printf("listen() failed with error: %d\n", WSAGetLastError() );
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    printf("Listening...\n");
  
    /*-----------------------------------------
     *  Accept an incoming connection request on the
     *  listening socket and transfer control to the 
     * accepting socket. */
    AcceptSocket = WSAAccept(ListenSocket, (SOCKADDR*) &saClient, &iClientSize, 
        &ConditionAcceptFunc, NULL);
 
    /*  Now do some work with the AcceptSocket 
     *  At this point, the application could
     *  handle data transfer on the socket, or other socket
     * functionality.*/
    
    /* Then clean up and quit */

    closesocket(AcceptSocket);
    closesocket(ListenSocket);
    WSACleanup();

    return 0;
}

Windows Phone 8: Windows Phone 8 及更高版本上Windows Phone应用商店应用支持此函数。

Windows 8.1Windows Server 2012 R2:Windows 8.1、Windows Server 2012 R2 及更高版本中的 Windows 应用商店应用支持此函数。

要求

要求 价值
最低支持的客户端 Windows 8.1、Windows Vista [桌面应用 |UWP 应用]
支持的最低服务器 Windows Server 2003 [桌面应用 |UWP 应用]
目标平台 窗户
标头 winsock2.h
Ws2_32.lib
DLL Ws2_32.dll

另请参阅

WSAAsyncSelect

WSAConnect

WSASocket

Winsock 函数

Winsock 参考

接受

绑定

连接

getsockopt

侦听

选择

sockaddr

套接字