套接字编程基础
1. 套接字结构
1. IPV4
truct in_addr{
in_addr_t s_addr;
};
struct sockaddr_in{
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
sin_len
是为了增加对OSI协议的支持而随4.3 BSD-Reno 添加的。在此之前,第一个成员是sin_family
,它是一个无符号短整数。- 即使有长度字段,我们也
无需设置和检查它
。 - POSIX 标准只需要这个结构中的三个字段:sin_family、sin_addr和sin_port。对于符合POSIX的实现来说,定义额外的结构字段是可以接受的。
1.2 IPV6
IPV6的套接字地址结构定义在头文件<netinet6/in6.h>中,具体如下:
/*
* IPv6 address
*/
typedef struct in6_addr {
union {
__uint8_t __u6_addr8[16];
__uint16_t __u6_addr16[8];
__uint32_t __u6_addr32[4];
} __u6_addr; /* 128-bit IP6 address */
} in6_addr_t;
/*
* Socket address for IPv6
*/
#define SIN6_LEN /* required for compile-time tests */
struct sockaddr_in6 {
__uint8_t sin6_len; /* length of this struct(sa_family_t) */
sa_family_t sin6_family; /* AF_INET6 (sa_family_t) */
in_port_t sin6_port; /* Transport layer port # (in_port_t) */
__uint32_t sin6_flowinfo; /* IP6 flow information */
struct in6_addr sin6_addr; /* IP6 address */
__uint32_t sin6_scope_id; /* scope zone index */
};
地址族sin_family,IPV4为AF_INET,IPV6为AF_INET6
2. 值、结果参数 (还是有点不理解)
从进程到内核
从进程到内核传递套接字地址结构的函数有3个:
bind()
connect()
具体为connect(sockfd, (struct sockaddr*) &serv, sizeof(serv))
sendto()
即,把套接字地址结构的指针和所指内容的大小都传递给了内核(kernel),于是*kernel知道它需要从进程中复制多少数据进来*。
从内核到进程
从内核到进程传递套接字地址结构的函数有4个:
accept()
recvfrom()
getsockname()
getpeername()
具体为getpeername(unixfd, (struct sockaddr*) &cli, &len)
为什么在这里不传递地址结构的大小而是传其大小的指针呢?
原因在于:
当函数被调用时,结构大小是一个value,它告诉内核该结构的大小,这样内核在写该结构时不至于越界;
当函数返回时,结构大小是一个result,它通过该指针指向的整数*告诉进程内核在该结构中究竟存储了多少信息*。
3. 字节排序函数
考虑一个16位整数,它由2个字节组成。内存中储存的这两个字节有两种方法:一种是将低序字节储存在起始地址,这称为小端字节序;另一种方法是将高序字节储存在起始地址,这称作大端字节序。如下图:
遗憾的是这两种字节序之间没有标准可循,我们称之为主机字节序。而网络协议必须指定一个网络字节序。故我们需要在这两种字节序转换。
#include <netinet/in.h>
/*
* h: host n: network
* s: short(16-bit) l: long(32-bit)
*/
// 返回:网络字节序
uint16_t htons (uint16_t host16bitvalue);
uint32_t htonl (uint32_t host32bitvalue);
// 返回主机字节序
uint16_t ntohs (uint16_t net16bitvalue);
uint32_t ntohl (uint32_t net32bitvalue);
4. 地址转换函数
地址转换函数:将ASCII字符串(IPV4点分十进制)和网络字节序的二进制序互换。
4.1 IPV4
IPV4主要有下列3个:
#include <arpa/inet.h>
/*
* a: ASCII n: network byte order
*/
/*ASCII-->网络二进制
*将strptr所指的字符串转换成32位网络字节序的二进制地址,通过addrptr存储
*返回:1成功 0失败
*/
int inet_aton (const char* strptr, struct in_addr* addrptr);
/*ASCII-->网络二进制【已经被废弃】
*将strptr所指的字符串转换成32位网络字节序的二进制地址
*返回:32位网络字节序的二进制地址 失败返回INADDR_NONE(全1,意味着本函数不能正常处理广播地址255.255.255.255)
*/
in_addr_t inet_addr (const char* strptr);
/*网络二进制-->ASCII
*将32位网络字节序的二进制地址inaddr转换成ASCII字符串
*返回:指向点分十进制字符串的指针
*/
char* inet_ntoa (struct in_addr inaddr);
4.2 IPV6 & IPV4
下面介绍的这两组函数,IPV6和IPV4都适用。
#include <arpa/inet.h>
/*
* p: presentation(表达,ASCII字符串) n: numeric(数值,二进制)
*/
/*ASCII-->网络二进制
*将strptr所指的字符串转换成32位网络字节序的二进制地址,通过addrptr存储,family指定协议族(AF_INET, AF_INET6)
*返回:1成功 0非有效表达格式 -1出错
*/
int inet_pton (int family, const char* strptr. void* addrptr);
/*网络二进制-->ASCII
*将addrptr所指的网络字节序的二进制地址转换成ASCII字符串,通过strptr存储,family指定协议族,len指定目标存储单元的大小
*<netinet/in.h>中定义了len的大小,IPV4 INET_ADDRSTRLEN 16, IPV6 INET6_ADDRSTRLEN 46
*返回:指向ASCII字符串的指针
*/
const char* inet_ntop (int family, const void* addrptr, char* strptr, size_t len);
4.3 协议无关的地址转换函数sock_ntop
上述的函数inet_ntop()
需要调用前根据某个协议创建关于它的struct sockaddr_in addr
或struct sockaddr_in6 addr6
.这样在函数调用时就会编出“协议相关性”程序。
如何才能编出“协议无关性”的地址转换程序?
下面给出1组协议无关性的地址转换函数sock_ntop()
.这个函数的参数是指向某个套接字地址结构的指针,它通过查看该结构的内部,确定其协议族,如何调用适当的函数来返回该地址的字符串表达。
下面给出的了该函数仅支持AD_INET的情况。
#include "unp.h"
char * sock_ntop (const struct sockaddr* sa, socklen_t salen)
{
char portstr[8];
static char str[128]; /* Unix domain is largest */
switch (sa->sa_family) {
case AF_INET: {
struct sockaddr_in *sin = (struct sockaddr_in *) sa;
if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
return(NULL);
if (ntohs(sin->sin_port) != 0) {
snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
strcat(str, portstr);
}
return(str);
}