Site Overlay

Linux C 笔记:htons,inet_addr,socket,bind 函数和 sockaddr_in 结构体

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()
};
  1. sin_family: 协议簇,AF_INET 就表示 TCP/IP 协议
  2. sin_port: 端口号
  3. sin_addr: IP 地址
  4. sin_zero: 为了让 sockaddrsockaddr_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);
  1. 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 的资料我没有找到。
  2. type:创建的 socket 的类型,有 SOCK_STREAM, SOCK_DGRAM, or SOCK_RAW.

    1. SOCK_DGRAM:提供数据报,这些数据报是固定最大长度的无连接消息,其可靠性无法保证。 数据报可能已损坏,无序接收,丢失或多次传送。 AF_INET,AF_INET6 和 AF_UNIX 域支持此类型。
    2. SOCK_RAW:提供内部协议(如IP和ICMP)的接口。 AF_INET和AF_INET6域支持此类型。必须是 root 用户才能使用此类型。
    3. SOCK_STREAM:提供可靠且面向连接的有序双向字节流。 它们支持带外数据的机制。 AF_INET,AF_INET6和AF_UNIX域支持此类型。
  3. 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);
  1. sockfd: socket 描述符,就是前面用 socket 创建的。
  2. addr:协议地址指针。ipv4 对应的就是 sockaddr_in 结构体(前面说过)。ipv6 对应 sockaddr_in6 结构体。
  3. 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; // 返回套接字描述符
}

参考文献

  1. struct sockaddr_in, struct in_addr
  2. Endian
  3. htons(), htonl(), ntohs(), ntohl()
  4. socket() — Create a socket
  5. what does 0 indicate in socket() system call?

发表评论

电子邮件地址不会被公开。 必填项已用*标注