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

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

[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 引用的整数最初包含 addr 指向的空间量。返回时,它将包含返回的地址的实际长度(以字节为单位)。

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

注解

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

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

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

addr 参数是一个结果参数,它填充有连接实体的地址,即通信层。 addr 参数的确切格式由发生通信的地址系列确定。 addrlen 是 value-result 参数;它最初应包含 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。 许多网络协议不支持连接时调用方数据。 大多数传统网络协议在连接请求时都支持调用方标识符信息。 lpCallerId 指向的 WSABUF 的 buf 部分指向 sockaddrsockaddr 结构根据其地址系列进行解释 (通常通过将 sockaddr 强制转换为特定于地址系列) 的某个类型。

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

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

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

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

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

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

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

注意 为了防止使用 WSAAccept 函数免受 SYN 攻击,应用程序必须在报告连接请求之前 (SYN-SYNACK-ACK) 执行完整的 TCP 握手。 以这种方式防范 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 应用]
目标平台 Windows
标头 winsock2.h
Library Ws2_32.lib
DLL Ws2_32.dll

另请参阅

WSAAsyncSelect

WSAConnect

WSASocket

Winsock 函数

Winsock 参考

accept

bind

connect

getsockopt

listen

select

sockaddr

socket