《LINUX系统编程》笔记p13
UDP通信
UDP (User Datagram Protocol)
特点:无连接、不可靠、数据报协议。
用途:用于不需要可靠连接,但实时性要求比较强的场合,如音视频数据的网络播放。
UDP协议头
创建套接字
int socket(int domain, int type, int protocol);
// 参数
// domain是网络层协议
// AF_NET:IPv4
// AF_NET6:IPv6
// type: 传输层协议类型
// SOCK_STREAM TCP协议(数据流协议)
// SOCK_DGRAM UDP协议(数据报文协议)
// protocal: 子协议,用于其他协议,一般为0.
// 返回值:套接字的文件描述符
发送数据函数 sendto
#include <sys/types.h>
#include <sys/socket.h>ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);// 参数
// sockfd UDP套接字
// buf 发送缓冲区。
// len 发送的字节数(不能为零)。
// flags: 标记位,默认为零。
// dest_addr: 目标地址的 IPv4 地址族结构。
// addrlen: 目标地址的 IPv4 地址族结构的长度。
// 返回值:返回发送的字节数,错误返回-1同时设置错误号。
接收数据的函数recvfrom
#include <sys/types.h>
#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
// 参数
// sockfd UDP套接字
// buf 接收缓冲区。
// len 接收缓冲区的长度。
// flags: 标记位,默认为零。
// dest_addr: 发送方地址的 IPv4 地址族结构。
// addrlen: 发送方地址的 IPv4 地址族结构的长度的地址。
// 返回值:返回发送的字节数,错误返回-1同时设置错误号。
关闭套接字
close(sock_fd);
udp客户端代码示例
// UDP 客户端实现
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h> // man 7 ip
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#define SERVER_IP "192.144.206.112"
#define SERVER_PORT 6666#define BUF_SIZE (1024)int main(int argc, char * argv[]) {int sock_fd;int ret;struct sockaddr_in sin; // 服务器端IPv4地址族结构struct sockaddr_in cin; // 客户端IPv4地址族结构char buf_read[BUF_SIZE]; // 读数据缓冲区char buf_write[BUF_SIZE]; // 写数据缓冲区// 创建套接字sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sock_fd) {perror("socket");}sin.sin_family = AF_INET;sin.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP, &sin.sin_addr);// 与服务器端通信while(1) {ret = read(0, buf_write, BUF_SIZE);buf_write[ret-1] = 0;// 将buf_write的内容发送给,服务器端.ret = sendto(sock_fd, buf_write, strlen(buf_write), 0, (struct sockaddr*)&sin, sizeof(sin));if (-1 == ret) {perror("sendto");break;}if(0 == strcmp("exit", buf_write))break;ret = recv(sock_fd, buf_read, BUF_SIZE-1, 0);buf_read[ret] = 0;printf("服务器回复:%s\n", buf_read);}// 断开连接 close(sock_fd);printf("主进程结束\n");return 0;
}
udp服务器端代码
// UDP 服务器端实现
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h> // man 7 ip
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#define SERVER_IP "192.144.206.112"
#define SERVER_PORT 6666#define BUF_SIZE (1024)int main(int argc, char * argv[]) {int sock_fd;int ret;struct sockaddr_in sin; // 服务器端IPv4地址族结构struct sockaddr_in cin; // 客户端IPv4地址族结构char buf_read[BUF_SIZE]; // 读数据缓冲区char buf_write[BUF_SIZE]; // 写数据缓冲区// 创建套接字sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sock_fd) {perror("socket");}sin.sin_family = AF_INET;sin.sin_port = htons(SERVER_PORT);sin.sin_addr.s_addr = INADDR_ANY;// inet_pton(AF_INET, SERVER_IP, &sin.sin_addr);ret = bind(sock_fd, (struct sockaddr*)&sin,sizeof(sin));if (-1 == ret) {perror("bind");close(sock_fd);return 1;}// 与服务器端通信while(1) {socklen_t cin_len = sizeof(cin);// cin 用来保存客户端地址ret = recvfrom(sock_fd, buf_read, BUF_SIZE-1, 0,(struct sockaddr*)&cin, &cin_len);// 打印客户端的IP地址和端口号printf("客户端IP:%s, 端口号:%d\n",inet_ntop(AF_INET, &cin.sin_addr, buf_write,BUF_SIZE), ntohs(cin.sin_port));buf_read[ret] = 0;if (0 == strcmp("hello", buf_read)) {strcpy(buf_write, "Hi");} else {strcpy(buf_write,"I don't know what you said!");}ret = sendto(sock_fd, buf_write, strlen(buf_write), 0, (struct sockaddr*)&cin, sizeof(cin));if (-1 == ret) {perror("sendto");break;}}// 断开连接 close(sock_fd);printf("主进程结束\n");return 0;
}
UDP链接图示
广播
局域网内一对多的UDP传输方式。
广播地址:
255.255.255.255 所有的网络设备都发送
IP地址 | ~子网掩码
如: 192.168.1.100 | ~255.255.255.0.的广播地址是
192.168.1.255
设置和获取套接字选项
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
// 参数
// sockfd 套接字
// level 套接字层:如 SOL_SOCKET、IPPROTO_IP
// optname 选项名:
// SO_REUSEADDR 让端口号可以重复绑定
// IP_ADD_MEMBERSHIP 加入多播组
// IP_DROP_MEMBERSHIP 退出多播组
// optval: 选项值。
// optlen: 选项值的内存长度。
// 返回值:返回发送的字节数,错误返回-1同时设置错误号。
广播接收端示例代码
// UDP 广播接收端实现
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h> // man 7 ip
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#define BROADCAST_PORT 6666#define BUF_SIZE (1024)int main(int argc, char * argv[]) {int sock_fd;int ret;struct sockaddr_in sin; // 服务器端IPv4地址族结构struct sockaddr_in cin; // 客户端IPv4地址族结构char buf_read[BUF_SIZE]; // 读数据缓冲区// char buf_write[BUF_SIZE]; // 写数据缓冲区int reuse_enble = 1; // 值为 1 表示同意int cin_len = sizeof(cin); // cin的长度// 创建UDP套接字sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sock_fd) {perror("socket");return 1;}// 设置套接字选项,允许多个进程绑定到同一个端口ret = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &reuse_enble, sizeof(reuse_enble));if (-1 == ret) {perror("setsockopt");return 2;}//绑定本局域网内的某个广播用的端口号.sin.sin_family = AF_INET;sin.sin_port = htons(BROADCAST_PORT);sin.sin_addr.s_addr = INADDR_ANY;ret = bind(sock_fd, (struct sockaddr*) &sin, sizeof(sin));if (-1 == ret) {perror("bind");return 3;}printf("准备接收广播...\n");// 与服务器端通信while(1) {ret = recvfrom(sock_fd, buf_read, BUF_SIZE-1, 0, (struct sockaddr*)&cin, &cin_len);buf_read[ret] = 0;printf("收到广播:%s\n", buf_read);if (0 == strcmp("exit", buf_read))break;char buf[20];printf("发送者IP:%s, 端口号:%d\n",inet_ntop(AF_INET, &cin.sin_addr, buf,20),ntohs(cin.sin_port));}// 断开连接 close(sock_fd);printf("主进程结束\n");return 0;
}
UDP 广播发送端实现
// UDP 广播发送端实现
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h> // man 7 ip
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#define BROADCAST_IP "255.255.255.255"
// #define BROADCAST_IP "10.11.9.255"
// #define BROADCAST_IP "127.0.0.255"
#define BROADCAST_PORT 6666#define BUF_SIZE (1024)int main(int argc, char * argv[]) {int sock_fd;int ret;struct sockaddr_in sin; // 服务器端IPv4地址族结构char buf_write[BUF_SIZE]; // 写数据缓冲区int broadcast_enble = 1; // 值为 1 表示同意// 创建UDP套接字sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sock_fd) {perror("socket");return 1;}// 设置套接字选项,允许广播ret = setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, &broadcast_enble, sizeof(broadcast_enble));if (-1 == ret) {perror("setsockopt");return 2;}//设置广播用的IP和端口号信息sin.sin_family = AF_INET;sin.sin_port = htons(BROADCAST_PORT);// sin.sin_addr.s_addr = INADDR_ANY;inet_pton(AF_INET, BROADCAST_IP, &sin.sin_addr);printf("准备发送广播...\n");// 循环广播 60 秒后退出for (int i = 0; i < 60; i++) {sprintf(buf_write, "广播数字:%d", i);ret = sendto(sock_fd, buf_write, strlen(buf_write), 0, (struct sockaddr*)&sin, sizeof(sin));if (-1 == ret) {close(sock_fd);return 1;}sleep(1);}// 断开连接 close(sock_fd);printf("主进程结束\n");return 0;
}
多播
什么是多播
多播,也叫组播,是一种网络数据包传输方式,它允许一个发送者向一组特定的接收者发送数据包。
单播、广播、多播
单播
方式: 一对一通信。发送者为每一个接收者都发送一份独立的数据副本。
比喻: 打电话。如果你想通知10个人,你需要打10个电话,把同样的话说10遍。
缺点: 当接收者数量巨大时(如视频直播有百万观众),发送者和网络链路的压力会呈指数级增长,效率极低,浪费带宽。
广播
方式: 一对所有通信。发送者把数据发到网络中的所有设备,无论它们是否需要。
比喻: 大喇叭广播。
缺点: 安全性差(所有人都能收到),会干扰不需要该数据的设备,而且路由器默认会隔离广播域,意味着广播包通常无法跨越网段(如从公司财务部网络传到研发部网络),因此不能用于互联网范围的传输。
多播
方式: 一对一组通信。发送者只发送一份数据副本。网络中的路由器(如果配置了多播路由)会负责根据需要将数据复制并转发给有接收者的分支路径。
比喻: 杂志资料,给每个省,每个省自己复制在下发给需要的县、县在复制给需要的人。
优点: 极大节省了发送者和网络带宽。无论组内有多少接收者,发送者都只发送一份数据。只有在网络路径需要分叉时,路由器才会复制数据包。
多播组地址:这是一个逻辑上的组,用一个IP地址来标识。
IPv4中,多播地址合法的范围是 224.0.0.0 到 239.255.255.255。
一般 239.0.0.0 ~ 239.255.255.255 常用于开发测试(相当于局域网的多播地址),测试时不会和公网数据冲突。
UDP 多播接收端实现
// UDP 多播接收端实现
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h> // man 7 ip
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#define MULTICAST_GROUP "239.0.0.10" // 标准多播地址
#define MULTICAST_PORT 6666
#define BUF_SIZE (1024)int main(int argc, char * argv[]) {int sock_fd;int ret;struct sockaddr_in sin; //IPv4地址族结构struct sockaddr_in cin; //IPv4地址族结构int cin_len = sizeof(cin);int reuse_enble = 1;struct ip_mreq mreq; // 多播放组请求结构char buf_read[BUF_SIZE]; // 读数据缓冲区// 创建UDP套接字sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sock_fd) {perror("socket");return 1;}// 设置套接字选项,允许多个进程绑定到同一个端口ret = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &reuse_enble, sizeof(reuse_enble));if (-1 == ret) {perror("setsockopt");return 2;}//绑定本局域网内的某个多播用的端口号.sin.sin_family = AF_INET;sin.sin_port = htons(MULTICAST_PORT);sin.sin_addr.s_addr = INADDR_ANY;ret = bind(sock_fd, (struct sockaddr*) &sin, sizeof(sin));if (-1 == ret) {perror("bind");return 3;}// 加入多播(组播)inet_pton(AF_INET, MULTICAST_GROUP, &mreq.imr_multiaddr.s_addr); // 设置多播组的地址mreq.imr_interface.s_addr = INADDR_ANY; // 设置本机器的地址。ret = setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));if (-1 == ret) {perror("setsockopt"); // 加入多播组失败.return 4;}printf("准备接收多播(组播)...\n");// 与服务器端通信while(1) {ret = recvfrom(sock_fd, buf_read, BUF_SIZE-1, 0, (struct sockaddr*)&cin, &cin_len);buf_read[ret] = 0;printf("收到广播:%s\n", buf_read);if (0 == strcmp("exit", buf_read))break;char buf[20];printf("发送者IP:%s, 端口号:%d\n",inet_ntop(AF_INET, &cin.sin_addr, buf,20),ntohs(cin.sin_port));}// 退出多播(组播)inet_pton(AF_INET, MULTICAST_GROUP, &mreq.imr_multiaddr.s_addr); // 设置多播组的地址mreq.imr_interface.s_addr = INADDR_ANY; // 设置本机器的地址。ret = setsockopt(sock_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));if (-1 == ret) {perror("setsockopt"); // 加入多播组失败.return 4;}// 关闭套接字 close(sock_fd);printf("主进程结束\n");return 0;
}
UDP 多播发送端实现
// UDP 多播发送端实现
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h> // man 7 ip
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#define MULTICAST_GROUP "239.0.0.10"
#define BROADCAST_PORT 6666#define BUF_SIZE (1024)int main(int argc, char * argv[]) {int sock_fd;int ret;struct sockaddr_in sin; // 服务器端IPv4地址族结构char buf_write[BUF_SIZE]; // 写数据缓冲区int reuse_enble = 1; // 值为 1 表示同意int ttl = 64; // 生存时间,数据包可以经过64跳。// 创建UDP套接字sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sock_fd) {perror("socket");return 1;}// 2. 设置多播组TTL(生存时间)ret = setsockopt(sock_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));if (-1 == ret) {perror("setsockopt");return 2;}// 3. 让端口号可以重新绑定ret = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &reuse_enble, sizeof(reuse_enble));if (-1 == ret) {perror("setsockopt");return 2;}//4. 设置多播用的IP和端口号信息sin.sin_family = AF_INET;sin.sin_port = htons(BROADCAST_PORT);// sin.sin_addr.s_addr = INADDR_ANY;inet_pton(AF_INET, MULTICAST_GROUP, &sin.sin_addr);printf("准备发送广播...\n");// 循环多播 60 秒后退出for (int i = 0; i < 60; i++) {sprintf(buf_write, "广播数字:%d", i);ret = sendto(sock_fd, buf_write, strlen(buf_write), 0, (struct sockaddr*)&sin, sizeof(sin));if (-1 == ret) {close(sock_fd);return 1;}sleep(1);}// 断开连接 close(sock_fd);printf("主进程结束\n");return 0;
}