嵌入式学习-网络-Day05
嵌入式学习-网络-Day05
1.网络超时检测
1.1应用场景
1.2设置超时检测
1)通过参数设置
2)setsockopt属性设置
3)定时器alarm设置
2.广播
2.1广播发送流程:
2.2广播接收流程:
3.组播
3.1组播发送流程
3.2组播接收流程
4.本地通信
1.网络超时检测
1.1应用场景
l超时检测的必要性
¡避免进程在没有数据时无限制地阻塞
¡实现某些特定协议要求,比如某些设备规定,发送请求数据后,如果多长时间后没有收到来自设备的回复,需要做出一些特殊处理
1.2设置超时检测
1)通过参数设置
select/poll
1.select
struct timeval tm={2,0};
select(maxfd + 1, &tempfds, NULL, NULL, &tm);
第五个参数:
struct timeval {
long tv_sec; /*秒*/
long tv_usec; /*微秒*/
};
2.poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
第三个参数:时间单位是毫秒 -1阻塞, 2000=2s
ret = poll(event, num, 2000);//超时检测时间为2s
以鼠标键盘事件为例,添加超时检测
2)setsockopt属性设置
Linux中socket属性
功能:获得/设置套接字属性
原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
const 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;
设置超时检测操作
struct timeval {
long tv_sec; /*秒*/
long tv_usec; /*微秒*/
};
//设置接收超时
struct timeval tm={2,0};
setsockopt(acceptfd,SOL_SOCKET,SO_RCVTIMEO,&tm,sizeof(tm));
//设置超时之后时间到打断接下来的阻塞在这个文件描述符的函数,直接错误返回
补充:
//设置端口和地址重用
int optval=1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
设置连接、接收超时检测(基于循环服务器)
3)定时器alarm设置
alarm(5) 闹钟 定时器
//5秒之后会,会有一个信号产生(SIGALRM)
//功能:让SIGALRM在几秒钟内发送给进程,参数为0,取消报警;
// 再次调用alarm之前的alarm会被取消
//返回值:返回的是上一次闹钟的剩余时间,如果没有则返回0
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
功能:对接收到的指定信号处理
signum 信号
struct sigaction
{
void (*sa_handler)(int); //信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理函数
sigset_t sa_mask;
int sa_flags; //信号属性; SA_RESTART自重启属性
#define SA_RESTART 0x10000000
void (*sa_restorer)(void);
};
//设置信号属性
struct sigaction act;
sigaction(SIGALRM,NULL,&act);//获取原来的属性
act.sa_handler = handler;//修改属性
sigaction(SIGALRM,&act,NULL);//将修改的属性设置
注:在recv前调用alarm函数
alarm的 SIGALRM信号产生后会打断(终端)下面的系统调用recv;
打断后相当于recv执行完毕。
2.广播
l前面介绍的数据包发送方式只有一个接受方,称为单播
l如果同时发给局域网中的所有主机,称为广播
l只有用户数据报(使用UDP协议)套接字才能广播
l一般被设计成局域网搜索协议
l广播地址
192.168.1.0(255.255.255.0)最大主机地址:192.168.1.255(广播地址)
发送到该地址的数据被所有同网段主机接收
2.1广播发送流程:
1.socket() 创建数据报套接字
2.setsockopt 缺省套接字不允许发送广播数据,需要设置属性
int on=1;
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));3.填充结构体
ip地址:广播地址
端口号:与接收端端口号保持一致
4.发送数据
send.c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.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("sock err:");
return -1;
}
// 2.修改属性,允许发送广播数据
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
// 3.填充结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(saddr);
char buf[128] = {};
// 4.
while (1)
{
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
if (!strncmp(buf, "quit", 4))
break;
sendto(sockfd, buf, 128, 0, (struct sockaddr *)&saddr, len);
printf("send ok\n");
}
close(sockfd);
return 0;
}
2.2广播接收流程:
- socket 创建数据报套接字
- 填充结构体 绑定ip和端口
- bind 端口和发送端端口号保持一致
- recvfrom
recv.c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define N 128
#include<string.h>
int main(int argc, char const *argv[])
{
//1.创建数据报套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
perror("sock err:");
return -1;
}
//2.填充结构体
struct sockaddr_in saddr,caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr=inet_addr(argv[1]);
socklen_t len = sizeof(saddr);
//3.绑定
if(bind(sockfd,(struct sockaddr *)&saddr,len) < 0)
{
perror("bind err");
return -1;
}
//4.接收
while(1)
{
char buf[N]={};
recvfrom(sockfd,buf,N,0,(struct sockaddr *)&caddr,&len);
printf("ip:%s,port:%d,said:%s\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),buf);
// printf("buf:%s\n",buf);
}
//5.关闭
close(sockfd);
return 0;
}
3.组播
- 单播方式只能发给一个接收方。
- 广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。
- 组播是一个人发送,加入到多播组的人接收数据。
- 多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)
组播地址:
不分网络地址和主机地址,第1字节的前4位固定为1110
224.0.0.1 ~ 239.255.255.254
3.1组播发送流程
- 创建数据报套接字
- 接收方的地址设置为组播地址
- 指定端口信息
- 发送信息
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.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("sock err:");
return -1;
}
// 2.填充结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = inet_addr("224.10.10.1");
socklen_t len = sizeof(saddr);
char buf[128] = {};
while (1)
{
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
if (!strncmp(buf, "quit", 4))
break;
sendto(sockfd, buf, 128, 0, (struct sockaddr *)&saddr, len);
printf("send ok\n");
}
close(sockfd);
return 0;
}
3.2组播接收流程
- 创建数据报套接字
- 加入多播组 ,打开权限(setsockopt)
- 绑定ip地址和端口
- 等待接收
struct ip_mreq
{
struct in_addr imr_multiaddr; /* 指定多播组IP */
struct in_addr imr_interface; /* 本地网卡地址,通常指定为 INADDR_ANY--0.0.0.0*/};
}
struct ip_mreq mreq;
//bzero(&mreq, sizeof(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));
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define N 128
#include <string.h>
int main(int argc, char const *argv[])
{
// 1.创建数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("sock err:");
return -1;
}
// 2.加入多播组
struct ip_mreq mreq;
// bzero(&mreq, sizeof(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));
// 3.填充结构体
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
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;
}
printf("bind success\n");
// 4.接收
while (1)
{
char buf[N] = {};
recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&caddr, &len);
printf("ip:%s,port:%d,said:%s\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port), buf);
// printf("buf:%s\n",buf);
}
// 5.关闭
close(sockfd);
return 0;
}
4.本地通信
#include <sys/socket.h>
#include <sys/un.h>
unix_socket = socket(AF_UNIX, type, 0);
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* 本地路径 */
};
struct sockaddr_un myaddr;
bzero(&myaddr, sizeof(myaddr));
myaddr.sun_family = AF_UNIX;
strcpy(myaddr.sun_path, "mysocket"); //可以指定路径