一、网络信息检索
1、网络信息检索 常用函数
- gethostname(); 获得主机名
- getpeername(); 获得与套接字相连的远程协议地址
- getsockname(); 获得本地套接口协议地址;
- gethostbyname(); 根据主机名 取得主机信息
endhostent() :使用endhostent() 清空 申请的 结构体的空间;
- gethostbyaddr(); 根据主机地址取得主机信息
- getprotobyname(); 根据协议名取得 主机协议信息
- getprotobynumber(); 根据协议号取得主机协议信息
- getservbyname(); 根据服务器名取得 相关服务信息
- getservbyport(); 根据端口号取得相关服务信息
2、在客户端代码中,使用 gethostbyname()函数去 输入域名 的方法 去连接服务器;
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);
name 指向主机名的指针(域名或 IP地址)
#include <sys/socket.h> /* for AF_INET */
struct hostent *gethostbyaddr(const void *addr,socklen_t len, int type);
注意:
IPv4中使用gethostbyname()函数完成主机名到地址解析,这个函数仅仅支持IPv4,且不允许调用者指
定所需地址类型的任何信息,返回的结构只包含了用于存储IPv4地址的空间。IPv6中引入了getaddrinfo()
的新API,它是协议无关的,即可用于IPv4,也可用于IPv6.
- 返回值:
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses地址列表,指向主机的多个网络地址(网络字节序32位整数). */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
- 结构体中,地址列表的存放示例:
3、错误处理 herror()、hstrerror()
extern int h_errno;//出错号
void herror(const char *s);/打印错误信息
const char *hstrerror(int err);//打印错误信息
4、客户端使用 gethonstbyname 指定域名 连接 服务器示例:
#include "net_pth.h"
#include <netdb.h>
void usage(const char* s){
printf("\n %s serv_ip ser_port \n",s);
printf("\n\t serv_ip: server ip address");
printf("\r\n serv_port: server port(>5000)\n\n");
}
int main(int argc, const char *argv[])
{
int fd = -1;
int port = 0;
struct sockaddr_in sin;
struct hostent *hs = NULL;
/* 1、创建socket fd */
if((fd = socket(AF_INET , SOCK_STREAM,0)) < 0 ){ //创建TCP 通信 Socket文件描述符
perror("socket");
exit(1);
}
if(argc != 3 ){
usage(argv[0]);
exit(1);
}
port = atoi(argv[2]);
if (port < 5000){
usage(argv[0]);
exit(1);
}
if((hs = gethostbyname(argv[1])) == NULL){ //根据主机名 获取 主机信息
perror("gethostbyname");
exit(1);
}
/* 2、连接服务器 */
/* 2.1 填充 struct sockaddr_in 结构体变量 */
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = *(uint32_t *)hs->h_addr; //把获取的主机IP地址,填充进入结构体中
endhostent();
hs = NULL;
printf("Client staring .... OK! \n");
/* 2、2 连接服务器 */
if( connect(fd ,(struct sockaddr *)&sin,sizeof(sin)) < 0){
perror("connect");
exit(1);
}
/* 3、读写数据*/
char buf[BUFSIZ];
while(1){
bzero(buf, sizeof(buf));
if(fgets(buf,BUFSIZ-1,stdin) == NULL){
continue;
}
write(fd , buf , strlen(buf));
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){
break;
}
}
/* 4、关闭套接字*/
close(fd);
}
二、网络属性设置
1、网络属性设置接口
- getsockopt 和 setsockopt
- 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);
- level 指定控制套接字的层次,可以取三种值:
- SOL_SOCKET: 通用套接字选项 (应用层)
- IPPROTO_IP: IP选项;(网络层)
- IPPROTO_TCP: TCP选项(传输层)
- level 指定控制套接字的层次,可以取三种值:
- 以setsockopt为主;
- optname 指定控制的方式(选项的名称),我们下面详细解释;
— 划线部分一般是对应层,常用控制的方式;
2、示例
在以上例子中 ,替换其中的参数,得到一般的想要的效果;
其中发送与接收超时的数据类型为:struct timeval结构体类型;
其中 setsockopt()函数中 参数const void *optval --- 传给实现效果的相应的参数;
其中timeval 的定义如下:
struct timeval{
long tv_sec; /*seconds : 秒*/
long tv_usec; /*microseconds: 微妙 */
}
/*允许广播 */
int b_br = 1;
setsockopt(fd , SOL_SOCKET , SO_BROADCAST, &b_br, sizeof(int));
/*设置 接收超时 */
struct timeval tout;
tout.tv_sec = 5;
tout.tv_usec = 0;
setsockopt(fd,SOL_SOCKET , SO_RCVTIMEO , &tout , sizeof(tout));
三、超时检查
- 网络超时:在网络通信中,很多操作会使得进程阻塞;
- TCP套接字中的recv/accept/connect;
- UDP套接字中的recvfrom
- 超时检测的必要性
- 避免进程在没有数据时无限制地阻塞;
- 当设定的时间到时,进程从原操作返回继续运行;
1、网络超时优化 第一种方法:设置socket 的属性 SO_RCVTIMEO
参考代码如下:
struct timeval tv;
tv.tv_sec = 5; //设置5秒时间
tv.tv_usec = 0; //
setsockopt(sockfd , SOL_SOCKET , SO_RCVTIMEO , &tv , sizeof(tv)); //设置接收超时
recv()/recvfrom() //从socket读取数据;
2、网络超时优化 第二种方法:用select 是否 ‘ready’
参考代码如下:
struct fd_set rdfs;
while(1)
{
struct timeval tv = {5,0};
FD_ZERO(&rdfs);
FD_SET(socket,&rdfs);
if(select(sockfd -1,&rdfs,NULL,&tv) > 0)//socket就绪
{
recv() / recvfrom () // 从socket 读取数据
}
}
3、网络超时优化 第三种方法:设置定时器(timer),捕捉SIGALRM信号
参考代码如下:
void handler(int signo) {return ;}
struct sigaction act;
sigaction(SIGALRMR , NULL ,&act);
act.sa_flags &= ~SA_RESTART;//清除掉SIGALRM信号SA_RESTART
sigaction(SIGALRM,&act,NULL);
alarm(5);
if(recv(, , , ) < 0) ......;
四、心跳检测
1、如何在linux中动态检查到是否有网络以及网络中途的掉线/连接的检查?
- 应用层
— 心跳检测;
- 内核中
— 网卡驱动中 2.6内核里面,使能1s的周期性检查定时器;
— 网卡硬件或者我们通过GPIO,插拔网线时候产生中断,处理相应中断 ;//立即检测到
2、心跳检测的两种方法
方法一:
客户端 隔一定的时间向服务器发送一段数据,告诉服务器客服端在保持连接;规定在一定的次数,接收不到对方的回应则判定异常;
方法二:
- 可通过setkeepAlive()函数 ,来改变套接字的属性;(默认两个小时去检测一次客户端是否掉线)
参考代码如下:
void setKeepAlive(int sockfd , int attr_on ,socklen_t idle_time,socklen_t interval,socklen_t cnt)
{
setsockopt(sockfd , SOL_SOCKET , SO_KEEPALIVE,(const char *)&attr_on,sizeof(attr_on));
setsockopt(sockfd , SOL_TCP , TCP_KEEPIDLE , (const char*)&idle_time,sizeof(idle_time));
setsockopt(sockfd , SOL_TCP , TCP_KEEPINTVL , (const char *)&interval,sizeof(interval));
setsockopt(sockfd , SOL_TCP , TCP_KEEPCNT , (const char *)&cnt,sizeof(cnt));
}
void cli_data_handle(void *arg)
{
int newfd = *(int *)arg;
int keepAlive = 1; //设定keepAlive
int keepIdle = 5; //开始首次keepAlive探测前的TCP空闲时间
int kepInterval = 5; //两次keepAlive 探测间的 间隔时间
int keepCount = 3; // 判定断开前的keepAlive 的探测次数
setKeepAlive(newfd ,keepAlive ,keepIdle ,kepInterval ,keepCount );
//..和newfd进行数据读写
int ret = -1;
char buf[BUFSIZE];
char resp_buf[BUFSIZE + 10];
while(1)
{
bzero(buf,BUFSIZE);
do{
ret = read (newfd , buf ,BUFSIZE - 1);
}while(ret < 0 && BINTR == errno);
if(ret < 0)
{
perror("read");
exit(1);
}
}
}