//
// main.c
// draft
//
// Created by Ron on 2017/11/27.
// Copyright © 2017年 Ron. All rights reserved.
//
/**********************************************************************
struct sockaddr_in{
sa_family_t sin_family; // 地址族
uint16_t sin_port; // 端口号
struct in_addr sin_addr; // 32位IP地址
char sin_zero[8]; // 不使用
};
其中struct in_addr结构体定义如下:
struct in_addr{
in_addr_t s_addr; // 32位IP地址,#define in_addr_t uint32_t
};
但实际上,还有一种结构体也可以表示地址信息,如下所示:
struct sockaddr{
sa_family_t sin_family; // 地址族
char sa_data[14]; // IP地址和端口
};
成员sa_data保存的信息包含 IP 地址和端口号,剩余部分填充0。
在网络编程中,常用的是 struct sockaddr_in 结构体,因为相对于 struct sockaddr 结构体,前者填充数据比较方便。
不过网络编程接口函数定义使用的是 struct sockaddr 结构体类型,这是由于最先使用的是 struct sockaddr 结构体,struct sockaddr_in 结构体是后来为了方便填充地址信息数据定义。这就出现矛盾了,不过也不用担心上面两个结构体之间是可以相互转换的。定义地址信息时使用 struct sockaddr_in 结构体,然后将该结构体类型转为 struct sockaddr 结构体类型传递给网络编程接口函数即可
**********************************************************************/
/**********************************************************************
在通过网络传输数据时约定统一方式,这种约定称为网络字节序(Network Byte Order),非常简单——统一为大端序。
把数据数组转化为大端序格式再进行网络传输
**********************************************************************/
//#include <stdio.h>
//#include <stdlib.h>
//int main(int argc, const char * argv[]) {
// unsigned short hosts = 0x1234;
// unsigned short nets;
// unsigned long hostl = 0x12345678;
// unsigned long netl;
//
// nets = htons(hosts);
// netl = htonl(hostl);
//
// printf("Host ordered short: %#x \n", hosts);
// printf("Network ordered short: %#x \n", nets);
//
// printf("Host ordered long: %#lx \n", hostl);
// printf("Network ordered long: %#lx \n", netl);
//
// return 0;
//}
/**********************************************************************
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
该函数的作用是把当前的时间放入 struct timeval 结构体中返回。
注意:
1.精确级别,微妙级别
2.受系统时间修改影响
3.返回的秒数是从1970年1月1日0时0分0秒开始
其参数 tv 是保存获取时间结果的结构体,参数 tz 用于保存时区结果。
结构体 timeval 的定义为:
struct timeval
{
long int tv_sec; // 秒数
long int tv_usec; // 微秒数
}
结构体timezone的定义为:
struct timezone
{
int tz_minuteswest;//格林威治时间往西方的时差
int tz_dsttime; //DST 时间的修正方式
}
timezone 参数若不使用则传入0即可,本项目传入0。
**********************************************************************/
//#include <stdio.h>
//#include <string.h>
//#include <unistd.h>
//#include <sys/time.h>
//
//// 计算时间差,单位:ms
//float timediff(struct timeval *begin,
// struct timeval *end){
// int n;
// // 先计算两个时间点相差多少微秒
// n = ( end->tv_sec - begin->tv_sec ) * 1000000 + ( end->tv_usec - begin->tv_usec );
//
// // 转化为毫秒返回
// return (float) (n / 1000);
//}
//
//int main(int argc, const char * argv[]){
// struct timeval begin, end;
//
// gettimeofday(&begin, 0);
//
// // 这里让程序挂起一秒
// printf("do something here...");
// sleep(1);
//
// gettimeofday(&end, 0);
//
// printf("running time : %fms\n", timediff(&begin, &end));
//
// return 0;
//}
/**********************************************************************
#include <arpa/inet.h>
in_addr_t inet_addr(const char *string);
该函数的作用是将用点分十进制字符串格式表示的IP地址转换成32位大端序整型。
成功时返回32位大端序整型数值,失败时返回 INADDR_NONE 。
**********************************************************************/
//#include <stdio.h>
//#include <arpa/inet.h>
//int main(int argc, const char * argv[]){
// char *addr1 = "1.2.3.4";
// char *addr2 = "192.168.1.1";
//
// in_addr_t data;
//
// data = inet_addr(addr1);
// printf(" %s -> %#lx \n", addr1, (long)data );
//
// data = inet_addr(addr2);
// printf(" %s -> %#lx \n", addr2, (long)data );
// return 0;
//}
/**********************************************************************
char * inet_ntoa(struct in_addr addr);
该函数的作用与 inet_addr 正好相反。将32位大端序整型格式IP地址转换为点分十进制格式。
成功时返回转换的字符串地址值,失败时返回-1。
**********************************************************************/
//#include <stdio.h>
//#include <arpa/inet.h>
//#include <string.h>
//int main(int argc, const char * argv[]){
//// 在网络编程中,常用的是 struct sockaddr_in 结构体,因为相对于 struct sockaddr 结构体,前者填充数据比较方便。
//// 不过网络编程接口函数定义使用的是 struct sockaddr 结构体类型,这是由于最先使用的是 struct sockaddr 结构体,struct sockaddr_in 结构体是后来为了方便填充地址信息数据定义。这就出现矛盾了,不过也不用担心上面两个结构体之间是可以相互转换的。定义地址信息时使用 struct sockaddr_in 结构体,然后将该结构体类型转为 struct sockaddr 结构体类型传递给网络编程接口函数即可。
//
// struct in_addr addr1, addr2;
// char * str1, * str2;
// char buf1[20],buf2[20];
//
// addr1.s_addr = ntohl(0x1020304);
// addr2.s_addr = ntohl(0xc0a80101);
// //inet_ntoa函数总是使用统一块内存空间,因此需要把每次返回的数据辅助到其他的空间,否则会被覆盖
// str1 = inet_ntoa(addr1);
// memcpy(buf1, str1, sizeof(&str1));
// str2 = inet_ntoa(addr2);
// //为什么用buf2的长度打印不完整?(126行)
//// memcpy(buf2, str2, sizeof(&str1));
//
// printf("%#lx -> %s \n", (long)addr1.s_addr, buf1);
// printf("%#lx -> %s \n", (long)addr2.s_addr, str2);
// return 0;
//}
/**********************************************************************
#include <netdb.h>
struct hostent * gethostbyname(const char * hostname);
该函数的作用是根据域名获取IP地址。成功时返回hostent结构体地址,失败时返回NULL指针。
struct hosten结构体定义如下:
struct hostent{
char * h_name;
char ** h_aliases;
char h_addrtype;
char h_length;
char ** h_addr_list;
};
我们最关心的是h_addr_list成员,它保存的就是域名对应IP地址。由于一个域名对应的IP地址不止一个,所以h_addr_list成员是char **类型,相当于二维字符数组。
**********************************************************************/
//#include <stdio.h>
//#include <stdlib.h>
//#include <unistd.h>
//#include <arpa/inet.h>
//#include <netdb.h>
//
//int main(int argc, char * argv[]){
// int i;
// struct hostent *host;
//
// if(argc < 2){
// printf("Use : %s <hostname> \n", argv[0]);
// exit(1);
// }
//
// host = gethostbyname(argv[1]);
//
// for(i = 0; host->h_addr_list[i]; i++){
// printf("IP addr %d : %s \n", i+1,
// inet_ntoa(*(struct in_addr *)host->h_addr_list[i]));
// }
//
// return 0;
//}
/**********************************************************************
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void * buff, size_t nbytes, int flags, struct sockaddr * from, socklen_t * addrlen);
ssize_t sendto(int sockfd, const void * buff, size_t nbytes, int flags,const struct sockaddr * to, socklen_t addrlen);
成功时返回读写的字节数,失败时返回-1。
sockfd参数:套接字描述符。
buff参数:指向读入或写出缓冲区的指针。
nbytes参数:读写字节数。
flags参数:本项目中设置为0。
recvfrom 的 from 参数指向一个将由该函数在返回时填写数据发送者的地址信息的结构体,而该结构体中填写的字节数则放在 addrlen 参数所指的整数中。
sendto 的 to 参数指向一个含有数据报接收者的地址信息的结构体,其大小由addrlen参数指定。
#include <sys/socket.h>
int socket(int family, int type, int protocol);
成功时返回文件描述符,失败时返回-1。
该函数的作用是创建一个套接字。一般为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数。
第一个参数family:指明套接字中使用的协议族信息。
第二个参数type:指明套接口类型,也即套接字的数据传输方式。
在常见的使用socket进行网络编程中,经常使用SOCK_STREAM和SOCK_DGRAM,也就是TCP和UDP编程。在本项目中,我们将使用SOCK_RAW(原始套接字)。
ping 命令使用的就是 ICMP 协议,因此我们不能直接通过建立一个 SOCK_STREAM或SOCK_DGRAM 来发送协议包,只能自己构建 ICMP 包通过 SOCK_RAW 来发送。
第三个参数 protocol:指明协议类型。
参数 protocol 指明了所要接收的协议包。
如果指定了 IPPROTO_ICMP,则内核碰到ip头中 protocol 域和创建 socket 所使用参数 protocol 相同的 IP 包,就会交给我们创建的原始套接字来处理。
因此,一般来说,要想接收什么样的数据包,就应该在参数protocol里来指定相应的协议。当内核向我们创建的原始套接字交付数据包的时候,是包括整个IP头的,并且是已经重组好的IP包。
这里的数据也就是前面所说的时间戳。
但是,当我们发送IP包的时候,却不用自己处理IP首部,IP首部由内核自己维护,首部中的协议字段被设置成调用 socket 函数时传递给它的第三个参数。
我们发送 IP 包时,发送数据时从 IP 首部的第一个字节开始的,所以只需要构造一个如下所示的数据缓冲区就可以了
**********************************************************************/