UDP 编程

发布时间 2023-08-05 12:11:39作者: 某某人8265

UDP 编程

#include <sys/socket.h>
// 可以发送或接收一个长度为0的数据报

ssize_t recvfrom(int sockfd, 
                 void *buf, size_t nbytes, 
                 int flags,   // 常与 recv send recvmsg sendmsg 配合
                 struct sockaddr *from, socklen_t *addrlen); // 可用空指针表示不关心发送者

ssize_t sendto(int sockdf, 
               const void *buf, size_t nbytes, 
               int flags, 
               const struct sockaddr *to, socklen_t *addrlen);
服务端
 // 包含所有的头文件
#include <arpa/inet.h>
#include <bits/stdc++.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>

const int SERV_PORT = 60000;

int main(int argc, char const *argv[]) {
  int sockfd{0};
  struct sockaddr_in serv_addr;
  struct sockaddr_in client_addr;
  char buf_msg[BUFSIZ]{0};
  char buf_addr[BUFSIZ]{0};
  int n{0};

  bzero(&serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  serv_addr.sin_port = htons(SERV_PORT);

  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (sockfd == -1) {
    perror("socket");
    exit(1);
  }

  if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
    perror("bind");
    exit(1);
  }

  while (true) {
    socklen_t client_addr_len = sizeof(client_addr);
    bzero(buf_msg, sizeof(buf_msg));
    n = recvfrom(sockfd, buf_msg, BUFSIZ, 0, (struct sockaddr *)&client_addr,
                 &client_addr_len);
    if (n == -1) {
      perror("recvfrom");
      exit(1);
    }
    printf(
        "received from %s at PORT %d: %s\n",
        inet_ntop(AF_INET, &client_addr.sin_addr, buf_addr, sizeof(buf_addr)),
        ntohs(client_addr.sin_port), buf_msg);

    sendto(sockfd, buf_msg, n, 0, (struct sockaddr *)&client_addr,
           sizeof(client_addr));
  }

  return 0;
}
客户端
 #include <arpa/inet.h>
#include <bits/stdc++.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>

// const int SERV_PORT = 60000;
// const char *SERV_ADDR = "127.0.0.1";

int main(int argc, char const *argv[]) {
  int sockfd{0};
  struct sockaddr_in serv_addr;
  struct sockaddr_in recv_addr;

  char buf_addr[BUFSIZ]{0};
  char buf_msg[BUFSIZ]{0};

  char SERV_ADDR[] = "127.0.0.1";
  int SERV_PORT = 60000;

  if (argc >= 2) {
    strncpy(SERV_ADDR, argv[1], strlen(argv[1]));
  }
  if (argc == 3) {
    int port_num = atoi(argv[2]);
    if (port_num > 0 && port_num < 65536) {
      SERV_PORT = port_num;
    }
  }

  bzero(&serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  if (inet_pton(AF_INET, SERV_ADDR, &serv_addr.sin_addr) != 1) {
    perror("inet_pton");
    exit(1);
  }
  serv_addr.sin_port = htons(SERV_PORT);

  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (sockfd == -1) {
    perror("socket");
    exit(1);
  }

  // // 客户端也可绑定端口,也可以由系统自动分配。设置本地ip和端口号
  // if (bind(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr))) {
  //   perror("bind");
  //   exit(1);
  // }

  // connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

  while (printf("input >> "), scanf("%s%*c", buf_msg) > 0) {
    sendto(sockfd, buf_msg, strlen(buf_msg), 0, (struct sockaddr *)&serv_addr,
           sizeof(serv_addr));

    socklen_t recv_addr_len = sizeof(recv_addr);
    int n = recvfrom(sockfd, buf_msg, BUFSIZ, 0, (struct sockaddr *)&recv_addr,
                     &recv_addr_len);
    if (n == -1) {
      perror("recvfrom");
      exit(1);
    }

    inet_ntop(AF_INET, &recv_addr.sin_addr, buf_addr, sizeof(buf_addr));
    int recv_port = ntohs(recv_addr.sin_port);

    // 忽略来自其他地址的数据报
    if (strlen(buf_addr) != strlen(SERV_ADDR) ||
        strncmp(buf_addr, SERV_ADDR, strlen(SERV_ADDR)) != 0 ||
        recv_port != SERV_PORT) {
      printf("Datagram from %s:%d was ignored.\n", buf_addr, recv_port);
      continue;
    }

    printf("received from %s at PORT %d: %s\n", buf_addr,
           ntohs(recv_addr.sin_port), buf_msg);
  }

  return 0;
}

异步错误

当服务器进程退出后,客户端的recvfrom请求会永远阻塞。本地网络上,客户端发送udp数据报前进行一次ARP请求和应答,服务器返回一个ICMP端口不可达,但这个ICMP错误不会返回给客户进程。这个ICMP错误称为一个异步错误。

对于一个UDP套接字,它引发的异步错误不会发送给他,除非他使用 connect 进行了连接。如果不进行绑定,recvfrom仅能返回errno,不能返回IP和端口。

connect

使用connect绑定后,可以返回异步错误。实现记录对端iP和端口号
之后发送数据不可以再用 sentdo 指定目的地址,而是使用 write sendmsg 或 第五个参数为空的sendto
接收数据不可以再用 recvfrom 指定源地址,而是使用 read recvmsg 或 第五个参数为空的recvfrom

可以多次调用 connect 指定新的IP和端口号,将套接字地址结构体的 sin_family 指定为 AF_UNSPEC 可以断开连接。

使用 connect 显示指定目的地后会提升效率,类似的UDP也可以使用 bind 函数指定本地套接字IP和端口