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广播发送流程
- 创建用户数据报套接字
- 填充结构体信息(IP:广播地址)
- 允许发送广播数据(setsockopt)
- 发送数据
- 关闭
send.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
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] == '
') // 去掉
buf[strlen(buf) - 1] = ' ';
sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&saddr,len);
printf("send ok
");
}
//5.关闭套接字
close(sockfd);
return 0;
}
1.3广播接收流程
- 创建用户数据报套接字
- 填充结构体信息(IP:广播地址)
- 绑定
- 接收
- 关闭
recv.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
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
", 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
#include
#include
#include
#include
#include
#include
#include
#include
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] == '
') // 去掉
buf[strlen(buf) - 1] = ' ';
sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&saddr,len);
printf("send ok
");
}
//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
#include
#include
#include
#include
#include
#include
#include
#include
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
", 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 /* See NOTES */
#include
#include
#include
#include
#include
#include
#include
#include
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
");
break;
}
printf("%s
", buf);
}
close(acceptfd);
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("please input ip,port
");
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
", 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同时连接多个客户端(同时检测两个事件:键盘、客户端连接)











