Diretório do artigo
Conhecimento básico
O UDP exporta a camada de transporte no protocolo TCP / IP em camadas.
O UDP é um protocolo de comunicação não confiável, sem retransmissão e reconhecimento, sem controle de pedidos e sem controle de congestionamento.
O UDP não garante a entrega efetiva de pacotes, nem a ordem dos pacotes, ou seja, ao usar o UDP, precisamos fazer um bom trabalho de perda de pacotes, retransmissão e montagem de mensagens.
O UDP é relativamente simples e existem muitos cenários adequados: nossos serviços DNS comuns e serviços SNMP são baseados no protocolo UDP, que não são particularmente sensíveis ao atraso e à perda de pacotes. Além disso, cenários de comunicação para várias pessoas, como salas de bate-papo, jogos com vários jogadores etc. também usarão o protocolo UDP.
Formato da mensagem
O cabeçalho UDP tem 8 bytes, que são a porta de origem, a porta de destino, o comprimento do pacote UDP e a soma de verificação .
Como o tamanho de uma mensagem UDP é registrado apenas com 2 bytes, o tamanho máximo de uma mensagem, incluindo o comprimento do cabeçalho, é 65535 bytes.
Ao enviar com o protocolo UDP, o comprimento máximo dos dados que podem ser enviados com a função sendto é: cabeçalho 65535-IP (20) - cabeçalhoUDP (8) = 65507 bytes. Ao enviar dados com a função sendto, se o comprimento dos dados enviados for maior que esse valor, a função retornará um erro.
Como o IP tem o MTU máximo,
o tamanho do pacote UDP deve ser o cabeçalho 1500-IP (20) -UDP header (8) = 1472 (bytes)
Programação UDP
Servidor:
1. Crie um soquete.
2. Ligue o ip e a porta a serem monitorados.
3. Loop:
3.1 Ligue para recvfrom para ler a mensagem recebida e bloqueie-a se não houver mensagem.
3.2 Depois de receber a mensagem, ligue para sendto e envie para o cliente de acordo.
Cliente:
1. Crie um soquete.
2. Loop:
2.1 Ligue para enviar para enviar a solicitação.
2.2 Chame recvfrom para receber o correspondente.
#include <sys/socket.h>
// 返回值:收到数据的字节数
// 参数:
// sockfd:socket描述符
// buff:本地缓存
// nbytes:缓存最大接收字节数
// flags:I/O 相关的参数,一般使用 0 即可
// from:发送端的 ip 和 port 等信息
// addrlen:from 的大小
ssize_t
recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
struct sockaddr *from, socklen_t *addrlen);
// 返回值:发送了多少字节
// 参数:和上面的 recvfrom 类似
ssize_t
sendto(int sockfd, const void *buff, size_t nbytes, int flags,
const struct sockaddr *to, socklen_t addrlen);
O código refere-se ao meu artigo anterior: Programa de eco UDP https://blog.csdn.net/Hanoi_ahoj/article/details/105358383
O recurso "sem conexão" dos pacotes UDP permite continuar enviando pacotes após a reinicialização do servidor UDP. Esta é a melhor explicação para o "sem contexto" dos pacotes UDP.
UDP conectado
Através do exposto acima, no UDP, não é necessário estabelecer uma conexão semelhante à conexão na programação TCP.
De fato, o UDP também pode ser "conectado".
Vamos testá-lo através de um programa:
Cliente: Note -se que após a criação do soquete, ele é conectado e vinculado ao ip e à porta do servidor.
// UDP connect 测试客户端
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (socket_fd < 0) {
perror("socket");
return -1;
}
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(9090);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret < 0) {
perror("connect");
return -1;
}
while (1) {
char buf[1024] = {0};
printf("input>");
scanf("%s", buf);
ssize_t n = sendto(socket_fd, buf, strlen(buf), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (n < 0) {
perror("sendto");
continue;
}
printf("%zd bytes sent to [%s:%d]\n", n, inet_ntoa(server_addr.sin_addr), ntohs(server_addr.sin_port));
bzero(buf, sizeof(buf));
n = recvfrom(socket_fd, buf, sizeof(buf), 0, NULL, NULL);
if (n < 0) {
perror("recvfrom");
return -1;
}
printf("resp: %s\n", buf);
}
close(socket_fd);
return 0;
}
Servidor: é um servidor normal e a solicitação é retornada inalterada.
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (socket_fd < 0) {
perror("socket");
return -1;
}
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
server_addr.sin_port = htons(9090);
int ret = bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret < 0) {
perror("bind");
return -1;
}
// 一般服务器不进行 connect 操作
while (1) {
char buf[1024] = {0};
struct sockaddr_in client_addr;
bzero(&client_addr, sizeof(client_addr));
socklen_t client_addr_len = sizeof(client_addr);
ssize_t n = recvfrom(socket_fd, buf, sizeof(buf) - 1, 0,
(struct sockaddr*)&client_addr, &client_addr_len);
if (n < 0) {
perror("recvfrom");
continue;
}
buf[n] = '\0';
printf("req->[%s:%d] %s\n", inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port), buf);
n = sendto(socket_fd, buf, strlen(buf), 0, (struct sockaddr*)&client_addr, client_addr_len);
if (n < 0) {
perror("sendto");
continue;
}
}
close(socket_fd);
return 0;
}
gcc client.c -o client
gcc server.c -o server
Teste:
1. Não execute o servidor, apenas execute o cliente.
Como você pode ver, ele foi enviado quando o sendto foi chamado, mas houve um erro que o Connection recusou ao caminhar para recvfrom.
2. Execute servidor e cliente.
Receba e processe solicitações normalmente.
O papel da conexão UDP
Se você não se conectar, não inicie o servidor, execute o cliente, o programa será bloqueado novamente. Até o servidor reiniciar ou atingir o tempo limite.
Geralmente, o servidor UDP não precisa se conectar, porque o servidor só pode se conectar ao cliente após a conexão.
A função do connect é permitir que o programa receba a mensagem de erro o mais rápido possível e retorne:
Ao executar uma operação de conexão em um soquete UDP, é estabelecido um "contexto" para o soquete UDP, que possui uma conexão com o endereço e a porta do lado do servidor.É esse relacionamento de ligação que fornece ao kernel do sistema operacional as informações necessárias , Pode associar as informações recebidas pelo kernel do sistema operacional ao soquete correspondente.
Quando a função de operação sendto ou send é chamada, a mensagem do aplicativo é enviada, nosso aplicativo retorna, o kernel do sistema operacional assume a mensagem e, em seguida, o sistema operacional começa a tentar enviar para o endereço e porta correspondentes, porque o endereço e a porta correspondentes não são Uma vez alcançada, uma mensagem ICMP será retornada ao kernel do sistema operacional.A mensagem ICMP contém informações como o endereço de destino e a porta.
A operação de conexão foi realizada para ajudar o kernel do sistema operacional a estabelecer o relacionamento de mapeamento entre (endereço de destino do soquete UDP + porta) Quando uma mensagem inacessível do ICMP é recebida, o kernel do sistema operacional pode encontrá-lo na tabela de mapeamento Em qual soquete UDP tem o endereço e a porta de destino, não se esqueça que o soquete é globalmente exclusivo dentro do sistema operacional. Quando chamamos o método recvfrom ou recv no soquete novamente, podemos receber a operação A mensagem "Conexão recusada" retornada pelo kernel do sistema.
Após a conexão com o UDP, muitos livros sobre o uso das funções de envio e recebimento são recomendados: função de
envio ou gravação a ser enviada, se você usar o envio, precisará definir zero as informações relevantes para endereçar;
use a função recv ou leitura para receber, se O uso de recvfrom precisa definir as informações correspondentes do endereço como zero.
De fato, diferentes implementações do UNIX se comportam de maneira diferente.
Fator de eficiência:
Como se você não usar o método de conexão, sempre que enviar uma mensagem, será necessário este processo:
conectar soquete → enviar mensagem → desconectar soquete → conectar soquete → enviar mensagem → desconectar soquete → ...... ...
Se você usar o método de conexão, ele se tornará o seguinte:
conectar soquete → enviar mensagem → enviar mensagem → ... → finalmente desconectar o soquete
Sabemos que conectar soquetes requer uma certa quantidade de sobrecarga, como a necessidade de procurar informações da tabela de roteamento. Portanto, o programa cliente UDP pode obter uma certa melhoria de desempenho através da conexão.
Referência: Programação de Rede Prática em Tempo de Geek ( https://time.geekbang.org/column/article/129807 )
EOF