基于 UDP 的服务端和客户端

基于 UDP 的服务端和客户端

UDP 就相当于以前的信件邮寄,我们将信件的地址信息填好,放入邮筒以后,信件是否到达指定的地方我们是无法知道的,信件是否完好无损我们也是无法知道的,信件是否丢失也无法知道。UDP 就是只管将消息发送出去,而不管接收者是否接收到的一种套接字。

UDP 不像 TCP 那样需要建立连接,TCP 因为建立连接,套接字就知道目标地址信息,而 UDP 是无法知道地址信息,因此在进行通信时需要知道目标的地址信息。如下函数中需要传入目标地址信息

  • Linux 中的接收和发送数据函数接口
#include <sys/socket.h>

// @return 成功返回传输的字节数,失败返回 -1
// @ params:
//   sock: 用于传输数据的 UDP 套接字文件描述符
//   buff: 保存待传输数据的缓冲地址值
//   nbytes: 待传输的数据长度,以字节为单位
//   flags: 可选项参数,若没有则传递 0
//   to/from: 存有目标地址信息的 sockaddr 结构体变量的地址值
//   adrlen: 传递给参数 to 的地址值结构体变量长度
ssize_t sendto(int sock, void *buff, size_t nbytes, int flags,
               struct sockaddr *to, socklen_t addrlen);

ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags,
                 struct sockaddr *from, socklen_t *addrlen);
  • Windows 中的接收和发送数据函数接口
#include <winsock2.h>

// @return 成功返回对应的字节数,失败返回 -1
// @ params:
//   sock: 用于传输数据的 UDP 套接字文件描述符
//   buff: 保存待传输数据的缓冲地址值
//   nbytes: 待传输的数据长度,以字节为单位
//   flags: 可选项参数,若没有则传递 0
//   to/from: 存有目标地址信息的 sockaddr 结构体变量的地址值
//   adrlen: 传递给参数 to 的地址值结构体变量长度
int sendto(SOCKET sock, const char *buff, size_t nbytes, int flags,
           const struct sockaddr *to, int addrlen);

int recvfrom(SOCKET sock, const char *buff, size_t nbytes, int flags,
             const struct sockaddr *from, int *addrlen);

UDP 的服务端实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

int main(int argc, char *argv[]) {
  if (2 != argc) {
    fprintf(stderr, "Usage: %s <port>\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  // 1. 创建套接字
  int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (-1 == sockfd) {
    perror("socket() error");
    exit(EXIT_FAILURE);
  }

  // 2. 使用 bind 分配 IP 地址,减轻 sendto 的功能
  struct sockaddr_in saddr;
  memset(&saddr, 0, sizeof(saddr));
  saddr.sin_family = AF_INET;
  saddr.sin_addr.s_addr = htonl(INADDR_ANY);
  saddr.sin_port = htons(atoi(argv[1]));
  if (-1 == bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr))) {
    perror("bind() error");
    exit(EXIT_FAILURE);
  }

  struct sockaddr_in caddr;
  memset(&caddr, 0, sizeof(caddr));
  socklen_t addr_len = sizeof(caddr);
  while (1) {
    char message[1024] = {0};
    int ret = recvfrom(sockfd, message, 1024, 0, (struct sockaddr *)&caddr, &addr_len);
    if (-1 == ret) {
      perror("recvfrom() error");
      close(sockfd);
      exit(EXIT_FAILURE);
    }

    sendto(sockfd, message, 1024, 0, (struct sockaddr *)&caddr, addr_len);
  }

  close(sockfd);

  return 0;
}

UDP 的客户端实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

int main(int argc, char *argv[]) {
  if (3 != argc) {
    fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  // 1. 创建套接字
  int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (-1 == sockfd) {
    perror("socket() error");
    exit(EXIT_FAILURE);
  }

  struct sockaddr_in caddr;
  memset(&caddr, 0, sizeof(caddr));
  caddr.sin_family = AF_INET;
  caddr.sin_addr.s_addr = inet_addr(argv[1]);
  caddr.sin_port = htons(atoi(argv[2]));
  while (1) {
    printf("Input message(q/Q to quit): ");
    char message[1024] = {0};
    fgets(message, 1024, stdin);
    if (!strcmp(message, "Q\n") || !strcmp(message, "q\n"))
      break;

    sendto(sockfd, message, 1024, 0, (struct sockaddr *)&caddr, sizeof(caddr));

    socklen_t addr_len = sizeof(caddr);
    recvfrom(sockfd, message, 1024, 0, (struct sockaddr *)&caddr, &addr_len);
    printf("message from server: %s\n", message);
  }

  close(sockfd);

  return 0;
}

sendto 在发现尚未分配地址信息,自动给套接字分配 IP 地址和端口,但是 TCP 中通过 bindconnect 进行地址信息的分配,在 UDP 中也可以使用这两个函数。如果 UDP 的服务需要长时间通信,则建议使用 connectbind 进行地址分配,简化 sendto 的功能,能提升程序的整体效率。

UDP 的数据传输特性

UDP 的数据传输特性不同于 TCP,UDP 数据是存在数据边界的,简单的说 UDP 中发几次数据,就得分几次接收,通过下面的程序进行测试。

服务器端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

int main(int argc, char *argv[]) {
  if (2 != argc) {
    fprintf(stderr, "Usage: %s <port>\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  // 1. 创建套接字
  int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (-1 == sockfd) {
    perror("socket() error");
    exit(EXIT_FAILURE);
  }

  // 2. 使用 bind 分配 IP 地址,减轻 sendto 的功能
  struct sockaddr_in saddr;
  memset(&saddr, 0, sizeof(saddr));
  saddr.sin_family = AF_INET;
  saddr.sin_addr.s_addr = htonl(INADDR_ANY);
  saddr.sin_port = htons(atoi(argv[1]));
  if (-1 == bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr))) {
    perror("bind() error");
    exit(EXIT_FAILURE);
  }

  char message[1024];
  struct sockaddr_in caddr;
  memset(&caddr, 0, sizeof(caddr));
  socklen_t addr_len = sizeof(caddr);
  for (int i = 0; i < 3; ++i) {
    sleep(5);
    int ret = recvfrom(sockfd, message, 1024, 0, (struct sockaddr *)&caddr, &addr_len);
    printf("Message %d: %s\n", i+1, message);
  }

  close(sockfd);

  return 0;
}

客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

int main(int argc, char *argv[]) {
  if (3 != argc) {
    fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  // 1. 创建套接字
  int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (-1 == sockfd) {
    perror("socket() error");
    exit(EXIT_FAILURE);
  }

  struct sockaddr_in saddr;
  memset(&saddr, 0, sizeof(saddr));
  saddr.sin_family = AF_INET;
  saddr.sin_addr.s_addr = inet_addr(argv[1]);
  saddr.sin_port = htons(atoi(argv[2]));
  socklen_t addr_len = sizeof(saddr);
  if (-1 == connect(sockfd, (struct sockaddr *)&saddr, addr_len)) {
    perror("connect() error");
    close(sockfd);
    exit(EXIT_FAILURE);
  } else {
    printf("Connected ......\n");
  }

  char msg1[] = "Hi!";
  char msg2[] = "I'm another UDP host";
  char msg3[] = "Nice to meet you";
  sendto(sockfd, msg1, sizeof(msg1), 0, (struct sockaddr *)&saddr, sizeof(saddr));
  sendto(sockfd, msg2, sizeof(msg1), 0, (struct sockaddr *)&saddr, sizeof(saddr));
  sendto(sockfd, msg3, sizeof(msg1), 0, (struct sockaddr *)&saddr, sizeof(saddr));

  close(sockfd);

  return 0;
}

运行结果,客户端在启动的一瞬间就结束,服务器端会间隔 5 秒显示客户端发来的 3 条数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xjjeffery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值