网络Socket编程的主要技能,可能通Echo程序可以不断演进来描述网络开发的各个细节。
Echo程序是指在一个C/S模型中,当客户端通过IP网络向服务器服务器发送一个字符串,服务器再原样把字符串发送出来。此谓Echo.
在应用一级进行编程主要采用Socket库,而且Socket库的基本技能是处理Socket地址。
一.Socket地址的表示
----------------------------------------------------------------------------
首先当年BSD Socket设计时,当时已经有多种网络并存,而且BSD Socket 也希望未来统一新的网络。
因此它设计了一个通用的网络地址结构struct sockaddr来代表所有网络的地址。
因此所有的SOCKET的库的接口的地址都采用struct sockaddr 地址的传输。
以下是sockaddr地址结构,在Linux下它定义在
/usr/include/linux/socket.h
struct sockaddr { unsigned short sa_family; /* 地址族, AF_xxx */ char sa_data[14]; /* 14 字节的协议地址 */ }; |
sa_family 的地址类型,参见有 AF_INET (ipv4) ,AF_INET6 (ipv6)
但是socket还是要在具体网络上运行。因为在处理具体网络地址时,还是转换成相应网络的地址结构进行处理。 ipv4的地址结构是
struct sockaddr_in,而ipv6的地址结构是 struct sockaddr_in6.这里的in是inetnet的缩写
以ipv4为例,它的在Linux下定义在
/usr/include/netinet/in.h
数据结构定义如下,其中最后的sin_zero是为了使数据结构的宽度正好与struct sockaddr 一致,特意补上去的。
218 /* Structure describing an Internet socket address. */ 219 struct sockaddr_in 220 { 221 __SOCKADDR_COMMON (sin_); 222 in_port_t sin_port; /* Port number. */ 223 struct in_addr sin_addr; /* Internet address. */ 224 225 /* Pad to size of `struct sockaddr'. */ 226 unsigned char sin_zero[sizeof (struct sockaddr) - 227 __SOCKADDR_COMMON_SIZE - 228 sizeof (in_port_t) - 229 sizeof (struct in_addr)]; 230 };
|
这是sin_port是网络序的端口号.而sin_addr的数据结据 struct in_addr定义如下。它实际上一个以big-endian的整数。
/* Type to represent a port. */ typedef uint16_t in_port_t;
struct in_addr { unsigned long s_addr; }; |
因此一个ipv4的地址,struct sockaddr_in正常工作,必须设三个成员值.sa_family,sin_port和sin_port.s_addr. 但所有socket接口成员函数都采用struct sockaddr * 定义,因此每一次操作都做指针转换。这是让初学者容易感到不解的地方。
ipv6的地址定义类似,只不过成员名发生变化
/* Ditto, for IPv6. */ 233 struct sockaddr_in6 234 { 235 __SOCKADDR_COMMON (sin6_); 236 in_port_t sin6_port; /* Transport layer port # */ 237 uint32_t sin6_flowinfo; /* IPv6 flow information */ 238 struct in6_addr sin6_addr; /* IPv6 address */ 239 uint32_t sin6_scope_id; /* IPv6 scope-id */ 240 };
|
二.socket地址的转换
---------------------------------------------------------------------
对于ip地址,人类更为习惯的是点分法的形式 ,即"192.168.1.100"这种表示法。但是编程中需要变换成 struct sockaddr 的形式来。因此需要一些转换函数。
1.把点分法地址转换成in_addr地址
int inet_aton(const char *cp, struct in_addr *inp);
cp是点分法地址字符串,inp是转换成的地址 ,成功返回0,失败返回-1
2.把点分法地址转换成in_addr地址2
in_addr_t inet_addr(const char *cp);
功能同inet_aton,但是把转换后的地址直接作为返回值返回.如果失败,返回 INADDR_NONE (即-1).
3.把in_addr地址转换成点分法地址
char *inet_ntoa(struct in_addr in);
把in转换成点分地址
4.支持ipv6的地址转换
int inet_pton(int af, const char *src, void *dst);
将协议点分地址转换成相应的地址结构,af是地址族的类型.AF_INET/AF_INET6
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t cnt);
将地址结构转换成点分地址
5. 网络序/本机序互相转换
网络序是big-endian数字表示,而本机序取决于CPU自身定义
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
ipv4的socket_in地址标准设置
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
//表示 127.0.0.1:2000端口
addr.sin_family = AF_INET ; /* 表示IPV4 */ addr.sin_port = htons(2000); /* 发送前转为网络序 */ addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
三.域名与ip地址的转换
----------------------------------------------------
很多用户习惯于采用容易记忆的域名来访问远端机器.但Socket只采用数字形式的IP地址
来访问 .因此必须一个函数来完成这一转换,以便程序界面更加友好. 如ie的地址栏,就是
IE自行在内部把域名作了转换,后然后使用IP地址去联接.
不同的操作系统采用不同函数来作域名转换.Linux/Windows采用gethostbyname()作域名转换.
gethostbyname()首先会去检查 /etc/hosts这个文件,如果对应的ip与域名在这个文件里有记录。如果没有记录,则会发送DNS解析包给DNS服务器,请求解析。如果成功,它将把返回结果写在struct hostent结构当中。
struct hostent *gethostbyname(const char *name);
其中hostent的定义如下
struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses */ } #define h_addr h_addr_list[0] /* for backward compatibility */
|
h_name是主机名(域名),h_addr_type地址类型,h_addr_list是域名对应的一系列的地址的列表,注意里面采用整数数组形式,4byte为单位,它的总长度是h_lenght.
以下是通用的解析代码,可以识别出ip和域名
/* 域名处理 */ int GetIpByHost(char * host_name,char * ip,int ip_len) { struct in_addr addr; struct hostent * ent;
/* 把host_name当成ip地址试着转换一次 */ if(inet_aton(host_name,&addr)) { /* 说明点分法表示的IP地址 */ snprintf(ip,ip_len,"%s",inet_ntoa(addr)); return 0; }
//否则将其当成域名来处理
ent = gethostbyname(host_name); if(ent == NULL) { fprintf(stderr,"error host name %s/n",host_name); return -1; }
// printf("h_name %s/n",ent->h_name);
if(ent->h_length<=0) { return -2; }
{ struct sockaddr_in dest; //把h_addr_list 当成socket in地址
memcpy(&(dest.sin_addr), ent->h_addr, ent->h_length); dest.sin_family = ent->h_addrtype; snprintf(ip,ip_len,"%s", inet_ntoa(dest.sin_addr));
}
return 0; }
#define RESOLVE_HOST(s) {/ char buf[32];/ if(GetIpByHost(s,buf,sizeof(buf))==0) / printf("host %s addr is %s/n",s,buf);}
void test3() { RESOLVE_HOST("192.168.0000.1"); RESOLVE_HOST("www.icbc.com.cn"); RESOLVE_HOST("www.baidu.com"); RESOLVE_HOST("ww22.asdfasfasdfasdf.com"); } |