[Computer network] UDP protocol, UDP programming, connected UDP

Basic knowledge

UDP exports the transport layer in the layered TCP / IP protocol.
UDP is an unreliable communication protocol with no retransmission and acknowledgment, no order control, and no congestion control.
UDP does not guarantee the effective delivery of packets, nor does it guarantee the order of packets. That is to say, when using UDP, we need to do a good job of packet loss, retransmission, and message assembly.
UDP is relatively simple, and there are many suitable scenarios. Our common DNS services and SNMP services are based on the UDP protocol. These scenarios are not particularly sensitive to delay and packet loss. In addition, multi-person communication scenarios, such as chat rooms, multi-player games, etc., will also use the UDP protocol.
OSI and TCP / IP

Message format

The UDP header is 8 bytes long, which are the source port, destination port, UDP packet length, and checksum .
Because the length of a UDP message is only recorded with 2 bytes, the maximum length of a message including the header length is 65535 bytes.

When sending with UDP protocol, the maximum length of data that can be sent with the sendto function is: 65535-IP header (20)-UDP header (8) = 65507 bytes. When sending data with the sendto function, if the length of the sent data is greater than this value, the function returns an error.
Since IP has the maximum MTU,
the size of the UDP packet should be 1500-IP header (20)-UDP header (8) = 1472 (Bytes)

UDP message

UDP programming

Server:
1. Create a socket.
2. Bind the ip and port to be monitored.
3. Loop:
3.1. Call recvfrom to read the received message, and block it if there is no message.
3.2. After receiving the message, call sendto and send it to the client accordingly.

Client:
1. Create a socket.
2. Loop:
2.1. Call sendto to send the request.
2.2. Call recvfrom to receive the corresponding.

#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); 

UDP programming timing
The code refers to my previous article: UDP echo program https://blog.csdn.net/Hanoi_ahoj/article/details/105358383

The "connectionless" feature of UDP packets allows you to continue sending packets after the UDP server is restarted. This is the best explanation of the "no context" of UDP packets.

Connected UDP

Through the above, in UDP, it is not necessary to establish a connection similar to connect in TCP programming.
In fact, UDP can also be "connected".

Let's test it through a program:
Client: It should be noted that after the socket is created, it is connected and bound to the server's ip and port.

// 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;
}

Server: It is a normal server, and the request is returned unchanged.

#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

Test:
1. Do not run the server, only run the client.
As you can see, it was sent when sendto was called, but there was an error Connection refused when walking to recvfrom.

client
2. Run server and client.
Receive and process requests normally.
server && client

The role of UDP connect

If you do not connect, do not start the server, run the client, the program will block on recvfrom. Until the server restarts or times out.
Generally, the UDP server does not need to connect, because the server can only connect to the client after connecting.

The role of connect is to let the program receive the error message as soon as possible and return:

By performing a connect operation on a UDP socket, a "context" is established for the UDP socket, which has a connection with the server-side address and port. It is this binding relationship that gives the operating system kernel the necessary information , Can associate the information received by the operating system kernel with the corresponding socket.

When the sendto or send operation function is called, the application message is sent, our application returns, the operating system kernel takes over the message, and then the operating system begins to try to send to the corresponding address and port, because the corresponding address and port are not Once reached, an ICMP message will be returned to the operating system kernel. The ICMP message contains information such as the destination address and port.

The connect operation was carried out to help the operating system kernel establish the mapping relationship between (UDP socket-destination address + port). When an ICMP unreachable message is received, the operating system kernel can find it from the mapping table Out which UDP socket has the destination address and port, do n’t forget that the socket is globally unique inside the operating system, when we call the recvfrom or recv method on the socket again, we can receive the operation The "Connection Refused" message returned by the system kernel.

After connecting to UDP, many books about the use of send and receive functions are recommended:
send or write function to send, if you use sendto need to set the relevant to address information to zero;
use recv or read function to receive, if Using recvfrom needs to set the corresponding from address information to zero.
In fact, different UNIX implementations behave differently.

Efficiency factor:

Because if you do not use the connect method, every time you send a message, you will need this process:
connect socket → send message → disconnect socket → connect socket → send message → disconnect socket → ...... …

If you use the connect method, it will become as follows:
connect socket → send message → send message → ... → finally disconnect the socket

We know that connecting sockets requires a certain amount of overhead, such as the need to look up routing table information. Therefore, the UDP client program can obtain a certain performance improvement through connect.


Reference: Geek Time-Practical Network Programming ( https://time.geekbang.org/column/article/129807 )

EOF

98 original articles have been published · 91 praises · 40,000+ views

Guess you like

Origin blog.csdn.net/Hanoi_ahoj/article/details/105460489