alin的学习之路(Linux网络编程:七)(UDP广播、UDP组播、本地套接字)
1. UDP 广播
通过UDP 广播可以将数据发送给同一网段下的所有指定端口号的进程。但是不能够在Internet上传输数据,只能在局域网中。
注意:只要是存在在与服务器同一网段内的主机,都会收到广播的数据,无论是否需要。
- 广播地址:192.168 .xxx. 255
- 服务器的地址(IP+port)不那么重要了。 可以依赖隐式绑定。
- 客户端的port,必须要固定。不能再依赖隐式绑定。
- 只适用于 “局域网” 通信。
1. 开放广播权限
使用 setsockopt() 函数
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
// 示例:
int flag = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag));
sockfd:套接字fd
level:SOL_SOCKET
optname:SO_BROADCAST (表示广播)
optval:&flag
optlen:flag的长度。
2. 实现广播的流程
server端实现流程
- socket() 函数创建套接字sockfd
- bind() 函数绑定自己的ip+port。可以依赖隐式绑定即不写该代码。因为:1.不用建立连接。2.数据不发回
- setsockopt() 函数开放广播权限
- 初始化客户端clt_addr,不能依赖隐式绑定,ip要设为指定的 “192.168.xxx.255”,端口号设置一个固定的值
- sendto(sockfd,&clt_addr);
- close(sockfd);
client端实现流程
- socket() 函数创建套接字sockfd
- 初始化客户端自己的地址结构clt_addr,port必须是服务器设置的值
- bind() 函数绑定设置好的地址结构
- recvfrom() 函数接收数据
- close(sockfd);
2. 代码实现
server端
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SRV_PORT 8000
#define BROADCAST_PORT 9000
#define BROADCAST_IP "192.168.124.255"
int main()
{
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in srv_addr,clt_addr;
/*
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd,(struct sockaddr*)&srv_addr, sizeof(srv_addr));
*/
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (void*)&opt, sizeof(opt));
clt_addr.sin_family = AF_INET;
clt_addr.sin_port = htons(BROADCAST_PORT);
inet_pton(AF_INET, BROADCAST_IP, &clt_addr.sin_addr.s_addr);
char buf[BUFSIZ];
int i=0;
while(1)
{
sprintf(buf,"broadcast %dth message\n",i++);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&clt_addr, sizeof(clt_addr));
sleep(1);
}
return 0;
}
client 端
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#define BROADCAST_PORT 9000
int main()
{
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in clt_addr;
clt_addr.sin_family = AF_INET;
clt_addr.sin_port = htons(BROADCAST_PORT);
clt_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd,(struct sockaddr*)&clt_addr,sizeof(clt_addr));
char buf[BUFSIZ];
while(1)
{
int n = recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
write(STDOUT_FILENO, buf, n);
}
return 0;
}
2. UDP 组播(多播)
既适用于 “局域网”, 也适用于 “广域网”。
组播组可以是永久的也可以是临时的。组播组地址中,有一部分由官方分配的,称为永久组播
组。永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。永久组播组中成员的
数量都可以是任意的,甚至可以为零。那些没有保留下来供永久组播组使用的ip组播地址,可以
被临时组播组利用。
1. 组播预备知识
1. 组播地址
224.0.0.0~224.0.0.255 为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
224.0.1.0~224.0.1.255 是公用组播地址,可以用于Internet;欲使用需申请。
224.0.2.0~238.255.255.255 为用户可用的组播地址(临时组地址),全网范围内有效;
239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效。 —— 测试学习使用 该组播地址。如:239.0.0.7
2. 查看网卡信息
-
命令:ip address ---- ip ad
ip ad 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 00:0c:29:1e:51:21 brd ff:ff:ff:ff:ff:ff inet 192.168.124.128/24 brd 192.168.124.255 scope global dynamic ens33 valid_lft 1473sec preferred_lft 1473sec inet6 fe80::20c:29ff:fe1e:5121/64 scope link valid_lft forever preferred_lft forever
-
获取网卡对应编号
#include <net/if.h> unsigned int if_nametoindex(const char *ifname); 参数:网卡名称(借助 ip ad 命令查看) 返回值:网卡序号(编号)
3. 开放组播权限和加入组播组
使用 setsockopt() 函数
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
// 例:
struct ip_mreqn group;
初始化 group
// 开发组播权限 --- server端
setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group));
// 加入到指定的组播组 --- client端
setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
4. 结构体 struct ip_mreqn
- 组播地址结构,用于向 setsockopt() 函数传参
- 可以使用命令
grep -r "struct ip_mreqn {" /usr/bin/include/ -n
,查找结构体的定义
#include <net/if.h>
struct ip_mreqn {
struct in_addr imr_multiaddr; /* 组播IP地址:如:“239.0.0.7” */
struct in_addr imr_address; /* 本机IP地址:192.168.0.105、0.0.0.0 */
int imr_ifindex; /* 网卡序号:命令获取、函数获取 */
};
// 举例:
定义组播地址:239.0.0.7
struct ip_mreqn group;
inet_pton(AF_INET, "239.0.0.7", &group.imr_multiaddr); // 设置组播IP地址
inet_pton(AF_INET, "0.0.0.0", &group.imr_address); // 设置IP地址 == INADDR_ANY
group.imr_ifindex = if_nametoindex("ens33"); // 设置网卡序号
2. 实现流程
server端
- 创建套接字 sockfd = socket()
- bind() 绑定服务器的ip地址和端口号,可省略。因为:1. 不需要建立连接。2. 不写回数据
- 创建 struct ip_mreqn group ,并绑定组播的ip地址,设置主机ip地址,设置网卡序号
- 使用 setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group)); 开放组播权限
- 初始化通信对端的地址结构 clt_addr,初始化ip地址为组播地址,端口号为指定端口号,客户端也要使用这个端口号
- sendto() 函数指定发送给 clt_addr
- close(sockfd)
client端
-
创建套接字 sockfd = socket()
-
bind() 绑定服务器的ip地址和端口号,端口号是服务器端绑定的指定端口号,不可省略。
-
创建 struct ip_mreqn group ,并绑定组播的ip地址,设置主机ip地址,设置网卡序号
-
使用 setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)); 加入组播组
-
recvfrom() 接收客户端发送的数据,最后两个参数可传NULL,不关心发送端的地址结构
-
close(sockfd);
3. 代码实现
server端
#include <stdio.h>
#include <string.h>
#include <net/if.h>
#include <unistd.h>
#include <arpa/inet.h>
#define MULTICAST_IP "239.0.0.7"
#define PORT 9001
#define SRV_PORT 8001
int main()
{
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in clt_addr;
clt_addr.sin_family = AF_INET;
clt_addr.sin_port = htons(PORT);
inet_pton(AF_INET, MULTICAST_IP, &clt_addr.sin_addr.s_addr);
struct ip_mreqn group;
inet_pton(AF_INET, MULTICAST_IP, &group.imr_multiaddr);
inet_pton(AF_INET, "0.0.0.0", &group.imr_address);
group.imr_ifindex = if_nametoindex("ens33");
setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group,sizeof(group));
int i=0;
char buf[BUFSIZ];
while(1)
{
sprintf(buf, "muticast %dth message\n", i++);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&clt_addr, sizeof(clt_addr));
sleep(1);
}
return 0;
}
client端
#include <stdio.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 9001
#define MULTICAST_IP "239.0.124.7"
int main()
{
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in clt_addr;
clt_addr.sin_family = AF_INET;
clt_addr.sin_port = htons(PORT);
inet_pton(AF_INET, MULTICAST_IP, &clt_addr.sin_addr.s_addr);
bind(sockfd, (struct sockaddr*)&clt_addr, sizeof(clt_addr));
struct ip_mreqn group;
inet_pton(AF_INET, MULTICAST_IP, &group.imr_multiaddr);
inet_pton(AF_INET, "0.0.0.0", &group.imr_address);
group.imr_ifindex = if_nametoindex("ens33");
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
char buf[BUFSIZ];
while(1)
{
int n = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
write(STDOUT_FILENO, buf, n);
}
return 0;
}
3. 本地套接字 domain
本地套接字文件是个伪文件,不占用磁盘空间
1. 常见IPC(进程间通信)
- 匿名管道 pipe :使用简单
- 命名管道 fifo : 可以在无血缘关系的进程间通信,一次性读取
- 共享存储映射 mmap:可以在无血缘关系的进程间通信,通过mmap写入的通信内容会保留在内存中,可以反复读取
- 信号 signal:开销最小,但传递的信息量有限
- 本地套接字 domain:稳定性强
2. 对比网络套接字的实现流程
- socket() 函数
int socket(int domain, int type, int protocol);
domain : AF_INET ----> AF_UNIX/AF_LOCAL
type : SOCK_STREAM 和 SOCK_DGRAM 均可
- 地址结构和 bind() 函数
#include <sys/un.h>
struct sockaddr_in ------> struct sockaddr_un
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* Pathname */
};
sun_family = AF_UNIX;
strcpy(sun_path,"xxxx");
bind(lfd, (struct sockaddr*)&xxx_addr, 实际长度len);
实际长度len = offsetof(struct sockaddr_un, sun_path) + strlen(sun_path);
offsetof(类型,变量); #include <stddef.h> 注意:第二个参数不是具体的变量名,而是定义中的变量名
用于求类型中对应变量的偏移量,所以 offsetof(struct sockaddr_un, sun_path) 相当于AF_UNIX的大小,即为 2
- bind() 函数 调用成功后会产生一个文件socket文件(“srv.socket”)。因此为了保证在创建时该目录下没有该文件,通常在 bind() 函数前调用一个 unlink() 函数用于删除一个硬链接,即删除文件。
- 客户端不能依赖隐式绑定,客户端亦同,需要显示绑定socket文件名。client 客户端中需要绑定两个地址:srv_addr 和 clt_addr
- 其他步骤均同:listen()、accept()、connect()
注意:accept() 函数中 len的类型转换
3. 编码实现
server端
#include <strings.h>
#include <ctype.h>
#include <stddef.h>
#include <sys/un.h>
#include <string.h>
#include "wrap.h"
#define SRV_FILENAME "srv.socket"
int main()
{
int lfd, cfd, ret, len;
struct sockaddr_un srv_addr, clt_addr;
srv_addr.sun_family = AF_UNIX;
strcpy(srv_addr.sun_path, SRV_FILENAME);
len = offsetof(struct sockaddr_un, sun_path) + strlen(srv_addr.sun_path);
lfd = Socket(AF_UNIX, SOCK_STREAM, 0);
unlink(SRV_FILENAME);
Bind(lfd, (struct sockaddr*)&srv_addr, len);
Listen(lfd, 128);
cfd = Accept(lfd, (struct sockaddr*)&clt_addr, (socklen_t*)&len);
len -= offsetof(struct sockaddr_un, sun_path);
clt_addr.sun_path[len] = '\0';
printf("客户端本地套接字文件:%s\n",clt_addr.sun_path);
char buf[BUFSIZ];
while(1)
{
ret = Read(cfd, buf, sizeof(buf));
if(0 == ret)
{
Close(cfd);
printf("客户端退出\n");
break;
}
for(int i=0 ;i<ret ;++i)
{
buf[i] = toupper(buf[i]);
}
Write(cfd, buf, ret);
Write(STDOUT_FILENO, buf, ret);
}
Close(lfd);
return 0;
}
client端
#include <strings.h>
#include <ctype.h>
#include <stddef.h>
#include <sys/un.h>
#include <string.h>
#include "wrap.h"
#define SRV_FILENAME "srv.socket"
#define CLT_FILENAME "clt.socket"
int main()
{
int cfd, ret, len;
struct sockaddr_un srv_addr, clt_addr;
cfd = Socket(AF_UNIX, SOCK_STREAM, 0);
clt_addr.sun_family = AF_UNIX;
strcpy(clt_addr.sun_path, CLT_FILENAME);
len = offsetof(struct sockaddr_un, sun_path) + strlen(clt_addr.sun_path);
unlink(CLT_FILENAME);
Bind(cfd, (struct sockaddr*)&clt_addr, len);
srv_addr.sun_family = AF_UNIX;
strcpy(srv_addr.sun_path, SRV_FILENAME);
len = offsetof(struct sockaddr_un, sun_path) + strlen(srv_addr.sun_path);
Connect(cfd, (struct sockaddr*)&srv_addr, len);
printf("客户端与服务器连接成功\n");
char buf[BUFSIZ];
while(1)
{
Write(cfd, "hello\n", strlen("hello\n"));
int n = Read(cfd, buf, sizeof(buf));
Write(STDOUT_FILENO, buf, n);
sleep(1);
}
return 0;
}
4. 本地套接字与网络套接字的比较
网络套接字 | 本地套接字 | |
---|---|---|
server | lfd = socket(AF_INET, SOCK_STREAM, 0); | lfd = socket(AF_UNIX, SOCK_STREAM, 0); |
bzero() ---- struct sockaddr_in serv_addr; | bzero() ---- struct sockaddr_un serv_addr, clie_addr; | |
serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(8888); |
serv_addr.sun_family = AF_UNIX; strcpy(serv_addr.sun_path, “套接字文件名”) len = offsetof(sockaddr_un, sun_path) + strlen(serv_addr.sun_path); |
|
bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); | unlink(“套接字文件名”); bind(lfd, (struct sockaddr *)&serv_addr, len); 创建新文件 |
|
Listen(lfd, 128); | Listen(lfd, 128); | |
cfd = Accept(lfd, ()&clie_addr, &len); | cfd = Accept(lfd, ()&clie_addr, &len); | |
client | lfd = socket(AF_INET, SOCK_STREAM, 0); | lfd = socket(AF_UNIX, SOCK_STREAM, 0); |
" 隐式绑定 获取 IP+port" | bzero() ---- struct sockaddr_un clie_addr; clie_addr.sun_family = AF_UNIX; strcpy(clie_addr.sun_path, “client套接字文件名”) len = offsetof(sockaddr_un, sun_path) + strlen(); unlink( “client套接字文件名”); bind(lfd, (struct sockaddr *)&clie_addr, len); |
|
bzero() ---- struct sockaddr_in serv_addr; | bzero() ---- struct sockaddr_un serv_addr; | |
serv_addr.sin_family = AF_INET; | serv_addr.sun_family = AF_UNIX; | |
inet_pton(AF_INT, “服务器IP”, &sin_addr.s_addr); serv_addr.sin_port = htons(“服务器端口”); |
strcpy(serv_addr.sun_path, “server套接字文件名”) len = offsetof(sockaddr_un, sun_path) + strlen(); |
|
connect(lfd, &serv_addr, sizeof()); | connect(lfd, &serv_addr, len); |
4. some small point
- tcp的拥塞机制对应滑动窗口,udp没有拥塞机制
- udp 无连接