htons 函数有什么用?
在上古时代,不同的计算机使用不同的字节顺序来存放多字节整数(多字节整数,就是大于一个字符 char 的整数)。这样做的结果就是:如果你在 Intel box 和 Mac 电脑之间的一台发送一个数字 1,另一边就会认为自己收到的是 256,反之同理。(Just to make you really unhappy, different computers use different byte orderings internally for their multibyte integers (i.e. any integer that’s larger than a char.) The upshot of this is that if you send () a two-byte short int from an Intel box to a Mac (before they became Intel boxes, too, I mean), what one computer thinks is the number 1, the other will think is the number 256, and vice-versa.)
解决方法,就是统一标准。最后人们采用了 Motorola 和 IBM 的标准,也就是在发送之前先转换为 “big-endian”(将高序字节存储在起始地址,也叫高位编址,BE。比如 0x01020304
,在内存中存为,01020304,和我们的书写顺序一致。而 LE(little-endian)和书写顺序相反)
htons 是 host to network short 的缩写,意思就是把本机的 short 整数转换为网络传输的(也即上文的 BE)整数。这就是 htons 的作用。
类似的函数还有四个:
htons() // host to network short
htonl() // host to network long
ntohs() // network to host short
ntohl() // network to host long
sockaddr_in 结构体
定义
#include <netinet/in.h>
struct sockaddr_in {
short sin_family; // e.g. AF_INET
unsigned short sin_port; // e.g. htons(3490)
struct in_addr sin_addr; // see struct in_addr, below
char sin_zero[8]; // zero this if you want to
};
struct in_addr {
unsigned long s_addr; // load with inet_aton()
};
- sin_family: 协议簇,AF_INET 就表示 TCP/IP 协议
- sin_port: 端口号
- sin_addr: IP 地址
- sin_zero: 为了让
sockaddr
与sockaddr_in
两个数据结构保持大小相同而保留的空字节。
用法
-
sin_family 直接赋值
myaddr.sin_family = AF_INET;
-
sin_port
首先介绍 atoi (表示 ascii to integer),是把字符串转换成整型数的一个函数。
如果我们要把 “80” 这样的字符串转换为整数,可以这样:
atoi("80"); // 80
sin_port 存网络端口,这个端口是 BE 规范化的端口,所以要用 htons () 转换。
所以,sin_port 应该这样赋值:
myaddr.sin_port = htons(80);
- sin_addr
显然,我们要赋予其一个 in_addr 类型的结构体,这种结构体含有一个 s_addr 成员。我们常见的 IP 地址都是 xxx.xxx.xxx.xxx 这样的形式,我们要如何转换呢?使用 inet_addr 函数即可。
my_in_addr.s_addr = inet_addr("xxx.xxx.xxx.xx");
这样就行了
- sin_zero
这个我也没找到具体的应用。
socket 函数
定义
int socket(int domain, int type, int protocol);
-
domain:有四种类型:AF_INET, AF_INET6, AF_UNIX, or AF_RAW. AF 表示 ADDRESS FAMILY 地址族,PF 表示 PROTOCOL FAMILY 协议族。AF_INET 就是 ipv4,AF_INET6 就是 ipv6,两个常用在夸及其通信。常用在 AF_UNIX 本机通讯。AF_RAW 的资料我没有找到。
-
type:创建的 socket 的类型,有 SOCK_STREAM, SOCK_DGRAM, or SOCK_RAW.
- SOCK_DGRAM:提供数据报,这些数据报是固定最大长度的无连接消息,其可靠性无法保证。 数据报可能已损坏,无序接收,丢失或多次传送。 AF_INET,AF_INET6 和 AF_UNIX 域支持此类型。
- SOCK_RAW:提供内部协议(如 IP 和 ICMP)的接口。 AF_INET 和 AF_INET6 域支持此类型。必须是 root 用户才能使用此类型。
- SOCK_STREAM:提供可靠且面向连接的有序双向字节流。 它们支持带外数据的机制。 AF_INET,AF_INET6 和 AF_UNIX 域支持此类型。
-
protocol:使用的协议,有 0, IPPROTO_UDP, or IPPROTO_TCP。如果 domain 设置为 AF_UNIX,则必须设置 protocol 为 0。0 表示不指定协议,让其取决于服务提供者。
有什么用
socket 函数创建一个通信端点,并返回表示端点的套接字描述符。 不同类型的套接字提供不同的通信服务。类比打开文件,打开文件会返回文件描述符,而调用 socket 函数会返回一个套接字描述符。这样之后你就可以进行读写等操作。
怎么用
调用,用一个变量接收,然后使用 bind 函数绑定,然后就可以读写网络流了,最后关闭。
bind 函数
定义
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd: socket 描述符,就是前面用 socket 创建的。
- addr:协议地址指针。ipv4 对应的就是 sockaddr_in 结构体(前面说过)。ipv6 对应 sockaddr_in6 结构体。
- addrlen:地址的长度。用的时候直接 sizeof (addr) 就行了。
例子:
int bind_server_socket (char *s1, char *s2) // 创建主机 socket, s1 表示 IP 地址,s2 表示端口
{
unsigned long inaddr; // 用来储存 ip 地址
struct sockaddr_in saddr; // 地址结构
memset (&saddr, 0, sizeof (struct sockaddr_in)); // 调用了 memset,把地址结构体的内存以 0 填充。
int sock_id_s;
sock_id_s = socket (AF_INET, SOCK_STREAM, 0); // 调用了 socket (),函数从而获得了套接字描述符。
if (sock_id_s == -1) // 错误处理,当套接字创建失败会返回 -1 。
return -1;
saddr.sin_family = AF_INET;
inaddr = inet_addr (s1); // 将点分地址转化为无符号长整型
memcpy (&saddr.sin_addr, &inaddr, sizeof (inaddr)); // 把 inaddr 的地址处内存复制给 saddr.sin_addr 处内存。从而实现拷贝赋值。不过据说等号赋值更快:https://blog.csdn.net/pngynghay/article/details/17142401
saddr.sin_port = htons (atoi (s2)); // 将端口号转化为网络字节序
if (bind (sock_id_s, (struct sockaddr *)&saddr, sizeof (saddr)) != 0) // 绑定到地址
return -1;
return sock_id_s; // 返回套接字描述符
}