文章目录
- 套接字(Socket)的概念
- Socket数据结构
- 数据存储方式
- IP 点分十进制与二进制的相互转化
- 域名与IP地址相互转化
套接字(Socket)的概念
套接字: 是系统内核中的一种数据结构,也是网络间进程之间一种通信机制,同时也是** I/O 文件描述符**。
在利用套接字进行网络通信时,套接字是如何唯一确定网络中的进程的呢?
在网络中,每一个主机可以通过 IP 地址唯一确定,主机内的进程可以用进程ID 唯一确定,但是套接字使用的不是 进程ID ,使用的是 端口号。在主机内,端口号 与 进程ID 是一一对应的关系,每一个进程都被分配了一个唯一性的端口号。
套接字有两种格式:
- 半相关描述的套接字格式为:{协议,本地IP,本地端口};
- 完全相关描述的套接字格式为:{协议、本地IP,本地端口,对方IP,对方端口}。
其中协议主要分为三种:
- 流式Socket(SOCK_STREAM): 用于 TCP 通信。流式套接字提供面向连接的可靠的通信流;
- 数据报Socket(SOCK_DGRAM): 用于 UDP 通信。数据报套接字提供无连接不可靠的数据传输服务;
- 原始Socket(SOCK_RAW): 用于新网络协议的测试。
Socket 通信在实现过程中,也有一个类似于打开文件的函数,它能返回一个整型的 Socket 描述符,我们利用这个 Socket 描述符实现网络连接、数据传输等操作。
Socket数据结构
Socket 的信息数据结构有两种,如下所示:
- 通用型Socket信息结构:
struct sockaddr {
unsigned short sa_family; // 地址族
char sa_data[14]; //14字节协议地址,保存 Socket 的 IP 地址和端口号
};
- 具体型Socket信息结构:
struct sockaddr_in {
short int sa_family; // 地址族
unsigned short int sin_port; // 端口号
struct in_addr sin_addr; // IP地址
unsigned char sin_zero[8]; // 填充0以保持和 struct sockaddr 一致
};
struct in_addr {
unsigned long int s_addr; // 32为 IPv4地址,网络字节序
};
在头文件 <netinet/in.h>
中,地址族可以取如下值:
-
sa_family::AF_INET
表示 IPv4 协议; -
sa_family::AF_INET6
表示 IPv6 协议。
通用型Socket的数据结构 可以和 具体型Socket的数据结构 相互转化。
数据存储方式
计算机数据存储有两种方式:
- 大端模式: 高字节优先;内存的高地址存放数据的低字节,内存的低地址存放数据的高字节;
- 小端模式: 低字节优先:内存的高地址存放数据的高字节,内存的低地址存放数据的低字节;
例如,对于数据 而言,如果采用 大端模式 存放,则真实的数为:;如果采用 小端模式 存放,则真实的数为:0x78563412。
- 系统中存放数据的字节序称为主机字节序,主机字节序有可能采用小端模式,也有可能采用 大端模式。但是,
- 网络中流动的数据称为:网络字节序,网络字节序采用的是大端模式。
当数据从主机发送到网络时,需要进行 字节序转化,Linux 提供了四个函数用于转化 字节序:
-
htons()
:主机字节序 向 网络字节序 的转化;用于端口转换。 -
ntohs()
:网络字节序 向 主机字节序 的转化;用于端口转换。 -
htonl()
:主机字节序 向 网络字节序 的转化;用于 IP 转换。 -
ntohl()
:网络字节序 向 主机字节序 的转化;用于 IP 转换。
其中,各个简写的含义如下:
-
h
表示host
; -
n
表示network
; -
s
表示short
,表示 - l
表示
long`,表示
IP 点分十进制与二进制的相互转化
把 IP 点分十进制 与 二进制 进行相互转换。
- 将 点分十进制 的 IP 转化为 网络字节序 的 二进制:
-
inet_addr(const char *cp)
; -
inet_aton(const char *cp, struct in_addr *inp)
。
两个函数已经包含了 主机字节序 向 网络字节序 的转化
- 将 二进制 IP 转化为 点分十进制 的 IP:
-
inet_ntoa(struct in_addr in)
。
这个函数包含了 主机字节序 向 网络字节序 的转化。
其中,IPv4 和 IPv6 兼容的函数有 inet_pton()
和 inet_ntop()
。
例子:网络地址与二进制地址的转化
// ip_addr.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
char ip[] = "192.168.0.101"; // 点分十进制的 IP
struct in_addr myaddr; // 存储 IP 的二进制
/*inet_aton*/
int iRet = inet_aton(ip, &myaddr); // 把 IP 的点分十进制转化为 二进制
printf("%x\n", myaddr.s_addr);
/*inet_addr*/
printf("%x\n", inet_addr(ip)); // 把 IP 的点分十进制转化为 二进制
/*inet_pton*/
iRet = inet_pton(AF_INET, ip, &myaddr); // 把 IP 的点分十进制转化为 二进制
printf("%x\n", myaddr.s_addr);
myaddr.s_addr = 0xac100ac4;
/*inet_ntoa*/
printf("%s\n", inet_ntoa(myaddr)); // 把 IP 的二进制转化为 点分十进制格式
/*inet_ntop*/
inet_ntop(AF_INET, &myaddr, ip, 16); // 把 IP 的二进制转化为 点分十进制格式
puts(ip);
return 0;
}
域名与IP地址相互转化
一般使用 主机名 或 域名 来代替 IP 地址的表达。
主机名 与 域名 的区别为:
- 主机名 通常在局域网里面使用,通过
/etc/hosts
文件可以将主机名解析到对应的 IP; - 而 域名 通常在 Internet 上使用,例如:
www.baidu.com
。
在 Linux 一般使用 gethostbyname()
将主机名转化为 IP 地址;使用 gethostbyaddr()
将 IP 地址转化为主机名。这两个函数在 IPv4 和 IPv5 中都适用。函数原型如下:
-
gethostbyname 函数
:用于将域名( www. baidu. com)或主机名转换为 IP 地址,参数hostname
指向存放域名或主机名的字符串; gethostbyaddr 函数
:用于将 IP 地址转换为域名或主机名,
- 参数 addr 是 一个 IP 地址, 此时这个 IP 地址不是普通的字符串, 而是要通过函数
inet_ aton
转换 的; - len 为 IP 地址 的 长度, AF_ INET 为 4;
- family 可用
AF_ INET: IPv4
或AF_ INET6: IPv6
。
结构体为:
例子:由域名获取主机的相关信息
// name_to_ip.c
// transform www.baidu.com to ip
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
char *ptr = NULL, **pptr = NULL;
struct hostent *hptr = NULL;
char str[32] = {'\0'};
ptr = argv[1]; // 域名: www.baidu.com
if((hptr = gethostbyname(ptr)) == NULL) {
printf("gethostbyname error for host:%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);
/*根据地址类型,将地址打印出来*/
switch (hptr->h_addrtype)
{
case AF_INET:
case AF_INET6:
pptr = hptr->h_addr_list; // 指向ip地址列表
/*将刚才得到的所有地址都打印出来, 其中调用了inet_ntop函数*/
for(; *pptr != NULL; pptr++) {
inet_ntop(hptr->h_addrtype, (void *)*pptr, str, sizeof(str)); // 把二进制ip转化为点分十进制
printf("address: %s\n", str);
}
break;
default:
printf("unknown address type \n");
}
return 0;
}
运行结果如下:
$ gcc name_to_ip.c -o name_to_ip
$ ./name_to_ip www.baidu.com
official hostname: www.a.shifen.com
alias:www.baidu.com
address: 14.215.177.38
address: 14.215.177.39