一点一滴成长

导航

windows socket扩展函数

1、AcceptEx()

AcceptEx()用于异步接收连接,可以取得客户程序发送的第一块数据。

 

[cpp] view plaincopy
 
  1. BOOL AcceptEx(  
  2.   _In_  SOCKET       sListenSocket,        //监听套接字句柄  
  3.   _In_  SOCKET       sAcceptSocket,        //指定一个未被使用的套接字,在这个套接字上接收新的连接
  4.   _In_  PVOID        lpOutputBuffer,       //指定一个缓冲区,用来取得在新连接上接收到的第一块数据、服务器的本地地址、客户端地址, 该参数必须指定  
  5.   _In_  DWORD        dwReceiveDataLength,  //lpOutputBuffer中数据缓冲区的大小,这一大小不包括服务器的本地地址的大小也不包括客户端的远程地址大小,                                                                                为0表示AcceptEx将不等待接收任何数据,而是尽快建立连接。
  6.   _In_  DWORD        dwLocalAddressLength, //lpOutputBuffer缓冲区中为本地地址预留的长度。必须比最大地址长度多16  
  7.   _In_  DWORD        dwRemoteAddressLength,//lpOutputBuffer缓冲区中中为远程地址预留的长度。必须比最大地址长度多16  
  8.   _Out_ LPDWORD      lpdwBytesReceived,    //接收到数据的长度,这个参数只在同步完成时有效,如果函数返回ERROR_IO_PENDING并在迟些时候完成操作,那                                                                   么这个DWORD没有意义,这时你必须获得从完成通知机制中读取操作字节数  
  9.   _In_  LPOVERLAPPED lpOverlapped          //用来处理本请求的OVERLAPPED结构,不能为NULL  
  10. );  


AcceptEx()成功完成后执行了三个操作:1、接受了新的连接;2、新连接的本地地址和远程地址都会返回;3、接收到了远程主机发来的第一块数据。

如果没有错误发生,AcceptEx函数成功完成并返回TRUE。
如果函数失败,AcceptEx返回FALSE。可以调用WSAGetLastError函数获得扩展的错误信息,如果WSAGetLastError返回ERROR_IO_PENDING,那么这次行动成功启动并仍在进行中。

如果提供了数据接收缓冲区(dwReceiveDataLength不为0),AcceptEx()投递的重叠操作直到接受到连接并且读到数据之后才会完成。可以使用getsockopt的SO_CONNECT_TIME选项来检查一个连接是否已经接受,如果它已被接受,你可以获得连接已经建立了多长时间(秒数),如果套接字未连接,getsockopt返回0xFFFFFFFF。应用程序通过检查重叠操作是否完成,并组合SO_CONNECT_TIME选项可以确定是否连接已建立了一段时间但没有收到任何数据,我们建议您通过关闭连接来终止这些连接,从而使AcceptEx()完成操作并返回一个错误状态。例如:

int seconds;
int bytes = sizeof(seconds);
int iResult = 0;
iResult = getsockopt(s, SOL_SOCKET, SO_CONNECT_TIME, (char *)&seconds, (PINT)&bytes);
if (iResult != NO_ERROR) 
{
    printf("getsockopt(SO_CONNECT_TIME) failed with error: %u\n", WSAGetLastError());
}
else 
{
    if (seconds == 0xFFFFFFFF)
        printf("Connection not established yet\n");
    else
        printf("Connection has been established %ld seconds\n", seconds);
}

较accept函数而言,程序使用AcceptEx可以更快连接到一个套接字。

AcceptEx()是一个Microsoft扩展函数,它是从Mswsock.lib库中导出的,为了能够直接调用它而不链接到Mswsock.lib库(因为直接链接到这个库的话会将程序绑定在MicrosoftWinsock提供者上),需要使用WSAIoctl()将AcceptEx()加载到内存。WSAIoctl()是ioctlsocket()的扩展,它可以使用重叠I/O,函数的第3个到第6个参数是输入和输出缓冲区,在这里传递AcceptEx()函数的指针。具体如下:

// 加载扩展函数AcceptEx  
DWORD dwBytes;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
LPFN_ACCEPTEX lpfnAcceptEx = NULL;
int iResult = WSAIoctl(ListenSocket,
                        SIO_GET_EXTENSION_FUNCTION_POINTER,
                        &GuidAcceptEx,
                        sizeof(GuidAcceptEx),
                        &lpfnAcceptEx,
                        sizeof(lpfnAcceptEx),
                        &dwBytes,
                        NULL,
                        NULL);
if (iResult == SOCKET_ERROR) {
    printf("WSAIoctl failed with error: %u\n", WSAGetLastError());
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}

......

//调用AcceptEx
BOOL bRetVal = lpfnAcceptEx(ListenSocket, 
                                AcceptSocket,
                                lpOutputBuf,
                                outBufLen - ((sizeof(sockaddr_in) + 16) * 2),
                                sizeof(sockaddr_in) + 16, 
                                sizeof(sockaddr_in) + 16,
                                &dwBytes, 
                                &olOverlap);
if (bRetVal == FALSE) {
    printf(L"AcceptEx failed with error: %u\n", WSAGetLastError());
    closesocket(AcceptSocket);
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}
 

2、GetAcceptExSockaddrs()

GetAcceptExSockaddrs()是专为AcceptEx()准备的,它粘贴从AcceptEx()获得的数据,将本地和远程地址传递到sockaddr结构。

void GetAcceptExSockaddrs(
_In_ PVOID lpOutputBuffer,                //指向传递给AcceptEx()接收客户第一块数据的缓冲区, 与AcceptEx()的lpOutputBuffer参数相同
_In_ DWORD dwReceiveDataLength,    //上一个参数的大小,应与AcceptEx()的dwReceiveDataLength参数一致
_In_ DWORD dwLocalAddressLength,   //为本地地址预留的空间大小,应与AcceptEx()的dwLocalAddressLength参数一致
_In_ DWORD dwRemoteAddressLength,//为远程地址预留的空间大小,应与AcceptEx()的dwRemoteAddressLength参数一致
_Out_ LPSOCKADDR *LocalSockaddr,   //用来获得连接的本地地址
_Out_ LPINT LocalSockaddrLength,       //用来获得连接的本地地址长度
_Out_ LPSOCKADDR *RemoteSockaddr,//用来获得连接的远程地址
_Out_ LPINT RemoteSockaddrLength     ////用来获得连接的远程地址长度
);
当使用AcceptEx时,必须使用GetAcceptExSockaddrs函数将输出缓冲区的内容解析到三个不同部分的缓冲区 (data, 
local socket address, and remote socket address)。 在windows XP 及随后版本中,当 AcceptEx函数完成操 
作并且SO_UPDATE_ACCEPT_CONTEXT选项在被接受的socket中被设置时, 与被接受socket相关的本地地址(local 
address )可以使用getsockname函数获得,类似的,与被接受socket相关的远程端地址(the remote address)可 
以使用getpeername函数获得。
使用WSAIoctl()加载GetAcceptExSockaddrs():
    DWORD dwBytes;
    GUID GuidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
    LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs = NULL;
    int Result = WSAIoctl(ListenSocket,
        SIO_GET_EXTENSION_FUNCTION_POINTER,
        &GuidGetAcceptExSockaddrs,
        sizeof(GuidGetAcceptExSockaddrs),
        &lpfnGetAcceptExSockaddrs,
        sizeof(lpfnGetAcceptExSockaddrs),
        &dwBytes,
        NULL,
        NULL);

3、TransmitFile

TransmitFile()函数使用操作系统的缓存管理器来发送文件数据,它避免了循环调用ReadFile() 和WSASend ()产生的多重用户模式到内核模式的切换,提高了文件传输的性能,linux的sendfile()、sendfile64()与其类似:
BOOL PASCAL TransmitFile(
   SOCKET                  hSocket, //连接套接字,不能是SOCK_DGRAM或SOCK_RAM类型
   HANDLE                  hFile, //文件句柄,在CreateFile()打开文件的时候可以指定FILE_FLAG_SEQUENTIAL_SCAN标识来提高缓存性能
   DWORD                   nNumberOfBytesToWrite, //要传输的字节,0为传输整个文件
   DWORD                   nNumberOfBytesPerSend, //每次发送的数据块的大小,0为默认大小
   LPOVERLAPPED            lpOverlapped, //如果套接字是已重叠方式创建的,指定这个参数可以进行异步I/O和指定文件偏移量,默认情况下套接字是以重叠方式创建的?
   LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, //指定在文件数据发送之前和之后要发送的数据
   DWORD                   dwFlags //标志
);

typedef void (*LPFN_TRANSMITFILE)();

如果hFile设为NULL的话,lpTransmitBuffers将被传输。
lpOverlapped为NULL的话,传输会从当前文件指针开始,否则OVERLAPPED结构中的偏移量值将指定文件偏移值。
TransmitFile()一次只能发送2的32次方减1大小的文件(大约为2G),超过这个大小的话将nNumberOfBytesToWrite参数设为非0的合理值,多次调用TransmitFile()即可。
在客户操作系统中只有两个TransmitFile()请求会被同时处理,服务器版本的操作系统则没有这个最大并发限制,并且可以通过创建注册表项来设置这个最大并发值。
dwFlags可以为以下值的组合:
一般我们同时指定前两个标志,在这种情况下,当文件或缓冲区数据传输操作完成后套接字会断开,而传递给此函数的套接字可以被AcceptEx()或ConnectEx()重复使用,这样可以节省套接字创建的开销,因为套接字创建的开销很大。
如果hFile和lpTransmitBuffers都设为NULL的话(同时指定了前两个标志),函数不会发送任何数据,只是设置套接字允许重用。
TransmitFile 着重于服务器应用程序,因此只有在 Windows的服务器版本上,其功能才能得到完全发挥。对于家庭版或专业版,在任何时候,只可以有两个未完成的TransmitFile(或TransmitPackets)调用,如果超过这个数目,则多余的将排除等候,直到正在执行的调用结束之后,才会被处理。
    SOCKET ConnectSocket = (SOCKET)lpParameter;
    HANDLE hFile = CreateFileA("file.data",
        GENERIC_READ,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_SEQUENTIAL_SCAN,
        NULL
    );
    if (hFile == INVALID_HANDLE_VALUE)
    {
        int iErrno = WSAGetLastError();
        printf("createfile() error:%d\n", iErrno);
        return -1;
    }

    /************************调用TransmitFile()发送一个文件,同步方式***********************/
    BOOL bRet = FALSE;
    bRet = TransmitFile(ConnectSocket, hFile, 0, 0, NULL/*&ov*/, NULL, TF_USE_DEFAULT_WORKER);
    if (!bRet)
    {
        int iErrno = WSAGetLastError();
        printf("TransmitFile() failed: %d\n", iErrno);
    }
    else
    {
        printf("transfer end\n");
    }

    /************************调用TransmitFile()发送一个文件,异步方式***********************/
    OVERLAPPED ov;
    memset(&ov, 0, sizeof(ov));
    ov.hEvent = WSACreateEvent();
    BOOL bRet = TransmitFile(ConnectSocket, hFile, 0, 0, &ov, NULL, TF_USE_DEFAULT_WORKER);
    if (!bRet)
    {
        int iErrno = WSAGetLastError();
        if (iErrno == WSA_IO_PENDING || iErrno == ERROR_IO_PENDING)
        {
            DWORD flags;
            DWORD sendBytes;
            WSAGetOverlappedResult(ConnectSocket, &ov, &sendBytes, TRUE, &flags);
            if (!ov.Internal)
            {
                printf("TransmitFile() sucess, send %d bytes\n", sendBytes);
            }
            else
            {
                printf("TransmitFile() failed: %d\n", WSAGetLastError());
            }
        }
        else
        {
            printf("TransmitFile() failed immediately: %d\n", iErrno);
        }
    }
    
    //关闭建立连接的套接字  
    closesocket(ConnectSocket);
    CloseHandle(hFile);
4、TransmitPackets()
TransmitPackets()与TransmitFile()功能类似,不同的是它可以发送多个文件或多个内存缓冲区中的数据。

BOOL PASCAL TransmitPackets(
SOCKET hSocket, //连接套接字,可以是SOCK_DGRAM
LPTRANSMIT_PACKETS_ELEMENT lpPacketArray, //封包元素数组
DWORD nElementCount, //lpPacketArray中封包元素的的数量
DWORD nSendSize, //每次发送数据的大小
LPOVERLAPPED lpOverlapped, //同TransmitFile()

DWORD dwFlags //同TransmitFile,不过不是以TF开头而是以TP开头
);

typedef void (*LPFN_TRANSMITPACKETS)();

lpPacketArray封包元素数组是LPTRANSMIT_PACKETS_ELEMENT结构类型的数组:

typedef struct _TRANSMIT_PACKETS_ELEMENT {
  ULONG dwElFlags; //指定此元素中包含的缓冲区类型:文件TP_ELEMENT_FILE或内存TP_ELEMENT_MEMORY或TP_ELEMENT_EOP
  ULONG cLength; //指定要传输文件的多少个字节,0为传输整个文件
  union {
    struct {
      LARGE_INTEGER nFileOffset;//文件的偏移量,-1表示从当前文件指针传输
      HANDLE        hFile;//文件句柄
    };
    PVOID  pBuffer;//数据内存缓冲区
  };
} TRANSMIT_PACKETS_ELEMENT;
dwElFlags成员的TP_ELEMENT_EOP标志可以和另外两个标志按位或组合,指示在发送中这个元素不应该和后面的元素混合起来,这是用来精确的控制面向数据报或消息的socket传输。
使用TransmitFile()和TransmitPackets()的除了可以提高发送文件的效率外的另一个好处就是可以通过指定TF_REUSE_SOCKET和TF_DISCONNECT标志来重用套接字句柄。每当API完成数据的传输工作后,就会在传输层级别断开连接,这样这个套接字就又可以重新提供给AcceptEx()使用。采用这种优化的方法编程,将减轻那个专门做接受操作的线程创建套接字的压力
5、ConnectEx()

ConnectEx()用来异步连接调用,连接建立之后也可以发送数据。由于ConnectEx使用的是异步通知机制,所以如果我们的客户端程序需要多个连接的话使用ConnectEx就不用为每个连接使用一个线程来管理这个连接了。
BOOL PASCAL ConnectEx(
  _In_     SOCKET                s, //未连接的socket
  _In_     const struct sockaddr *name, //要连接的远程地址
  _In_     int                   namelen, //远程地址长度
  _In_opt_ PVOID                 lpSendBuffer, //建立连接后要发送的数据,NULL为不发送
  _In_     DWORD                 dwSendDataLength, //lpSendBuffer中数据长度
  _Out_    LPDWORD               lpdwBytesSent, //实际发送的字节数
_In_     LPOVERLAPPED          lpOverlapped //重叠结构,必须指定);
连接可能不会立即成功,这时ConnectEx()返回FALSE,调用WSAGetLastError()返回ERROR_IO_PENDING表明连接正在进行。如果错误码是 WSAECONNREFUSEDWSAENETUNREACH, 或 WSAETIMEDOUT那么可以再次调用ConnectEx进行连接。
当连接成功或失败后lpOverlapped指向的重叠结构会得到通知,可以使用事件或完成端口作为完成通知机制。GetQueuedCompletionStatus or GetOverlappedResult or WSAGetOverlappedResult函数的lpNumberOfBytesTransferred参数可以获得发送的字节数。
6、DisConnectEx()
DisConnectEx()支持异步关闭套接字上的连接,并允许重用套接字。
BOOL DisconnectEx(
  _In_ SOCKET       hSocket, //面向连接的套接字
  _In_ LPOVERLAPPED lpOverlapped, //如果套接字是以重叠方式创建的,指定这个参数以进行重叠I/O操作
  _In_ DWORD        dwFlags, //TF_REUSE_SOCKET或0,0为仅仅断开连接,TF_REUSE_SOCKET为可重用套接字
_In_ DWORD        reserved //保留 );
如果这个函数接受了一个重叠结构,并且在要关闭的套接字上仍有未决操作,它会返回FALSE,出错代码是WSA_IP_PENDING,一旦套接字上的所有未决操作都返回,DisConnectEx()投递的操作就会完成。
如果以阻塞方式调用这个函数的话,它将在所有未决I/O都完成后才返回。

posted on 2016-12-09 14:44  整鬼专家  阅读(1926)  评论(0编辑  收藏  举报