报式套接字(Datagram Socket)是一种无连接的网络通信接口,使用UDP协议传输数据,数据以独立的数据包形式发送,不保证数据的顺序和可靠性,但具有较低的延迟和开销。
使用流程
Server端流程:
- 创建套接字:使用
socket(AF_INET, SOCK_DGRAM, 0)
创建UDP套接字 - 绑定地址:使用
bind()
函数绑定服务器的IP地址和端口号 - 接收数据:使用
recvfrom()
接收客户端发送的数据包 - 发送响应:使用
sendto()
向客户端发送数据(可选) - 关闭套接字:使用
close()
关闭套接字
Client端流程:
- 创建套接字:同样使用
socket(AF_INET, SOCK_DGRAM, 0)
创建UDP套接字 - 发送数据:使用
sendto()
向服务器指定的地址和端口发送数据包 - 接收响应:使用
recvfrom()
接收服务器的响应数据(可选) - 关闭套接字:使用
close()
关闭套接字
NOTE当UDP客户端不调用bind()时,操作系统会在第一次调用sendto()时自动为其随机分配
函数
recvfrom
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
作用
recvfrom
用于从套接字接收消息。它可以用于接收无连接和面向连接套接字上的数据。该函数将接收到的消息放入缓冲区 buf
中。
参数
int sockfd
:指定从哪个套接字接收数据。void *buf
:指向缓冲区的指针,用于存放接收到的数据。size_t len
:指定缓冲区的大小。int flags
:指定接收操作的标志。
选项 | 说明 |
---|---|
MSG_CMSG_CLOEXEC | (仅限 recvmsg ) 设置通过 UNIX 域文件描述符接收的文件描述符的 close-on-exec 标志。 |
MSG_DONTWAIT | 启用非阻塞操作;如果操作会阻塞,则调用失败,并返回错误 EAGAIN 或 EWOULDBLOCK 。 |
MSG_ERRQUEUE | 指定应从套接字错误队列中接收排队的错误。 |
MSG_OOB | 请求接收带外数据,这些数据不会在正常数据流中接收。 |
MSG_PEEK | 使接收操作返回接收队列开头的数据,而不从队列中删除该数据。 |
MSG_TRUNC | 返回数据包或数据报的真实长度,即使它比传递的缓冲区长。 |
MSG_WAITALL | 请求操作阻塞直到满足完整请求。 |
struct sockaddr *src_addr
:如果此参数不为 NULL,并且底层协议提供了消息的源地址,则将源地址放入此参数指向的缓冲区中。socklen_t *addrlen
:这是一个值-结果参数。在调用之前,应将其初始化为与src_addr
关联的缓冲区的大小。返回时,addrlen
会更新为包含源地址的实际大小。
返回值
- 成功时返回接收的字节数。
- 失败时返回 -1,并设置
errno
指示错误类型。
sendto
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
作用
sendto
系统调用用于在套接字上发送消息。与 send
不同,sendto
可以在未连接的套接字上使用,并通过 dest_addr
和 addrlen
参数指定目标地址。对于面向连接的套接字(如 SOCK_STREAM
或 SOCK_SEQPACKET
),如果套接字已连接,则 dest_addr
和 addrlen
参数会被忽略;
参数
int sockfd
:发送套接字的文件描述符。const void *buf
:指向要发送的数据缓冲区的指针。size_t len
:要发送的数据长度(以字节为单位)。int flags
:控制发送行为的标志位,可以通过按位或组合多个标志。const struct sockaddr *dest_addr
:目标地址结构体的指针。socklen_t addrlen
:目标地址结构体的大小。
flags 参数选项
选项 | 说明 |
---|---|
MSG_CONFIRM | 通知链路层已收到对方的成功响应,适用于 SOCK_DGRAM 和 SOCK_RAW 套接字。 |
MSG_DONTROUTE | 不使用网关发送数据包,仅发送到直接连接的主机,通常用于诊断或路由程序。 |
MSG_DONTWAIT | 启用非阻塞操作;如果操作会阻塞,则返回 EAGAIN 或 EWOULDBLOCK 。 |
MSG_EOR | 终止一条记录仅适用于支持此特性的套接字类型,如 SOCK_SEQPACKET 。 |
MSG_MORE | 表示调用者还有更多数据要发送。对于 TCP 套接字,可实现与 TCP_CORK 类似的效果;对于 UDP 套接字,可将多次调用的数据打包成一个数据报。 |
MSG_NOSIGNAL | 发送数据时,如果对端关闭了连接,不生成 SIGPIPE 信号。 |
MSG_OOB | 在支持带外数据的套接字上发送带外数据(例如 SOCK_STREAM )。 |
MSG_FASTOPEN | 尝试使用 TCP Fast Open(RFC7413),在 SYN 包中发送数据。 |
返回值
- 成功时返回已发送的字节数。
- 失败时返回 -1,并设置
errno
指示错误类型。
示例Get Score
这是一个基于UDP协议的简单客户端-服务器通信程序,用于传输学生姓名和成绩信息。 客户端发送学生信息(姓名、语文成绩、数学成绩)到服务器,服务器接收并显示这些信息。
client.c
#include "arpa/inet.h"#include "proto.h"#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/socket.h"int main(int argc, char *argv[]) { if (argc != 4) { fprintf(stderr, "ERROR!Usage:<name> <chinese> <math>"); exit(1); }
struct msg_st msg; strcpy(msg.name, argv[1]); msg.chinese = atoi(argv[2]); msg.math = atoi(argv[3]); int sd; sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { perror("socket()"); exit(1); }
struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); socklen_t socklen; socklen = sizeof(addr); if (sendto(sd, &msg, sizeof(msg), 0, (void *)&addr, socklen) < 0) { perror("sendto()"); exit(1); }
return 0;}
server.c
#include "proto.h"#include "stdio.h"#include "stdlib.h"#include "sys/socket.h"#include <arpa/inet.h>
#define BUFFSIZE 1024#define IPSIZE 24int main() { int sd; struct sockaddr_in laddr; struct sockaddr_in raddr;
laddr.sin_family = AF_INET; laddr.sin_port = htons(atoi(RCVPORT)); inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr);
struct msg_st *rbuf; rbuf = malloc(sizeof(struct msg_st)); if (rbuf == NULL) { perror("malloc()"); exit(1); }
socklen_t raddr_len; raddr_len = sizeof(raddr);
sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { perror("socket()"); exit(1); } if (bind(sd, (struct sockaddr *)&laddr, sizeof(struct sockaddr_in)) < 0) { perror("bind()"); exit(1); } char ipstr[IPSIZE];
while (1) { recvfrom(sd, rbuf, sizeof(rbuf), 0, (void *)&raddr, &raddr_len);
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, raddr_len); printf("--MESSAGE FROM %s:%d--\n", ipstr, ntohs(raddr.sin_port)); printf("NAME=%s\n", rbuf->name); printf("CHINESE=%d\n", rbuf->chinese); printf("MATH=%d\n", rbuf->math); }}
proto.h
#pragma once
#define NAMESIZE 16#define RCVPORT "1989"
#include "stdint.h"struct msg_st { uint8_t name[NAMESIZE]; uint32_t math; uint32_t chinese;} __attribute__((packed));
运行结果:
动态数据报
数据报的结构和大小在运行时可变。对于可变长的数据报成为动态数据报。动态数据报可以更灵活地适应不同类型的数据负载。
对于一个变长结构体
struct msg_st { uint32_t math; uint32_t chinese; uint8_t name[];} __attribute__((packed));
UDP数据包推荐长度为512,UPD报头长度为8。
uint8_t name[]
的大小可以定义为:
UPD推荐长度-UPD报头长度-固定成员大小
#define NAMEMAX 512-8-8
对于修改后的程序,client(发送方),创建一个变长结构体进行发送。
server(接收方),按照最大长度进行接收。
proto.h
#pragma once
#define NAMESIZE 16#define RCVPORT "1989"
#define NAME_MAX (512 - 8 - 8)
#include "stdint.h"struct msg_st { uint32_t math; uint32_t chinese; uint8_t name[];
} __attribute__((packed));
client.c
#include "arpa/inet.h"#include "proto.h"#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/socket.h"int main(int argc, char *argv[]) { if (argc != 4) { fprintf(stderr, "ERROR!Usage:<name> <chinese> <math>"); exit(1); }
if (strlen(argv[1]) > NAME_MAX) { fprintf(stderr, "name is too long!\n"); exit(1); } int size = sizeof(struct msg_st) + strlen(argv[1]); struct msg_st *msg_p; msg_p = malloc(size); if (msg_p == NULL) { perror("malloc"); exit(1); }
strcpy(msg_p->name, argv[1]); msg_p->chinese = atoi(argv[2]); msg_p->math = atoi(argv[3]);
int sd; sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { perror("socket()"); exit(1); }
struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); socklen_t socklen; socklen = sizeof(addr); if (sendto(sd, msg_p, size, 0, (void *)&addr, socklen) < 0) { perror("sendto()"); exit(1); } free(msg_p); return 0;}
server.c
#include "proto.h"#include "stdio.h"#include "stdlib.h"#include "sys/socket.h"#include <arpa/inet.h>
#define BUFFSIZE 1024#define IPSIZE 24int main() { int sd; struct sockaddr_in laddr; struct sockaddr_in raddr;
laddr.sin_family = AF_INET; laddr.sin_port = htons(atoi(RCVPORT)); inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr);
int size = sizeof(struct msg_st) + NAME_MAX; struct msg_st *rbuf; rbuf = malloc(size); if (rbuf == NULL) { perror("malloc()"); exit(1); }
socklen_t raddr_len; raddr_len = sizeof(raddr);
sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { perror("socket()"); exit(1); } if (bind(sd, (struct sockaddr *)&laddr, sizeof(struct sockaddr_in)) < 0) { perror("bind()"); exit(1); } char ipstr[IPSIZE];
while (1) { recvfrom(sd, rbuf, size, 0, (void *)&raddr, &raddr_len);
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, raddr_len); printf("--MESSAGE FROM %s:%d--\n", ipstr, ntohs(raddr.sin_port)); printf("NAME=%s\n", rbuf->name); printf("CHINESE=%d\n", rbuf->chinese); printf("MATH=%d\n", rbuf->math); }}
运行结果: 相比原固有长度的数据报,该方案可以适应不同名字的大小,最大不超过NAMEMAX。
多点通信
相比流式套接字点对点通信,报式套接字可以做到多点通信。
setsockopt
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
作用
setsockopt
用于设置与套接字关联的各种选项。这些选项可以存在于多个协议级别,但它们始终存在于最高层的套接字级别。
参数
int sockfd
:指定套接字的文件描述符。int level
:指定选项所在的协议级别。要操作套接字 API 级别的选项,应将此参数设置为SOL_SOCKET
。对于其他级别的选项,应提供控制该选项的相应协议的协议号。int optname
:指定要设置的选项名称。const void *optval
:指向包含选项值的缓冲区。socklen_t optlen
:指定optval
缓冲区的大小。
选项 | 说明 |
---|---|
SOL_SOCKET | 表示选项位于套接字 API 级别。 |
IPPROTO_TCP | 表示选项由 TCP 协议控制。 |
IPPROTO_IP | 表示选项由 IP 协议控制。 |
返回值
- 成功时返回 0。
- 失败时返回 -1,并设置
errno
指示错误类型。
SOCKET操作选项
选项名称 | 简介 | 说明 |
---|---|---|
SO_ACCEPTCONN | 接受连接状态 | 返回值指示套接字是否已标记为通过 listen(2) 接受连接。0表示非监听套接字,1表示监听套接字(只读) |
SO_ATTACH_FILTER | 附加BPF过滤器 | 将经典BPF程序附加到套接字作为传入数据包的过滤器(Linux 2.2+) |
SO_ATTACH_BPF | 附加扩展BPF | 将扩展BPF程序附加到套接字(Linux 3.19+) |
SO_ATTACH_REUSEPORT_CBPF | 附加reuseport BPF | 与SO_REUSEPORT一起使用,设置经典BPF程序定义数据包如何分配给reuseport组中的套接字 |
SO_ATTACH_REUSEPORT_EBPF | 附加reuseport EBPF | 与SO_REUSEPORT一起使用,设置扩展BPF程序定义数据包如何分配给reuseport组中的套接字 |
SO_BINDTODEVICE | 绑定到设备 | 将套接字绑定到特定设备如”eth0” |
SO_BROADCAST | 广播标志 | 允许数据报套接字向广播地址发送数据包 |
SO_BSDCOMPAT | BSD错误兼容性 | 启用BSD错误兼容性(Linux 2.0和2.2中由UDP使用) |
SO_DEBUG | 套接字调试 | 启用套接字调试(需要CAP_NET_ADMIN能力或用户ID为0) |
SO_DETACH_FILTER | 移除BPF过滤器 | 移除通过SO_ATTACH_FILTER附加到套接字的BPF程序(Linux 2.2+) |
SO_DETACH_BPF | 移除扩展BPF | 移除通过SO_ATTACH_BPF附加到套接字的扩展BPF程序(Linux 3.19+) |
SO_DOMAIN | 套接字域 | 检索套接字域作为整数,返回AF_INET6等值(只读,Linux 2.6.32+) |
SO_ERROR | 套接字错误 | 获取并清除挂起的套接字错误(只读) |
SO_DONTROUTE | 不使用路由 | 不通过网关发送,仅直接发送到连接的主机 |
SO_INCOMING_CPU | CPU亲和性 | 设置或获取套接字的CPU亲和性(Linux 3.19+获取,4.4+设置) |
SO_INCOMING_NAPI_ID | NAPI ID | 返回与接收最后一个数据包的RX队列相关联的NAPI ID(Linux 4.12+) |
SO_KEEPALIVE | 保持连接 | 在面向连接的套接字上启用保活消息的发送 |
SO_LINGER | 延迟关闭 | 设置或获取SO_LINGER选项,参数是linger结构 |
SO_LOCK_FILTER | 锁定过滤器 | 设置后防止更改与套接字关联的过滤器 |
SO_MARK | 数据包标记 | 为通过此套接字发送的每个数据包设置标记(Linux 2.6.25+) |
SO_OOBINLINE | 带外数据 | 如果启用,带外数据将直接放入接收数据流中 |
SO_PASSCRED | 传递凭证 | 启用或禁用接收SCM_CREDENTIALS控制消息 |
SO_PASSSEC | 传递安全上下文 | 启用或禁用接收SCM_SECURITY控制消息 |
SO_PEEK_OFF | 窥视偏移 | 设置recv(2)系统调用与MSG_PEEK标志一起使用时的”窥视偏移量”值(Linux 3.4+,仅unix套接字) |
SO_PEERCRED | 对等进程凭证 | 返回连接到此套接字的对等进程的凭证 |
SO_PEERSEC | 对等安全上下文 | 返回连接到此套接字的对等套接字的安全上下文(Linux 2.6.2+) |
SO_PRIORITY | 协议优先级 | 为在此套接字上发送的所有数据包设置协议定义的优先级 |
SO_PROTOCOL | 套接字协议 | 检索套接字协议作为整数,返回IPPROTO_SCTP等值(只读,Linux 2.6.32+) |
SO_RCVBUF | 接收缓冲区大小 | 设置或获取以字节为单位的最大套接字接收缓冲区 |
SO_RCVBUFFORCE | 强制接收缓冲区大小 | 特权进程可以执行与SO_RCVBUF相同任务,但可以覆盖rmem_max限制(Linux 2.6.14+) |
SO_RCVLOWAT | 接收低水位标记 | 指定缓冲区中的最小字节数,直到套接字层将数据传递给用户 |
SO_RCVTIMEO | 接收超时 | 指定接收超时,直到报告错误 |
SO_REUSEADDR | 地址重用 | 指示在验证bind(2)调用中提供的地址时应允许重用本地地址 |
SO_REUSEPORT | 端口重用 | 允许多个AF_INET或AF_INET6套接字绑定到相同的套接字地址(Linux 3.9+) |
SO_RXQ_OVFL | 接收队列溢出 | 指示应将无符号32位值辅助消息附加到接收的skbs,指示自创建套接字以来丢弃的数据包数(Linux 2.6.33+) |
SO_SELECT_ERR_QUEUE | 错误队列选择 | 当设置此选项时,套接字上的错误条件不仅会通过select(2)的exceptfds集合通知(Linux 3.10+) |
SO_SNDBUF | 发送缓冲区大小 | 设置或获取以字节为单位的最大套接字发送缓冲区 |
SO_SNDBUFFORCE | 强制发送缓冲区大小 | 特权进程可以执行与SO_SNDBUF相同任务,但可以覆盖wmem_max限制(Linux 2.6.14+) |
SO_SNDLOWAT | 发送低水位标记 | 指定缓冲区中的最小字节数,直到套接字层将数据传递给协议 |
SO_SNDTIMEO | 发送超时 | 指定发送超时,直到报告错误 |
SO_TIMESTAMP | 时间戳 | 启用或禁用接收SO_TIMESTAMP控制消息 |
SO_TIMESTAMPNS | 纳秒时间戳 | 启用或禁用接收SO_TIMESTAMPNS控制消息(Linux 2.6.22+) |
SO_TYPE | 套接字类型 | 获取套接字类型作为整数(如SOCK_STREAM)(只读) |
SO_BUSY_POLL | 忙轮询 | 设置在没有数据时忙轮询阻塞接收的近似时间(微秒)(Linux 3.11+) |
全网/子网广播
广播允许一个设备向网络中的所有其他设备同时发送数据。
在创建socket后,需要setsockop
设置socket广播标志
server端需要设置全网地址255.255.255.255
(或子网广播地址)。
proto.h
#pragma once
#define NAMESIZE 16#define SERVER_PORT "1999"
#include "stdint.h"struct msg_st { uint8_t name[NAMESIZE]; uint32_t math; uint32_t chinese;} __attribute__((packed));
server.c
#include "proto.h"#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/socket.h"#include "unistd.h"#include <arpa/inet.h>#define BUFFSIZE 1024#define IPSIZE 24
#define BCAST_SERVER "255.255.255.255"int main(int argc, char *argv[]) {
struct msg_st *sbuf_p; sbuf_p = malloc(sizeof(struct msg_st)); if (sbuf_p == NULL) { perror("malloc()"); exit(1); } sbuf_p->chinese = htonl(rand() % 100); sbuf_p->math = htonl(rand() % 100); strcpy(sbuf_p->name, "Jim");
int sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { perror("socket()"); exit(1); } int32_t opval = 1; //设置全局广播 if (setsockopt(sd, SOL_SOCKET, SO_BROADCAST, &opval, sizeof(opval)) < 0) { perror("setsockopt()"); exit(1); }
struct sockaddr_in s_addr; s_addr.sin_family = AF_INET; s_addr.sin_port = htons(atoi(SERVER_PORT)); inet_pton(AF_INET, BCAST_SERVER, &s_addr.sin_addr); socklen_t len; len = sizeof(s_addr); // bind(sd, (const struct sockaddr *)&s_addr, len);
while (1) { sbuf_p->chinese = htonl(rand() % 100); sbuf_p->math = htonl(rand() % 100); if (sendto(sd, sbuf_p, sizeof(struct msg_st), 0, (const struct sockaddr *)&s_addr, len) < 0) { perror("sendto"); exit(1); } puts("OK"); sleep(1); }
free(sbuf_p);
return 0;}
client.c
#include "arpa/inet.h"#include "proto.h"#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/socket.h"int main(int argc, char *argv[]) {
struct msg_st msg;
int sd; sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { perror("socket()"); exit(1); }
struct sockaddr_in laddr; struct sockaddr_in saddr;
socklen_t saddr_len; saddr_len = sizeof(saddr);
laddr.sin_family = AF_INET; laddr.sin_port = htons(atoi(SERVER_PORT));
inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr); socklen_t laddr_len; laddr_len = sizeof(laddr);
if (bind(sd, (void *)&laddr, sizeof(laddr)) < 0) { perror("bind()"); exit(1); }
if (recvfrom(sd, &msg, sizeof(struct msg_st), 0, (struct sockaddr *)&saddr, &saddr_len) < 0) { perror("recvfrom()"); exit(1); } char ipstr[20]; inet_ntop(AF_INET, &saddr.sin_addr, ipstr, saddr_len);
printf("--MESSAGE FROM %s:%d--\n", ipstr, ntohs(saddr.sin_port)); printf("NAME=%s\n", msg.name); printf("CHINESE=%d\n", ntohl(msg.chinese)); printf("MATH=%d\n", ntohl(msg.math));
return 0;}
运行结果:
![[Pasted image 20250826115159.png]]
多播/组播
组播(Multicast)允许一个或多个发送者(组播源)向多个接收者同时发送数据。
proto.h
#pragma once
#define NAMESIZE 16#define SERVER_PORT "1999"#define MTGROUP "224.2.2.2"
#include "stdint.h"struct msg_st { uint8_t name[NAMESIZE]; uint32_t math; uint32_t chinese;} __attribute__((packed));
server.c
#include "net/if.h"#include "netinet/in.h"#include "netinet/ip.h"#include "proto.h"#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/socket.h"#include "unistd.h"#include <arpa/inet.h>
#define BUFFSIZE 1024#define IPSIZE 24
int main(int argc, char *argv[]) {
struct msg_st *sbuf_p; sbuf_p = malloc(sizeof(struct msg_st)); if (sbuf_p == NULL) { perror("malloc()"); exit(1); } sbuf_p->chinese = htonl(rand() % 100); sbuf_p->math = htonl(rand() % 100); // sbuf_p->name = "Jim"; strcpy(sbuf_p->name, "Jim");
int sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { perror("socket()"); exit(1); } struct ip_mreqn mreq; inet_pton(AF_INET, MTGROUP, &mreq.imr_multiaddr); inet_pton(AF_INET, "0.0.0.0", &mreq.imr_address); mreq.imr_ifindex = if_nametoindex("wlp0s20f3"); if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, &mreq, sizeof(struct ip_mreqn)) < 0) { perror("setsockopt() IP_MULTICAST_IF"); exit(1); }
struct sockaddr_in s_addr; s_addr.sin_family = AF_INET; s_addr.sin_port = htons(atoi(SERVER_PORT)); inet_pton(AF_INET, MTGROUP, &s_addr.sin_addr); socklen_t len; len = sizeof(s_addr); // bind(sd, (const struct sockaddr *)&s_addr, len);
while (1) { sbuf_p->chinese = htonl(rand() % 100); sbuf_p->math = htonl(rand() % 100); if (sendto(sd, sbuf_p, sizeof(struct msg_st), 0, (const struct sockaddr *)&s_addr, len) < 0) { perror("sendto"); exit(1); } puts("OK"); sleep(1); }
free(sbuf_p);
return 0;}
client.c
#include "arpa/inet.h"#include "net/if.h"#include "proto.h"#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/socket.h"int main(int argc, char *argv[]) {
struct msg_st msg;
int sd; sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { perror("socket()"); exit(1); } struct ip_mreqn mreq;
inet_pton(AF_INET, MTGROUP, &mreq.imr_multiaddr); inet_pton(AF_INET, "0.0.0.0", &mreq.imr_address); mreq.imr_ifindex = if_nametoindex("wlp0s20f3");
if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(struct ip_mreqn)) < 0) { perror("setsockop()"); exit(1); }
struct sockaddr_in laddr; struct sockaddr_in saddr;
socklen_t saddr_len; saddr_len = sizeof(saddr);
laddr.sin_family = AF_INET; laddr.sin_port = htons(atoi(SERVER_PORT));
inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr); socklen_t laddr_len; laddr_len = sizeof(laddr);
if (bind(sd, (void *)&laddr, sizeof(laddr)) < 0) { perror("bind()"); exit(1); }
if (recvfrom(sd, &msg, sizeof(struct msg_st), 0, (struct sockaddr *)&saddr, &saddr_len) < 0) { perror("recvfrom()"); exit(1); } char ipstr[20]; inet_ntop(AF_INET, &saddr.sin_addr, ipstr, saddr_len);
printf("--MESSAGE FROM %s:%d--\n", ipstr, ntohs(saddr.sin_port)); printf("NAME=%s\n", msg.name); printf("CHINESE=%d\n", ntohl(msg.chinese)); printf("MATH=%d\n", ntohl(msg.math));
return 0;}
运行结果: