UNIX编程随笔(三十四)socket地址
2010年04月22日
不同的处理器架构对于大于一个字节的数据类型的各个字节如何存放存在差别,分为
[b]大端[/b]:高位字节到低位字节存放地址为从低到高,也就是对于一个4字节的整数比如0x01020304,在内存的起始地址为0x00000000,则0x00000000存放0x01,0x00000001存放0x02,0x00000002存放0x03,0x00000003存放0x04。
[b]小端[/b]:高位字节到低位字节存放地址为从高到低,也就是对于一个4字节的整数比如0x01020304,在内存的起始地址为0x00000000,则0x00000000存放0x04,0x00000001存放0x03,0x00000002存放0x02,0x00000003存放0x01。
TCP/IP协议使用大端字节序,因此在小端系统(Linux,Windows,FreeBSD)上必须进行转换。
分别调用以下函数完成本机字节序到网络字节序的转换:
uint32_t htonl(uint32_t hostint32); //将本地字节序的32位整数转换成网络字节序的32位整数
uint16_t htons(uint16_t hostint16); //将本地字节序的16位整数转换成网络字节序的16位整数
uint32_t ntohl(uint32_t netint32); //将网络字节序的32位整数转换成本地字节序的32位整数
uint16_t ntohs(uint16_t netint16); //将网络字节序的16位整数转换成本地字节序的16位整数
对于不同的协议族有不同的地址,posix定义了通用的地址数据结构:
struct sockaddr
{
sa_family_t sa_family; //协议族
char sa_data[];
//实现可以加上自己的字段以及指定sa_data的长度
};
IPv4的地址(AF_INET)结构定义如下:
struct in_addr
{
in_addr_t s_addr; //IPV4地址,实际上为uint32_t
};
struct sockaddr_in
{
sa_family_t sin_family; //协议族,AF_INET
in_port_t sin_port; //端口号,uint16_t
struct in_addr sin_addr; //IPV4地址
//实现可以添加自己的字段
};
IPV6的地址(AF_INET6)结构定义如下:
struct in6_addr {
uint8_t s6_addr[16]; //IPv6地址,128字节
};
struct sockaddr_in6 {
sa_family_t sin6_family; //协议族,AF_INET6
in_port_t sin6_port; //端口号,uint16_t
uint32_t sin6_flowinfo; //流信息
struct in6_addr sin6_addr; //IPv6地址
uint32_t sin6_scope_id;
//实现可以添加自己的字段
};
调用const char *inet_ntop(int domain, const void *restrict addr,char *restrict str, socklen_t size);将网络字节序的二进制地址转换成可读的字符串,只支持AF_INET以及AF_INET6协议。str用来接收返回的字符串,对于AF_INET可以使用INET_ADDRSTRLEN分配缓冲区,而对于AF_INET6来说可以使用INET6_ADDRSTRLEN。
调用int inet_pton(int domain, const char *restrict str, void *restrict addr);将可读的字符串地址转换成网络字节序的二进制地址,只支持AF_INET以及AF_INET6协议。对于AF_INET,addr为32位,而对于AF_INET6,addr为128位。
调用以下函数取得本机知道的所有主机信息:
struct hostent *gethostent(void);
void sethostent(int stayopen);
void endhostent(void);
依次调用gethostent可以取得本机知道的所有主机信息,返回的struct hostent结构定义如下:
struct hostent {
char *h_name; //主机名
char **h_aliases; //主机别名列表
int h_addrtype; //地址类型
int h_length; //地址长度
char **h_addr_list; //地址列表
//实现可以添加自己定义的字段
};
调用以下函数可以得到网络名以及网络号:
struct netent *getnetbyaddr(uint32_t net, int type);
struct netent *getnetbyname(const char *name);
struct netent *getnetent(void);
void setnetent(int stayopen);
void endnetent(void);
返回的struct netent结构定义如下:
struct netent {
char *n_name; //网络名
char **n_aliases; //网络别名列表
int n_addrtype; //地址类型(AF_INET,AF_INET6,AF_UNIX,AF_UNSPEC)
uint32_t n_net; //网络号
//实现可以添加自己定义的字段
};
调用以下函数得到协议名以及协议号:
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);
void setprotoent(int stayopen);
void endprotoent(void);
返回的struct protoent结构定义如下:
struct protoent {
char *p_name; //协议名
char **p_aliases; //协议别名列表
int p_proto; //协议号
//实现可以添加自己定义的字段
};
调用以下函数得到网络服务(services)信息:
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
struct servent *getservent(void);
void setservent(int stayopen);
void endservent(void);
返回的struct servent结构定义如下:
struct servent {
char *s_name; //服务名
char **s_aliases; //服务别名列表
int s_port; //服务对应的端口号
char *s_proto; //服务对应的协议名
//实现可以添加自己定义的字段
};
调用以下函数根据主机名以及服务名得到对应的网络地址:
int getaddrinfo(const char *restrict host,
const char *restrict service,
const struct addrinfo *restrict hint,
struct addrinfo **restrict res);
void freeaddrinfo(struct addrinfo *ai);
可以传入主机名或者服务名,也可以两个都传入(不传的使用null即可),host可以传入主机名也可以传入以小数点.分隔的ip地址。service可以传入服务名也可以传入十进制端口号数串。res返回struct addrinfo结构列表,struct addrinfo结构定义如下:
struct addrinfo {
int ai_flags; //标志位
int ai_family; //协议族
int ai_socktype; //socket类型
int ai_protocol; //协议号
socklen_t ai_addrlen; //地址长度
struct sockaddr *ai_addr; //地址
char *ai_canonname; //正式主机名
struct addrinfo *ai_next; //下一个struct addrinfo指针
//实现可以添加自己定义的字段
};
参数hint用来对返回的地址进行过滤,仅可以设置ai_family, ai_flags, ai_protocol以及 ai_socktype字段的值进行过滤。ai_flags取值如下:
AI_ADDRCONFIG 查询所有地址类型(IPV4,IPV6)
AI_ALL 与AI_V4MAPPED搭配使用,查询IPV4以及IPV6地址,IPV4映射成IPV6返回
AI_CANONNAME ai_canonname返回正式主机名(非别名)
AI_NUMERICHOST 如果hint指定此标志则host只能传入小数点分隔的IP地址,不用进行主机名到IP的转换
AI_NUMERICSERV 如果hint指定此标志则host只能传入十进制端口号数串,不用进行服务名到端口号的转换
AI_PASSIVE 返回的ai_addr设置为INADDR_ANY(IPV4)或者IN6ADDR_ANY_INIT(IPV6),如果没有指定则为回路(loopback)地址。
AI_V4MAPPED 如果ai_family设置为AF_INET6,并且没有找到IPV6的地址,则就IPV4的地址映射成IPV6地址返回,如果有IPV6地址则只返回IPV6地址
如果getaddrinfo[b]失败则返回错误号(函数返回值[/b]),必须调用const char *gai_strerror(int error);取得错误信息。
反之可以调用getnameinfo根据网络地址得到对应的主机名以及服务名:
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, size_t hostlen, char *serv, size_t servlen,
int flags);
flags取值如下:
NI_DGRAM 返回UDP服务
NI_NAMEREQD 找不到主机名则返回错误
NI_NOFQDN 对于本地主机只返回FQDN中的主机名部分(FQDN带有域名)
NI_NUMERICHOST 返回小数点分隔的IP地址
NI_NUMERICSERV 返回十进制的数串服务端口
调用int bind(int sockfd, const struct sockaddr *addr, socklen_t len);绑定一个socket到一个网络地址上,addr指定的网络地址必须为本机上的网络地址,而且网络地址对应的协议必须跟创建socket使用的协议匹配,如果地址中的端口号小于1024则只有拥有root权限的进程才能绑定成功,另外一般来说一个地址只能绑定到一个socket上(除非有的协议支持多重绑定)。[b]调用listen以及connet函数之前如果对socket没有调用bind绑定网络地址,则listen以及connect会自动绑定一个随机的端口给socket[/b]。bind函数一般在服务器端调用,绑定socket到一个众所周知的端口供客户端连接,而客户端一般不需要调用bind函数,由connet函数自动绑定一个即可。
调用int getsockname(int sockfd, struct sockaddr*restrict addr, socklen_t *restrict alenp);得到socket本地端对应的地址信息。
调用int getpeername(int sockfd, struct sockaddr*restrict addr, socklen_t *restrict alenp);得到socket连接的对端socket的地址信息。
2010年04月22日
不同的处理器架构对于大于一个字节的数据类型的各个字节如何存放存在差别,分为
[b]大端[/b]:高位字节到低位字节存放地址为从低到高,也就是对于一个4字节的整数比如0x01020304,在内存的起始地址为0x00000000,则0x00000000存放0x01,0x00000001存放0x02,0x00000002存放0x03,0x00000003存放0x04。
[b]小端[/b]:高位字节到低位字节存放地址为从高到低,也就是对于一个4字节的整数比如0x01020304,在内存的起始地址为0x00000000,则0x00000000存放0x04,0x00000001存放0x03,0x00000002存放0x02,0x00000003存放0x01。
TCP/IP协议使用大端字节序,因此在小端系统(Linux,Windows,FreeBSD)上必须进行转换。
分别调用以下函数完成本机字节序到网络字节序的转换:
uint32_t htonl(uint32_t hostint32); //将本地字节序的32位整数转换成网络字节序的32位整数
uint16_t htons(uint16_t hostint16); //将本地字节序的16位整数转换成网络字节序的16位整数
uint32_t ntohl(uint32_t netint32); //将网络字节序的32位整数转换成本地字节序的32位整数
uint16_t ntohs(uint16_t netint16); //将网络字节序的16位整数转换成本地字节序的16位整数
对于不同的协议族有不同的地址,posix定义了通用的地址数据结构:
struct sockaddr
{
sa_family_t sa_family; //协议族
char sa_data[];
//实现可以加上自己的字段以及指定sa_data的长度
};
IPv4的地址(AF_INET)结构定义如下:
struct in_addr
{
in_addr_t s_addr; //IPV4地址,实际上为uint32_t
};
struct sockaddr_in
{
sa_family_t sin_family; //协议族,AF_INET
in_port_t sin_port; //端口号,uint16_t
struct in_addr sin_addr; //IPV4地址
//实现可以添加自己的字段
};
IPV6的地址(AF_INET6)结构定义如下:
struct in6_addr {
uint8_t s6_addr[16]; //IPv6地址,128字节
};
struct sockaddr_in6 {
sa_family_t sin6_family; //协议族,AF_INET6
in_port_t sin6_port; //端口号,uint16_t
uint32_t sin6_flowinfo; //流信息
struct in6_addr sin6_addr; //IPv6地址
uint32_t sin6_scope_id;
//实现可以添加自己的字段
};
调用const char *inet_ntop(int domain, const void *restrict addr,char *restrict str, socklen_t size);将网络字节序的二进制地址转换成可读的字符串,只支持AF_INET以及AF_INET6协议。str用来接收返回的字符串,对于AF_INET可以使用INET_ADDRSTRLEN分配缓冲区,而对于AF_INET6来说可以使用INET6_ADDRSTRLEN。
调用int inet_pton(int domain, const char *restrict str, void *restrict addr);将可读的字符串地址转换成网络字节序的二进制地址,只支持AF_INET以及AF_INET6协议。对于AF_INET,addr为32位,而对于AF_INET6,addr为128位。
调用以下函数取得本机知道的所有主机信息:
struct hostent *gethostent(void);
void sethostent(int stayopen);
void endhostent(void);
依次调用gethostent可以取得本机知道的所有主机信息,返回的struct hostent结构定义如下:
struct hostent {
char *h_name; //主机名
char **h_aliases; //主机别名列表
int h_addrtype; //地址类型
int h_length; //地址长度
char **h_addr_list; //地址列表
//实现可以添加自己定义的字段
};
调用以下函数可以得到网络名以及网络号:
struct netent *getnetbyaddr(uint32_t net, int type);
struct netent *getnetbyname(const char *name);
struct netent *getnetent(void);
void setnetent(int stayopen);
void endnetent(void);
返回的struct netent结构定义如下:
struct netent {
char *n_name; //网络名
char **n_aliases; //网络别名列表
int n_addrtype; //地址类型(AF_INET,AF_INET6,AF_UNIX,AF_UNSPEC)
uint32_t n_net; //网络号
//实现可以添加自己定义的字段
};
调用以下函数得到协议名以及协议号:
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);
void setprotoent(int stayopen);
void endprotoent(void);
返回的struct protoent结构定义如下:
struct protoent {
char *p_name; //协议名
char **p_aliases; //协议别名列表
int p_proto; //协议号
//实现可以添加自己定义的字段
};
调用以下函数得到网络服务(services)信息:
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
struct servent *getservent(void);
void setservent(int stayopen);
void endservent(void);
返回的struct servent结构定义如下:
struct servent {
char *s_name; //服务名
char **s_aliases; //服务别名列表
int s_port; //服务对应的端口号
char *s_proto; //服务对应的协议名
//实现可以添加自己定义的字段
};
调用以下函数根据主机名以及服务名得到对应的网络地址:
int getaddrinfo(const char *restrict host,
const char *restrict service,
const struct addrinfo *restrict hint,
struct addrinfo **restrict res);
void freeaddrinfo(struct addrinfo *ai);
可以传入主机名或者服务名,也可以两个都传入(不传的使用null即可),host可以传入主机名也可以传入以小数点.分隔的ip地址。service可以传入服务名也可以传入十进制端口号数串。res返回struct addrinfo结构列表,struct addrinfo结构定义如下:
struct addrinfo {
int ai_flags; //标志位
int ai_family; //协议族
int ai_socktype; //socket类型
int ai_protocol; //协议号
socklen_t ai_addrlen; //地址长度
struct sockaddr *ai_addr; //地址
char *ai_canonname; //正式主机名
struct addrinfo *ai_next; //下一个struct addrinfo指针
//实现可以添加自己定义的字段
};
参数hint用来对返回的地址进行过滤,仅可以设置ai_family, ai_flags, ai_protocol以及 ai_socktype字段的值进行过滤。ai_flags取值如下:
AI_ADDRCONFIG 查询所有地址类型(IPV4,IPV6)
AI_ALL 与AI_V4MAPPED搭配使用,查询IPV4以及IPV6地址,IPV4映射成IPV6返回
AI_CANONNAME ai_canonname返回正式主机名(非别名)
AI_NUMERICHOST 如果hint指定此标志则host只能传入小数点分隔的IP地址,不用进行主机名到IP的转换
AI_NUMERICSERV 如果hint指定此标志则host只能传入十进制端口号数串,不用进行服务名到端口号的转换
AI_PASSIVE 返回的ai_addr设置为INADDR_ANY(IPV4)或者IN6ADDR_ANY_INIT(IPV6),如果没有指定则为回路(loopback)地址。
AI_V4MAPPED 如果ai_family设置为AF_INET6,并且没有找到IPV6的地址,则就IPV4的地址映射成IPV6地址返回,如果有IPV6地址则只返回IPV6地址
如果getaddrinfo[b]失败则返回错误号(函数返回值[/b]),必须调用const char *gai_strerror(int error);取得错误信息。
反之可以调用getnameinfo根据网络地址得到对应的主机名以及服务名:
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, size_t hostlen, char *serv, size_t servlen,
int flags);
flags取值如下:
NI_DGRAM 返回UDP服务
NI_NAMEREQD 找不到主机名则返回错误
NI_NOFQDN 对于本地主机只返回FQDN中的主机名部分(FQDN带有域名)
NI_NUMERICHOST 返回小数点分隔的IP地址
NI_NUMERICSERV 返回十进制的数串服务端口
调用int bind(int sockfd, const struct sockaddr *addr, socklen_t len);绑定一个socket到一个网络地址上,addr指定的网络地址必须为本机上的网络地址,而且网络地址对应的协议必须跟创建socket使用的协议匹配,如果地址中的端口号小于1024则只有拥有root权限的进程才能绑定成功,另外一般来说一个地址只能绑定到一个socket上(除非有的协议支持多重绑定)。[b]调用listen以及connet函数之前如果对socket没有调用bind绑定网络地址,则listen以及connect会自动绑定一个随机的端口给socket[/b]。bind函数一般在服务器端调用,绑定socket到一个众所周知的端口供客户端连接,而客户端一般不需要调用bind函数,由connet函数自动绑定一个即可。
调用int getsockname(int sockfd, struct sockaddr*restrict addr, socklen_t *restrict alenp);得到socket本地端对应的地址信息。
调用int getpeername(int sockfd, struct sockaddr*restrict addr, socklen_t *restrict alenp);得到socket连接的对端socket的地址信息。