//===如何增加UDP协议的可靠性===============
1:利用现有的协议来实现(如UDT协议)
UDT(UDP-based Data Transfer Protocol)
基于UDP的数据传输协议(UDP-based Data Transfer Protocol,简称UDT)是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。 顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。 由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等。
2:自己在UDP之上定义协议
//==========================select==============================
fd_set :代表一个集合
typedef unsigned long fds_bits[32] fd_set;//一共可以容纳1024个文件描述符
int select(int n, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
n:表示最大的文件描述符的值加1
read_fds:要关注的可读(有数据可读、有新的连接请求)的文件描述符的集合
write_fds:要关注的可写的文件描述符的集合
except_fds:要关注的异常(带外数据)的文件描述符的集合
timeout:超时时间,如果在规定的时间内,没有任何文件描述符准备好,则select直接返回,填NULL表示如果没有IO准备好,则永久阻塞
struct timeval {
long tv_sec; /*秒数 */
long tv_usec; /*微秒 */
};
//select调用一次,只能进行一次监控,如果要连续监控,则需使用循环
//select返回之后,不满足条件的文件描述符对应的为都被清零,所以如果要重新检查,必须将fd重新加入集合
返回值:
<0:出错
==0:超时
>0: 有文件描述符准备好(某些IO端口满足可读、可写或异常的条件)
select不会告诉用户具体是哪一个IO准备好,需要程员自己来判断
struct timeval {
long tv_sec; /*秒数*/
long tv_usec; /*微秒数*/
};
void FD_ZERO(fd_set *fdset);//把文件描述符集合中所有的位都清零
void FD_SET(int fd,fd_set *fdset);//将文件描述符fd加入到集合fdset中
void FD_CLR(int fd,fd_set *fdset);//将文件描述符从集合fdset中去除
int FD_ISSET(int fd,fd_set *fdset);//判断文件描述符fd是否在集合fdset中,即检测集合中fd对应的位是否为1
select编程步骤:
1:获得文件描述符
int fd_serial = open("/dev/ttySAC0", O_RDWR);
int fd_usb = open("/dev/ttyUSB0", O_RDWR);
int fd_socket = socket(AF_INET, SOCK_STREAM, 0);
bind(xxx);
listen(xxxx);
2:把所有关注的文件描述符加入到集合中
fd_set fdset;
FD_ZERO(&fdset);//对所有位进行清零
FD_SET(fd_serial, &fdset);
FD_SET(fd_socket, &fdset);
3:调用select进行监控
struct timeval tm = {5,0};
max_fd = fd_serial>fd_socket?fd_serial:fd_socket;
ret = select(max_fd +1 ,&fdset, NULL, NULL, &tm);
4:根据返回情况进行判断
if(ret < 0)
{
perror("select");
exit(1)
}
if(ret == 0)
{
printf("说明在5秒之内,没有任何IO准备好,超时\n!");
}
if(ret> 0) //有IO满足可读条件
{
if(FD_ISSET(fd_serial,&fdset))
{
//串口有数据可读
read(fd_serial, buf, 20);
}
if(FD_ISSET(fd_socket,&fdset))
{
//有新的连接请求
conn_fd = accept(fd_socket, xxxxx);
}
}
作业:
使用select实现服务器模型(模仿多线程)
//根据主机名或域名获取主机信息
struct hostent *gethostbyname(const char *name);
本质:解析文件 /etc/hosts
struct hostent {
char *h_name; /*官方主机名*/
char **h_aliases; /* 主机的别名列表 */ ---->char *h_aliases[] = {"host1", "host2"}
int h_addrtype; /*主机的地址类型(IPV4或ipv6)*/
int h_length; /*地址长度*/
char **h_addr_list; /*主机的ip地址列表*/--->char *h_addr_list[] = {ip1, ip2}
}
//===============设置套接字的属性=============
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
level:指定控制套接字的层次.可以取三种值:
1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项.
optname:获得或者是设置套接字选项
optval:为套接字设置的参数的地址
optlen:为套接字设置的参数的地址长度
//======实例==================
本质:解析文件 /etc/hosts
/*允许IP被重复绑定*/
int on = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
/*允许套接字发送广播*/
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
/*设定套接字定时阻塞*/
struct timeval tv;
tv.tv_sec = 5; // 设置5秒时间
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); // 设置接收超时
accept();
//===========================================
struct fd_set rdfs;
struct timeval tv = {5 , 0}; // 设置5秒时间
FD_ZERO(&rdfs);
FD_SET(sockfd, &rdfs);
if (select(sockfd+1, &rdfs, NULL, NULL, &tv) > 0) // socket就绪
{
recv() / recvfrom() // 从socket读取数据
}
//=======================
void handler(int signo) { return; }
signal(SIGALRM, handler);
alarm(5);
if(recv(,,,) < 0) ……
//=========================心跳检测==================
为了维持服务器和客户端的之间的连接,客户端每隔一个固定的时间(1-255秒)向服务器发送一个心跳包,
以表明客户端和软硬件运行正常,如果服务器在间隔时间内,没有收到心跳包,则认为客户端出现问题,可以
根据相应情况进行处理。
//====================带外数据=================
1:带外数据,又称紧急数据,数据在TCP头存放,发送时 URG=1
2:带外数据一次只能发送一个字节,如果发送多个字节的带外数据,只会成功发送最后一个字节
3:发送和接收带外数据,不能用read和write,必须用send和recv
send(int sockfd, void *buf, int len, int flags);//flags :MSG_OOB
recv(int sockfd, void *buf, int len, int flags);//flags :MSG_OOB
4:当一个进程收到带外数据时,会收到一个SIGURG信号,该信号时内核发给用户的
signal(SIGURG, catch_urg);//注册SIGURG信号
fcntl(new_fd, F_SETOWN, getpid());
//设置信号的拥有者为本进程,也就是说只有getpid()的进程才能收到SIGURG信号
//同时进程会将pid写入file结构体,然后传递给内核,内核拿到pid之后,就知道SIGURG,信号要发送给本进程
5:带外数据只能使用TCP协议
//=====================广播===========================
1:在一个局域网内,将将信息发送给所有的主机,这就是广播
2:发送广播必须使用UDP协议,将信息发送到广播地址(所有的主机号都为1:192.168.7.255)
3:广播方式发给所有的主机,每个主机只有当数据包到达传输层,才能确定是否接收该数据包。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。
目的IP:xxx.xxx.xxx.255
目的MAC:FF:FF:FF:FF:FF:FF
4:一个套接字默认是不能发送广播的,要想发送广播信息,必须对套接字的属性进行设定
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
//====================多播================
多播IP:224.0.0.1 – 239.255.255.255
1:接收端只有加入多播组,才能接收多播信息
struct ip_mreq
{
struct in_addr imr_multiaddr;//用来存放多播组的IP
struct in_addr imr_interface;//用来存放本主机的IP
};
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.1");
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
//inet_pton(AF_INET,”192.168.7.89”, (void *)& mreq.imr_interface);
//加入多播组
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
//离开多播组
setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
//========================UNIX套接字====================
1:用于同一台主机不同进程之间的通信,和普通的IPC通信类似,使用socket的API来实现进程间通信:
2:和其他进程间通信方式相比使用方便、效率更高
3:本质上,进程之间的通信时通过套接字文件来完成的
4:可以用在TCP和UDP协议。
int sock_fd = socket(AF_LOCAL, SOCK_STREAM, 0);//创建一个本地套接字,只能用于本地通信
struct sockaddr_un // <sys/un.h>
{
sa_family_t sun_family; //AF_UNIX,AF_LOCAL,PF_LOCAL, PF_UNIX
char sun_path[108]; // 套接字文件的路径,一般要写绝对路径
};
unlink("./mysocket");//当文件的进程使用数为0,或硬连接数为0时,就会把文件从物理存储设备上删除
1:利用现有的协议来实现(如UDT协议)
UDT(UDP-based Data Transfer Protocol)
基于UDP的数据传输协议(UDP-based Data Transfer Protocol,简称UDT)是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。 顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。 由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等。
2:自己在UDP之上定义协议
//==========================select==============================
fd_set :代表一个集合
typedef unsigned long fds_bits[32] fd_set;//一共可以容纳1024个文件描述符
int select(int n, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
n:表示最大的文件描述符的值加1
read_fds:要关注的可读(有数据可读、有新的连接请求)的文件描述符的集合
write_fds:要关注的可写的文件描述符的集合
except_fds:要关注的异常(带外数据)的文件描述符的集合
timeout:超时时间,如果在规定的时间内,没有任何文件描述符准备好,则select直接返回,填NULL表示如果没有IO准备好,则永久阻塞
struct timeval {
long tv_sec; /*秒数 */
long tv_usec; /*微秒 */
};
//select调用一次,只能进行一次监控,如果要连续监控,则需使用循环
//select返回之后,不满足条件的文件描述符对应的为都被清零,所以如果要重新检查,必须将fd重新加入集合
返回值:
<0:出错
==0:超时
>0: 有文件描述符准备好(某些IO端口满足可读、可写或异常的条件)
select不会告诉用户具体是哪一个IO准备好,需要程员自己来判断
struct timeval {
long tv_sec; /*秒数*/
long tv_usec; /*微秒数*/
};
void FD_ZERO(fd_set *fdset);//把文件描述符集合中所有的位都清零
void FD_SET(int fd,fd_set *fdset);//将文件描述符fd加入到集合fdset中
void FD_CLR(int fd,fd_set *fdset);//将文件描述符从集合fdset中去除
int FD_ISSET(int fd,fd_set *fdset);//判断文件描述符fd是否在集合fdset中,即检测集合中fd对应的位是否为1
select编程步骤:
1:获得文件描述符
int fd_serial = open("/dev/ttySAC0", O_RDWR);
int fd_usb = open("/dev/ttyUSB0", O_RDWR);
int fd_socket = socket(AF_INET, SOCK_STREAM, 0);
bind(xxx);
listen(xxxx);
2:把所有关注的文件描述符加入到集合中
fd_set fdset;
FD_ZERO(&fdset);//对所有位进行清零
FD_SET(fd_serial, &fdset);
FD_SET(fd_socket, &fdset);
3:调用select进行监控
struct timeval tm = {5,0};
max_fd = fd_serial>fd_socket?fd_serial:fd_socket;
ret = select(max_fd +1 ,&fdset, NULL, NULL, &tm);
4:根据返回情况进行判断
if(ret < 0)
{
perror("select");
exit(1)
}
if(ret == 0)
{
printf("说明在5秒之内,没有任何IO准备好,超时\n!");
}
if(ret> 0) //有IO满足可读条件
{
if(FD_ISSET(fd_serial,&fdset))
{
//串口有数据可读
read(fd_serial, buf, 20);
}
if(FD_ISSET(fd_socket,&fdset))
{
//有新的连接请求
conn_fd = accept(fd_socket, xxxxx);
}
}
作业:
使用select实现服务器模型(模仿多线程)
//根据主机名或域名获取主机信息
struct hostent *gethostbyname(const char *name);
本质:解析文件 /etc/hosts
struct hostent {
char *h_name; /*官方主机名*/
char **h_aliases; /* 主机的别名列表 */ ---->char *h_aliases[] = {"host1", "host2"}
int h_addrtype; /*主机的地址类型(IPV4或ipv6)*/
int h_length; /*地址长度*/
char **h_addr_list; /*主机的ip地址列表*/--->char *h_addr_list[] = {ip1, ip2}
}
//===============设置套接字的属性=============
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
level:指定控制套接字的层次.可以取三种值:
1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项.
optname:获得或者是设置套接字选项
optval:为套接字设置的参数的地址
optlen:为套接字设置的参数的地址长度
//======实例==================
本质:解析文件 /etc/hosts
/*允许IP被重复绑定*/
int on = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
/*允许套接字发送广播*/
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
/*设定套接字定时阻塞*/
struct timeval tv;
tv.tv_sec = 5; // 设置5秒时间
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); // 设置接收超时
accept();
//===========================================
struct fd_set rdfs;
struct timeval tv = {5 , 0}; // 设置5秒时间
FD_ZERO(&rdfs);
FD_SET(sockfd, &rdfs);
if (select(sockfd+1, &rdfs, NULL, NULL, &tv) > 0) // socket就绪
{
recv() / recvfrom() // 从socket读取数据
}
//=======================
void handler(int signo) { return; }
signal(SIGALRM, handler);
alarm(5);
if(recv(,,,) < 0) ……
//=========================心跳检测==================
为了维持服务器和客户端的之间的连接,客户端每隔一个固定的时间(1-255秒)向服务器发送一个心跳包,
以表明客户端和软硬件运行正常,如果服务器在间隔时间内,没有收到心跳包,则认为客户端出现问题,可以
根据相应情况进行处理。
//====================带外数据=================
1:带外数据,又称紧急数据,数据在TCP头存放,发送时 URG=1
2:带外数据一次只能发送一个字节,如果发送多个字节的带外数据,只会成功发送最后一个字节
3:发送和接收带外数据,不能用read和write,必须用send和recv
send(int sockfd, void *buf, int len, int flags);//flags :MSG_OOB
recv(int sockfd, void *buf, int len, int flags);//flags :MSG_OOB
4:当一个进程收到带外数据时,会收到一个SIGURG信号,该信号时内核发给用户的
signal(SIGURG, catch_urg);//注册SIGURG信号
fcntl(new_fd, F_SETOWN, getpid());
//设置信号的拥有者为本进程,也就是说只有getpid()的进程才能收到SIGURG信号
//同时进程会将pid写入file结构体,然后传递给内核,内核拿到pid之后,就知道SIGURG,信号要发送给本进程
5:带外数据只能使用TCP协议
//=====================广播===========================
1:在一个局域网内,将将信息发送给所有的主机,这就是广播
2:发送广播必须使用UDP协议,将信息发送到广播地址(所有的主机号都为1:192.168.7.255)
3:广播方式发给所有的主机,每个主机只有当数据包到达传输层,才能确定是否接收该数据包。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。
目的IP:xxx.xxx.xxx.255
目的MAC:FF:FF:FF:FF:FF:FF
4:一个套接字默认是不能发送广播的,要想发送广播信息,必须对套接字的属性进行设定
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
//====================多播================
多播IP:224.0.0.1 – 239.255.255.255
1:接收端只有加入多播组,才能接收多播信息
struct ip_mreq
{
struct in_addr imr_multiaddr;//用来存放多播组的IP
struct in_addr imr_interface;//用来存放本主机的IP
};
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.1");
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
//inet_pton(AF_INET,”192.168.7.89”, (void *)& mreq.imr_interface);
//加入多播组
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
//离开多播组
setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
//========================UNIX套接字====================
1:用于同一台主机不同进程之间的通信,和普通的IPC通信类似,使用socket的API来实现进程间通信:
2:和其他进程间通信方式相比使用方便、效率更高
3:本质上,进程之间的通信时通过套接字文件来完成的
4:可以用在TCP和UDP协议。
int sock_fd = socket(AF_LOCAL, SOCK_STREAM, 0);//创建一个本地套接字,只能用于本地通信
struct sockaddr_un // <sys/un.h>
{
sa_family_t sun_family; //AF_UNIX,AF_LOCAL,PF_LOCAL, PF_UNIX
char sun_path[108]; // 套接字文件的路径,一般要写绝对路径
};
unlink("./mysocket");//当文件的进程使用数为0,或硬连接数为0时,就会把文件从物理存储设备上删除