【Linux C编程】第十八章 高并发服务器(五)
八、广播
广播结构图:
注意:广播只适用于局域网
代码实现:
server.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8
9 int main(int argc, const char* argv[])
10 {
11 // 创建套接字
12 int fd = socket(AF_INET, SOCK_DGRAM, 0);
13 if(fd == -1)
14 {
15 perror("socket error");
16 exit(1);
17 }
18
19 // 绑定server的iP和端口
20 struct sockaddr_in serv;
21 memset(&serv, 0, sizeof(serv));
22 serv.sin_family = AF_INET;
23 serv.sin_port = htons(8787); // server端口
24 serv.sin_addr.s_addr = htonl(INADDR_ANY);
25 int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv));
26 if(ret == -1)
27 {
28 perror("bind error");
29 exit(1);
30 }
31
32 // 初始化客户端地址信息
33 struct sockaddr_in client;
34 memset(&client, 0, sizeof(client));
35 client.sin_family = AF_INET;
36 client.sin_port = htons(6767); // 客户端要绑定的端口
37 // 使用广播地址给客户端发数据
38 inet_pton(AF_INET, "192.168.30.255", &client.sin_addr.s_addr);
39
40 // 给服务器开放广播权限
41 int flag = 1;
42 setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag));
43
44 // 通信
45 while(1)
46 {
47 // 一直给客户端发数据
48 static int num = 0;
49 char buf[1024] = {0};
50 sprintf(buf, "hello, udp == %d\n", num++);
51 int ret = sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client));
52 if(ret == -1)
53 {
54 perror("sendto error");
55 break;
56 }
57
58 printf("server == send buf: %s\n", buf);
59
60 sleep(1);
61 }
62
63 close(fd);
64
65 return 0;
66 }
client.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8
9 int main(int argc, const char* argv[])
10 {
11 int fd = socket(AF_INET, SOCK_DGRAM, 0);
12 if(fd == -1)
13 {
14 perror("socket error");
15 exit(1);
16 }
17
18 // 绑定iP和端口
19 struct sockaddr_in client;
20 memset(&client, 0, sizeof(client));
21 client.sin_family = AF_INET;
22 client.sin_port = htons(6767);
23 inet_pton(AF_INET, "0.0.0.0", &client.sin_addr.s_addr);
24 int ret = bind(fd, (struct sockaddr*)&client, sizeof(client));
25 if(ret == -1)
26 {
27 perror("bind error");
28 exit(1);
29 }
30
31 // 接收数据
32 while(1)
33 {
34 char buf[1024] = {0};
35 int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
36 if(len == -1)
37 {
38 perror("recvfrom error");
39 break;
40 }
41
42 printf("client == recv buf: %s\n", buf);
43 }
44
45 close(fd);
46
47 return 0;
48 }
九、多播(组播)
使用范围:
- 局域网
- Internet
组播结构图:
结构体:
struct ip_mreqn
{
// 组播组的IP地址.
struct in_addr imr_multiaddr;
// 本地某一网络设备接口的IP地址。
struct in_addr imr_interface;
int imr_ifindex; // 网卡编号
};
struct in_addr
{
in_addr_t s_addr;
};
组播组可以是永久的也可以是临时的。组播组地址中,有一部分由官方分配的,称为永久组播组。永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。永久组播组中成员的数量
都可以是任意的,甚至可以为零。那些没有保留下来供永久组播组使用的ip组播地址,可以被临时组播组利用。
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 为本地管理组播地址,仅在特定的本地范围内有效。
可使用ip ad命令查看网卡编号,如:
[root@centos ~]# 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 pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:60:ad:b3 brd ff:ff:ff:ff:ff:ff
inet 192.168.30.137/24 brd 192.168.30.255 scope global noprefixroute dynamic ens33
valid_lft 1642sec preferred_lft 1642sec
inet6 fe80::7341:e2f1:498c:8501/64 scope link noprefixroute
valid_lft forever preferred_lft forever
if_nametoindex 命令可以根据网卡名,获取网卡序号。
代码实现:
server.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8 #include <net/if.h>
9
10 int main(int argc, const char* argv[])
11 {
12 // 创建套接字
13 int fd = socket(AF_INET, SOCK_DGRAM, 0);
14 if(fd == -1)
15 {
16 perror("socket error");
17 exit(1);
18 }
19
20 // 绑定server的iP和端口
21 struct sockaddr_in serv;
22 memset(&serv, 0, sizeof(serv));
23 serv.sin_family = AF_INET;
24 serv.sin_port = htons(8787); // server端口
25 serv.sin_addr.s_addr = htonl(INADDR_ANY);
26 int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv));
27 if(ret == -1)
28 {
29 perror("bind error");
30 exit(1);
31 }
32
33 // 初始化客户端地址信息
34 struct sockaddr_in client;
35 memset(&client, 0, sizeof(client));
36 client.sin_family = AF_INET;
37 client.sin_port = htons(6666); // 客户端要绑定的端口
38 // 使用组播地址给客户端发数据
39 inet_pton(AF_INET, "239.0.0.10", &client.sin_addr.s_addr);
40
41 // 给服务器开放组播权限
42 struct ip_mreqn flag;
43 // init flag
44 inet_pton(AF_INET, "239.0.0.10", &flag.imr_multiaddr.s_addr); // 组播地址
45 inet_pton(AF_INET, "0.0.0.0", &flag.imr_address.s_addr); // 本地IP
46 flag.imr_ifindex = if_nametoindex("ens33");
47 setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &flag, sizeof(flag));
48
49 // 通信
50 while(1)
51 {
52 // 一直给客户端发数据
53 static int num = 0;
54 char buf[1024] = {0};
55 sprintf(buf, "hello, udp == %d\n", num++);
56 int ret = sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client));
57 if(ret == -1)
58 {
59 perror("sendto error");
60 break;
61 }
62
63 printf("server == send buf: %s\n", buf);
64
65 sleep(1);
66 }
67
68 close(fd);
69
70 return 0;
71 }
client.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8 #include <net/if.h>
9
10 int main(int argc, const char* argv[])
11 {
12 int fd = socket(AF_INET, SOCK_DGRAM, 0);
13 if(fd == -1)
14 {
15 perror("socket error");
16 exit(1);
17 }
18
19 // 绑定iP和端口
20 struct sockaddr_in client;
21 memset(&client, 0, sizeof(client));
22 client.sin_family = AF_INET;
23 client.sin_port = htons(6666); // ........
24 inet_pton(AF_INET, "0.0.0.0", &client.sin_addr.s_addr);
25 int ret = bind(fd, (struct sockaddr*)&client, sizeof(client));
26 if(ret == -1)
27 {
28 perror("bind error");
29 exit(1);
30 }
31
32 // 加入到组播地址
33 struct ip_mreqn fl;
34 inet_pton(AF_INET, "239.0.0.10", &fl.imr_multiaddr.s_addr);
35 inet_pton(AF_INET, "0.0.0.0", &fl.imr_address.s_addr);
36 fl.imr_ifindex = if_nametoindex("ens33");
37 setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &fl, sizeof(fl));
38
39 // 接收数据
40 while(1)
41 {
42 char buf[1024] = {0};
43 int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
44 if(len == -1)
45 {
46 perror("recvfrom error");
47 break;
48 }
49
50 printf("client == recv buf: %s\n", buf);
51 }
52
53 close(fd);
54
55 return 0;
56 }
十、socket IPC(本地套接字domain)
通过管道与通过本地套接字实现进程间通信:
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址
127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因
为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket
也是可靠的,消息既不会丢失也不会顺序错乱。
UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIXDomain Socket
通讯的。
使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,
protocol参数仍然指定为0即可。
UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型
的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。
对比网络套接字地址结构和本地套接字地址结构:
struct sockaddr_in {
__kernel_sa_family_t sin_family; /*
Address family */ 地址结构类型
__be16 sin_port; /* Port
number */ 端口号
struct in_addr sin_addr; /*
Internet address */ IP地址
};
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */ 地址结构类型
char sun_path[UNIX_PATH_MAX]; /* pathname */ socket文件名(含路径)
};
以下程序将UNIX Domain socket绑定到一个地址。
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
#define offsetof(type, member) ((int)&((type *)0)->MEMBER)
代码实现:
server.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8 #include <sys/un.h>
9
10 int main(int argc, const char* argv[])
11 {
12 int lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
13 if(lfd == -1)
14 {
15 perror("socket error");
16 exit(1);
17 }
18
19 // 如果套接字文件存在, 删除套接字文件
20 unlink("server.sock");
21
22 // 绑定
23 struct sockaddr_un serv;
24 serv.sun_family = AF_LOCAL;
25 strcpy(serv.sun_path, "server.sock");
26 int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
27 if(ret == -1)
28 {
29 perror("bind error");
30 exit(1);
31 }
32
33 // 监听
34 ret = listen(lfd, 36);
35 if(ret == -1)
36 {
37 perror("listen error");
38 exit(1);
39 }
40
41 // 等待接收连接请求
42 struct sockaddr_un client;
43 socklen_t len = sizeof(client);
44 int cfd = accept(lfd, (struct sockaddr*)&client, &len);
45 if(cfd == -1)
46 {
47 perror("accept error");
48 exit(1);
49 }
50 printf("======client bind file: %s\n", client.sun_path);
51
52 // 通信
53 while(1)
54 {
55 char buf[1024] = {0};
56 int recvlen = recv(cfd, buf, sizeof(buf), 0);
57 if(recvlen == -1)
58 {
59 perror("recv error");
60 exit(1);
61 }
62 else if(recvlen == 0)
63 {
64 printf("clietn disconnect ....\n");
65 close(cfd);
66 break;
67 }
68 else
69 {
70 printf("recv buf: %s\n", buf);
71 send(cfd, buf, recvlen, 0);
72 }
73 }
74 close(cfd);
75 close(lfd);
76
77 return 0;
78 }
client.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8 #include <sys/un.h>
9
10 int main(int argc, const char* argv[])
11 {
12 int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
13 if(fd == -1)
14 {
15 perror("socket error");
16 exit(1);
17 }
18
19 unlink("client.sock");
20
21 // ================================
22 // 给客户端绑定一个套接字文件
23 struct sockaddr_un client;
24 client.sun_family = AF_LOCAL;
25 strcpy(client.sun_path, "client.sock");
26 int ret = bind(fd, (struct sockaddr*)&client, sizeof(client));
27 if(ret == -1)
28 {
29 perror("bind error");
30 exit(1);
31 }
32
33 // 初始化server信息
34 struct sockaddr_un serv;
35 serv.sun_family = AF_LOCAL;
36 strcpy(serv.sun_path, "server.sock");
37
38 // 连接服务器
39 connect(fd, (struct sockaddr*)&serv, sizeof(serv));
40
41 // 通信
42 while(1)
43 {
44 char buf[1024] = {0};
45 fgets(buf, sizeof(buf), stdin);
46 send(fd, buf, strlen(buf)+1, 0);
47
48 // 接收数据
49 recv(fd, buf, sizeof(buf), 0);
50 printf("recv buf: %s\n", buf);
51 }
52
53 close(fd);
54
55 return 0;
56 }
57
十一、其他常用函数
1. 名字与地址转换
gethostbyname根据给定的主机名,获取主机信息。
过时,仅用于IPv4,且线程不安全。
示例
1 #include <stdio.h>
2 #include <netdb.h>
3 #include <arpa/inet.h>
4
5 extern int h_errno;
6
7 int main(int argc, char *argv[])
8 {
9 struct hostent *host;
10 char str[128];
11 host = gethostbyname(argv[1]);
12 printf("%s\n", host->h_name);
13
14 while (*(host->h_aliases) != NULL)
15 printf("%s\n", *host->h_aliases++);
16
17 switch (host->h_addrtype) {
18 case AF_INET:
19 while (*(host->h_addr_list) != NULL)
20 printf("%s\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str)));
21 break;
22 default:
23 printf("unknown address type\n");
24 break;
25 }
26 return 0;
27 }
gethostbyaddr函数。
此函数只能获取域名解析服务器的url和/etc/hosts里登记的IP对应的域名。
示例
1 #include <stdio.h>
2 #include <netdb.h>
3 #include <arpa/inet.h>
4
5 extern int h_errno;
6
7 int main(int argc, char *argv[])
8 {
9 struct hostent *host;
10 char str[128];
11 struct in_addr addr;
12
13 inet_pton(AF_INET, argv[1], &addr);
14 host = gethostbyaddr((char *)&addr, 4, AF_INET);
15 printf("%s\n", host->h_name);
16
17 while (*(host->h_aliases) != NULL)
18 printf("%s\n", *host->h_aliases++);
19 switch (host->h_addrtype) {
20 case AF_INET:
21 while (*(host->h_addr_list) != NULL)
22 printf("%s\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str)));
23 break;
24 default:
25 printf("unknown address type\n");
26 break;
27 }
28 return 0;
29 }
getservbyname
getservbyport
根据服务程序名字或端口号获取信息。使用频率不高。
getaddrinfo
getnameinfo
freeaddrinfo
可同时处理IPv4和IPv6,线程安全的。
2. 套接口和地址关联
getsockname
根据accpet返回的sockfd,得到临时端口号
getpeername
根据accpet返回的sockfd,得到远端链接的端口号,在exec后可以获取客户端信息。
- 点赞
- 收藏
- 关注作者
评论(0)