setsockopt套接字属性设置,广播,组播,并发服务器

setsockopt

复制代码
`

`int` `setsockopt(int sockfd,int level,int optname,void` `*optval,socklen_t optlen)`

`功能:设置套接字属性`
` 参数:`
`	sockfd:套接字描述符 (指定要设置/获取哪个套接字的属性)`
`	level:协议层 (指定要控制的协议层次)`
`		SOL_SOCKET(应用层) 通用套接字选项;`  
`		IPPROTO_TCP(传输层)`
`		IPPROTO_IP(网络层) `
`	optname:选项名(指定要控制的内容,指定控制方式)`
     
 `--- SOL_SOCKET: man 7 socket -----`
       
`        SO_REUSEADDR:允许端口快速重用            int*`
`		SO_BROADCAST     允许发送广播数据          int` 
`		SO_RCVBUF       接收缓冲区大小              int` 
`		SO_SNDBUF       发送缓冲区大小              int` 
`		SO_RCVTIMEO      接收超时                 struct` `timeval` 
`		SO_SNDTIMEO      发送超时                 struct` `timeval`
		
	`void` `*optval:根据optname不同,该类型不同;`
`    socklen_t optlen/socklen_t *optlen:真实的optval指针指向的内存空间的大小;`
    
`返回值:`
`    成功,返回0;`
`    失败,返回-1,更新errno;`

`

1 】广播

1. 1理论

前面介绍的数据包发送方式只有一个接受方,称为单播

l如果同时发给局域网中的所有主机,称为广播

l只有用户数据报(使用UDP协议)套接字才能广播

l一般被设计成局域网搜索协议

l广播地址

¡以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址

¡发到该地址的数据包被所有的主机接收

1 .2广播发送流程

  1. 创建用户数据报套接字
  2. 填充结构体信息(IP:广播地址)
  3. 允许发送广播数据(setsockopt)
  4. 发送数据
  5. 关闭

send.c

复制代码
#include<stdio.h>`
`#include <sys/types.h>`
`#include <sys/socket.h>`
`#include <netinet/in.h>`
`#include <netinet/ip.h>`
`#include <arpa/inet.h>`
`#include <unistd.h>`
`#include <stdlib.h>`
`#include <string.h>`
`int` `main(int argc,` `char` `const` `*argv[])`
`{`
    `//1.创建用户数据报套接字`
    `int sockfd =` `socket(AF_INET,SOCK_DGRAM,0);`
    `if(sockfd <` `0` `)`
    `{`
        `perror("socket err");`
        `return` `-1;`
    `}`

    `//2.填充服务器信息`
    `struct` `sockaddr_in saddr;`
`    saddr.sin_family=AF_INET;`
`    saddr.sin_port=htons(9999);`
`    saddr.sin_addr.s_addr=inet_addr("192.168.1.255");`
`    socklen_t len  =` `sizeof(saddr);`

    `//3.允许发送广播数据`
    `int opt =1;`
    `setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt));`

    `//4.发送数据`
    `char buf[128]={0};`
    `while(1)`
    `{`
        `fgets(buf,sizeof(buf),stdin);`
        `if` `(buf[strlen(buf)` `-` `1]` `==` `'\n')` `// 去掉\n`
`            buf[strlen(buf)` `-` `1]` `=` `'\0';`

        `sendto(sockfd,buf,sizeof(buf),0,(struct` `sockaddr` `*)&saddr,len);`

        `printf("send ok\n");`
    `}`

    `//5.关闭套接字`
    `close(sockfd);`

    `return` `0;`
`}`

`

1 .3广播接收流程

  1. 创建用户数据报套接字
  2. 填充结构体信息(IP:广播地址)
  3. 绑定
  4. 接收
  5. 关闭

recv.c

复制代码
#include <stdio.h>`
`#include <sys/types.h>`
`#include <sys/socket.h>`
`#include <netinet/in.h>`
`#include <netinet/ip.h>`
`#include <arpa/inet.h>`
`#include <unistd.h>`
`#include <stdlib.h>`
`#include <string.h>`

`int` `main(int argc,` `char` `const` `*argv[])`
`{`
    `// 1.创建用户数据报套接字`
    `int sockfd =` `socket(AF_INET, SOCK_DGRAM,` `0);`
    `if` `(sockfd <` `0)`
    `{`
        `perror("socket err");`
        `return` `-1;`
    `}`

    `// 2.填充服务器信息`
    `struct` `sockaddr_in saddr,caddr;`
`    saddr.sin_family = AF_INET;`
`    saddr.sin_port =` `htons(9999);`
`    saddr.sin_addr.s_addr =` `inet_addr("192.168.1.255");`
`    socklen_t len =` `sizeof(saddr);`
    `// 3.绑定`
    `if` `(bind(sockfd,` `(struct` `sockaddr` `*)&saddr, len)` `<` `0)`
    `{`
        `perror("bind err");`
        `return` `-1;`
    `}`
    `// 4.接收数据`
    `char buf[128]` `=` `{0};`
    `while` `(1)`
    `{`
        `recvfrom(sockfd, buf,` `sizeof(buf),` `0,` `(struct` `sockaddr` `*)&caddr,` `&len);`

        `// 5.打印数据`
        `printf("ip:%s   port:%d   said:%s\n",` `inet_ntoa(caddr.sin_addr),` `ntohs(caddr.sin_port), buf);`

        `memset(buf,` `sizeof(buf),` `0);`
    `}`

    `// 6.关闭套接字`
    `close(sockfd);`

    `return` `0;`
`}`

`

2 】组播

2 .1理论

  • 单播方式只能发给一个接收方。
  • 广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。
  • 组播是一个人发送,加入到多播组的人接收数据。
  • 多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)

2 .2组播地址

不分网络地址和主机地址,第1字节的前4位固定为1110 。是D类IP

224.0.0.0~~239.255.255.255

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为本地管理组播地址,仅在特定的本地范围内有效

2 .3组播发送流程

  1. 创建用户数据报套接字
  2. 填充结构体(组播IP)
  3. 发送
  4. 关闭

send.c

复制代码
#include<stdio.h>`
`#include <sys/types.h>`
`#include <sys/socket.h>`
`#include <netinet/in.h>`
`#include <netinet/ip.h>`
`#include <arpa/inet.h>`
`#include <unistd.h>`
`#include <stdlib.h>`
`#include <string.h>`
`int main(int argc, char const *argv[])`
`{`
`    //1.创建用户数据报套接字`
`    int sockfd = socket(AF_INET,SOCK_DGRAM,0);`
`    if(sockfd < 0 )`
`    {`
`        perror("socket err");`
`        return -1;`
`    }`

`    //2.填充服务器信息`
`    struct sockaddr_in saddr;`
`    saddr.sin_family=AF_INET;`
`    saddr.sin_port=htons(9999);`
`    saddr.sin_addr.s_addr=inet_addr("224.10.10.1");`
`    socklen_t len  = sizeof(saddr);`

`    //3.发送数据`
`    char buf[128]={0};`
`    while(1)`
`    {`
`        fgets(buf,sizeof(buf),stdin);`
`        if (buf[strlen(buf) - 1] == '\n') // 去掉\n`
`            buf[strlen(buf) - 1] = '\0';`

`        sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&saddr,len);`

`        printf("send ok\n");`
`    }`

`    //5.关闭套接字`
`    close(sockfd);`

`    return 0;`
`}`

`

2. 4组播接收流程

  1. 创建用户数据报套接字
  2. 填充结构体(组播IP)
  3. 绑定
  4. 加入多播组
  5. 等待接收数据
复制代码
struct ip_mreq`
`{`
`	struct  in_addr  imr_multiaddr;   /* 指定多播组IP */`
`	struct  in_addr  imr_interface;   /* 本地网卡地址,通常指定为 INADDR_ANY--0.0.0.0*/};`
`}`

`

recv.c

复制代码
#include <stdio.h>`
`#include <sys/types.h>`
`#include <sys/socket.h>`
`#include <netinet/in.h>`
`#include <netinet/ip.h>`
`#include <arpa/inet.h>`
`#include <unistd.h>`
`#include <stdlib.h>`
`#include <string.h>`

`int` `main(int argc,` `char` `const` `*argv[])`
`{`
    `// 1.创建用户数据报套接字`
    `int sockfd =` `socket(AF_INET, SOCK_DGRAM,` `0);`
    `if` `(sockfd <` `0)`
    `{`
        `perror("socket err");`
        `return` `-1;`
    `}`

    `// 2.填充服务器信息`
    `struct` `sockaddr_in saddr,caddr;`
`    saddr.sin_family = AF_INET;`
`    saddr.sin_port =` `htons(9999);`
`    saddr.sin_addr.s_addr =` `inet_addr("224.10.10.1");`
`    socklen_t len =` `sizeof(saddr);`

    `// 3.绑定`
    `if` `(bind(sockfd,` `(struct` `sockaddr` `*)&saddr, len)` `<` `0)`
    `{`
        `perror("bind err");`
        `return` `-1;`
    `}`

    `//4.加入多播组`
    `struct` `ip_mreq mreq;`
`    mreq.imr_multiaddr.s_addr =` `inet_addr("224.10.10.1");`
`    mreq.imr_interface.s_addr = INADDR_ANY;`

    `setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));`

    `// 5.接收数据`
    `char buf[128]` `=` `{0};`
    `while` `(1)`
    `{`
        `recvfrom(sockfd, buf,` `sizeof(buf),` `0,` `(struct` `sockaddr` `*)&caddr,` `&len);`

        `// 6.打印数据`
        `printf("ip:%s   port:%d   said:%s\n",` `inet_ntoa(caddr.sin_addr),` `ntohs(caddr.sin_port), buf);`

        `memset(buf,` `sizeof(buf),` `0);`
    `}`

    `// 7.关闭套接字`
    `close(sockfd);`

    `return` `0;`
`}`

`

【3】服务器模型

l在网络程序里面,通常都是一个服务器处理多个客户机。

l为了处理多个客户机的请求, 服务器端的程序有不同的处理方式。

3.1 循环服务器模型

同一个时刻只能响应一个客户端的请求,伪代码如下

复制代码
socket();`
`bind();`
`listen();`
`while(1)`
`{`
    `accept();`
    `while(1)`
    `{`
        `//处理`
    `}`
    `close();`
`}`
`close();`
`

3.2 并发服务器模型

同一个时刻可以响应多个客户端的请求,常用的模型有多进程模型/多线程模型/IO多路复用 模型。

多进程模型

每来一个客户端连接,开一个子进程来专门处理客户端的数据,实现简单,但是系统开销相对较大,更推荐使用线程模型。伪代码如下:

复制代码
socket();`
`bind();`
`listen();`
`while(1)`
`{`
    `accept();`
    `if(fork()` `==` `0)`
    `{`
        `while(1)`
        `{`
            `//处理`
        `}`
        `close();`
        `exit();`
    `}`
`}`
`

多进程特点总结

  1. fork之前的代码被复制,但是不会重新执行一遍;fork之后的代码被复制,并且再被执行一遍。
  2. fork之后两个进程相互独立,子进程拷贝了父进程的所有代码,但内存空间独立
  3. fork之前打开文件,fork之后拿到的是同一个文件描述符,操作的是同一个文件指针

多线程模型(重点)

每来一个客户端连接,开一个子线程来专门处理客户端的数据,实现简单,占用资源较少,属于使用比较广泛的模型:

复制代码
socket();`
`bind();`
`listen();`
`while(1)`
`{`
    `accept();`
    `pthread_create();`
`}`
`

pthread_socket.c

复制代码
#include <sys/types.h> /* See NOTES */`
`#include <sys/socket.h>`
`#include <stdio.h>`
`#include <netinet/in.h>`
`#include <netinet/ip.h>`
`#include <arpa/inet.h>`
`#include <unistd.h>`
`#include <stdlib.h>`
`#include <pthread.h>`

`void` `*mythread(void` `*arg)`
`{`
    `int acceptfd =` `*((int` `*)arg);`
    `//接收数据`
    `char buf[32]` `=` `{0};`
    `while` `(1)`
    `{`
        `int ret =` `recv(acceptfd, buf,` `sizeof(buf),` `0);`
        `if` `(ret <` `0)`
        `{`
            `perror("recv err");`
            `return` `NULL;`
        `}`
        `else` `if` `(ret ==` `0)`
        `{`
            `printf("client exit\n");`
            `break;`
        `}`

        `printf("%s\n", buf);`
    `}`
    `close(acceptfd);`
    `pthread_exit(NULL);`
`}`

`int` `main(int argc,` `char` `const` `*argv[])`
`{`

    `if` `(argc !=` `2)`
    `{`
        `printf("please input ip,port\n");`
        `return` `-1;`
    `}`

    `// 1.创建流式套接字`
    `int sockfd =` `socket(AF_INET, SOCK_STREAM,` `0);`
    `if` `(sockfd <` `0)`
    `{`
        `perror("socket err");`
        `return` `-1;`
    `}`

    `// 2.填充结构体信息`
    `struct` `sockaddr_in addr, caddr;`
`    addr.sin_family = AF_INET;`
`    addr.sin_port =` `htons(atoi(argv[1]));`
    `// addr.sin_addr.s_addr = inet_addr(argv[1]);`
    `// addr.sin_addr.s_addr = inet_addr("0.0.0.0");`
`    addr.sin_addr.s_addr = INADDR_ANY;`

`    socklen_t len =` `sizeof(addr);`
    `// 3.绑定`
    `if` `(bind(sockfd,` `(struct` `sockaddr` `*)&addr, len)` `<` `0)`
    `{`
        `perror("bind err");`
        `return` `-1;`
    `}`

    `// 4.监听`
    `if` `(listen(sockfd,` `5)` `<` `0)`
    `{`
        `perror("listen err");`
        `return` `-1;`
    `}`
    `while` `(1)`
    `{`
        `// 5.等待客户端连接`
        `int acceptfd =` `accept(sockfd,` `(struct` `sockaddr` `*)&caddr,` `&len);`
        `if` `(acceptfd <` `0)`
        `{`
            `perror("accept err");`
            `return` `-1;`
        `}`
        `printf("ip:%s  port:%d\n",` `inet_ntoa(caddr.sin_addr),` `ntohs(caddr.sin_port));`

`        pthread_t tid;`
        `pthread_create(&tid,` `NULL, mythread,` `&acceptfd);`
        `pthread_detach(tid);`
    `}`
    `close(sockfd);`

    `return` `0;`
`}`

`

注意:编译链接库 -lpthread

IO多路复用实现并发服务器(重点)

TCP同时连接多个客户端(同时检测两个事件:键盘、客户端连接)

相关推荐
灵感菇_9 天前
Android Broadcast全面解析
android·广播·四大组件
赖small强24 天前
【Linux 网络基础】网络通信中的组播与广播:基础概念、原理、抓包与应用
linux·网络·broadcast·组播·广播·multicast
这个杀手不太累2 个月前
Android 通过广播监听home键和任务键
android·广播·home键·任务键
mct1234 个月前
QUdpSocket发送组播和接受组播数据
qt·组播
Tipriest_5 个月前
激光雷达的单播和广播模式介绍
激光雷达·广播·单播
m0_549314867 个月前
二、IGMP
网络·网络协议·组播·icmp·igmp·multicast
容沁风1 年前
libOnvif通过组播不能发现相机
组播·大华·onvif·libonvif
SuperHeroWu71 年前
【HarmonyOS】鸿蒙应用低功耗蓝牙BLE的使用心得 (二)
华为·harmonyos·低功耗蓝牙·ble·扫描·特征值·广播
spygg1 年前
Qt低版本多网卡组播bug
qt·组播·多网卡组播·qt5.7.0