UDP 概述
UDP 客户端方法
有两种方式构建 UDP 客户端:
- 使用 connect(), send()/recv()
- 使用 sendto()/recvfrom()
在 TCP 类型的套接字上调用 connect(), 会通过网络向服务器发送数据包进行握手.
在 UDP 类型的套接字上调用 connect(), 只是将地址保存在本地.
调用 send() 会将数据发送至 connect() 指定的地址.
调用 recv() 只接收来自 connect() 指定地址的数据.
调用 sendto() 会将数据发送至函数参数指定的地址.
调用 recvfrom() 会返回所有收到的数据, 无论发送者是谁.
UDP 服务器方法
TCP, 每个套接字服务一条连接.
UDP, 一个套接字可以和任意数量的节点通信.
TCP 服务器需要调用 listen() 监听新连接, 调用 accept() 建立新连接.
UDP 则不用, 绑定本地地址后, 就可以立即开始发送和接受数据.
TCP 服务器使用 select() 就像是在监测多个独立的套接字, 而 UDP 只需监测一个.
如果程序同时使用 TCP 和 UDP 套接字, 监测这些套接字只需调用一次 select().
一个简单的 UDP 客户端
- /* sendto.udp.c */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <stdbool.h>
- #include "network.h"
- int main(int argc, char ** argv) {
- printf("Configuring remote address ...\n");
- struct addrinfo info_addr;
- memset(&info_addr, 0, sizeof(info_addr));
- info_addr.ai_family = AF_INET;
- info_addr.ai_socktype = SOCK_DGRAM;
- struct addrinfo * addr_peer;
- if (getaddrinfo("127.0.0.1", "8080", &info_addr, &addr_peer)) {
- fprintf(stderr, "getaddrinfo() failed, errno: %d, %s\n", errno, strerror(errno));
- return EXIT_FAILURE;
- }
- char addr_buff[100];
- char serv_buff[100];
- getnameinfo(
- addr_peer->ai_addr, addr_peer->ai_addrlen,
- addr_buff, sizeof(addr_buff),
- serv_buff, sizeof(serv_buff),
- NI_NUMERICHOST | NI_NUMERICSERV
- );
- printf("Remote address: %s %s\n", addr_buff, serv_buff);
- printf("Creating socket ...\n");
- int sock_peer = socket(
- addr_peer->ai_family, addr_peer->ai_socktype, addr_peer->ai_protocol
- );
- if (sock_peer < 0) {
- fprintf(stderr, "socket() failed, errno: %d, %s\n", errno, strerror(errno));
- return EXIT_FAILURE;
- }
- const char * hello_msg = "Hello World";
- printf("Sending: %s\n", hello_msg);
- int bytes_sent = sendto(
- sock_peer,
- hello_msg, strlen(hello_msg),
- 0,
- addr_peer->ai_addr, addr_peer->ai_addrlen
- );
- printf("Sent %d bytes ...\n", bytes_sent);
- freeaddrinfo(addr_peer);
- close(sock_peer);
- printf("Finished ...\n");
- return EXIT_SUCCESS;
- }
调用 sendto(), 向 127.0.0.1:8080 发送字符串 "Hello World".
无论 127.0.0.1:8080 是否有服务在运行, sendto.udp 都产生相同的输出.
即是说, sendto() 函数并不关心数据包是否真的被传输.
将字符串字符转换成大写形式(UDP 版本)
- /* toupper.server.udp.c */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
- #include <stdbool.h>
- #include "network.h"
- int main(int argc, char ** argv) {
- printf("Configuring local address ...\n");
- struct addrinfo info_local;
- memset(&info_local, 0, sizeof(info_local));
- info_local.ai_family = AF_INET6;
- info_local.ai_socktype = SOCK_DGRAM;
- info_local.ai_flags = AI_PASSIVE;
- struct addrinfo * addr_local;
- getaddrinfo(NULL, "8080", &info_local, &addr_local);
- printf("Creating socket ...\n");
- int sock_listen = socket(
- addr_local->ai_family, addr_local->ai_socktype, addr_local->ai_protocol
- );
- if (sock_listen < 0) {
- fprintf(stderr, "socket() failed, errno: %d, %s\n", errno, strerror(errno));
- return EXIT_FAILURE;
- }
- printf("Binding socket to local address ...\n");
- if (bind(
- sock_listen,
- addr_local->ai_addr, addr_local->ai_addrlen
- )) {
- fprintf(stderr, "bind() failed, errno: %d, %s\n", errno, strerror(errno));
- return EXIT_FAILURE;
- }
- freeaddrinfo(addr_local);
- fd_set sset;
- FD_ZERO(&sset);
- FD_SET(sock_listen, &sset);
- int max_sock = sock_listen;
- int i;
- printf("Waiting for clients ...\n");
- while (true) {
- fd_set reads = sset;
- if (select(max_sock+1, &reads, 0, 0, NULL) < 0) {
- fprintf(stderr, "select() failed, errno: %d, %s\n", errno, strerror(errno));
- return EXIT_FAILURE;
- }
- if (FD_ISSET(sock_listen, &sset)) {
- struct sockaddr_storage addr_client;
- socklen_t client_len = sizeof(addr_client);
- char data_buff[1024];
- int bytes_recv = recvfrom(
- sock_listen,
- data_buff, sizeof(data_buff),
- 0,
- (struct sockaddr *) &addr_client, &client_len
- );
- if (bytes_recv < 1) {
- fprintf(stderr, "recvfrom() failed, errno: %d, %s\n", errno, strerror(errno));
- return EXIT_FAILURE;
- }
- char addr_buff[100];
- char serv_buff[100];
- getnameinfo(
- (struct sockaddr *) &addr_client, client_len,
- addr_buff, sizeof(addr_buff),
- serv_buff, sizeof(serv_buff),
- NI_NUMERICHOST | NI_NUMERICSERV
- );
- printf("New client: %s %s\n", addr_buff, serv_buff);
- for (i = 0; i < bytes_recv; ++i)
- data_buff[i] = toupper(data_buff[i]);
- sendto(
- sock_listen,
- data_buff, bytes_recv,
- 0,
- (struct sockaddr *) &addr_client, client_len
- );
- }
- }
- printf("Closing listening socket ...\n");
- close(sock_listen);
- printf("Finished ...\n");
- return EXIT_SUCCESS;
- }