Pergunta

Qual é a maneira normal pela qual as pessoas que escrevem código de rede em Delphi usam E/S de soquete assíncrono sobreposto no estilo Windows?

Aqui está minha pesquisa anterior sobre esta questão:

O Índia os componentes parecem totalmente síncronos.Por outro lado, embora a unidade ScktComp use WSAAsyncSelect, ela basicamente apenas assincroniza um aplicativo de soquete multiplexado estilo BSD.Você é descartado em um retorno de chamada de evento único, como se tivesse acabado de retornar de select() em um loop, e precisa fazer toda a navegação da máquina de estado sozinho.

A situação do .NET é consideravelmente melhor, com Socket.BeginRead/Socket.EndRead, onde a continuação é passada diretamente para Socket.BeginRead, e é aí que você retoma.Uma continuação codificada como encerramento obviamente tem todo o contexto que você precisa e muito mais.

Foi útil?

Solução

Outras dicas

Descobri que Indy, embora seja um conceito mais simples no início, é difícil de gerenciar devido à necessidade de eliminar soquetes para liberar threads no encerramento do aplicativo.Além disso, a biblioteca Indy parou de funcionar após uma atualização de patch do sistema operacional.ScktComp funciona bem para meu aplicativo.

@Roddy - Soquetes síncronos não são o que procuro.Gravar um thread inteiro para uma conexão possivelmente de longa duração significa limitar a quantidade de conexões simultâneas ao número de threads que seu processo pode conter.Como os threads usam muitos recursos - espaço de endereço de pilha reservado, memória de pilha comprometida e transições de kernel para trocas de contexto - eles não são escalonados quando você precisa suportar centenas de conexões, muito menos milhares ou mais.

Qual é a maneira como as pessoas que escrevem o código de rede em Delphi usam a E/S de soquete assíncrono sobreposto ao Windows?

Bem, Indy tem sido a biblioteca 'padrão' para E/S de soquete há muito tempo - e é baseada no bloqueio de soquetes.Isso significa que se você deseja um comportamento assíncrono, use threads adicionais para conectar/ler/gravar dados.Na minha opinião, isso é realmente uma grande vantagem, pois não há necessidade de gerenciar qualquer tipo de navegação na máquina de estado ou de se preocupar com processos de retorno de chamada ou coisas semelhantes.Acho que a lógica do meu thread de 'leitura' é menos confusa e muito mais portátil do que os soquetes sem bloqueio permitiriam.

Índia 9 tem sido basicamente à prova de bombas, rápido e confiável para nós.No entanto, a mudança para a Indy 10 de Tiburon está me causando um pouco de preocupação.

@Mike:"...a necessidade de eliminar soquetes para liberar threads...".

Isso fez "hein?" Até me lembrar de nossa biblioteca de threading, use uma técnica baseada em exceção para matar threads 'esperando' com segurança.Nós chamamos QueueUserAPC para enfileirar uma função que gera uma exceção C++ (NÃO derivada da classe Exception) que só deve ser capturada por nosso procedimento wrapper de thread.Todos os destruidores são chamados para que todos os threads terminem de forma limpa e organizados na saída.

"Soquetes síncronos não são o que procuro."

Entendido - mas acho que nesse caso a resposta à sua pergunta original é que simplesmente não existe um Delphi idioma para soquete assíncrono IO porque, na verdade, é um requisito altamente especializado e incomum.

Como questão secundária, você pode achar esses links interessantes.Ambos são um pouco antigos e mais * nxy que o Windows.A segunda implica que - no ambiente certo - os threads podem não ser tão ruins quanto você pensa.

O problema C10K

Por que os eventos são uma má ideia (para servidores de alta simultaneidade)

@Chris Miller - O que você afirmou em sua resposta é factualmente impreciso.

O assíncrono de estilo de mensagem do Windows, conforme disponível por meio de WSAAsyncSelect, é de fato uma solução alternativa para a falta de um modelo de threading adequado nos dias do Win 3.x.

O início/fim do .NET, no entanto, é não usando threads extras.Em vez disso, ele está usando E/S sobreposta, usando o argumento extra em WSASend/WSARecv, especificamente a rotina de conclusão sobreposta, para especificar a continuação.

Isso significa que o estilo .NET aproveita o suporte de E/S assíncrona do sistema operacional Windows para evitar queimando um thread bloqueando um soquete.

Como os threads são geralmente caros (a menos que você especifique um tamanho de pilha muito pequeno para CreateThread), ter threads bloqueados em soquetes impedirá que você aumente para 10.000 conexões simultâneas.

É por isso que é importante que a E/S assíncrona seja usada se você quiser escalar, e também porque o .NET é não, repito, é não, simplesmente "usando threads, [...] apenas gerenciados pelo Framework".

@Roddy - Já li os links que você aponta, ambos são referenciados na apresentação de Paul Tyma "Milhares de Threads e Bloqueio de E/S - A maneira antiga de escrever servidores Java é nova novamente".

Algumas das coisas que não saltam necessariamente da apresentação de Paul, no entanto, são que ele especificou -Xss:48k para a JVM na inicialização e que ele está assumindo que a implementação NIO da JVM é eficiente para que seja válida. comparação.

Indy faz não especifique um tamanho de pilha igualmente reduzido e fortemente restrito.Não há chamadas para BeginThread (a rotina de criação de thread RTL do Delphi, que você deve usar para tais situações) ou CreateThread (a chamada WinAPI bruta) na base de código Indy.

O tamanho da pilha padrão é armazenado no PE e, para o compilador Delphi, o padrão é 1 MB de espaço de endereço reservado (o espaço é confirmado página por página pelo sistema operacional em blocos de 4K;na verdade, o compilador precisa gerar código para tocar nas páginas se houver >4K de locais em uma função, porque a extensão é controlada por falhas de página, mas apenas para a página mais baixa (guarda) na pilha).Isso significa que você ficará sem espaço de endereço após no máximo 2.000 threads simultâneos manipulando conexões.

Agora, você pode alterar o tamanho da pilha padrão no PE usando a diretiva {$M minStackSize [,maxStackSize]}, mas isso afetará todos tópicos, incluindo o tópico principal.Espero que você não faça muita recursão, porque 48K ou (semelhante) não é muito espaço.

Agora, se Paul está certo sobre o não desempenho de E/S assíncrona para Windows em particular, não tenho 100% de certeza - teria que medi-lo para ter certeza.O que eu sei, entretanto, é que os argumentos sobre a programação encadeada ser mais fácil do que a programação assíncrona baseada em eventos apresentam uma falsa dicotomia.

O código assíncrono não precisar ser baseado em eventos;ele pode ser baseado em continuação, como no .NET, e se você especificar um encerramento como sua continuação, o estado será mantido gratuitamente para você.Além disso, a conversão de código de estilo de thread linear para código assíncrono de estilo de passagem de continuação pode ser mecânica por um compilador (a transformação CPS é mecânica), portanto, também não há custo na clareza do código.

Existem componentes de soquete IOCP (portas de conclusão) gratuitos: http://www.torry.net/authorsmore.php?id=7131 (código fonte incluído)

"Por Naberegnyh Sergey N..Servidor de soquete de alto desempenho com base na porta de conclusão do Windows e no uso de extensões de soquete do Windows.IPv6 suportado."

eu descobri enquanto procurava melhores componentes/biblioteca para reestruturar meu pequeno servidor de mensagens instantâneas.Ainda não experimentei, mas parece bem codificado como primeira impressão.

Indy usa soquetes síncronos porque é uma forma mais simples de programar.O bloqueio de soquete assíncrono foi algo adicionado à pilha do Winsock na época do Windows 3.x.O Windows 3.x não suportava threads e não era possível executar E/S de soquete sem threads.Para obter algumas informações adicionais sobre por que Indy usa o modelo de bloqueio, consulte Este artigo.

As chamadas .NET Socket.BeginRead/EndRead estão usando threads, são gerenciadas apenas pelo Framework e não por você.

@Roddy, o Indy 10 vem junto com o Delphi desde o Delphi 2006.Descobri que migrar da Indy 9 para a Indy 10 é uma tarefa simples.

Com as classes ScktComp, você precisa usar um servidor ThreadBlocking em vez de um tipo de servidor NonBlocking.Use o evento OnGetThread para entregar o parâmetro ClientSocket para um novo thread de sua criação.Depois de instanciar uma instância herdada de TServerClientThread, você criará uma instância de TWinSocketStream (dentro do thread) que poderá usar para ler e gravar no soquete.Este método evita que você tente processar dados no manipulador de eventos.Esses threads podem existir apenas pelo curto período necessário para leitura ou gravação, ou permanecer durante o período com a finalidade de serem reutilizados.

O assunto de escrever um servidor de soquete é bastante vasto.Existem muitas técnicas e práticas que você pode optar por implementar.O método de leitura e gravação no mesmo soquete do TServerClientThread é direto e adequado para aplicativos simples.Se você precisar de um modelo para alta disponibilidade e alta simultaneidade, precisará examinar padrões como o padrão Proactor.

Boa sorte!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top