1. Internet domain socket
Internet domain 流socket 是基于 TCP 之上的,它们提供了可靠的双向字节流通信信道。
底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再进行写入。字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,这为我们向IO设别写入或读取字符串提供了一点点方便。
Internet domain 数据报 socket 是基于 UDP 之上的, UDP socket 与之在 Unix domain 中的对应实体类似,但需要注意下列差异:
- Unix domain 数据报 socket 是可靠的,但 UDP socket 则是不可靠的--数据报可能会丢失,重复,或者到达的顺序与他们被发送的顺序不同。
- 在一个 Unix domain 数据报 socket 上发送数据会在接收 socket 的数据队列为满时阻塞。与之不同的是,使用 UDP 时如果进入的数据报会使接收者的队列溢出,那么这些数据报会被直接丢弃。
2. 网络字节序
IP 地址和端口号是整数值,在将这些值在网络中传递时碰到的一个问题是不同的硬件结构会以不通的顺序来存储一个多字节整数的字节,如下图可以看出,存储整数时,先存储(最小内存地址处)最高有效位的被称为大端,那些先存储最低有效位的被称为小端。小端结构中最值得关注的是 x86,其他大多数架构都是大端的,有些硬件设备可以在两种格式之间来回切换。在特性主机上使用的字节序被称为主机字节序。
由于端口号和 IP 地址必须在网络中的所有主机之间传递并且需要它们被理解,因此必须要使用一个标准的字节序,这种字节序被称为网络字节序,是大端的。有时候可能会直接使用 IP 地址和端口号的整数常量形式,比如可能会选择将端口号硬编码进程序中,或者将端口号作为一个命令行参数传递给程序,或者在指定一个 IPV4地址时,使用类似 IMADDR_ANY 和 INADDR_LOOPBACK 之类的常量。这些值在 C 语言中是按照主机的规则来表示的。
htons(), htonl(), ntohs()以及 ntohl()函数被定义用来在主机和网络字节序之间转换整数。
#include <arpa/inet.h>
uint16_t htons(uint16_t host_uint16);
uint32_t htonl(uint32_t host_uint32);
uint16_t ntohs(uint16_t net_uint16);
uint32_t ntohs(uint32_t net_uint32);
3. Internet socket 地址
3.1 IPv4 SOCKET地址: struct sockaddr_in
一个ipv4 socket 地址会被存放在一个sockaddr_in 结构中,该结构在<netinet/in.h>中进行定义,具体如下:
struct in_addr {
in_addr_t s_addr; // ipv4 4 byte address
}
struct sockaddr_in {
sa_family_t sin_family; // Address family AF_INET
in_port_t sin_port; // port number
struct in_addr sin_addr; // ipv4 address
unsigned char __pad[x];
}
3.2 inet_pton()和 inet_ntop()函数
inet_pton 和 inet_ntop 函数允许在 IPV4和 IPV6地址的二进制形式和点分十进制表示法或者十六进制字符串表示法之间相互转换。
#include <arpa/inet.h>
int inet_pton(int domain, const char *src_str, void *addrptr);
const char* inet_ntop(int domain, const void *addrptr, char *dst_str, size_t len);
这两个函数名称中, P 表示『展现, presentation』, N表示『网络,network』.展现形式是人类可以轻松理解的字符串。
inet_pton()函数将 src_str 中包含的字符串转换成网络字节序的二进制 IP 地址,domain 参数应该被指定为 AF_INET 或者 AF_INET6,转换得到的地址会被存放在 addrptr 指向的结构体中。它应该根据在 domain 参数中指定的值指向一个 in_addr 或者 in6_addr。
inet_ntop()函数执行逆向转换,同样 domain 也需要被指定为 AF_INET 或者 AF_INET6, addrptr 指向一个待转换的 in_addr 结构体变量,得到的是以 null 结尾的字符串,会被放置在 dst_str 指向的缓冲器。len 参数必须指定为缓冲器的大小。
3.3 getaddrinfo() 函数
给定一个主机名和服务名,getaddrinfo 函数返回一个 socket地址结构体列表,每个结构体都包含一个地址和端口号。
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);
node:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串); service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等; hints:可以是一个空指针,也可以是一个指向某个 addrinfo结构体的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说:如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。res:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。返回值:0——成功,非0——出错。
addrinfo 结构的形式如下:
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};
- ai_family会被设置成 SOCK_STREAM或者 SOCK_DGRAM,表示这个地址结构是用于 TCP 服务还是 UDP 服务。
- ai_protocal 字段会返回与地址族和 socket 类型匹配的协议值。(ai_family, ai_socktype 以及 ai_protocal 三个字段为调用 socket()创建该地址上的 socket 时所需的参数提供了取值)。
- ai_addrlen 字段给出了 ai_addr 指向的 socket 地址结构的大小(字节数),in_addr 字段指向 socket地址结构(IPv4是一个 in_addr 结构)。
以下是 man 命令中给出的详细 demo描述该函数:
//Server program
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#define BUF_SIZE 500
int
main(int argc, char *argv[])
{
struct addrinfo hints;
struct addrinfo *result, *rp;
int sfd, s;
struct sockaddr_storage peer_addr;
socklen_t peer_addr_len;
ssize_t nread;
char buf[BUF_SIZE];
if (argc != 2) {
fprintf(stderr, "Usage: %s port\n", argv[0]);
exit(EXIT_FAILURE);
}
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
hints.ai_protocol = 0; /* Any protocol */
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
s = getaddrinfo(NULL, argv[1], &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
exit(EXIT_FAILURE);
}
/* getaddrinfo() returns a list of address structures.
Try each address until we successfully bind(2).
If socket(2) (or bind(2)) fails, we (close the socket
and) try the next address.
*/
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype,
rp->ai_protocol);
if (sfd == -1)
continue;
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
break; /* Success */
close(sfd);
}
if (rp == NULL) { /* No address succeeded */
fprintf(stderr, "Could not bind\n");
exit(EXIT_FAILURE);
}
freeaddrinfo(result); /* No longer needed */
/* Read datagrams and echo them back to sender */
for (;;) {
peer_addr_len = sizeof(struct sockaddr_storage);
nread = recvfrom(sfd, buf, BUF_SIZE, 0,
(struct sockaddr *) &peer_addr, &peer_addr_len);
if (nread == -1)
continue; /* Ignore failed request */
char host[NI_MAXHOST], service[NI_MAXSERV];
s = getnameinfo((struct sockaddr *) &peer_addr,
peer_addr_len, host, NI_MAXHOST,
service, NI_MAXSERV, NI_NUMERICSERV);
if (s == 0)
printf("Received %ld bytes from %s:%s\n",
(long) nread, host, service);
else
fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));
if (sendto(sfd, buf, nread, 0,
(struct sockaddr *) &peer_addr,
peer_addr_len) != nread)
fprintf(stderr, "Error sending response\n");
}
}
?
|