地址族与数据序列----------网络编程(linux----C)
1、分配给套接字的IP地址与端口号
(1)网络地址
IPv4:4字节地址族。
IPv6:16字节地址族。
IPv4标准的4字节IP地址分为网络地址和主机(指计算机)地址,分为A、B、C、D、E等类型。
(2)用于区分套接字的端口号
端口号就是在同一操作系统内为区分不同套接字而设置的,因此无法将1个端口号分配给不同套接字。端口号由16位构成,可分配的端口号范围为0-65535.
虽然端口号不能重复,但TCP套接字和UDP套接字不会共用端口号,所以允许重复。例如:如果某TCP套接字使用9190号端口,则其他TCP套接字就无法使用该端口号,但UDP套接字可以使用。
2、地址信息的表示
应用程序中使用的IP地址和端口号以结构体的形式给出了定义。
(1)表示IPv4地址的结构体
struct sockaddr_in { sa_family_t sin_family; //地址族(Address Family) uint16_t sin_port; //16位TCP/UDP端口号 struct sin_addr; //32位IP地址 char sin_zero[8]; //不使用 };
//结构体in_addr用来存放32位IP地址
struct in_addr
{
In_addr_t s_addr; //32位IPv4地址
};
sockaddr_in成员分析:
3、网络字节序与地址变换
(1)字节序与网络字节序
大端序:高位字节存放到低位地址。
小端序:高位字节存放到高位地址。
在0x20号开始的地址中保存4字节int类型数0x12345678。整数0x12345678中,0x12是最高位字节,0x78是最低位字节。
(2)字节序转换
转换字节序函数
- unsigned short htons(unsigned short);
- unsigned short ntohs(unsigned short);
- unsigned long htonl(unsigned long);
- unsigned long ntohl(unsigned long);
htons中的h代表主机(host)字节序。
htons中的n代表网络(network)字节序。
s表示short,l指的是long。htons是h、to、n、s的组合,解释为“把short型数据从主机字节序转化为网络字节序”。
ntohs解释为“把short型数据从网络字节序转化为主机字节序”。
实例1、
endian_conv.c代码:
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{
unsigned short host_port=0x1234;
unsigned short net_port;
unsigned long host_addr=0x12345678;
unsigned long net_addr;
net_port=htons(host_port);
net_addr=htonl(host_addr);
printf("Host ordered port:%#x \n",host_port);
printf("Network ordered port:%#x \n",net_port);
printf("Host ordered address:%#lx \n",host_addr);
printf("Network ordered address:%#lx \n",net_addr);
return 0;
}
编译:gcc endian_conv.c -o conv
执行:./conv
结果:
4、网络地址的初始化与分配
(1)将字符串信息转换为网络字节序的整数型
sockaddr_in中保存地址信息的成员为32位整数型。为了分配IP地址,需要将其表示为32位整数型数据。
对于IP地址的表示,我们熟悉的是点分十进制表示法,而非整数型数据表示法。
函数1:#include <arpa/inet.h>
in_addr_t inet_addr(const char *string);
成功时返回32位大端序整数型值,失败时返回INADDR_NONE。
实例2、函数调用过程
inet.addr.c代码:
#include <stdio.h> #include <arpa/inet.h> int main(int argc,char *argv[]) { char *addr1="1.2.3.4"; char *addr2="1.2.3.256"; unsigned long conv_addr=inet_addr(addr1); if(conv_addr==INADDR_NONE) printf("Error occured!\n"); else printf("Network ordered integer addr:%#lx\n",conv_addr); conv_addr=inet_addr(addr2); if(conv_addr==INADDR_NONE) printf("Error occured!\n"); else printf("Network ordered integer addr:%#lx\n",conv_addr); return 0; }
编译:gcc inet_addr.c -o addr
执行:./addr
不仅可以把IP地址转成32位整数型,而且可以检测无效的IP地址。
函数2:#include <arpa/inet.h>
int inet_aton(const char *sting , struct in_addr *addr);
成功返回1(true),失败返回0(false)。
参数:string:含有需转换的IP地址信息的字符串地址值。
addr:将保存转换结果的in_addr结构体变量的地址值。
inet_aton函数与inet_addr函数在功能上完全相同,也将字符串形式的IP地址转换为32位网络字节序整数并返回,只不过该函数利用了in_addr结构体,且使用频率更高。
实例3、函数调用过程
inet_aton.c代码:
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
void error_handling(char *message);
int main(int argc,char *argv[])
{
char *addr="127.232.124.79";
struct sockaddr_in addr_inet;
if(!inet_aton(addr,&addr_inet.sin_addr))
error_handling("Conversion error");
else
printf("Network ordered integer addr:%#x\n",addr_inet.sin_addr.s_addr);
return 0;
}
void error_handling(char *message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
编译:gcc inet_aton.c -o aton
执行:./aton
函数3:#include <arpa/inet.h>
char *inet_ntoa(struct in_addr adr);
成功时返回转换的字符串地址值,失败时返回-1.
实例4、
inet_ntoa.c代码:
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{
struct sockaddr_in addr1,addr2;
char *str_ptr;
char str_arr[20];
addr1.sin_addr.s_addr=htonl(0x1020304);
addr2.sin_addr.s_addr=htonl(0x1010101);
str_ptr=inet_ntoa(addr1.sin_addr);
strcpy(str_arr,str_ptr);
printf("Dotted-Decimal notation1:%s\n",str_ptr);
inet_ntoa(addr2.sin_addr);
printf("Dotted-Decimal notation2:%s\n",str_ptr);
printf("Dotted-Decimal notation3:%s\n",str_arr);
return 0;
}
编译:gcc inet_ntoa.c -o ntoa
执行:./ntoa
(2)网络地址初始化
struct sockaddr_in addr; char *serv_ip="211.217.168.13"; //声明IP地址字符串 char *serv_port="9190"; //声明端口号字符串 memset(&addr,0,sizeof(addr)); //结构体变量addr的所有成员初始化为0 addr.sin_family=AF_INET; //指定地址族 addr.sin_addr.s_addr=inet_addr(serv_ip); //基于字符串的IP地址初始化 addr.sin_port=htons(atoi(serv_port)); //基于字符串的端口号初始化
(3)客户端地址信息初始化
上述的网络地址信息初始化过程主要针对服务器端而非客户端。服务器端的准备工作通过bind函数完成,而客户端则通过connect函数完成。因此,函数调用前需准备的地址值类型也不同。
- 服务器端声明sockaddr_in结构体变量,将其初始化为赋予服务器端IP和套接字端口号,然后调用bind函数;
- 而客户端则声明sockaddr_in结构体,并初始化为要与之连接的服务器端套接字的IP和端口号,然后调用connect函数。
INADDR_ANY
每次创建服务器端套接字都要输入IP地址会有些繁琐,此时可如下初始化地址信息:
struct sockaddr_in addr; char *serv_port="9190"; //声明端口号字符串 memset(&addr,0,sizeof(addr)); //结构体变量addr的所有成员初始化为0 addr.sin_family=AF_INET; //指定地址族 addr.sin_addr.s_addr=htonl(INADDR_ANY); addr.sin_port=htons(atoi(serv_port)); //基于字符串的端口号初始化
利用常数INADDR_ANY分配服务器端的IP地址,若采用这种方式,则可自动获取运行服务器端的计算机IP地址,不必亲自输入。若同一计算机中已分配多个IP地址,则只要端口号一致,就可以从不同的IP地址接收数据。
因此,服务器端中优先考虑这种方式。客户端中除非带有一部分服务器端功能,否则不会采用。
示例:hello_server.c和hello_client.c运行过程
- ./hserver 9190
分析可知,向main函数传递的9190端口号。通过此端口创建服务器套接字并运行程序,但未传送IP地址,因为可以通过INADDR_ANY指定IP地址。
- ./hclient 127.0.0.1 9190
函数:#include <sys/socket.h>
int bind (int sockfd,struct sockaddr*myaddr,socklen_t addrlen);
成功时返回0,失败时返回-1。
参数:
- sockfd:要分配地址信息(IP地址和端口号)的套接字文件描述符。
- myaddr:存有地址信息的结构体变量地址值。
- addrlen:第二个结构体变量的长度。
int serv_sock; struct sockaddr_in serv_addr; char *serv_port="9190"; /*创建服务器端套接字(监听套接字)*/ serv_sock=socket(PF_INET,SOCK_STREAM,0); /*地址信息初始化*/ memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=htonl(INADRR_ANY); serv_addr.sin_port=htons(atoi(serv_port)); /*分配地址信息*/ bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));