文章目录
套接字
套接字结构可以在两个方向上传递:从进程到内核,从内核到进程;
套接字结构
大多数套接字都需要一个指向套接字地址结构的指针作为参数,每个协议族都定义它自己的套接字地址结构;
IPV4套接字结构
IPV4套接字结构通常成为"网际套接字地址结构",它以sockaddr_in命名,命名在**<netinet/in.h>**头文件中;
struct sockaddr_in {
sa_family_t sin_family; /* 地址族: AF_INET */
u_int16_t sin_port; /* 按网络字节次序的端口 */
struct in_addr sin_addr; /* internet地址 */
};
/* Internet地址. */
struct in_addr {
u_int32_t s_addr; /* 按网络字节次序的地址 */
};
IPV4地址和TCP或UDP端口在套接字地址结构中总是以字节序来存储
通用套接字结构
当作为一个参数传递进任何套接字函数时,套接字地址结构是以引用形式(指向结构的指针)来传递。然而以这样的地址作为函数参数之一的任何套接字函数必须处理来自所有支持协议族的套接字地址结构;
struct sockaddr {
sa_family_t sin_family; //地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
这些函数的任何调用都必须指向特定于协议的套接字地址结构的指针进行类型强制转换,编程某个通用套接字结构的指针;
IPV6套接字结构
struct sockaddr_in6 {
unsigned short intsin6_family; /* AF_INET6 */
__be16 sin6_port; /* Transport layer port # */
__be32 sin6_flowinfo; /* IPv6 flow information */
struct in6_addrsin6_addr; /* IPv6 address */
__u32 sin6_scope_id; /* scope id (new in RFC2553) */
};
新的通用套接字结构
IPV6套接字的API的一部分定义的新的通用套接字结构克服了现有的一些缺点,新的struct sockaddr_storage足以容纳系统所支持的任何套接字结构地址;
struct sockaddr_storage
{
sa_family_t ss_family; /* Address family */
__ss_aligntype __ss_align; /* Force desired alignment. */
char __ss_padding[_SS_PADSIZE];
};
- 如果系统支持的任何套接字有对齐的需要,那么sockadd_storage能满足最苛刻的对其要求;
- sockaddr_storage足够强大,能够容纳系统支持的任何套接字结构
套接字结构比较
参数
当往一个套接字结构函数传递一个套接字地址结构的时候,该结构总是以引用传递,也就是传递结构体指针;
从进程传递给内核套接字地址的函数有三个:bind、connect、sendto。这些函数的一个参数是指向某个套接字地址结构的指针,另一个参数是大小;
从内核传递套接字地址结构的函数有四个:accept、recvfrom、getsockname、getpeername。这四个函数的其中两个参数是指向某个套接字地址结构的指针和指向标识该结构大小的整数变量的指针;
字节序
考虑一个16位的整形,它由两个字节组成。内存中存储的方法一般有两种:大端序和小端序;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define CPU_VENDOR_OS "i686-pc-linux-gnu"
union {
short s;
char c[sizeof(short)];
}un;
int main(int argc,char* args[])
{
un.s = 0x0102;
printf("%s\n",CPU_VENDOR_OS);
if(sizeof(short) == 2)
{
if(un.c[0] == 1 && un.c[1] == 2)
{
printf("big-endian\n");
}
else if(un.c[0] == 2 && un.c[1] == 1)
{
printf("little-endian\n");
}
else
{
printf("Unknow\n");
}
}
else
{
printf("sizeof(short) = %d\n",sizeof(short));
}
exit(0);
}
从理论上来说,具体实现可以按主机字节序存储套接字地址结构中的各个字段,等到需要在这些字段和协议首部相应字段之间移动式,再在主机字节序在网络字节序之间进行转换;
- h代表host
- n代表network
- s代表short
- l代表long
字节序操作
将通过三个函数来实现32位的网络字节序与二进制值之间的转换网络地址;
以下两个函数把ip地址转化为用于网络传输的二进制数值:
inet_aton
int inet_aton(const char *string, struct in_addr*addr);
inet_aton() 转换网络主机地址ip(如192.168.1.10)为二进制数值,并存储在struct in_addr结构中,即第二个参数*inp,函数返回非0表示cp主机有地有效,返回0表示主机地址无效。(这个转换完后不能用于网络传输,还需要调用htons或htonl函数才能将主机字节顺序转化为网络字节顺序)
inet_addr
in_addr_t inet_addr(const char *cp);
inet_addr函数转换网络主机地址(如192.168.1.10)为网络字节序二进制值,如果参数char *cp无效,函数返回-1(INADDR_NONE),这个函数在处理地址为255.255.255.255时也返回-1,255.255.255.255是一个有效的地址,不过inet_addr无法处理;
inet_pton
将网络传输的二进制数值转化为成点分十进制的ip地址:
inet_pton、inet_ntop这两个函数是随IPv6出现的函数,对于IPv4地址和IPv6地址都适用,函数中p和n分别代表表达(presentation)和数值(numeric)。地址的表达格式通常是ASCII字符串,数值格式则是存放到套接字地址结构的二进制值。!
int inet_pton(int family, const char *strptr, void *addrptr); //将点分十进制的ip地址转化为用于网络传输的数值格式
返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
inet_ntop
const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len); //将数值格式转化为点分十进制的ip地址格式
返回值:若成功则为指向结构的指针,若出错则为NULL
(1)这两个函数的family参数既可以是AF_INET(ipv4)也可以是AF_INET6(ipv6)。如果,以不被支持的地址族作为family参数,这两个函数都返回一个错误,并将errno置为EAFNOSUPPORT.
(2)第一个函数尝试转换由strptr指针所指向的字符串,并通过addrptr指针存放二进制结果,若成功则返回值为1,否则如果所指定的family而言输入字符串不是有效的表达式格式,那么返回值为0.
(3)inet_ntop进行相反的转换,从数值格式(addrptr)转换到表达式(strptr)。inet_ntop函数的strptr参数不可以是一个空指针。调用者必须为目标存储单元分配内存并指定其大小,调用成功时,这个指针就是该函数的返回值。len参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区。如果len太小,不足以容纳表达式结果,那么返回一个空指针,并置为errno为ENOSPC。
#define INET_ADDRSTRLEN 16 /* for IPv4 dotted-decimal */
#define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */
sock_ntop
它要求调用者传递一个指向二进制地址的指针,而该地址通常包含在某一个套接字地址中,这就说,如果需要使用这个函数,必须编写以下代码:
struct sockaddr_in addr;
inet_ntop(AF_INET, &addr.sin_addr,str,sizeof(str));
读写函数
字节流套接字上的read和write函数所表现的行为不同于通常的文件I/O,字节流套接字上调用read或write输入或输出的字节数可能比请求的数少;