Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

Gray-Ice

个人博客兼个人网站

本篇只记录CSAPP网络编程章节中的函数所属的头文件以及参数,不会有任何额外的知识,因为经过了之前写”信号”笔记的毒打,我深刻的意识到了整本书都是需要记的知识点这个惨痛的事实。另外,本篇的内容与我的博文: Unix网络编程篇有所重复,但我依然会记载Unix网络编程篇出现过并且在CSAPP也出现过的函数与结构,所以无需担心我因偷懒而导致内容不全。

struct in_addr

1
2
3
4
#include <arpa/inet.h>  // 头文件
struct in_addr{
uint32_t s_addr; // Address in network byte order (big-endian) 博主解释: 这个是用来存储IP地址的,采用大端法。而且这个结构体只有这一个成员。
}

网络/主机 字节顺序转换

1
2
3
4
5
6
7
8
#include <arpa/inet.h>  // 函数所属头文件
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostport);
// 返回: 按照网络字节顺序的值。

uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
// 返回: 按照主机字节顺序的值。

IP地址和点分十进制串转换

1
2
3
4
#include <arpa/inet.h>  // 头文件
int inet_pton(AF_INET, const char *src, void *dst); // 返回: 若成功则为1, 若src为非法点分十进制则为0,若出错则为-1。

const char* inet_ntop(AF_INET, const void *src, char *dst, socklen_t size); // 返回: 若成功则指向点分十进制字符串的指针,若出错则为NULL。

struct sockaddr_in

1
2
3
4
5
6
7
8
9
10
11
12
#include <arpa/inet.h>  // 头文件
struct sockaddr_in{
uint16_t sin_family; // Protocol family.
uint16_t sin_port; // Port number in network byte order.
struct in_addr sin_addr; // IP address in network byte order.
unsigned char sin_zero[8]; // Pad to sizeof(struct sockaddr).
}

struct sockaddr{
uint16_t sa_family; // Protocol family.
char sa_data[14]; // Address data.
}

注意,_in后缀是互联网络(internet)的缩写,而不是输入(input)的缩写。

另外,结构体sockaddr_in中的成员sin_zero是用来填充结构体sockaddr_in来使其与结构体sockaddr保持相同大小的。sockaddr_in的大小为2 bytes + 2 bytes + 4 bytes + 8 bytes = 16 bytes,sockaddr的大小为2 bytes + 14 bytes = 16 bytes。

socket()

客户端和服务器使用socket函数来创建一个套接字描述符(socket descriptor)。

1
2
#include <sys/socket.h>  // 头文件
int socket(int domain, int type, int protocol); // 返回: 若成功则为非负描述符,若出错则为-1。

connect()

客户端通过调用connect函数来建立和服务器的连接。

1
2
#include <sys/socket.h>  // 头文件
int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen); // 返回: 若成功则为0,若出错则为-1。

bind()

bind函数告诉内核将addr中的服务器套接字地址和套接字描述符sockfd联系起来。参数addrlen就是sizeof(sockaddr_in)。

1
2
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 返回: 若成功则为0,若出错则为-1。

listen()

服务器调用listen函数告诉内核,描述符是被服务器而不是客户端使用的。

1
2
#include <sys/socket.h>
int listen(int sockfd, int backlog); // 返回: 若成功则为0,若出错则为-1。

listen函数将sockfd从一个主动套接字转化为一个监听套接字(listening socket),该套接字可以接受来自客户端的连接请求,backlog参数暗示了内核在开始拒绝连接请求之前,队列中要排队的未完成的连接请求的数量。

accept()

服务器通过调用accept函数来等待来自客户端的连接请求。

1
2
#include <sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, socklen_t *addrlen); // 返回: 若成功则为非负连接描述符,若出错则为-1。

主机和服务的转换

getaddrinfo()

Linux提供了一些强大的函数(称为getaddrinfo和getnameinfo)实现二进制套接字地址结构和主机名,主机地址,服务名和端口号的字符串表示之间的相互转化。

1
2
3
4
5
6
7
#include <netdb.h>  // 头文件

int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct addrinfo **result); // 返回: 如果成功则为0,如果错误则为非零的错误代码。

void freeaddrinfo(struct addrinfo *result); // 用来释放链表,result填getaddrinfo中使用的result。

const char *gai_strerror(int errcode); // 返回: 错误消息。这个错误消息是根据errcode来变化的。

给定host和service(套接字地址的两个组成部分),getaddrinfo返回result,result一个指向addrinfo结构的链表,其中每个结构指向一个对应于host和service的套接字地址结构。

getaddrinfo的host参数可以是域名,也可以是数字地址(如点分十进制IP地址)。service参数可以是服务名(如http),也可以是十进制端口号。如果不想把主机名转换成地址,可以把host参数设置为NULL.对service来说也是一样。但是必须指定两者中至少一个。

可选的参数hints是一个getaddrinfo结构,它提供对getaddrinfo返回的套接字地址列表的更好的控制。如果要传递hints参数,只能设置下列字段: ai_family,ai_socktype,ai_protocol和ai_flags字段。其他字段必须设置为0(或NULL)。

  • getaddrinfo默认可以返回IPv4和IPv6套接字地址。ai_family设置为AF_INET会将列表限制为IPv4地址;设置为AF_INET6则限制为IPv6地址。
  • 对于host关联的每个地址,getaddrinfo函数默认最多返回三个addrinfo结构,每个的ai_socktype字段不同: 一个是连接,一个是数据报,一个是原始套接字。ai_socktype设置为SOCK_STREAM将列表限制为对每个地址最多一个addrinfo结构,该结构的套接字地址可以作为连接的一个端点。
  • ai_flags字段是一个位掩码,可以进一步修改默认行为。可以把各种值用OR组合起来得到该掩码。下面是一些有用的值:
    • AI_ADDRCONFIG。如果在使用连接,就推荐这个标志。它要求只有当本地主机被配置为IPv4时,getaddrinfo返回IPv4地址。对IPv6也是类似。
    • AI_CANONNAME。ai_canonname字段默认为NULL。如果设置了该标志,就是告诉getaddrinfo将列表中第一个addrinfo结构的ai_canonname字段指向host的权威(官方)名字。
    • AI_NUMERICSERV。参数service默认可以是服务名或端口号。这个标志强制参数service为端口号。
    • AI_PASSIVE。getaddrinfo默认返回套接字地址,客户端可以在调用connect时用作主动套接字。这个标志告诉该函数,返回的套接字地址可能被服务器用作监听套接字。在这种情况下,参数host应该为NULL。得到的套接字地址字段会是通配符地址(wildcard address),告诉内核这个服务器会接受发送到该主机所有IP地址的请求。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // getaddrinfo使用的addrinfo结构
      struct addrinfo{
      int ai_flags; // Hints argument flags
      int ai_family; // First arg to socket function
      int ai_socktype; // Second arg to socket function
      int ai_protocol; // Third arg to socket function
      char *ai_canonname; // Canonical hostname
      size_t ai_addrlen; // Size of ai_addr struct
      struct sockaddr *ai_addr; // Ptr to socket address structure
      struct addrinfo *ai_next; // Ptr to next item in linked list
      }

当getaddrinfo创建输出列表中的addrinfo结构时,会填写每个字段,除了ai_flags。ai_addr字段指向一个套接字地址结构,ai_addrlen字段给出这个套接字地址结构的大小,而ai_next字段指向列表中下一个addrinfo结构。其他字段描述这个套接字地址的各种属性。

getaddrinfo一个很好的方面是addrinfo结构中的字段是不透明的,即它们可以直接传递给套接字接口中的函数,应用程序代码无需再做任何处理。这个强大的属性使得我们编写的客户端和服务器能够独立于某个特殊版本的IP协议。

getnameinfo()

getnameinfo函数和getaddrinfo是相反的,将一个套接字地址结构转换成相应的主机和服务名字符串。它是已弃用的gethostbyaddr和getservbyport函数的新的替代品,和以前的那些函数不同,它是可重入和与协议无关的。

1
2
#include <netdb.h>  // 头文件
int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *service, size_t servlen, int flags); // 返回: 如果成功则为0,如果错误则为非零的错误代码。

参数sa指向大小为salen字节的套接字地址结构,host指向大小为hostlen字节的缓冲区,service指向大小为servlen字节的缓冲区。getnameinfo函数将套接字地址结构sa转换成对应的主机和服务名字符串,并将它们复制到host和service缓冲区。如果getnameinfo返回非零的错误代码,应用程序可以调用gai_strerror把它转化成字符串。

如果不想要主机名,可以把host设置为NULL,hostlen设置为0,对服务字段来说也是一样。不过,两者必须设置其中之一。

参数flags是一个位掩码,能够修改默认的行为。可以把各种值用OR组合起来得到该掩码。下面是两个有用的值。

  • NI_NUMERICHOST。getnameinfo默认试图返回host中的域名。设置该标志会使该函数返回一个数字地址字符串。
  • NI_NUMERICSERV。getnameinfo默认会检查/etc/services,如果可能,会返回服务名而不是端口号。设置该标志会使该函数跳过查找,简单地返回端口号。

getaddrinfo()和getnameinfo()示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <netdb.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
char host[30]; // host用来存储点分十进制地址
struct addrinfo hints, *rst, *p; // hints用来告诉getaddrinfo我们所选择的参数,rst将会在调用getaddrinfo成功后指向一个struct addrinfo的链表, p将用于迭代rst指向的链表
int gstatus; // gstatus用来存储getaddrinfo的结果

// 用0填充hints
memset((void*)&hints, 0, sizeof(struct addrinfo));
// 配置socktype为SOCK_STREAM
hints.ai_socktype = SOCK_STREAM;

// 如果调用成功,输出点分十进制地址,否则输出错误原因
if((gstatus = getaddrinfo("www.gray-ice.com", NULL, &hints, &rst)) == 0)
{
// 迭代addrinfo链表
for(p = rst; p != NULL; p = p->ai_next)
{
// 将struct sockaddr *类型的ai_addr指向的内容转换成字符串形式的点分十进制地址
if(getnameinfo(p->ai_addr, p->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0)
{
printf("getnameinfo failed.\n");
continue;
}

// 输出点分十进制地址
printf("%s\n", host);
}

// 释放addrinfo链表
freeaddrinfo(rst);
}
else{
// 输出错误原因
printf("%s\n", gai_strerror(gstatus));
}

return 0;
}

评论



愿火焰指引你