Qual é a diferença entre read()
e recv()
, e entre send()
e write()
na programação do soquete em termos de performances, velocidade e outros comportamentos?
Qual é a diferença entre read()
e recv()
, e entre send()
e write()
na programação do soquete em termos de performances, velocidade e outros comportamentos?
Respostas:
A diferença é que recv()
/ send()
funciona apenas em descritores de soquete e permite especificar determinadas opções para a operação real. Essas funções são um pouco mais especializadas (por exemplo, você pode definir um sinalizador para ignorar SIGPIPE
ou enviar mensagens fora da banda ...).
Funções read()
/ write()
são as funções universais de descrição de arquivos que funcionam em todos os descritores.
recv
e read
não fornecerá dados ao chamador, mas também nenhum erro. Para o chamador, o comportamento é o mesmo. O chamador pode nem saber nada sobre datagramas (pode não saber que esse é um soquete e não um arquivo, pode não saber que esse é um soquete de datagrama e não um soquete de fluxo). O fato de o datagrama permanecer pendente é um conhecimento implícito sobre como as pilhas IP funcionam nos kernels e não são visíveis para o chamador. Da perspectiva do chamador, eles ainda fornecerão um comportamento igual.
recv
? A razão pela qual recv
e send
onde foi introduzida em primeiro lugar foi o fato de que nem todos os conceitos de datagrama puderam ser mapeados para o mundo dos fluxos. read
e write
trate tudo como um fluxo de dados, seja um canal, um arquivo, um dispositivo (por exemplo, uma porta serial) ou um soquete. No entanto, um soquete é apenas um fluxo real se ele usa TCP. Se ele usa UDP, é mais como um dispositivo de bloco. Mas se os dois lados o usarem como um fluxo, ele funcionará como um fluxo e você não poderá nem enviar um pacote UDP vazio usando write
chamadas, para que essa situação não ocorra.
read () é equivalente a recv () com um parâmetro flags de 0. Outros valores para o parâmetro flags alteram o comportamento de recv (). Da mesma forma, write () é equivalente a send () com sinalizadores == 0.
recv
só pode ser usado em um soquete e produzirá um erro se você tentar usá-lo, digamos STDIN_FILENO
,.
read()
e write()
são mais genéricos, eles funcionam com qualquer descritor de arquivo. No entanto, eles não funcionarão no Windows.
Você pode passar opções adicionais para send()
e recv()
, portanto, pode ser necessário usá-las em alguns casos.
Acabei de notar recentemente que, quando usei write()
um soquete no Windows, quase funcionou (o FD passou para write()
não é o mesmo que o passou para send()
; eu costumava _open_osfhandle()
fazer o FD para passar write()
). No entanto, não funcionou quando tentei enviar dados binários que incluíam o caractere 10. Em write()
algum lugar, o caractere 13 foi inserido antes disso. Mudá-lo para send()
com um parâmetro flags de 0 corrigiu esse problema. read()
poderia ter o problema inverso se 13-10 forem consecutivos nos dados binários, mas eu não testei. Mas isso parece ser outra diferença possível entre send()
e write()
.
Outra coisa no linux é:
send
não permite operar em fd sem soquete. Assim, por exemplo, para escrever na porta USB, write
é necessário.
"Desempenho e velocidade"? Esse tipo de ... não é sinônimo, aqui?
De qualquer forma, a recv()
chamada recebe sinalizadores que read()
não o fazem, o que a torna mais poderosa ou pelo menos mais conveniente. Essa é uma diferença. Não acho que exista uma diferença significativa de desempenho, mas ainda não testei.
No Linux, também noto que:
Interrupção de chamadas do sistema e funções de biblioteca por manipuladores de sinal
Se um manipulador de sinal for chamado enquanto uma chamada de sistema ou chamada de função de biblioteca estiver bloqueada, então:
a chamada é reiniciada automaticamente após o retorno do manipulador de sinal; ou
a chamada falha com o erro EINTR.
... Os detalhes variam entre os sistemas UNIX; abaixo, os detalhes para Linux.
Se uma chamada bloqueada para uma das seguintes interfaces for interrompida por um manipulador de sinal, a chamada será reiniciada automaticamente depois que o manipulador de sinal retornar se o sinalizador SA_RESTART foi usado; caso contrário, a chamada falhará com o erro EINTR:
- read (2), readv (2), write (2), writev (2) e ioctl (2) chama dispositivos "lentos".
.....
As seguintes interfaces nunca são reiniciadas após serem interrompidas por um manipulador de sinal, independentemente do uso de SA_RESTART; eles sempre falham com o erro EINTR quando interrompidos por um manipulador de sinal:
Interfaces de soquete "Input", quando um tempo limite (SO_RCVTIMEO) foi definido no soquete usando setsockopt (2): accept (2), recv (2), recvfrom (2), recvmmsg (2) (também com um valor não NULL argumento de tempo limite) e recvmsg (2).
Interfaces de soquete "Output", quando um tempo limite (SO_RCVTIMEO) foi definido no soquete usando setsockopt (2): connect (2), send (2), sendto (2), sendto (2) e sendmsg (2).
Verifique man 7 signal
para mais detalhes.
Um uso simples seria usar o sinal para evitar o recvfrom
bloqueio indefinidamente.
Um exemplo de APUE :
#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>
#define BUFLEN 128
#define TIMEOUT 20
void
sigalrm(int signo)
{
}
void
print_uptime(int sockfd, struct addrinfo *aip)
{
int n;
char buf[BUFLEN];
buf[0] = 0;
if (sendto(sockfd, buf, 1, 0, aip->ai_addr, aip->ai_addrlen) < 0)
err_sys("sendto error");
alarm(TIMEOUT);
//here
if ((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) < 0) {
if (errno != EINTR)
alarm(0);
err_sys("recv error");
}
alarm(0);
write(STDOUT_FILENO, buf, n);
}
int
main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
int sockfd, err;
struct sigaction sa;
if (argc != 2)
err_quit("usage: ruptime hostname");
sa.sa_handler = sigalrm;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) < 0)
err_sys("sigaction error");
memset(&hint, 0, sizeof(hint));
hint.ai_socktype = SOCK_DGRAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
err_quit("getaddrinfo error: %s", gai_strerror(err));
for (aip = ailist; aip != NULL; aip = aip->ai_next) {
if ((sockfd = socket(aip->ai_family, SOCK_DGRAM, 0)) < 0) {
err = errno;
} else {
print_uptime(sockfd, aip);
exit(0);
}
}
fprintf(stderr, "can't contact %s: %s\n", argv[1], strerror(err));
exit(1);
}
#define write(...) send(##__VA_ARGS__, 0)
.