#include <stdio.h> #include <netdb.h> #include <sys/socket.h> int main(int argc, char *argv[]) { char *ptr, **pptr; struct hostent *hptr; char str[32] = {0}; int i; ptr = argv[1]; if((hptr = gethostbyname(ptr)) == NULL) { printf("gethostbyname error: %s\n", ptr); return 0; } printf("official hostname:%s\n", hptr->h_name); //主机规范名 for(pptr = hptr->h_aliases; *pptr != NULL; pptr++) //将主机别名打印出来 printf("alias: %s\n", *pptr); printf("type of hostname:%d\n",hptr->h_addrtype); //打印主机ip类型 if(hptr->h_addrtype != AF_INET) return 0; printf("length of hostname: %d\n",hptr->h_length); //主机IP地址字节长度 //for(;*pptr!=NULL;pptr++) //printf(" address:%s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str))); for(i=0;*(hptr->h_addr+i);i++){ if(NULL==inet_ntop(AF_INET,hptr->h_addr+i,str,32)) break; printf("hostname IP is: %s\n",str); } return 0; }
测试结果:
linux@ubuntu:~/Level6/day7$ ./dnstest www.baidu.com official hostname:www.a.shifen.com alias: www.baidu.com type of hostname:2 length of hostname: 4 hostname IP is: 61.135.169.121 hostname IP is: 135.169.121.61 hostname IP is: 169.121.61.135 hostname IP is: 121.61.135.185 hostname IP is: 61.135.185.32 hostname IP is: 135.185.32.97 hostname IP is: 185.32.97.46 hostname IP is: 32.97.46.115 hostname IP is: 97.46.115.104 hostname IP is: 46.115.104.105 hostname IP is: 115.104.105.102 hostname IP is: 104.105.102.101 hostname IP is: 105.102.101.110 hostname IP is: 102.101.110.46 hostname IP is: 101.110.46.99 hostname IP is: 110.46.99.111 hostname IP is: 46.99.111.109 hostname IP is: 99.111.109.0 hostname IP is: 111.109.0.101 hostname IP is: 109.0.101.114
(作业要求:做作业的时候不要再翻看视频上的教程,对函数理解不明白的全部通过man手册去查看,
自己思考框架,使用makefile编译,然后将测试的记录和结果添加到readme.txt文件中提交上来,代码实现完成测试通过后
再提交作业,网络部分学习不写代码不测试看不出问题的,良好的习惯帮助你们快速成长。)
server code:
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> #define SERV_PORT 5050 #define SERV_IP_ADDR "192.168.0.158" void* func_recall(void* arg); void handler(int signo) { if (signo == SIGCHLD) { printf("Recall SIGINT\n"); waitpid(-1, NULL, WNOHANG); } } int main() { int fd = -1; int result = 0; struct sockaddr_in mysockaddr; struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; static pthread_t nptd; /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); return -1; } /* 优化4: 允许绑定地址快速重用 阻塞延迟10s */ int b_reuse = 1; struct timeval tv = { 10,0 }; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)); /*2. 绑定 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号 mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY); /*2.2 绑定 */ result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); if (result != 0) { perror("bind"); return -1; } /*3. 调用listen()把主动套接字变成被动套接字 */ result = listen(fd, 5); if (result != 0) { perror("listen"); return -1; } /*4. 阻塞等待客户端连接请求 */ /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/ int fd_client; pid_t pid; signal(SIGCHLD, handler); while (1) { bzero((void*)&clientscokaddr, sizeof(clientscokaddr)); fd_client = accept(fd, (struct sockaddr*)&clientscokaddr, &clientaddrlen); if (-1 == result) { perror("accpet"); return 0; } char ipv4_addr[16]; if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) { perror("inet_ntop"); return -1; } printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port)); pid = fork(); if (pid < 0) { perror("fork"); return -1; } else if (pid > 0) { //父进程 close(fd_client); } else { //子进程 close(fd); func_recall((void*)&fd_client); return 0; } } close(fd); close(fd_client); return 0; } void* func_recall(void* arg) { int result = 0; int newfd = *(int*)arg; printf("handler thread: newfd =%d\n", newfd); //..和newfd进行数据读写 char buf[BUFSIZ]; while (1) { bzero(buf, BUFSIZ); do { result = read(newfd, buf, BUFSIZ - 1); } while (result < 0 && EINTR == errno); if (result < 0) { perror("read"); break; } if (!result) break; printf("message is:%s\n", buf); } close(newfd); return 0; }
client code:
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #define SERV_PORT 5050 #define SERV_IP_ADDR "192.168.0.158" int main() { int fd = -1; int result = 0; struct sockaddr_in mysockaddr; char MESSAGE[] = "hELLo wOLd...\n"; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); return -1; } bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT); #if 0 mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); #else result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr); if (result != 1) { perror("inet_pton"); return -1; } #endif result = connect(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); if (result != 0) { perror("connect"); return -1; } printf("Client staring...OK!\n"); while (1) { do { result = write(fd, (void*)MESSAGE, sizeof(MESSAGE)); } while (result < 0 && EINTR == errno); sleep(10); } close(fd); return 0; }
效果展示:
什么是心跳机制?
就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息则视客户端断开。
发包方:可以是客户也可以是服务端,看哪边实现方便合理。
心跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。
心跳包的发送,通常有两种技术:
1.应用层自己实现的心跳包
由应用程序自己发送心跳包来检测连接是否正常,服务器每隔一定时间向客户端发送一个短小的数据包,然后启动一个线程,在线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没有收到服务器的心跳包,则认为连接不可用。
2.使用SO_KEEPALIVE套接字选项
在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项. 不论是服务端还是客户端,一方开启KeepAlive功能后,就会自动在规定时间内向对方发送心跳包, 而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启默认的KeepAlive超时需要7,200,000 MilliSeconds, 即2小时,探测次数为5次。对于很多服务端应用程序来说,2小时的空闲时间太长。因此,我们需要手工开启KeepAlive功能并设置合理的KeepAlive参数
开启KeepAlive选项后会导致的三种情况:
1、对方接收一切正常:以期望的ACK响应,2小时后,TCP将发出另一个探测分节
2、对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接口本身则被关闭。
3、对方无任何响应:套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭.
有关SO_KEEPALIVE的三个参数:
1.tcp_keepalive_intvl,保活探测消息的发送频率。默认值为75s。
发送频率tcp_keepalive_intvl乘以发送次数tcp_keepalive_probes,就得到了从开始探测直到放弃探测确定连接断开的时间,大约为11min。
2.tcp_keepalive_probes,TCP发送保活探测消息以确定连接是否已断开的次数。默认值为9(次)。
3.tcp_keepalive_time,在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个保活探测消息的时间,即允许的持续空闲时间。默认值为7200s(2h)。
总结:
一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心跳包,代码较多 且稍显复杂。用TCP/IP协议层为内置的KeepAlive功能来实现心跳功能则简单得多。心跳包在按流量计费的环境下增加了费用.但TCP得在连接闲置2小时后才发送一个保持存活探测段,所以通常的方法是将保持存活参数改小,但这些参数按照内核去维护,而不是按照每个套接字维护,因此改动它们会影响所有开启该选项的套接字。
下面通过一个实例来展示心跳机制。
结构,一个客户程序,和一个服务程序。
步骤:
服务器:
1.经过socket、bind、listen、后用accept获取一个客户的连接请求,为了简单直观,这里服务器程序只接收一个connect请求,我们用clifd来获取唯一的一个连接。
2.为clifd修改KeepAlive的相关参数,并开启KeepAlive套接字选项,这里我们把间隔时间设为了5秒,闲置时间设置了5秒,探测次数设置为5次。
3.将clifd加入select监听的描述符号集
客户:很简单,只是连接上去,并停留在while死循环。
client 代码 cpp
#include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <strings.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <unistd.h> using namespace std; int main() { int skfd; if ((skfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror(""); exit(-1); } struct sockaddr_in saddr; bzero(&saddr, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(9999); saddr.sin_addr.s_addr = inet_addr("115.29.109.198"); if (connect(skfd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) { perror(""); exit(-1); } cout << "连接成功" << endl; while(1); return 0; }
server 代码 cpp
#include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <strings.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <unistd.h> #include <sys/select.h> #include <netinet/tcp.h> using namespace std; #define LISTENNUM 5 int main() { int skfd; if ((skfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror(""); exit(-1); } struct sockaddr_in saddr; bzero(&saddr, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(9999); saddr.sin_addr.s_addr = inet_addr("115.29.109.198"); if (bind(skfd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) { perror(""); exit(-1); } if (listen(skfd, LISTENNUM) < 0) { perror(""); exit(-1); } int clifd; if ((clifd = accept(skfd, NULL, NULL)) < 0) { perror(""); exit(-1); } cout << "有新连接" << endl; //setsockopt int tcp_keepalive_intvl = 5; //保活探测消息的发送频率。默认值为75s int tcp_keepalive_probes = 5; //TCP发送保活探测消息以确定连接是否已断开的次数。默认值为9次 int tcp_keepalive_time = 5; //允许的持续空闲时间。默认值为7200s(2h) int tcp_keepalive_on = 1; if (setsockopt(clifd, SOL_TCP, TCP_KEEPINTVL, &tcp_keepalive_intvl, sizeof(tcp_keepalive_intvl)) < 0) { perror(""); exit(-1); } if (setsockopt(clifd, SOL_TCP, TCP_KEEPCNT, &tcp_keepalive_probes, sizeof(tcp_keepalive_probes)) < 0) { perror(""); exit(-1); } if (setsockopt(clifd, SOL_TCP, TCP_KEEPIDLE, &tcp_keepalive_time, sizeof(tcp_keepalive_time)) < 0) { perror(""); exit(-1); } if (setsockopt(clifd, SOL_SOCKET, SO_KEEPALIVE, &tcp_keepalive_on, sizeof(tcp_keepalive_on))) { perror(""); exit(-1); } char buf[1025]; int r; int maxfd; fd_set rset; FD_ZERO(&rset); sleep(5); while (1) { FD_SET(clifd, &rset); maxfd = clifd + 1; if (select(maxfd, &rset, NULL, NULL, NULL) < 0) { perror(""); exit(-1); } if (FD_ISSET(clifd, &rset)) { r = read(clifd, buf, sizeof(buf)); if (r == 0) { cout << "接收到FIN" << endl; close(clifd); break; } else if (r == -1) { if (errno == EINTR) { cout << "errno: EINTR" << endl; continue; } if (errno == ECONNRESET) { cout << "errno: ECONNRESET" << endl; cout << "对端已崩溃且已重新启动" << endl; close(clifd); break; } if (errno == ETIMEDOUT) { cout << "errno: ETIMEDOUT" << endl; cout << "对端主机崩溃" << endl; close(clifd); break; } if (errno == EHOSTUNREACH) { cout << "errno: EHOSTUNREACH" << endl; cout << "对端主机不可达" << endl; close(clifd); break; } } } } close(skfd); return 0; }
执行服务程序,再执行客户程序,之后将网络连接断开一段时间后(大于KeepAlive空闲时间+重复探测时间),重新打开网络连接,发现打印了ETIMEDOUT,验证了在客户网络断开后,到达空闲时间时,服务器由于开启了KeepAlive选项,会向客户端发送探测包,几次还没收到客户端的回应,那么select将返回套接字可读的条件,并且read返回-1.设置相关错误,而与之相反的情况是如果不开启KeelAlive选项,那么即使客户端网络断开超过了整个的空闲和探测时间,服务端的select也不会返回可读的条件,即应用程序无法得到通知。