【Linux C编程】第十八章 高并发服务器(五)

举报
Yuchuan 发表于 2021/05/12 17:27:12 2021/05/12
【摘要】 高并发服务器进阶

八、广播

    广播结构图:

      注意:广播只适用于局域网

     代码实现:

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后可以获取客户端信息。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。