Pergunta

Estamos escrevendo um cliente e um servidor a fazer (o que eu pensava ser) comunicações de rede bastante simples. Os clientes Mulitple se conectam ao servidor que deve enviar os dados de volta a todos os outros clientes.

O servidor apenas fica em um bloqueio select O loop aguardando o tráfego e, quando chegar, envia os dados para os outros clientes. Isso parece funcionar muito bem.

O problema é o cliente. Em resposta a uma leitura, às vezes ele quer escrever.

No entanto, descobri que, se eu usar:

 rv = select(fdmax + 1, &master_list, NULL, NULL, NULL);

Meu código será bloqueado até que haja novos dados para ler. Mas às vezes (assíncrono, de outro tópico) terei novos dados para escrever no tópico de comunicação de rede. Então, eu quero que minha seleção acorde periodicamente e deixe -me verificar se há dados para escrever, como:

if (select(....) != -1)
{
  if (FD_SET(sockfd, &master_list))
     // handle data or disconnect
  else
     // look for data to write and write() / send() those.
}

Tentei definir o modo de seleção para pesquisar (ou ridiculamente curto tempo limite) com:

// master list contains the sockfd from the getaddrinfo/socket/connect seq
struct timeval t;
memset(&t, 0, sizeof t);
rv = select(fdmax + 1, &master_list, NULL, NULL, &t);

Mas descobriram que o cliente nunca recebe dados recebidos.

Eu também tentei definir o soquete FD para não ser bloqueado, como:

fcntl(sockfd, F_SETFL, O_NONBLOCK);

Mas isso não resolve o problema:

  1. Se meu cliente select() não tem struct timeval, ler dados funciona, mas nunca se desbloqueia para me deixar procurar dados graváveis.
  2. Se meu cliente select() tem um timeval Para fazer com que a pesquisa, nunca indica que existem dados de entrada para ler, e meu aplicativo congela pensando que não há conexão de rede feita (apesar do fato de que todas as outras chamadas de função foram bem -sucedidas)

Algum indicador sobre o que eu poderia estar fazendo de errado? Não é possível fazer leitura de leitura no mesmo soquete (não acredito que seja verdade).

(Editar: a resposta correta e a coisa que me lembrei no servidor, mas não no cliente, é ter um segundo fd_set e copiar master_list antes de cada chamada para selecionar ():

// declare and FD_ZERO read_fds:
// put sockfd in master_list

while (1)
{
   read_fds = master_list;
   select(...);

   if (FD_ISSET(read_fds))
     ....
   else
     // sleep or otherwise don't hog cpu resources
}

)

Foi útil?

Solução

Tudo parece bem, exceto a linha onde você faz if (FD_SET(sockfd, &master_list)). Eu tenho uma estrutura de código muito semelhante e usei FD_ISSET. Você deveria testar se a lista está definida, para não defini -la novamente. Fora isso, não vejo mais nada.

Editar. Além disso, tenho o seguinte para o tempo limite:

timeval listening_timeout;
listening_timeout.tv_sec = timeout_in_seconds;
listening_timeout.tv_usec = 0;

Talvez haja um problema se você o definir para 0 (como você parece estar fazendo?)

Edit2. Lembrei -me de ter encontrado um problema estranho quando não estava limpando o conjunto de leitura depois que o seleto saiu e antes de inseri -lo novamente. Eu tive que fazer algo como:

FD_ZERO(&sockfd);
FD_SET(sockfd, &rd);

Antes de eu estar entrando select. Não me lembro por que.

Outras dicas

Parece que me lembro de um truque sobre a criação e o compartilhamento de um arquivo de leitura/gravação entre o thread de rede e o encadeamento principal que é adicionado aos descritores na chamada selecionada. Este FD tem um byte escrito pelo tópico principal quando tem algo a enviar. O Write Wakes Up Up Network Thread na chamada Selecionar e o thread de rede é agarrado os dados de um buffer compartilhado e o grava na rede e depois volte a dormir na seleção.

Desculpe se isso é um pouco vago e sem código ... e minha memória pode estar incorreta .. para que outros possam ter que guiá -lo ainda mais.

Não vejo nada de errado com seu código, então deve funcionar. Se você não conseguir fazê -lo funcionar, uma maneira de contorná -lo seria criar um tubo a ser usado pelo seu tópico de leitura e pelo tópico que prepara as coisas para escrever e adicionar o final de leitura do tubo ao seu select definir. Então, quando o outro tópico prepara dados para escrever, ele apenas envia algo no tubo, seu tópico de leitura é acordado do select, e pode então escrever. Dependendo da frequência com que há dados para ler ou escrever, isso também pode ser mais eficiente.

2 threads devem poder trabalhar com o mesmo soquete de cada vez, para que seu encadeamento principal possa ser capaz de gravar em um cliente enquanto o outro dorme em seleção, aguardando dados de entrada. É claro que isso pressupõe que ambos os threads tenham acesso à lista de clientes.

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