UDP 概述

UDP 客户端方法


有两种方式构建 UDP 客户端:

  • 使用 connect(), send()/recv()
  • 在 TCP 类型的套接字上调用 connect(), 会通过网络向服务器发送数据包进行握手.
    在 UDP 类型的套接字上调用 connect(), 只是将地址保存在本地.
    调用 send() 会将数据发送至 connect() 指定的地址.
    调用 recv() 只接收来自 connect() 指定地址的数据.

  • 使用 sendto()/recvfrom()
  • 调用 sendto() 会将数据发送至函数参数指定的地址.
    调用 recvfrom() 会返回所有收到的数据, 无论发送者是谁.

UDP 服务器方法


TCP, 每个套接字服务一条连接.
UDP, 一个套接字可以和任意数量的节点通信.
TCP 服务器需要调用 listen() 监听新连接, 调用 accept() 建立新连接.
UDP 则不用, 绑定本地地址后, 就可以立即开始发送和接受数据.
TCP 服务器使用 select() 就像是在监测多个独立的套接字, 而 UDP 只需监测一个.
如果程序同时使用 TCP 和 UDP 套接字, 监测这些套接字只需调用一次 select().

一个简单的 UDP 客户端


  1. /* sendto.udp.c */
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <stdbool.h>
  6. #include "network.h"
  7. int main(int argc, char ** argv) {
  8. printf("Configuring remote address ...\n");
  9. struct addrinfo info_addr;
  10. memset(&info_addr, 0, sizeof(info_addr));
  11. info_addr.ai_family = AF_INET;
  12. info_addr.ai_socktype = SOCK_DGRAM;
  13. struct addrinfo * addr_peer;
  14. if (getaddrinfo("127.0.0.1", "8080", &info_addr, &addr_peer)) {
  15. fprintf(stderr, "getaddrinfo() failed, errno: %d, %s\n", errno, strerror(errno));
  16. return EXIT_FAILURE;
  17. }
  18. char addr_buff[100];
  19. char serv_buff[100];
  20. getnameinfo(
  21. addr_peer->ai_addr, addr_peer->ai_addrlen,
  22. addr_buff, sizeof(addr_buff),
  23. serv_buff, sizeof(serv_buff),
  24. NI_NUMERICHOST | NI_NUMERICSERV
  25. );
  26. printf("Remote address: %s %s\n", addr_buff, serv_buff);
  27. printf("Creating socket ...\n");
  28. int sock_peer = socket(
  29. addr_peer->ai_family, addr_peer->ai_socktype, addr_peer->ai_protocol
  30. );
  31. if (sock_peer < 0) {
  32. fprintf(stderr, "socket() failed, errno: %d, %s\n", errno, strerror(errno));
  33. return EXIT_FAILURE;
  34. }
  35. const char * hello_msg = "Hello World";
  36. printf("Sending: %s\n", hello_msg);
  37. int bytes_sent = sendto(
  38. sock_peer,
  39. hello_msg, strlen(hello_msg),
  40. 0,
  41. addr_peer->ai_addr, addr_peer->ai_addrlen
  42. );
  43. printf("Sent %d bytes ...\n", bytes_sent);
  44. freeaddrinfo(addr_peer);
  45. close(sock_peer);
  46. printf("Finished ...\n");
  47. return EXIT_SUCCESS;
  48. }

调用 sendto(), 向 127.0.0.1:8080 发送字符串 "Hello World".
无论 127.0.0.1:8080 是否有服务在运行, sendto.udp 都产生相同的输出.
即是说, sendto() 函数并不关心数据包是否真的被传输.

将字符串字符转换成大写形式(UDP 版本)


  1. /* toupper.server.udp.c */
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <ctype.h>
  6. #include <stdbool.h>
  7. #include "network.h"
  8. int main(int argc, char ** argv) {
  9. printf("Configuring local address ...\n");
  10. struct addrinfo info_local;
  11. memset(&info_local, 0, sizeof(info_local));
  12. info_local.ai_family = AF_INET6;
  13. info_local.ai_socktype = SOCK_DGRAM;
  14. info_local.ai_flags = AI_PASSIVE;
  15. struct addrinfo * addr_local;
  16. getaddrinfo(NULL, "8080", &info_local, &addr_local);
  17. printf("Creating socket ...\n");
  18. int sock_listen = socket(
  19. addr_local->ai_family, addr_local->ai_socktype, addr_local->ai_protocol
  20. );
  21. if (sock_listen < 0) {
  22. fprintf(stderr, "socket() failed, errno: %d, %s\n", errno, strerror(errno));
  23. return EXIT_FAILURE;
  24. }
  25. printf("Binding socket to local address ...\n");
  26. if (bind(
  27. sock_listen,
  28. addr_local->ai_addr, addr_local->ai_addrlen
  29. )) {
  30. fprintf(stderr, "bind() failed, errno: %d, %s\n", errno, strerror(errno));
  31. return EXIT_FAILURE;
  32. }
  33. freeaddrinfo(addr_local);
  34. fd_set sset;
  35. FD_ZERO(&sset);
  36. FD_SET(sock_listen, &sset);
  37. int max_sock = sock_listen;
  38. int i;
  39. printf("Waiting for clients ...\n");
  40. while (true) {
  41. fd_set reads = sset;
  42. if (select(max_sock+1, &reads, 0, 0, NULL) < 0) {
  43. fprintf(stderr, "select() failed, errno: %d, %s\n", errno, strerror(errno));
  44. return EXIT_FAILURE;
  45. }
  46. if (FD_ISSET(sock_listen, &sset)) {
  47. struct sockaddr_storage addr_client;
  48. socklen_t client_len = sizeof(addr_client);
  49. char data_buff[1024];
  50. int bytes_recv = recvfrom(
  51. sock_listen,
  52. data_buff, sizeof(data_buff),
  53. 0,
  54. (struct sockaddr *) &addr_client, &client_len
  55. );
  56. if (bytes_recv < 1) {
  57. fprintf(stderr, "recvfrom() failed, errno: %d, %s\n", errno, strerror(errno));
  58. return EXIT_FAILURE;
  59. }
  60. char addr_buff[100];
  61. char serv_buff[100];
  62. getnameinfo(
  63. (struct sockaddr *) &addr_client, client_len,
  64. addr_buff, sizeof(addr_buff),
  65. serv_buff, sizeof(serv_buff),
  66. NI_NUMERICHOST | NI_NUMERICSERV
  67. );
  68. printf("New client: %s %s\n", addr_buff, serv_buff);
  69. for (i = 0; i < bytes_recv; ++i)
  70. data_buff[i] = toupper(data_buff[i]);
  71. sendto(
  72. sock_listen,
  73. data_buff, bytes_recv,
  74. 0,
  75. (struct sockaddr *) &addr_client, client_len
  76. );
  77. }
  78. }
  79. printf("Closing listening socket ...\n");
  80. close(sock_listen);
  81. printf("Finished ...\n");
  82. return EXIT_SUCCESS;
  83. }

《C 语言网络编程实践》(《Hands-On Network Programming with C》)