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广播发送流程
- 创建用户数据报套接字
- 填充结构体信息(IP:广播地址)
- 允许发送广播数据(setsockopt)
- 发送数据
- 关闭
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广播接收流程
- 创建用户数据报套接字
- 填充结构体信息(IP:广播地址)
- 绑定
- 接收
- 关闭
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组播发送流程
- 创建用户数据报套接字
- 填充结构体(组播IP)
- 发送
- 关闭
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组播接收流程
- 创建用户数据报套接字
- 填充结构体(组播IP)
- 绑定
- 加入多播组
- 等待接收数据
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();`
`}`
`}`
`
多进程特点总结
- fork之前的代码被复制,但是不会重新执行一遍;fork之后的代码被复制,并且再被执行一遍。
- fork之后两个进程相互独立,子进程拷贝了父进程的所有代码,但内存空间独立
- 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同时连接多个客户端(同时检测两个事件:键盘、客户端连接)
