Cinco modelos de programação de soquete do Windows

Cliente: Crie um soquete, conecte-se ao servidor e envie e receba dados continuamente.

Um modelo de servidor mais fácil de pensar é usar uma thread principal para monitorar a solicitação de conexão do cliente. Ao receber a solicitação de conexão de um cliente, são criados um socket e uma thread secundária dedicada à comunicação com o cliente. Todas as interações subseqüentes entre o cliente e o servidor são concluídas neste encadeamento auxiliar. Esse método é mais intuitivo, o programa é muito simples e tem boa portabilidade, mas não consegue aproveitar os recursos relacionados à plataforma. Por exemplo, se o número de conexões aumenta (milhares de conexões), o número de threads aumenta exponencialmente, o sistema operacional fica ocupado alternando entre threads com frequência e a maioria dos threads fica inativa durante seu tempo de vida. Isso desperdiça muito recursos do sistema. Portanto, se você já sabe que seu código só rodará na plataforma Windows, é recomendável utilizar o modelo Winsock I/O.

1. Selecione o modelo: enquete coleção fd_set

Use a função de seleção para realizar o gerenciamento de E/S. Quando este modelo foi originalmente projetado, era voltado principalmente para determinados computadores que usavam o sistema operacional UNIX, que usava o esquema de soquete Berkeley. O modelo Select foi integrado ao Winsock 1.1, o que permite que aplicativos que desejam evitar o "bloqueio" inocente durante chamadas de soquete gerenciem vários soquetes simultaneamente de maneira ordenada.

int select(
int nfds,
fd_set* readfds,
fd_set* writefds,
fd_set* exceptfds,
const struct timeval* timeout
);
nfds: Este parâmetro é ignorado e é apenas para compatibilidade.
readfds: (opcional) ponteiro para um conjunto de soquetes aguardando verificações de legibilidade.
writefds: (opcional) ponteiro para um conjunto de soquetes esperando para serem verificados quanto à capacidade de escrita.
exceptfds: (opcional) ponteiro para um conjunto de soquetes aguardando verificação de erro.
timeout: O tempo máximo de espera para select() ou NULL para operações de bloqueio.

FD_CLR(s,*conjunto): Exclua os descritores s do conjunto definido.
FD_ISSET(s,*conjunto): Diferente de zero se s for um membro do conjunto; caso contrário, zero.
FD_SET(s,*conjunto): Adiciona descritor s ao conjunto.
FD_ZERO(*conjunto): Inicializa o conjunto como um conjunto vazio NULL.
O parâmetro timeout controla quando select() é concluído. Se o parâmetro de tempo limite for um ponteiro nulo, select() bloqueará até que uma palavra de descrição atenda à condição. Caso contrário, timeout aponta para uma estrutura timeval especificando quanto tempo a chamada select() espera antes de retornar. Se timeval for {0,0}, select() retornará imediatamente, o que pode ser usado para consultar o status do soquete selecionado.

O servidor verifica se um soquete ainda está no conjunto de leitura e, se estiver, recebe os dados. Se o comprimento dos dados recebidos for 0, ou ocorrer um erro WSAECONNRESET, significa que o soquete do cliente está ativamente fechado. Neste momento, é necessário liberar os recursos vinculados ao soquete correspondente no servidor e, em seguida, ajustar nosso socket array (Mover o último socket no array para a posição atual)
Além de aceitar condicionalmente a conexão do cliente, é necessário um tratamento especial quando o número de conexões for 0, porque se não houver socket no conjunto de leitura, a função select retorna imediatamente.

Ao chamar o modo sem bloqueio, pode-se dizer que o soquete define o tempo limite ao selecionar para bloquear o aplicativo.
select testará se a coleção fd_set está disponível durante o período de tempo limite e, se expirar/nenhum dado puder ser lido, limpará os membros da coleção atual.

2. Seleção assíncrona
O aplicativo pode receber notificações de eventos de rede com base em mensagens do WINDOWS em um soquete. A implementação desse modelo é definir automaticamente o soquete para o modo sem bloqueio chamando a função WSAAsynSelect, registrar um ou mais eventos de rede com o WINDOWS e fornecer um identificador de janela para notificação. Quando ocorre um evento registrado, a janela correspondente receberá uma notificação baseada em mensagem.

Três. Seleção de eventos
O Winsock fornece outro modelo útil de E/S assíncrona. Semelhante ao modelo WSAAsyncSelect, ele também permite que os aplicativos recebam notificações de eventos de rede baseadas em eventos em um ou mais soquetes.
A ideia básica é associar cada soquete a um objeto WSAEVENT e especificar quais eventos de rede precisam ser observados durante a associação. Uma vez que um evento de nossa preocupação (FD_READ e FD_CLOSE) ocorre em um soquete, o objeto WSAEVENT associado a ele é sinalizado.

4. Modelo de E/S sobreposto

A chamada de readfile ou writefile retornará imediatamente. Neste momento, você pode fazer o que quiser e o sistema concluirá automaticamente o readfile ou writefile para você. Depois de chamar readfile ou writefile, você continua a fazer seu trabalho, e o sistema simultaneamente. Também ajuda a concluir a operação de readfile ou writefile, que é a chamada sobreposição.

1. Modelo de E/S sobreposto realizado por notificação de evento Função de E/S
assíncrona WSARecv. Ao chamar WSARecv, especifique uma estrutura WSAOVERLAPPED, esta chamada não é bloqueante, ou seja, retornará imediatamente. Depois que os dados chegam, o hEvent na estrutura WSAOVERLAPPED especificada é sinalizado. O objeto WSAEVENT associado ao soquete também é Sinalizado, portanto, a operação de chamada de WSAWaitForMultipleEvents retorna com êxito.

2. Modelo de I/O sobreposto realizado pela rotina de conclusão

WSARecv passa o ponteiro CompletionROUTINE e a função de retorno de chamada. Quando a solicitação de IO é concluída, a função de retorno de chamada é chamada para concluir o trabalho que precisamos processar. Nesse modelo, o thread principal só precisa aceitar conexões continuamente; o thread auxiliar julga se há são novos clientes A conexão do cliente é estabelecida, se houver, ativa uma operação WSARecv assíncrona para esse soquete do cliente e, em seguida, chama SleepEx para colocar o thread em um estado de espera alertável, para que CompletionROUTINE possa ser chamado pelo kernel após o I /O está completo. Se o thread auxiliar não chamar SleepEx, o kernel não poderá chamar a rotina de conclusão após concluir uma operação de E/S (porque a rotina de conclusão deve ser executada no mesmo thread que o código que ativou a operação assíncrona WSARecv).

O Windows fornece quatro tecnologias IO assíncronas, os mecanismos são quase os mesmos, a diferença está na forma de notificar os resultados:
1. Fazer com que um objeto do kernel do dispositivo seja sinalizado
O Windows considera o identificador do dispositivo como um objeto sincronizável, ou seja, pode ser em um estado de sinal ou sem sinal. Ao criar um identificador de dispositivo e enviar uma solicitação de IO de forma assíncrona, o identificador está em um estado sem sinal. Após a conclusão do IO assíncrono, o identificador é confiável e a operação do dispositivo pode ser julgado como concluído corretamente por meio da função WaitForSingleobject ou WatiForMultipleObjects. Esta tecnologia só pode ser usada para um dispositivo enviar apenas uma solicitação de IO. Caso contrário, se um dispositivo corresponder a várias operações, quando o identificador é confiável, não se pode julgar que a operação do dispositivo foi concluída.
2. Faça com que um objeto de evento do kernel seja sinalizado. Vincule um objeto de evento do kernel
para cada operação de E/S e espere que a função de espera do evento aguarde até que o evento seja confiável. Quando a operação de E/S é concluída, o sistema faz liga-se à operação.O evento especificado é confiável, de modo a julgar que a operação foi concluída. Essa tecnologia resolve a deficiência de que um dispositivo pode corresponder apenas a uma operação na tecnologia de transformar um objeto do kernel do dispositivo em um sinal.
3. Warning I/O
Nesta tecnologia, quando uma solicitação IO do dispositivo é emitida, também somos obrigados a passar uma função de callback chamada de rotina de conclusão. Quando a solicitação IO é concluída, a função callback é chamada para concluir o trabalho que precisamos processar. Essa técnica permite que um único dispositivo faça várias solicitações de E/S simultaneamente.
4. Porta de conclusão A tecnologia de porta
de conclusão é usada principalmente para lidar com solicitações de grande escala, e alto desempenho pode ser alcançado por meio da tecnologia de pool de processos internos.

-5
.Complete o modelo de porta
Somente quando seu aplicativo precisar gerenciar centenas ou mesmo milhares de soquetes ao mesmo tempo e você esperar que, à medida que o número de CPUs instaladas no sistema aumentar, o desempenho do aplicativo também possa ser melhorado linearmente • Um modelo de "ponto de conclusão" deve ser considerado.

O gerenciamento do pool de threads é fornecido dentro da porta de conclusão, o que pode evitar a sobrecarga de criar threads repetidamente. Ao mesmo tempo, o número de threads pode ser determinado de forma flexível de acordo com o número de CPUs, e o número de agendamento de thread pode ser reduzido para melhorar o desempenho.
A primeira coisa a fazer é criar um objeto I/O Completion Port

HANDLE CreateIoCompletionPort (
  HANDLE FileHandle,              // handle to file

  HANDLE ExistingCompletionPort,  // handle to I/O completion port

  ULONG_PTR CompletionKey,        // completion key

  DWORD NumberOfConcurrentThreads // number of threads to execute concurrently

);

Antes de nos aprofundarmos nos parâmetros, é importante observar que essa função é realmente usada para duas finalidades distintas:
■ Ela é usada para criar um objeto de porta de conclusão.
■ Associar um identificador à porta de conclusão.
Ao criar inicialmente uma porta de conclusão, o único parâmetro de interesse é NumberOfConcurrentThreads (o número de
threads ); os três primeiros parâmetros são ignorados. O parâmetro NumberOfConcurrentThreads é especial porque define o número de encadeamentos que podem ser executados simultaneamente em uma porta de conclusão. Idealmente, queremos que cada processador seja responsável por executar um encadeamento, atender às portas de conclusão e evitar trocas de "cenas" de encadeamento muito frequentes. Se este parâmetro for definido como 0, indica quantos processadores estão instalados no sistema e quantas threads podem ser executadas ao mesmo tempo! Uma porta de conclusão de E/S pode ser criada com o seguinte código:
CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)

A função dessa instrução é retornar um identificador, que é usado para demarcar (referenciar) a porta de conclusão após um identificador de soquete ter sido alocado para essa
porta .

O thread de trabalho chama GetQueuedCompletionStatus para pesquisar a fila da porta de conclusão

Se você deseja criar aplicativos de servidor na plataforma Windows, o modelo de E/S é algo que deve ser considerado. O sistema operacional Windows
oferece
cinco modelos de E/S: Select, WSAAsyncSelect, WSAEventSelect, Overlapped I/O e Completion Port. Cada modelo é adequado para um cenário de aplicação específico. Os programadores devem ser muito claros sobre seus próprios requisitos de aplicativo e fazer suas próprias escolhas, levando em consideração
fatores como .
Apresentarei esses cinco modelos de I/O com uma resposta a um servidor reflexivo (igual ao Capítulo 8 de "Programação de rede do Windows").
Assumimos que o código do cliente é o seguinte (para que o código seja intuitivo, toda a verificação de erros é omitida, o mesmo abaixo):

#include <WINSOCK2.H>
#include <stdio.h>
#define SERVER_ADDRESS "137.117.2.148"
#define PORT           5150
#define MSGSIZE        1024
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA     wsaData;
SOCKET      sClient;
SOCKADDR_IN server;
char        szMessage[MSGSIZE];
int         ret;

// Initialize Windows socket library
WSAStartup(0x0202, &wsaData);
// Create client socket
sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Connect to server
memset(&server, 0, sizeof(SOCKADDR_IN));
server.sin_family = AF_INET;
server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);
server.sin_port = htons(PORT);
connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));
while (TRUE)
{
    printf("Send:");
gets(szMessage);
    // Send message
    send(sClient, szMessage, strlen(szMessage), 0);
    // Receive message
    ret = recv(sClient, szMessage, MSGSIZE, 0);
    szMessage[ret] = '\0';
    printf("Received [%d bytes]: '%s'\n", ret, szMessage);
}
// Clean up
closesocket(sClient);
WSACleanup();
return 0;
}
客户端所做的事情相当简单,创建套接字,连接服务器,然后不停的发送和接收数据。
比较容易想到的一种服务器模型就是采用一个主线程,负责监听客户端的连接请求,当接收到某个客户端的连接请求后,创建一个专门

Um soquete e um thread de trabalho para comunicação com o cliente. Todas as interações subseqüentes entre o cliente e o servidor são concluídas neste encadeamento auxiliar. Esse método é mais intuitivo, o programa é muito simples
e tem boa portabilidade, mas não consegue aproveitar os recursos relacionados à plataforma. Por exemplo, se o número de conexões aumenta (milhares de conexões), o número de threads aumenta exponencialmente, o sistema operacional fica ocupado alternando entre threads
com frequência e a maioria dos threads fica inativa durante sua vida útil. Isso desperdiça muito recursos do sistema. Portanto, se você já sabe que seu código só
rodará na plataforma Windows, é recomendável utilizar o modelo Winsock I/O.

1. Selecione o modelo

Select(选择)模型是Winsock中最常见的I/O模型。之所以称其为“Select模型”,是由于它的“中心思想”便是利用select函数,实现对

Gerenciamento de E/S. Quando este modelo foi originalmente projetado, era voltado principalmente para determinados computadores que usavam o sistema operacional UNIX, que usava o esquema de soquete Berkeley. O modelo Select foi integrado ao
Winsock 1.1, o que permite que aplicativos que desejam evitar o "bloqueio" inocente durante chamadas de soquete gerenciem vários soquetes simultaneamente de maneira ordenada.
Como o Winsock 1.1 é compatível com a implementação do soquete Berkeley, se houver um aplicativo de soquete Berkeley usando a função select, teoricamente falando,
ele pode ser executado normalmente sem nenhuma modificação. (Trecho do capítulo 8 de "Programação de rede do Windows")

O programa a seguir é o código do servidor Echo implementado usando o modelo de seleção (não pode mais ser simplificado):

#include <winsock.h>
#include <stdio.h>
#define PORT       5150
#define MSGSIZE    1024
#pragma comment(lib, "ws2_32.lib")
int    g_iTotalConn = 0;
SOCKET g_CliSocketArr[FD_SETSIZE];
DWORD WINAPI WorkerThread(LPVOID lpParameter);
int main()
{
WSADATA     wsaData;
SOCKET      sListen, sClient;
SOCKADDR_IN local, client;
int         iaddrSize = sizeof(SOCKADDR_IN);
DWORD       dwThreadId;
// Initialize Windows socket library
WSAStartup(0x0202, &wsaData);
// Create listening socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Bind
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
// Listen
listen(sListen, 3);
// Create worker thread
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId); 
while (TRUE)
{
    // Accept a connection
    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    // Add socket to g_CliSocketArr
    g_CliSocketArr[g_iTotalConn++] = sClient;
}

return 0;
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int            i;
fd_set         fdread;
int            ret;
struct timeval tv = {
   
   1, 0};
char           szMessage[MSGSIZE];

while (TRUE)
{
    FD_ZERO(&fdread);
    for (i = 0; i < g_iTotalConn; i++)
    {
      FD_SET(g_CliSocketArr[i], &fdread);
    }
    // We only care read event
    ret = select(0, &fdread, NULL, NULL, &tv);
    if (ret == 0)
    {
      // Time expired
      continue;
    }
    for (i = 0; i < g_iTotalConn; i++)
    {
      if (FD_ISSET(g_CliSocketArr[i], &fdread))
      {
        // A read event happened on g_CliSocketArr[i]
        ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0);
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
    {
     // Client socket closed
          printf("Client socket %d closed.\n", g_CliSocketArr[i]);
     closesocket(g_CliSocketArr[i]);
     if (i < g_iTotalConn - 1)
          {            
            g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];
          }
        }
    else
    {
     // We received a message from client
          szMessage[ret] = '\0';
     send(g_CliSocketArr[i], szMessage, strlen(szMessage), 0);
        }
      }
    }
}

return 0;
}

As principais ações do servidor são as seguintes:
1. Criar um soquete de escuta, vincular e escutar;
2. Criar um thread de trabalho;
3. Criar uma matriz de soquete para armazenar todos os soquetes de cliente ativos no momento. A matriz é atualizada toda vez que um a conexão é aceita
4. Aceite a conexão do cliente. Uma coisa a observar aqui é que não redefini a macro FD_SETSIZE, portanto, o número máximo de conexões simultâneas suportadas pelo servidor é 64. Além disso, não deve
haver , o servidor deve decidir se aceita uma conexão de um cliente com base no número atual de conexões. Uma solução de implementação melhor é usar a função WSAAccept
e permitir que WSAAccept chame de volta a função de condição implementada por ela mesma. Do seguinte modo:

int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
if (当前连接数 < FD_SETSIZE)
return CF_ACCEPT;
else
return CF_REJECT;
}

Há um loop infinito no thread de trabalho e as ações concluídas em um loop são:
1. Adicionar todos os soquetes de cliente atuais ao conjunto de leitura fdread;
2. Chamar a função select;
3. Verificar se um determinado soquete ainda está aberto em conjunto de leitura, se sim, receber dados. Se o comprimento dos dados recebidos for 0, ou ocorrer um erro WSAECONNRESET, significa que o soquete do cliente está
ativamente fechado. Neste momento, é necessário liberar os recursos vinculados ao soquete correspondente no servidor e, em seguida, ajustar nosso socket Array (mover o último socket do array para a posição atual)
além de aceitar condicionalmente a conexão do cliente, ele também precisa fazer um processamento especial quando o número de conexões for 0, pois se não houver socket no read set, a função select retornará imediatamente, o que
fará com que o thread de trabalho se torne um loop infinito sem pausa, e o uso da CPU atingirá imediatamente 100%.

2. Seleção assíncrona

Winsock提供了一个有用的异步I/O模型。利用这个模型,应用程序可在一个套接字上,接收以Windows

(Este artigo foi reproduzido por mim, mas não acho que WSAAsyncSelect pertença ao AIO, porque você ainda realiza operações de IO sozinho e o sistema operacional apenas usa mensagens para notificá-lo) Notificação de eventos de rede baseada em mensagens
. O método específico é chamar a função WSAAsyncSelect após criar um soquete. Este modelo apareceu pela primeira vez na versão 1.1 do Winsock para ajudar os desenvolvedores de aplicativos a direcionar algumas das primeiras plataformas Windows de 16 bits (como o Windows for Workgroups) e se adaptar ao seu ambiente de mensagens multitarefa "reverso". Os aplicativos ainda podem se beneficiar desse modelo, especialmente se usarem uma rotina padrão do Windows (geralmente chamada de "WndProc") para gerenciar as mensagens da janela. Esse modelo também foi adotado pelo objeto CSocket do Microsoft Foundation Class (Microsoft Basic Class, MFC). (Trecho do capítulo 8 de "Programação de rede do Windows")

Eu ainda posto o código primeiro e depois explico em detalhes:

#include <winsock.h>
#include <tchar.h>
#define PORT      5150
#define MSGSIZE   1024
#define WM_SOCKET WM_USER+0
#pragma comment(lib, "ws2_32.lib")
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = _T("AsyncSelect Model");
HWND         hwnd ;
MSG          msg ;
WNDCLASS     wndclass ;
wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc   = WndProc ;
wndclass.cbClsExtra    = 0 ;
wndclass.cbWndExtra    = 0 ;
wndclass.hInstance     = hInstance ;
wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass(&wndclass))
{
    MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;
    return 0 ;
}
hwnd = CreateWindow (szAppName,                  // window class name
                       TEXT ("AsyncSelect Model"), // window caption
                       WS_OVERLAPPEDWINDOW,        // window style
                       CW_USEDEFAULT,              // initial x position
                       CW_USEDEFAULT,              // initial y position
                       CW_USEDEFAULT,              // initial x size
                       CW_USEDEFAULT,              // initial y size
                       NULL,                       // parent window handle
                       NULL,                       // window menu handle
                       hInstance,                  // program instance handle
                       NULL) ;                     // creation parameters
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg) ;
    DispatchMessage(&msg) ;
}

return msg.wParam;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
WSADATA       wsd;
static SOCKET sListen;
SOCKET        sClient;
SOCKADDR_IN   local, client;
int           ret, iAddrSize = sizeof(client);
char          szMessage[MSGSIZE];
switch (message)
{
case WM_CREATE:
    // Initialize Windows Socket library
WSAStartup(0x0202, &wsd);

// Create listening socket
    sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// Bind
    local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
bind(sListen, (struct sockaddr *)&local, sizeof(local));

// Listen
    listen(sListen, 3);
    // Associate listening socket with FD_ACCEPT event
WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT);
return 0;
case WM_DESTROY:
    closesocket(sListen);
    WSACleanup();
    PostQuitMessage(0);
    return 0;

case WM_SOCKET:
    if (WSAGETSELECTERROR(lParam))
    {
      closesocket(wParam);
      break;
    }

    switch (WSAGETSELECTEVENT(lParam))
    {
    case FD_ACCEPT:
      // Accept a connection from client
      sClient = accept(wParam, (struct sockaddr *)&client, &iAddrSize);

      // Associate client socket with FD_READ and FD_CLOSE event
      WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
      break;
    case FD_READ:
      ret = recv(wParam, szMessage, MSGSIZE, 0);
      if (ret == 0 || ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)
      {
        closesocket(wParam);
      }
      else
      {
        szMessage[ret] = '\0';
        send(wParam, szMessage, strlen(szMessage), 0);
      }
      break;

    case FD_CLOSE:
      closesocket(wParam);      
      break;
    }
    return 0;
}

return DefWindowProc(hwnd, message, wParam, lParam);
}
在我看来,WSAAsyncSelect是最简单的一种Winsock I/O模型(之所以说它简单是因为一个主线程就搞定了)。使用Raw Windows API写

As pessoas que experimentaram aplicativos de janela devem ser capazes de entendê-lo. Aqui, tudo o que precisamos fazer é:

1. Na função de processamento de mensagens WM_CREATE, inicialize a biblioteca Windows Socket, crie um soquete de escuta, vincule, monitore e chame a função WSAAsyncSelect para indicar que nos
preocupamos
mensagem WM_SOCKET , uma vez que um evento ocorre no soquete que nos interessa (soquete de escuta e soquete do cliente), o sistema chamará WndProc e o
parâmetro de mensagem será definido como WM_SOCKET
; Eventos FD_CLOSE;
4. Na função de processamento da mensagem de destruição da janela (WM_DESTROY), fechamos o soquete de escuta e limpamos a biblioteca do Windows Socket

A seguinte tabela de tipos de eventos de rede para a função WSAAsyncSelect pode fornecer uma compreensão mais clara de cada evento de rede:

Tabela 1
FD_READ O aplicativo deseja ser notificado sobre a legibilidade para ler dados
em FD_WRITE O aplicativo deseja ser notificado sobre a capacidade de gravação para gravar dados
FD_OOB O aplicativo deseja receber a chegada de dados fora da banda (OOB) FD_ACCEPT
O aplicativo deseja receber notificações sobre conexões de entrada
FD_CONNECT O aplicativo deseja receber notificações sobre a conclusão de uma conexão ou operação de junção múltipla
FD_CLOSE O aplicativo deseja receber notificações sobre fechamentos de soquete
FD_QOS O aplicativo deseja receber notificações de soquetes sobre alterações de "Qualidade de serviço" (QoS)
FD_GROUP_QOS O aplicativo deseja receber notificações de alterações de "Qualidade de serviço" do grupo de soquetes (não é útil agora, reservado para uso futuro do grupo de soquetes) FD_ROUTING_INTERFACE_CHANGE O aplicativo
deseja receber
FD_ADDRESS_LIST_CHANGE O aplicativo deseja receber notificações de alterações na lista de endereços locais para o protocolo do soquete família

3. Seleção de eventos

Winsock提供了另一个有用的异步I/O模型。和WSAAsyncSelect模型类似的是,它也允许应用程序在一个

(Este artigo é reproduzido por mim, mas não acho que o WSAEventSelect pertença ao AIO, porque você ainda faz as operações de IO sozinho e o sistema operacional apenas usa eventos para notificá-lo) ou vários soquetes, receba notificação baseada em eventos de eventos de
rede . Para os eventos de rede resumidos na Tabela 1, adotados pelo modelo WSAAsyncSelect, todos eles podem ser transferidos intactos para o novo modelo. Todos esses eventos também podem ser recebidos e processados ​​em aplicativos desenvolvidos com o novo modelo. A principal diferença nesse modelo é que os eventos de rede são entregues a um identificador de objeto de evento em vez de uma rotina de janela.
(Trecho do capítulo 8 de "Programação de rede do Windows")

Ou vamos olhar o código primeiro e depois analisá-lo:

#include <winsock2.h>
#include <stdio.h>
#define PORT    5150
#define MSGSIZE 1024
#pragma comment(lib, "ws2_32.lib")
int      g_iTotalConn = 0;
SOCKET   g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
DWORD WINAPI WorkerThread(LPVOID);
void Cleanup(int index);
int main()
{
WSADATA     wsaData;
SOCKET      sListen, sClient;
SOCKADDR_IN local, client;
DWORD       dwThreadId;
int         iaddrSize = sizeof(SOCKADDR_IN);
// Initialize Windows Socket library
WSAStartup(0x0202, &wsaData);
// Create listening socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Bind
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
// Listen
listen(sListen, 3);
// Create worker thread
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
    // Accept a connection
    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    // Associate socket with network event
    g_CliSocketArr[g_iTotalConn] = sClient;
    g_CliEventArr[g_iTotalConn] = WSACreateEvent();
    WSAEventSelect(g_CliSocketArr[g_iTotalConn],
                   g_CliEventArr[g_iTotalConn],
                   FD_READ | FD_CLOSE);
    g_iTotalConn++;
}
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int              ret, index;
WSANETWORKEVENTS NetworkEvents;
char             szMessage[MSGSIZE];
while (TRUE)
{
    ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
    if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
    {
      continue;
    }
    index = ret - WSA_WAIT_EVENT_0;
    WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &NetworkEvents);
    if (NetworkEvents.lNetworkEvents & FD_READ)
    {
      // Receive message from client
      ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);
      if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
      {
        Cleanup(index);
      }
      else
      {
        szMessage[ret] = '\0';
        send(g_CliSocketArr[index], szMessage, strlen(szMessage), 0);
      }
    }
    if (NetworkEvents.lNetworkEvents & FD_CLOSE)
{
   Cleanup(index);
}
}
return 0;
}
void Cleanup(int index)
{
closesocket(g_CliSocketArr[index]);
WSACloseEvent(g_CliEventArr[index]);
if (index < g_iTotalConn - 1)
{
g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
}

g_iTotalConn--;
}
事件选择模型也比较简单,实现起来也不是太复杂,它的基本思想是将每个套接字都和一个WSAEVENT对象对应起来,并且在关联的时候

Especifique quais eventos de rede requerem atenção. Uma vez que um evento de nossa preocupação (FD_READ e FD_CLOSE) ocorre em um soquete, o objeto WSAEVENT associado a ele é sinalizado.
O programa define duas matrizes globais, uma matriz de soquete, uma matriz de objeto WSAEVENT, cujo tamanho é MAXIMUM_WAIT_OBJECTS (64), e os elementos nas duas matrizes correspondem um a um.

同样的,这里的程序没有考虑两个问题,一是不能无条件的调用accept,因为我们支持的并发连接数有限。解决方法是将套接字按

Grupos MAXIMUM_WAIT_OBJECTS, cada grupo de soquete MAXIMUM_WAIT_OBJECTS, cada grupo é atribuído a um thread de trabalho; ou use WSAAccept em vez de aceitar e chame de volta a função de condição definida por
você mesmo O segundo problema é que não há tratamento especial para a situação em que o número de conexões é 0. Quando o número de conexões é 0, a taxa de uso da CPU do programa é de 100%.

4. Modelo de E/S sobreposto

Winsock2的发布使得Socket I/O有了和文件I/O统一的接口。我们可以通过使用Win32文件操纵函数ReadFile和WriteFile来进行

E/S de soquete. Concomitantemente, o modelo de E/S sobreposto e o modelo de porta de conclusão usado para E/S de arquivo comum também são aplicáveis ​​ao Socket I/O. A vantagem desses modelos é que eles podem obter melhor desempenho
do sistema , mas a implementação é mais complicada e envolve mais habilidades em linguagem C. Por exemplo, costumamos usar os chamados "dados finais" no modelo de porta de conclusão.

1. Modelo de E/S sobreposto realizado por notificação de evento

#include <winsock2.h>
#include <stdio.h>
#define PORT    5150
#define MSGSIZE 1024
#pragma comment(lib, "ws2_32.lib")
typedef struct
{
WSAOVERLAPPED overlap;
WSABUF        Buffer;
char          szMessage[MSGSIZE];
DWORD         NumberOfBytesRecvd;
DWORD         Flags;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
int                     g_iTotalConn = 0;
SOCKET                  g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT                g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];
DWORD WINAPI WorkerThread(LPVOID);
void Cleanup(int);
int main()
{
WSADATA     wsaData;
SOCKET      sListen, sClient;
SOCKADDR_IN local, client;
DWORD       dwThreadId;
int         iaddrSize = sizeof(SOCKADDR_IN);
// Initialize Windows Socket library
WSAStartup(0x0202, &wsaData);
// Create listening socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Bind
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
// Listen
listen(sListen, 3);
// Create worker thread
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
    // Accept a connection
    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    g_CliSocketArr[g_iTotalConn] = sClient;

    // Allocate a PER_IO_OPERATION_DATA structure
    g_pPerIODataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(
      GetProcessHeap(),
      HEAP_ZERO_MEMORY,
      sizeof(PER_IO_OPERATION_DATA));
    g_pPerIODataArr[g_iTotalConn]->Buffer.len = MSGSIZE;
    g_pPerIODataArr[g_iTotalConn]->Buffer.buf = g_pPerIODataArr[g_iTotalConn]->szMessage;
    g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();
    // Launch an asynchronous operation
    WSARecv(
      g_CliSocketArr[g_iTotalConn],
      &g_pPerIODataArr[g_iTotalConn]->Buffer,
      1,
      &g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd,
      &g_pPerIODataArr[g_iTotalConn]->Flags,
      &g_pPerIODataArr[g_iTotalConn]->overlap,
      NULL);

    g_iTotalConn++;
}

closesocket(sListen);
WSACleanup();
return 0;
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int   ret, index;
DWORD cbTransferred;
while (TRUE)
{
    ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
    if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
    {
      continue;
    }
    index = ret - WSA_WAIT_EVENT_0;
    WSAResetEvent(g_CliEventArr[index]);
    WSAGetOverlappedResult(
      g_CliSocketArr[index],
      &g_pPerIODataArr[index]->overlap,
      &cbTransferred,
      TRUE,
      &g_pPerIODataArr[g_iTotalConn]->Flags);
    if (cbTransferred == 0)
    {
      // The connection was closed by client
      Cleanup(index);
    }
    else
    {
      // g_pPerIODataArr[index]->szMessage contains the received data
      g_pPerIODataArr[index]->szMessage[cbTransferred] = '\0';
      send(g_CliSocketArr[index], g_pPerIODataArr[index]->szMessage,\
        cbTransferred, 0);
      // Launch another asynchronous operation
      WSARecv(
        g_CliSocketArr[index],
        &g_pPerIODataArr[index]->Buffer,
        1,
        &g_pPerIODataArr[index]->NumberOfBytesRecvd,
        &g_pPerIODataArr[index]->Flags,
        &g_pPerIODataArr[index]->overlap,
        NULL);
    }
}
return 0;
}
void Cleanup(int index)
{
closesocket(g_CliSocketArr[index]);
WSACloseEvent(g_CliEventArr[index]);
HeapFree(GetProcessHeap(), 0, g_pPerIODataArr[index]);
if (index < g_iTotalConn - 1)
{
    g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
    g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
    g_pPerIODataArr[index] = g_pPerIODataArr[g_iTotalConn - 1];
}
g_pPerIODataArr[--g_iTotalConn] = NULL;
}
这个模型与上述其他模型不同的是它使用Winsock2提供的异步I/O函数WSARecv。在调用WSARecv时,指定一个WSAOVERLAPPED结构,这个

A chamada não está bloqueando, ou seja, retorna imediatamente. Depois que os dados chegam, o hEvent na estrutura WSAOVERLAPPED especificada é sinalizado. Devido a seguinte declaração

g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent;
使得与该套接字相关联的WSAEVENT对象也被Signaled,所以WSAWaitForMultipleEvents的调用操作成功返回。我们现在应该做的就是用

Chame WSAGetOverlappedResult com a mesma estrutura WSAOVERLAPPED como o parâmetro para chamar WSARecv, para obter as informações relevantes, como o número de bytes transferidos por essa E/S.
Após obter os dados recebidos , envie os dados intactos para o cliente e, em seguida, reative uma operação assíncrona WSARecv.

2. Modelo de I/O sobreposto realizado pela rotina de conclusão

#include <WINSOCK2.H>
#include <stdio.h>
#define PORT    5150
#define MSGSIZE 1024
#pragma comment(lib, "ws2_32.lib")
typedef struct
{
WSAOVERLAPPED overlap;
WSABUF        Buffer;
char          szMessage[MSGSIZE];
DWORD         NumberOfBytesRecvd;
DWORD         Flags; 
SOCKET        sClient;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
DWORD WINAPI WorkerThread(LPVOID);
void CALLBACK CompletionROUTINE(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
SOCKET g_sNewClientConnection;
BOOL   g_bNewConnectionArrived = FALSE;
int main()
{
WSADATA     wsaData;
SOCKET      sListen;
SOCKADDR_IN local, client;
DWORD       dwThreadId;
int         iaddrSize = sizeof(SOCKADDR_IN);
// Initialize Windows Socket library
WSAStartup(0x0202, &wsaData);
// Create listening socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Bind
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
// Listen
listen(sListen, 3);
// Create worker thread
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
    // Accept a connection
    g_sNewClientConnection = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    g_bNewConnectionArrived = TRUE;
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
}
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
while (TRUE)
{
    if (g_bNewConnectionArrived)
    {
      // Launch an asynchronous operation for new arrived connection
      lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
        GetProcessHeap(),
        HEAP_ZERO_MEMORY,
        sizeof(PER_IO_OPERATION_DATA));
      lpPerIOData->Buffer.len = MSGSIZE;
      lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
      lpPerIOData->sClient = g_sNewClientConnection;

      WSARecv(lpPerIOData->sClient,
        &lpPerIOData->Buffer,
        1,
        &lpPerIOData->NumberOfBytesRecvd,
        &lpPerIOData->Flags,
        &lpPerIOData->overlap,
        CompletionROUTINE);      

      g_bNewConnectionArrived = FALSE;
    }
    SleepEx(1000, TRUE);
}
return 0;
}
void CALLBACK CompletionROUTINE(DWORD dwError,
                                DWORD cbTransferred,
                                LPWSAOVERLAPPED lpOverlapped,
                                DWORD dwFlags)
{
LPPER_IO_OPERATION_DATA lpPerIOData = (LPPER_IO_OPERATION_DATA)lpOverlapped;

if (dwError != 0 || cbTransferred == 0)
{
    // Connection was closed by client
closesocket(lpPerIOData->sClient);
HeapFree(GetProcessHeap(), 0, lpPerIOData);
}
else
{
    lpPerIOData->szMessage[cbTransferred] = '\0';
    send(lpPerIOData->sClient, lpPerIOData->szMessage, cbTransferred, 0);

    // Launch another asynchronous operation
    memset(&lpPerIOData->overlap, 0, sizeof(WSAOVERLAPPED));
    lpPerIOData->Buffer.len = MSGSIZE;
    lpPerIOData->Buffer.buf = lpPerIOData->szMessage;    
    WSARecv(lpPerIOData->sClient,
      &lpPerIOData->Buffer,
      1,
      &lpPerIOData->NumberOfBytesRecvd,
      &lpPerIOData->Flags,
      &lpPerIOData->overlap,
      CompletionROUTINE);
}
}
用完成例程来实现重叠I/O比用事件通知简单得多。在这个模型中,主线程只用不停的接受连接即可;辅助线程判断有没有新的客户端连接

é estabelecido, se houver, ativa uma operação WSARecv assíncrona para esse soquete do cliente e, em seguida, chama SleepEx para colocar o encadeamento em um estado de espera alertável para que CompletionROUTINE possa ser chamado pelo kernel após a conclusão da E/S
. Se o thread auxiliar não chamar SleepEx, o kernel não poderá chamar a rotina de conclusão após concluir uma operação de E/S (porque a rotina de conclusão deve ser executada no mesmo thread que
o

完成例程内的实现代码比较简单,它取出接收到的数据,然后将数据原封不动的发送给客户端,最后重新激活另一个WSARecv异步操作。注

Observe que "dados finais" são usados ​​aqui. Quando chamamos WSARecv, o parâmetro lpOverlapped na verdade aponta para uma estrutura PER_IO_OPERATION_DATA que é muito maior do que ele.Além de WSAOVERLAPPED, essa
estrutura também é anexada com informações de estrutura de buffer e também inclui soquetes de cliente e outros dados importantes. informações. Dessa forma, não apenas a estrutura WSAOVERLAPPED é obtida por meio do parâmetro lpOverlapped na rotina de conclusão
, mas também as seguintes informações importantes, incluindo o soquete do cliente e o buffer de dados de recebimento. Este truque da linguagem C será usado quando eu
introduzir .

5. Complete o modelo de porta

“完成端口”模型是迄今为止最为复杂的一种I/O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往
可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NT和Windows 2000操作系统。因其设计的复杂性,只有在你的应用程序需
要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用
“完成端口”模型。要记住的一个基本准则是,假如要为Windows NT或Windows 2000开发高性能的服务器应用,同时希望为大量套接字I/O请
求提供服务(Web服务器便是这方面的典型例子),那么I/O完成端口模型便是最佳选择!(节选自《Windows网络编程》第八章)

完成端口模型是我最喜爱的一种模型。虽然其实现比较复杂(其实我觉得它的实现比用事件通知实现的重叠I/O简单多了),但其效率是

Surpreendente. Quando eu estava na empresa T, uma vez ajudei meus colegas a escrever um programa de teste de desempenho de servidor de correio, usando o modelo de porta. Os resultados mostram que o modelo de porta de conclusão pode atingir um throughput muito alto com apenas um ou dois threads auxiliares no caso de múltiplas conexões (
milhares ). Deixe-me começar com o código:

#include <WINSOCK2.H>
#include <stdio.h>
#define PORT    5150
#define MSGSIZE 1024
#pragma comment(lib, "ws2_32.lib")
typedef enum
{
RECV_POSTED
}OPERATION_TYPE;
typedef struct
{
WSAOVERLAPPED overlap;
WSABUF         Buffer;
char           szMessage[MSGSIZE];
DWORD          NumberOfBytesRecvd;
DWORD          Flags;
OPERATION_TYPE OperationType;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
DWORD WINAPI WorkerThread(LPVOID);
int main()
{
WSADATA                 wsaData;
SOCKET                  sListen, sClient;
SOCKADDR_IN             local, client;
DWORD                   i, dwThreadId;
int                     iaddrSize = sizeof(SOCKADDR_IN);
HANDLE                  CompletionPort = INVALID_HANDLE_VALUE;
SYSTEM_INFO             systeminfo;
LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
// Initialize Windows Socket library
WSAStartup(0x0202, &wsaData);
// Create completion port
CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
// Create worker thread
GetSystemInfo(&systeminfo);
for (i = 0; i < systeminfo.dwNumberOfProcessors; i++)
{
    CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId);
}

// Create listening socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Bind
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
// Listen
listen(sListen, 3);
while (TRUE)
{
    // Accept a connection
    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    // Associate the newly arrived client socket with completion port
    CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);

    // Launch an asynchronous operation for new arrived connection
    lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
      GetProcessHeap(),
      HEAP_ZERO_MEMORY,
      sizeof(PER_IO_OPERATION_DATA));
    lpPerIOData->Buffer.len = MSGSIZE;
    lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
    lpPerIOData->OperationType = RECV_POSTED;
    WSARecv(sClient,
      &lpPerIOData->Buffer,
      1,
      &lpPerIOData->NumberOfBytesRecvd,
      &lpPerIOData->Flags,
      &lpPerIOData->overlap,
      NULL);
}
PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL);
CloseHandle(CompletionPort);
closesocket(sListen);
WSACleanup();
return 0;
}
DWORD WINAPI WorkerThread(LPVOID CompletionPortID)
{
HANDLE                  CompletionPort=(HANDLE)CompletionPortID;
DWORD                   dwBytesTransferred;
SOCKET                  sClient;
LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
while (TRUE)
{
    GetQueuedCompletionStatus(
      CompletionPort,
      &dwBytesTransferred,
      &sClient,
      (LPOVERLAPPED *)&lpPerIOData,
      INFINITE);
    if (dwBytesTransferred == 0xFFFFFFFF)
    {
      return 0;
    }

    if (lpPerIOData->OperationType == RECV_POSTED)
    {
      if (dwBytesTransferred == 0)
      {
        // Connection was closed by client
        closesocket(sClient);
        HeapFree(GetProcessHeap(), 0, lpPerIOData);        
      }
      else
      {
        lpPerIOData->szMessage[dwBytesTransferred] = '\0';
        send(sClient, lpPerIOData->szMessage, dwBytesTransferred, 0);

        // Launch another asynchronous operation for sClient
        memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));
        lpPerIOData->Buffer.len = MSGSIZE;
        lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
        lpPerIOData->OperationType = RECV_POSTED;
        WSARecv(sClient,
          &lpPerIOData->Buffer,
          1,
          &lpPerIOData->NumberOfBytesRecvd,
          &lpPerIOData->Flags,
          &lpPerIOData->overlap,
          NULL);
      }
    }
}
return 0;
}

Primeiro, vamos falar sobre o thread principal:
1. Crie um objeto de porta
2. Crie um thread de trabalho (aqui o número de threads de trabalho é determinado de acordo com o número de CPUs, de modo a obter o melhor desempenho)
3. Crie uma escuta socket , vinculação, escuta e, em seguida, o programa entra no loop
4. No loop, faço o seguinte:
(1) Aceito uma conexão do cliente
(2). Vinculo o socket do cliente e a porta de conclusão juntos (CreateIoCompletionPort ainda é chamado , mas desta vez o efeito é diferente), observe que logicamente falando, o terceiro parâmetro passado
para CreateIoCompletionPort deve ser uma chave de conclusão neste momento , de modo geral, o programa passa um endereço de estrutura de dados de identificador único, os dados de identificador único
contêm informações relacionadas para a conexão do cliente, já que nos preocupamos apenas com o identificador de soquete, então passe diretamente o identificador de soquete como a chave de conclusão
; para receber dados segue o objeto WSAOVERLAPPED.Além disso, existem informações importantes como o
tipo .

No loop da thread de trabalho, nós

1. Chame GetQueuedCompletionStatus para obter as informações relevantes deste I/O (como identificador de soquete, número de bytes transferidos, endereço da estrutura de dados de I/O único, etc.) 2. Encontre o buffer de dados de recebimento por meio de dados de I/O único
área da estrutura e, em seguida, envie os dados intactos para o cliente
3. Acione uma operação assíncrona WSARecv novamente

Seis. Comparação de cinco modelos de E/S

Vou comparar a partir dos seguintes aspectos

* Existe um limite de 64 conexões por thread

如果在选择模型中没有重新定义FD_SETSIZE宏,则每个fd_set默认可以装下64个SOCKET。同样的,受MAXIMUM_WAIT_OBJECTS宏的影响,

A seleção de evento e a E/S sobreposta implementadas com notificação de evento têm no máximo 64 conexões por thread. Se o número de conexões for dezenas de milhares, os soquetes do cliente devem ser agrupados, o que inevitavelmente
aumentará complexidade do programa.

相反,异步选择、用完成例程实现的重叠I/O和完成端口不受此限制。

*Número de threads Modelos
diferentes de seleção assíncrona requerem pelo menos 2 threads. Uma thread principal e uma thread secundária. Da mesma forma, se o número de conexões for maior que 64, aumentará o número de threads para o modelo de seleção, seleção de evento e E/S sobreposta com notificação
de evento .

* Complexidade de implementação
Minha opinião pessoal é que, em termos de dificuldade de implementação, seleção assíncrona < seleção < E/S sobreposta com rotina de conclusão < seleção de evento < porta de conclusão < E/S sobreposta com notificação de evento

*Desempenho
Como o conjunto de leitura precisa ser redefinido todas as vezes no modelo de seleção e todos os soquetes são testados um por um após o retorno da função de seleção, sinto que a eficiência é relativamente ruim;
a sobreposição entre a porta de conclusão e a rotina de conclusão I/O basicamente não envolve dados globais, a eficiência deve ser a mais alta e a porta de conclusão é ainda maior no caso de multiprocessadores; a seleção de evento e a I/O sobreposta
realizada pela notificação de evento são implementadas usando WSAWaitForMultipleEvents Sinto que a eficiência é quase a mesma; quanto à seleção assíncrona, não é fácil comparar.

Análise de codificação de erro de protocolo Winsock

     Windows协议可以应用到很多通用环境。例如,要检查网络,可以使用Windows套接字(WinSock)脚本来查看缓冲区发送和接收到的实际数据。WinSock类型还可以用于录制其他低级通信会话。通过他可以录制回放Vuser类型不支持的应用协议。使用VuGen,您可以录制应用程序对 Winsock.dll或Wsock32.dll的API调用,但是这种协议的错误提示代表是什么呢!其实每次winsock报的错误,有很多都是 winsock协议的错误编号!loadrunner把这写错误编号在调试信息中显示出来。下边我把一些winsock协议的错误编号都是什么意思给大家列举以下,希望对大家有帮助!

10004—Chamada de função WSAEINTR interrompida. Esse erro indica que uma chamada foi interrompida à força devido a uma chamada para WSACancelBlockingCall.
10009—Erro de manipulação do arquivo WSAEBADF. Este erro indica que o identificador de arquivo fornecido é inválido. Em MicrosoftWindowsCE, a função de soquete pode retornar esse erro, indicando que a porta serial compartilhada está em um estado "ocupado".
10013—Permissão WSAEACCES negada. Uma operação foi tentada em um soquete, mas foi proibida. Esse erro ocorre se você tentar usar um endereço de transmissão em sendto ou WSASendTo, mas não tiver definido permissões de transmissão com as opções setsockopt e SO_BROADCAST.
10014—O endereço WSAEFAULT é inválido. O endereço do ponteiro passado para a função Winsock é inválido. Este erro também será gerado se o buffer especificado for muito pequeno.
10022 - O parâmetro WSAEINVAL é inválido. Um parâmetro inválido foi especificado. Por exemplo, esse erro ocorre se um código de controle inválido for especificado para uma chamada WSAIoctl. Além disso, também pode indicar que o estado atual do soquete está errado, como chamar accept ou WSAAccept em um soquete que não está atendendo no momento.10024-WSAEMFILE tem muitos arquivos abertos. Solicitar muitos soquetes abertos. Normalmente, os provedores da Microsoft são limitados apenas pela quantidade de recursos disponíveis no sistema.
10035—O recurso WSAEWOULDBLOCK está temporariamente indisponível. Para soquetes sem bloqueio, esse erro geralmente é retornado se a operação solicitada não puder ser executada imediatamente. Por exemplo, chamar connect em um soquete não suspenso retorna esse erro. Porque a solicitação de conexão não pode ser executada imediatamente.
10036—A operação WSAEINPROGRESS está em andamento. Uma operação sem bloqueio está em andamento. Geralmente, esse erro não deve aparecer, a menos que você esteja desenvolvendo um aplicativo Winsock de 16 bits.
10037—A operação WSAEALREADY foi concluída. Normalmente, esse erro é gerado quando uma operação que já está em andamento é tentada em um soquete sem bloqueio. Por exemplo, em um soquete sem bloqueio que já esteja em processo de conexão, chame connect ou WSAConnect novamente. Além disso, quando o provedor de serviços estiver executando a função de retorno de chamada (para a função Winsock que suporta a rotina de retorno de chamada) , também aparecerá esse erro.
10038—WSAENOTSOCK Uma operação de soquete em um soquete inválido. Qualquer função Winsock que usa um identificador SOCKET como um parâmetro retornará esse erro. Indica que o identificador de soquete fornecido não é válido.
10039 - WSAEDESTADDRREQ requer um endereço de destino. Este erro indica que nenhum endereço específico foi fornecido. Por exemplo, se o endereço de destino for definido como INADDR_ANY (qualquer endereço) ao chamar sendto, esse erro será retornado.
10040 - A mensagem WSAEMSGSIZE é muito longa. Este erro tem muitos significados. Esse erro ocorre se uma mensagem for enviada em um soquete de datagrama muito grande para o buffer interno. Para outro exemplo, devido às limitações da própria rede, se uma mensagem for muito longa, esse erro também ocorrerá. Por fim, se o buffer for muito pequeno para receber a mensagem após o recebimento do datagrama, esse erro também será gerado.
10041—O tipo de protocolo de soquete WSAEPROTOTYPE está incorreto. O protocolo especificado na chamada de soquete ou WSASocket não oferece suporte ao tipo de soquete especificado.
Por exemplo, se for necessário estabelecer um soquete IP do tipo SOCK_STREAM e o protocolo especificado for IPPROTO_UDP, esse erro ocorrerá.
10042—Erro de opção de protocolo WSAENOPROTOOPT. Indica que a opção ou classe de soquete especificada em uma chamada getsockopt ou setsockopt é desconhecida, sem suporte ou inválida.
10043 - Protocolo não suportado pelo WSAEPROTONOSUPPORT. O protocolo solicitado não está instalado ou não possui implementação correspondente no sistema. Por exemplo, se o TCP/IP não estiver instalado no sistema, esse erro será gerado ao tentar estabelecer um soquete TCP ou UDP.
10044—WSAESOCKTNOSUPPORT Tipo de soquete não suportado. Não há suporte de tipo de soquete concreto correspondente para a família de endereço especificada. Por exemplo, esse erro será gerado quando um tipo de soquete SOCK_RAW for solicitado de um protocolo que não oferece suporte a soquetes brutos.
10045 - Operação não suportada por WSAEOPNOTSUPP. Indica que a tentativa de operação não é suportada para o objeto especificado. Normalmente, esse erro ocorre ao tentar chamar o Winsock em um soquete que não oferece suporte à chamada de funções do Winsock. Por exemplo, esse erro ocorre ao chamar accept ou WSAAccept em um soquete de datagrama.
10046—Família de protocolo não suportada por WSAEPFNOSUPPORT. A família de protocolo solicitada não existe ou não está instalada no sistema. Na maioria dos casos, esse erro é intercambiável com WSAEAFNOSUPPORT (os dois são equivalentes); o último ocorre com mais frequência.
10047—A família de endereços WSAEAFNOSUPPORT não suporta a operação solicitada. Este erro ocorre ao tentar executar uma operação não suportada pelo tipo de soquete. Por exemplo, esse erro ocorre ao chamar sendto ou WSASendTo em um soquete do tipo SOCK_STREAM. Além disso, ao chamar a função de soquete ou WSASocket, se uma família de endereço inválido, tipo de soquete e combinação de protocolo forem solicitados ao mesmo tempo, esse erro também ocorrerá.
10048—O endereço WSAEADDRINUSE está em uso. Normalmente, apenas um endereço de soquete é permitido por soquete (por exemplo, um endereço de soquete IP consiste no endereço IP local e no número da porta). Esse erro geralmente está relacionado às três funções de ligação, conexão e WSAConnect. A opção de soquete SO_REUSEADDR pode ser definida na função setsockopt para permitir que vários soquetes acessem o mesmo endereço IP local e número de porta (consulte o Capítulo 9 para obter detalhes).
10049—WSAEADDRNOTAVAIL não pode alocar o endereço solicitado. Este erro ocorre quando um endereço especificado em uma chamada de API não é válido para essa função. Por exemplo, se um endereço IP for especificado na chamada de ligação, mas não houver uma interface IP local correspondente, esse erro será gerado. Além disso, quando a porta 0 é especificada para o computador remoto ser conectado por meio das quatro funções de conexão, WSAConnect, sendto, WSASendTo e WSAJoinLeaf, esse erro também ocorrerá.
10050—WSAENETDOWN A rede está desconectada. Ao tentar executar uma ação, a conexão de rede foi perdida. Isso pode ocorrer devido a um bug na pilha de rede, uma interface de rede com defeito ou um problema com a rede local.
10051—Rede WSAENETUNREACH inacessível. Ao tentar executar uma ação, a rede de destino foi considerada inacessível (inacessível). Isso significa que o host local não sabe como alcançar um host remoto. Em outras palavras, atualmente não há nenhuma rota conhecida para esse host de destino.
10052—WSAENETRESET desconectado durante a reinicialização da rede. A conexão de rede foi interrompida porque uma operação de manutenção de atividade detectou um erro.
Se a opção SO_KEEPALIVE for definida por meio da função setsockopt em uma conexão inválida, esse erro também ocorrerá.
10053—O software WSAECONNABORTED causou o cancelamento da conexão. Uma conexão estabelecida foi cancelada devido a um erro de software. Normalmente, isso significa que a conexão foi cancelada devido a um erro de protocolo ou tempo limite.
10054—WSAECONNRESET A conexão foi redefinida pelo par. Uma conexão estabelecida foi encerrada à força pelo host remoto. Este erro ocorre quando um processo no host remoto é interrompido de forma anormal (devido a um conflito de memória ou falha de hardware) ou se um fechamento forçado for executado no soquete. Para um fechamento forçado, um soquete pode ser configurado com a opção de soquete SO_LINGER e setsockopt (consulte o Capítulo 9 para obter detalhes).
10055—WSAENOBUFS não tem espaço de buffer. A operação solicitada não pode ser executada porque o sistema não possui espaço de buffer suficiente.
10056 — O soquete WSAEISCONN já está conectado. Indica que foi feita uma tentativa de estabelecer uma conexão em um soquete já estabelecido. Observe que esse erro é possível para soquetes de datagrama e fluxo. Ao usar um soquete de datagrama, se o endereço de um terminal tiver sido associado à comunicação do datagrama por meio da chamada de conexão ou WSAConnect com antecedência, tentar chamar sendto ou WSASendTo novamente mais tarde causará esse erro.
10057—O soquete WSAENOTCONN não foi conectado. Esse erro ocorre se uma solicitação para enviar ou receber dados for feita em um soquete "orientado à conexão" que ainda não estabeleceu uma conexão.
10058 - Não é possível enviar após o fechamento do soquete WSAESHUTDOWN. Indica que o soquete foi parcialmente fechado por uma chamada para desligamento, mas a operação de transmissão e recepção de dados é solicitada posteriormente. Deve-se observar que este erro ocorrerá apenas na direção do fluxo de dados que foi fechado. Por exemplo, se o desligamento for chamado depois que os dados forem enviados, qualquer chamada de envio de dados subsequente gerará esse erro.
10060—Tempo esgotado da conexão WSAETIMEDOUT. Este erro ocorre quando uma solicitação de conexão é feita e o computador remoto não responde adequadamente (ou não responde) após um período de tempo especificado. Para receber esse erro, você geralmente precisa primeiro definir as opções SO_SNDTIMEO e SO_RCVTIMEO no soquete e, em seguida, chamar as funções connect e WSAConnect.
Para obter detalhes sobre como definir as opções SO_SNDTIMEO e SO_RCVTIMEO nos soquetes, consulte o Capítulo 9.
10061—WSAECONNREFUSED Conexão recusada. A conexão não pôde ser estabelecida porque foi recusada pela máquina de destino. Isso geralmente ocorre porque nenhum aplicativo na máquina remota atende à conexão naquele endereço.
10064—WSAEHOSTDOWN O host está inativo. Esse erro indica que a operação falhou porque o host de destino está inativo. No entanto, é mais provável que o aplicativo receba um erro WSAETIMEDOUT (tempo limite de conexão esgotado) neste momento, porque o desligamento da outra parte geralmente ocorre ao tentar estabelecer uma conexão. 10065—WSAEHOSTUNREACH Nenhuma rota para o host. O aplicativo tentou acessar um host inacessível. O erro é semelhante a WSAENETUNREACH.10067 - Muitos processos WSAEPROCLIM. Alguns provedores de serviços Winsock limitam o número de processos que podem acessá-los simultaneamente.
10091—WSASYSNOTREADY O subsistema de rede não está disponível. Este erro é retornado ao chamar WSAStartup se o provedor não estiver funcionando corretamente (porque o sistema subjacente que fornece o serviço está indisponível).
10092—WSAVERNOTSUPPORTEDWinsock.dll é a versão errada. Indica que a versão do provedor Winsock solicitada não é suportada.
10093—WSANOTINITIALISEDWinsock não foi inicializado. Uma chamada para WSAStartup não foi concluída com êxito.
10101—WSAEDISCON está fechando normalmente. Esse erro é retornado por WSARecv e WSARecvFrom, indicando que o host remoto iniciou um desligamento normal. O erro está em um protocolo "orientado para mensagens", como o ATM.
10102 —WSAENOMORE não pôde localizar mais nenhum registro. Este erro é retornado da função WSALookupServiceNext, indicando que não há mais registros deixados. Esse erro geralmente é usado de forma intercambiável com WSA_E_NO_MORE. Em seu aplicativo, você deve verificar esse erro, bem como WSA_E_NO_MORE.10103—WSAECANCELLED A operação foi cancelada. Este erro indica que uma chamada para WSALookupServiceEnd (Abortar Serviço) foi feita enquanto a chamada WSALookupServiceNext ainda estava sendo processada. Neste ponto, WSALookupServiceNext retornará esse erro. Esse código de erro pode ser usado de forma intercambiável com WSA_E_CANCELLED. Como um aplicativo, você deve verificar esse erro, bem como WSA_E_CANCELLED.10104—WSAEINVALIDPROCTABLE A tabela de chamada de processo é inválida. Esse erro geralmente é retornado por um provedor de serviços se a tabela de processos contiver uma entrada inválida. Para obter detalhes sobre provedores de serviços, consulte o Capítulo 14.
10105—WSAEINVALIDPROVIDER Provedor de serviços inválido. Esse erro está associado ao provedor de serviços e ocorre sob a premissa de que o provedor não pode criar a versão correta do Winsock para funcionar corretamente.
10106—WSAEPROVIDERFAILEDINIT Falha na inicialização do provedor. Esse erro está associado a um provedor de serviços e geralmente é visto quando o provedor não consegue carregar uma DLL necessária.
10107—WSASYSCALLFAILURE A chamada do sistema falhou. Uma chamada de sistema que deveria nunca falhar falhou tristemente.
10108—WSASERVICE_NOT_FOUND Nenhum serviço encontrado. Este erro está geralmente relacionado com as funções de registo e resolução de nomes, que são geradas ao consultar o serviço (o Capítulo 10 explica estas funções em detalhe). Este erro indica que o serviço solicitado não foi encontrado no namespace fornecido.
10109—WSATYPE_NOT_FOUND O tipo da classe não pôde ser encontrado. Este erro também está associado às funções de registro e resolução de nomes e ocorre durante o processamento de classes de serviço (ServiceClass). Se uma instância de serviço for registrada, ela deverá fazer referência a um serviço que foi instalado anteriormente por meio de WSAInstallServiceClass.
10110 —WSA_E_NO_MORE Não foram encontrados mais registros. Este erro é retornado da chamada WSALookupServiceNext, indicando que não há registros restantes. Esse erro geralmente é usado de forma intercambiável com WSAENOMORE. Como um aplicativo, você deve verificar esse erro, bem como WSAENOMORE.10111—WSA_E_CANCELLED A operação foi cancelada. Esse erro indica que uma chamada para WSALookupServiceEnd (encerrar serviço) foi emitida enquanto a chamada para WSALookupServiceNext ainda não havia sido concluída. Assim, WSALookupServiceNext retornará o erro. Esse código de erro pode ser usado de forma intercambiável com WSAECANCELLED. Como um aplicativo, você deve verificar esse erro, bem como a consulta WSAECANCELLED.10112—WSAEREFUSED rejeitada. Uma operação de consulta de banco de dados falhou porque foi rejeitada ativamente.
11001—WSAHOST_NOT_FOUND Host não encontrado. Este erro é gerado ao chamar gethostbyname e gethostbyaddr, indicando que um host de resposta autoritativo (AuthoritativeAnswerHost) não foi encontrado.
11002—WSATRY_AGAIN Host não autorizado não encontrado. Este erro também é gerado ao chamar gethostbyname e gethostbyaddr, indicando que um host não autorizado não foi encontrado ou uma falha de servidor foi encontrada.
11003—WSANO_RECOVERY encontrou um erro irrecuperável. Este erro também é gerado ao chamar gethostbyname e gethostbyaddr, indicando que um erro irrecuperável foi encontrado e a operação deve ser tentada novamente.
11004—WSANO_DATA não encontrou um registro de dados do tipo solicitado. Este erro também é gerado ao chamar gethostbyname e gethostbyaddr, indicando que embora o nome fornecido seja válido, não foi encontrado nenhum registro de dados correspondente ao tipo solicitado.
11005—WSA_QOS_RECEIVERS Pelo menos uma mensagem de reserva chegou. Este valor está intimamente relacionado à Qualidade de Serviço IP (QoS) e não é realmente um "erro" (consulte o Capítulo 12 para obter detalhes sobre QoS). Indica que pelo menos um processo na rede deseja receber tráfego de QoS.
11006—WSA_QOS_SENDERS Pelo menos uma mensagem de caminho chegou. Esse valor está associado à QoS, que na verdade é mais como uma mensagem de relatório de status. Indica que na rede, pelo menos um processo deseja enviar dados de QoS.
11007—WSA_QOS_NO_SENDERS Sem remetentes de QoS. Este valor está associado ao QoS e indica que nenhum processo está mais interessado em enviar dados de QoS. Consulte o Capítulo 12 para obter uma lista completa do que acontece quando esse erro ocorre.
11008 — WSA_QOS_NO_RECEIVERS não possui receptores de QoS. Este valor está associado a QoS e indica que nenhum processo está mais interessado em receber dados de QoS. Consulte o Capítulo 12 para obter uma descrição completa desse erro.
11009—WSA_QOS_REQUEST_CONFIRMED A solicitação de reserva foi confirmada. O aplicativo QoS pode enviar uma solicitação com antecedência, esperando receber uma notificação após aprovar sua solicitação de reserva de largura de banda de rede. Se tal solicitação foi feita, uma vez aprovada, tal mensagem será recebida. Consulte o Capítulo 12 para obter uma descrição detalhada desta mensagem.
11010—WSA_QOS_ADMISSION_FAILURE Falta de erro de recursos. Não há recursos suficientes para satisfazer a solicitação de largura de banda de QoS.
11011—WSA_QOS_POLICY_FAILURE O certificado é inválido. Indica que, ao enviar a solicitação de reserva de QoS, o usuário não possui a autoridade correta ou o certificado fornecido é inválido.
11012—WSA_QOS_BAD_STYLE Estilo desconhecido ou conflitante. Os programas aplicativos de QoS podem estabelecer diferentes estilos de filtro para uma sessão especificada. Este erro indica que o tipo de estilo especificado é desconhecido ou conflitante. Consulte o Capítulo 12 para obter uma descrição detalhada dos estilos de filtro.
11013—WSA_QOS_BAD_OBJECT Estrutura FILTERSPEC inválida ou objeto específico do provedor. Esse erro será retornado se a estrutura FILTERSPEC fornecida para o objeto QoS for inválida ou se o buffer específico do provedor for inválido, consulte o Capítulo 12 para obter detalhes.
11014 — Há um problema com WSA_QOS_TRAFFIC_CTRL_ERRORFLOWSPEC. Se o componente de controle de comunicação descobrir que há um problema com o parâmetro FLOWSPEC especificado (passado como um membro do objeto QoS), ele retornará esse erro.
11015—WSA_QOS_GENERIC_ERROR Erro de QoS genérico. Este é um erro relativamente geral; se nenhum outro erro de QoS for aplicável, esse erro será retornado.
6—WSA_INVALID_HANDLE O objeto de evento especificado é inválido. Se a função Winsock correspondente à função Win32 for usada, tal erro Win32 pode ocorrer. Isso indica que um identificador passado para WSAWaitForMultipleEvents é inválido.
8—WSA_NOT_ENOUGH_MEMORY Memória insuficiente. Este erro do Win32 indica que a quantidade de memória é insuficiente para concluir a operação especificada.
87—WSA_INVALID_PARAMETER Um ou mais parâmetros são inválidos. Este erro do Win32 indica que um parâmetro inválido foi passado dentro de uma função. Se o parâmetro de contagem de eventos for inválido, esse erro também ocorrerá ao executar WSAWaitForMultipleEvents.
258—WSA_WAIT_TIMEOUT A operação atingiu o tempo limite. Esse erro do Win32 indica que uma operação de E/S sobreposta não foi concluída dentro do tempo alocado.
995—WSA_OPERATION_ABORTED Uma operação de sobreposição foi cancelada. Este erro do Win32 indica que uma operação de E/S sobreposta foi cancelada devido ao fechamento de um soquete.
Além disso, este erro também pode ocorrer ao executar o comando de controle de E/S SIO_FLUSH.
996—WSA_IO_INCOMPLETE O objeto de evento de E/S sobreposto não está no estado de sinalização. Este erro do Win32 também está intimamente relacionado a operações de E/S sobrepostas. Ele é gerado quando a função WSAGetOverlappedResults é chamada, indicando que a operação de E/s sobreposta ainda não foi concluída.
997—WSA_IO_PENDING Uma operação de sobreposição será concluída posteriormente. Ao usar a função Winsock para emitir uma operação de E/S sobreposta, se tal erro Win32 ocorrer, isso indica que a operação não foi concluída e será concluída posteriormente.

Acho que você gosta

Origin blog.csdn.net/u012842273/article/details/70318991
Recomendado
Clasificación