LInux_网络编程
2. 网络相关概念
1)套接口的概念 | 2)端口号的概念 | 3)ip 地址的表示: | ip 地址的分类 |
---|
struct sockaddr_in | struct in_addr | 变长结构体 |
---|
大端,小端模式 | 实例1:验证是小端模式还是大端模式 |
---|---|
主机字节序和网络字节序 | |
实例2:htons() 主机字节序转网络字节序 | 实例2.2:ntohs() 网络字节序转主机字节序 |
主机名与域名的区别 | ||
---|---|---|
主机名和地址的转化 | ||
函数 gethostbyname():域名或主机名转换为 IP 地址 | 函数 gethostbyaddr():将 IP 地址转换为域名或主机名 | |
实例4:gethostbyname 将域名或主机名转换为 IP 地址 |
1)套接口的概念: 套接口,也叫“套接字”。是操作系统内核中的一个数据结构,它是网络中的节点进行相互通信的门户。它是网络进程的 ID。
网络通信,归根到底还是进程间的通信(不同计算机上的进程间通信)。在网络中,每一个节点(计算机或路由)都有一个网络地址,也就是 IP 地址。两个进程通信时,首先要确定各自所在的网络节点的网络地址。但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中的哪一个进程进行通信,因此套接口中还需要包括其他的信息,也就是端口号(PORT)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。
所以,使用端口号和网络地址的组合可以唯一的确定整个网络中的一个网络进程。 例如,如网络中某一台计算机的 IP 为 10.92.20.160,操作系统分配给计算机中某一应用程序进程 的端口号为 1500,则此时 10.92.20.160 1500 就构成了一个套接口。
2)端口号的概念: 在网络技术中,端口大致有两种意思:
一是物理意义上的端口,如集线器、交换机、路由器等用于连接其他网络设备的接口。
二是指 TCP/IP 协议中的端口,端口号的范围从 0 ~ 65535,
(一类是由互联网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的 “周知的端口”,其值一般为0~1023。例如 http 的端口号是 80,ftp 为 21,ssh 为 22,telnet 为 23 等。)
(还有一类是用户自己定义的,通常是大于 1024 的整型值。 )
3)ip 地址的表示: 通常用户在表达 IP 地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制 Ipv6 地址),而在通常使用的 socket 编程中使用的则是二进制值,这就需要将这两个数值进行转换。
ipv4 地址:32bit, 4 字节,通常采用点分十进制记法。 例如对于:
10000000 00001011 00000011 00011111
点分十进制表示为:128.11.3.31
ip 地址的分类:
特殊的ip地址:
4)通信的五个要素:源ip,源端口,目的ip,目的端口,协议。
2.1. socket 概念
Linux 中的网络编程是通过 socket 接口来进行的。socket 是一种特殊的 I/O 接口,它也是一种文件描述符。它是一种常用的进程之间通信机制,通过它不仅能实现本地机器上的进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信。 每一个 socket 都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一 个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。
socket 也有一个类似于打开文件的函数调用,该函数返回一个整型的 socket 描述符,随后的连接建立、数据传输等操作都是通过 socket 来实现的;
2.2. socket 类型
(1)流式 socket(SOCK_STREAM
) →用于 TCP 通信
流式套接字提供可靠的、面向连接的通信流;它使用 TCP 协议,从而保证了数据传输的正确性和顺 序性。
(2)数据报 socket(SOCK_DGRAM
) →用于 UDP 通信
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保 证是可靠、无差错的。它使用数据报协议 UDP。
(3)原始 socket(SOCK_RAW
) →用于新的网络协议实现的测试等
原始套接字允许对底层协议如 IP 或 ICMP 进行直接访问,它功能强大但使用较为不便,主要用于一 些协议的开发。
2.3. socket 信息数据结构
头文件<netinet/in.h>
(不常用)
struct sockaddr
{
unsigned short sa_family; -地址族
char sa_data[14]; -14 字节的协议地址,包含该 socket 的 IP 地址和端口号
};
(常用)
struct sockaddr_in
{
short int sin_family; -地址族(2字节)
unsigned short int sin_port; -端口号(2字节)
struct in_addr sin_addr; -IP 地址 (见下)(4字节)
unsigned char sin_zero[8]; -填充 0 以保持与 struct sockaddr 同样大小(8字节)
};
sin_family:
AF_INET
→IPv4 协议
AF_INET6
→IPv6 协议
struct in_addr
{
in_addr_t s_addr; -32 位 IPv4 地址,网络字节序
(unsigned long int s_addr;) -64位为unsigned int
};
变长结构体
在结构体中定义了一个没有任何元素的字节数组,可以通过动态开辟一个比结构体大的空间,然后让buffer去指向那些额外的空间,这样就可以实现可变长的结构体了。
例子:
char *p = malloc(100);
*(long*)p = 100; -访问第一个结构体成员
*(int *)(p + 8); -访问第二个结构体成员
每一个成员访问不需要提前知道它的名字
2.4. 数据存储优先顺序的转换
计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端 模式)。
小端模式:内存的低地址存储数据的低字节,内存的高地址存储数据的高字节的方式
大端模式:内存的高地址存储数据的低字节,内存的低地址存储数据高字节的方式。
例:对于内存中存放的数 0x12345678 来说
如果是采用大端模式存放的,则其真实的数是:0x12345678
如果是采用小端模式存放的,则其真实的数是:0x78563412
实例1:验证是小端模式还是大端模式
int main()
{
int a = 0x00001234;
printf("a = %x\n", a);
char *pc = &a;
if(*pc == 0x12)
{
printf("big\n");
}
else{
printf("little\n");
}
return 0;
}
主机字节序: 不同硬件有差异,它可能是小端模式的,也可能是大端模式的。
网络字节序: 在TCP/IP规定的,如端口号和 IP 地址都是网络字节序存储,是大端模式。
要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。这里用到四个函数: htons()
,ntohs()
,htonl()
和 ntohl()
。
这四个地址分别实现网络字节序和主机字节序的转化,
h 代表 host(主机字节序转网络字节序)
n 代表 network(网络字节序转主机字节序)
s 代表 short
l 代表 long
通常 16 位的 IP 端口号用 s 代表,而 IP 地址用 l 来代表。
函数原型如下:
实例2:htons()
主机字节序转网络字节序
int main(int argc, char* argv[])
{
ARGS_CHECK(argc, 2);
short port = atoi(argv[1]); -内存: 0x34 0x12
short nport = htons(port); -内存: 0x12 0x34
printf("%x\n", nport);
return 0;
}
运行结果:
输入4660 (十六进制 0x1234),得:
3412
实例2.2:ntohs()
网络字节序转主机字节序
int main(int argc, char* argv[])
{
ARGS_CHECK(argc, 2);
short port = atoi(argv[1]);
short nport = htons(port);
printf("%x\n", nport);
port = 0;
port = ntohs(nport);
printf("%x\n", port);
return 0;
}
运行结果:
输入4660 (十六进制 0x1234),得:
3412
1234
2.5. 地址格式转化
通常用户在表达地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制 Ipv6 地址),而在通常使用的 socket 编程中使用的则是 32 位的网络字节序的二进制值,这就需要将这两个数值进行转换。
这里在 Ipv4 中用到的函数有 inet_aton()
、inet_addr()
和 inet_ntoa()
而 IPV4 和 Ipv6 兼容的函数有 inet_pton()
和 inet_ntop()
。
IPv4 的函数原型:
头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
1. 函数 inet_aton()
:
int inet_aton(const char *straddr, struct in_addr *addrptr);
作用: 将点分十进制数的 IP 地址转换成为网络字节序的 32 位二进制数值。
返回值: 成功,则返回 1,不成功返回 0
参数:
- 参数 straddr: 存放输入的点分十进制数 IP 地址字符串。
- 参数 addrptr: 传出参数,保存网络字节序的 32 位二进制数值。
struct in_addr { in_addr_t s_addr; -32 位 IPv4 地址,网络字节序 };
实例3:inet_aton
将点分十进制数的 IP 地址转换成为网络字节序的 32 位二进制数值
int main(int argc, char* argv[])
{
ARGS_CHECK(argc, 2);
struct in_addr netIp;
int ret;
ret = inet_aton(argv[1], &netIp);
if(0 == ret)
{
fprintf(stderr, "Invalid address\n");
return -1;
}
printf("%x\n", netIp.s_addr);
return 0;
}
运行结果:
输入192.168.5.21,得:
1505a8c0
15 05 a8 c0
即 21 5 168 192
打印为: 15 05 a8 c0
内存中储存为:c0 a8 05 15 (大端)
内核里,直接将大端得ip地址的每一个字节变成一个字符串,中间以点隔开(没有转换小端)
2. 函数 inet_ntoa()
:
char *inet_ntoa(struct in_addr inaddr);
作用: 将网络字节序的 32 位二进制数值转换为点分十进制的 IP 地址。
参数:
inaddr
: 网络字节序的 32 位二进制数值
如实例3,即又重新转换为点分十进制IP地址则
inet_ntoa(netIp));
3. 函数 inet_addr():
(常用)
in_addr_t inet_addr(const char *straddr);
(in_addr_t
就是 unsigned int
,代表 s_addr
)
作用: 功能与 inet_aton
相同,将点分十进制数的 IP 地址转换成为网络字节序的 32 位二进制数值。但是结果传递的方式不同。
返回值: inet_addr()
若成功则返回 32 位二进制的网络字节序地址。
参数 straddr
: 存放输入的点分十进制数 IP 地址字符串。
实例4: inet_addr()
将点分十进制数的 IP 地址转换成为网络字节序的 32 位二进制数值
int main(int argc, char* argv[])
{
ARGS_CHECK(argc, 2);
printf("%x\n", inet_addr(argv[1]));
return 0;
}
IPv4 和 IPv6 的函数原型:
头文件:
#include <arpa/inet.h>
1. 函数 inet_pton
int inet_pton(int family, const char *src, void *dst);
作用:跟 inet_aton
实现的功能类似,将点分十进制数的 IP 地址转换成为网络字节序的 32 位二进制数值。
参数:
- 多了
family
参数,
该参数指定为AF_INET
,表示是 IPv4 协议
如果是AF_INET6
,表示 IPv6 协议。 src
: 存放输入的点分十进制数 IP 地址字符串dst
: 传出参数,保存网络字节序的 32 位二进制数值。
2. 函数 inet_ntop
const char *inet_ntop(int family, const void *src, char *dst, socklen_t len);
跟 inet_ntoa
类似,将网络字节序的 32 位二进制数值转换为点分十进制的 IP 地址。
参数:
family
:AF_INET
,表示是 IPv4 协议,AF_INET6
,表示 IPv6 协议。src
:点分十进制数 IP 地址字符串dst
:网络字节序的 32 位二进制数值。len
:表示表示转换之后的长度(字符串的长度)。
Example:点分十进制数的 IP 地址 和 网络字节序的 32 位二进制数值 各接口得使用
int main()
{
char ip[] = "192.168.0.101";
struct in_addr myaddr;
**** inet_aton ****
int iRet = inet_aton(ip, &myaddr);
printf("%x\n", myaddr.s_addr);
*** inet_addr ****
printf("%x\n", inet_addr(ip));
**** inet_pton ****
iRet = inet_pton(AF_INET, ip, &myaddr);
printf("%x\n", myaddr.s_addr);
myaddr.s_addr = 0xac100ac4;
**** inet_ntoa ****
printf("%s\n", inet_ntoa(myaddr));
**** inet_ntop ****
inet_ntop(AF_INET, &myaddr, ip, 16);
puts(ip);
return 0;
}
2.6. 名字地址转化
通常,人们在使用过程中都不愿意记忆冗长的 IP 地址,尤其到 Ipv6 时,地址长度多达 128 位,那时就更加不可能一次性记忆那么长的 IP 地址了。因此,使用主机名或域名将会是很好的选择。
主机名与域名的区别:
主机名通常在局域网里面使用,通过/etc/hosts 文件,主机名可以解析到对应的 ip;
域名通常是在 internet 上使用。
众所周知,百度的域名为:www.baidu.com,而这个域名其实对应了一个百度公司的 IP 地址,那么 百度公司的 IP 地址是多少呢?我们可以利用 ping www.baidu.com 来得到百度公司的 ip 地址,如图。 那么,系统是如何将 www.baidu.com 这个域名转化为 IP 地址 220.181.111.148 的呢?
在 linux 中,有一些函数可以实现主机名和地址的转化,最常见的有 gethostbyname()
、 gethostbyaddr()
等,它们都可以实现 IPv4 和 IPv6 的地址和主机名之间的转化。
其中 gethostbyname()
是将主机名转化为 IP 地址,gethostbyaddr()
则是逆操作,是将 IP 地址转化为主机名。 (均为非线程安全函数,有同类型_r
函数)
函数原型:
#include <netdb.h>
1. 函数 gethostbyname()
:
struct hostent* gethostbyname(const char* hostname);
作用:用于将域名(如:www.baidu.com)或主机名转换为 IP 地址。
参数:hostname
指向存放域名或主机名的字符串。
**** 结构体 ****
struct hostent {
char *h_name; -正式主机名
char **h_aliases; -主机别名(指针数组的起始地址)(多个正式主机名指针)
int h_addrtype; -主机 IP 地址类型 IPv4 为 AF_INET
int h_length; -主机 IP 地址字节长度,对于 IPv4 是 4 字节,即 32 位
char **h_addr_list; -主机的 IP 地址列表(指向网络字节序,需要转换 ntop())
}
#define h_addr h_addr_list[0] -保存的是 ip 地址
2. 函数 gethostbyaddr()
:
struct hostent* gethostbyaddr(const char* addr, size_t len, int family);
作用:用于将 IP 地址转换为域名或主机名。
参数:
addr
是一个 IP 地址,此时这个 ip 地址不是普通的字符串,而是要通过函数 inet_aton()转换。len
为 IP 地址的长度,AF_INET 为 4family
可用AF_INET
:Ipv4 或AF_INET6
:Ipv6
(必须在/etc/hosts 中有配置 )
实例4: gethostbyname
将域名或主机名转换为 IP 地址。
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,2);
struct hostent *p;
p=gethostbyname(argv[1]);
if(NULL==p)
{
printf("gethostbyname fail\n");
return -1;
}
printf("host name: %s\n",p->h_name);
int i;
printf("host aliases :\n");
for(i=0;p->h_aliases[i]!=NULL;i++)
{
puts(p->h_aliases[i]);
}
printf("host addrtype: %d\n",p->h_addrtype);
printf("host length:%d\n",p->h_length);
char ip[16]={0};
printf("host address list:\n");
for(i=0;p->h_addr_list[i]!=NULL;i++)
{
bzero(ip,sizeof(ip));
inet_ntop(p->h_addrtype,p->h_addr_list[i],ip,sizeof(ip));
puts(ip);
}
return 0;
}
运行结果:
输入 www.baidu.com
host name: www.a.shifen.com
host aliases :
www.baidu.com
host addrtype: 2
host length:4
host address list:
106.120.159.141
106.120.159.142