IP是Internet Protocol (网络协议)的简写,是为首发网络数据而分配给计算机的值。端口号并不是赋予计算机值,而是为了区分程序中建立的套接字而分配给套接字的序号。linux
网络地址分为IPV4和IPV6,分别你别为4个字节地址簇和6个字节地址簇。IPV4标准的4个字节的地址分为网络地址和主机地址,且分为A、B、C、D、E 等类型。通常不多用到E类型。以下图所示:net-id指网络ID,host-id指主机ID程序员
说明:编程
A类地址的首字节范围是:0-127数组
B类地址的首字节范围是:128-191网络
C类地址的首字节范围是:192-223socket
或者也能够这样分ide
A类地址的首位是以0开头函数
B类地址的前两位是以10开头测试
C类地址的前三位是以110开头ui
概述
网络ID是为区分网络而设置的一部分IP地址。好比你向www.baidu.com公司传输数据,该公司内部构建了局域网,把全部的计算机链接起来。所以,首相向baidu.com网络传输数据,也就是说,并不是一开始就浏览全部4字节的IP地址,进而找到目标主机;而是仅浏览4字节IP地址的网络地址,先把数据传到baidu.com的网络。baidu.com网络接收到数据以后,浏览传输数据的主机地址并将数据传输给目标地址。通常的网络都会有路由器和交换机,因此其实是向路由器或交换机传递数据,由接收数据的路由器根据数据中的主机地址向目标主机传送数据。
用于区分套接字的端口号
IP用于区分计算机,只要有IP地址就能想目标主机传输数据,可是仅凭这些数据没法传输给最终的应用程序。假设在欣赏音乐的同时在听音乐或者上网浏览网页,这时至少须要1个接受视频数据的套接字和1个接受网页信息的套接字。可是如何区分它们呢,也就是说传输到计算机的网络数据是发送给视频播放器仍是音乐播放器?假设咱们开发了迅雷等应用程序,该程序用块单位分割一个文件,从多台计算机接受数据。那如何区分这些套接字呢?
计算机中通常都会有NIC(NetWork Interface Card,网络接口卡)数据传输设备。经过NIC向计算机内部传输数据时会用到IP。OS负责把传递到内部的数据适当的分配给套接字,这时就要利用端口号。经过NIC接受的数据内有端口号,操做系统正是参考此端口号把数据传输给相应端口的套接字。端口号就是在同一操做系统内为区分不一样套接字而设置的,所以没法将一个端口号分配给不一样的套接字。而且,端口号由16位构成,可分配的端口号的范围是0-65535,0-1023是知名端口号(Well-Know PORT),通常分配给特定应用程序,因此应当分配此范围以外的值。端口号是不能重复,但TCP套接字和UDP套接字不会公用端口号,因此容许重复。好比:某TCP套接字使用9190端口号,则其余TCP没法就没法使用端口号,可是UDP套接字就可使用。
总之,数据传输目标地址同时包含IP地址和端口号,只有这样,数据才会被传输到最总的目的应用程序(应用程序套接字)。
应用程序中使用的IP地址和端口号以结构体的形式给出了定义,咱们主要以IPV4为中心。
struct sockaddr_in { sa_family_t sin_family;//地址族 uint16_t sin_port;//16位TCP/UDP端口号 struct in_addr sin_addr;//32位IP地址 char sin_zero[8];//不使用 }; 该结构体中的 in_addr用来存放32位的IP地址,定义以下 struct in_addr { In_addr_t s_addr;//32位IP地址 };
uint16_t in_addr_t等类型能够参考POSIX,我这边简单说一下
数据类型 | 数据类型说明 | 声明的头文件 |
int8_t | signed 8-bit int | sys/types.h |
uint8_t | unsigned 8-bit int | sys/types.h |
int16_t | signed 16-bit int | sys/types.h |
uint16_t | unsigned 16-bit int | sys/types.h |
int32_t | signed 32-bit int | sys/types.h |
这主要是考虑到扩展性,若是使用int32_t类型的数据,就能保证在任什么时候候都占用4字节,即便未来用64位的来存储也是同样。
每种协议族适用的地址族不一样,好比IPV4使用4个字节地址族,IPV6使用16字节地址簇
地址簇 含义
AF_INET IPV4网络协议中使用的地址族
AF_INET6 IPV6网络协议中使用的地址族
AF_LOCAL 本地通讯中采用的UNIX协议的地址族
该成员保存16位端口号,具体在下面讲解。
保存32位的IP地址信息,且以网络字节序保存,结构体in_addr声明为uint32_t,一次只须要保存32位整数类型便可。
无特殊含义,只是未使用结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员,必须填充为0,不然没法获得想要的结果。以前在服务端bind函数的时候,
struct sockaddr_in serv_addr;
if(bind(serv_sock,(struct sockaddr *)&serv_addr,sizeof(serv_addr)==-1),其中第二个参数,是由sockaddr_in结构体转化而来的,而且但愿获得sockaddr结构体变量地址,包括地址簇、端口号、IP地址等。可是咱们直接向sockaddr结构体填充这些信息会带来麻烦。
struct sockaddr
{
sa_family_t sin_family;//地址族
char sa_data[14];//地址信息
};
sa_data保存地址信息,包括IP地址和端口号,剩余部分应填充为0,这也是bind函数的要求。这对于包含地址信息来说很是麻烦,从而有了新的结构体sockaddr_in。
cpu向内存保存数据有两种,这意味着cpu解析数据的方式也是两种分别为大端序和小端序。
好比0x00000001
大端序:内存低比特位 00000000 00000000 00000000 00000001 内存高比特位
小端序:内存低比特位 10000000 00000000 00000000 00000000 内存高比特位
还能够以下图表示:
因此出现了一个问题若是两台计算机的cpu的数据保存方式不一样,可是他们是如何传送数据的呢?如何进行网络传输的呢?因此就规定了一个标准,在经过网络传输的过程当中统一按照大端序。即先把数据数组转化为大端序格式而后进行传输。所以,全部计算机接受数据时应识别该数据的字节格式,小端序系统传输数据时应该转化为大端字节序的排列方式。
因此咱们懂应该知道为什么在填充sockadr_in结构提早将数据转化为网络字节序。转换字节的函数:
unsigned short htons (unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htons (unsigned long);
unsigned long ntohs (unsigned long);
经过函数名能够知道他的功能,只要了解一下细节。htons中的h表明主机(host)字节序,n表明网络字节序。s指的是short,l指的是long(linux 中long类型占用4个字节),能够解释为"把short类型数据从主机字节序转化为网络字节序"。因此ntohs也就知道了吧。咱们经过例子来看一下效果:
运行结果以后会看到以下图结果:
这就是小端序cpu运行结果,若是在大端值中是不会发生变化的。Intel和AMD系列的cpu都采用小端序标准。数据在传输过程当中须要通过转换吗?实际上没有必要,这个过程是自动的。除了想sockaddr_in结构体变量填充数据外,其余状况不须要考虑字节序问题。
sockaddr_in 中保存地址信息的成员为32位整数型。所以,为了分配IP地址将其转化为32位整数型数据。对咱们而言并不是易事。对于IP地址的的表示,咱们熟悉的是点分十进制,而非整型数据表示法。幸运的是,有个函数能够帮咱们将字符串形式的IP地址转化为32位的整型数据。此函数在转换类型的同时进行网络字节序转换。
#include <arpa/inet.h> in_addr_t inet_addr(const char* string) 成功时返回32位大端序整型数据,失败时返回INADDR_NONE
下面是测试代码:
运行结果以下图所示:
从运行结果能够看出,inet_addr函数不只能够把ip地址转换为32位整数,并且还能够检测无效的ip地址。而且输出的确实是网络字节序。还有一个函数与inet_addr函数功能彻底相同,只不过该函数利用了in_addr结构体,且使用频率更高。
#include <arpa/inet.h> int inet_aton(const char *string,struct in_addr *addr) 成功时返回1,失败时返回0;
实际编程中若要调用inet_addr函数,须要将转化后的IP地址信息代入sockaddr_in结构体中声明的in_addr结构体变量。而inet_aton函数不须要此过程。缘由在于,若传递in_addr结构体变量地址值,函数会自动把结果填入该结构体变量。ok,下面再讲解一个把网络字节序整数型IP地址转换成咱们熟悉的字符串形式。
#include <arpa/inet> char *inet_ntoa(struct in_addr adr); 成功时返回转换的字符串地址,失败时返回-1。
但在调用时当心,返回值是char类型的指针。返回字符串地址意味着字符串已保存到内存空间,但该函数未向程序员要求分配内存,而是在内部申请了内存并保存了字符串。也就是说,调用完函数后,应该当即将字符串信息复制到其余内存空间。由于在此调用该函数,则有可能覆盖以前保存的字符串信息。总之,再次调用该函数前返回的字符串地址值是有效的。如要长期保存,则应将字符串复制到其余内存空间。示例:
运行结果以下:
下面我把以前的代码彻底从新组合一下。
服务端代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <arpa/inet.h> 6 #include <sys/socket.h> 7 8 void ErrorMessage(char *message); 9 10 int main(int argc,char *argv[]) 11 { 12 int serv_sock; 13 int client_sock; 14 struct sockaddr_in serv_addr; 15 struct sockaddr_in client_addr; 16 char *serverIP= "127.0.0.1"; 17 char *servPort = "9190"; 18 char message[]="Hi,TCPIP"; 19 socklen_t clnt_addr_size; 20 serv_sock = socket(PF_INET,SOCK_STREAM,0); 21 if(serv_sock==-1) 22 { 23 ErrorMessage("Sock Error!"); 24 } 25 memset(&serv_addr,0,sizeof(serv_addr)); 26 serv_addr.sin_family=AF_INET; 27 serv_addr.sin_addr.s_addr=inet_addr(serverIP); 28 serv_addr.sin_port = htons(atoi(servPort)); 29 if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1) 30 { 31 ErrorMessage("Bind() Error"); 32 } 33 if(listen(serv_sock,5)==-1) 34 { 35 ErrorMessage("listen() error"); 36 } 37 clnt_addr_size = sizeof(client_addr); 38 client_sock = accept(serv_sock,(struct sockaddr*)&client_addr,&clnt_addr_size); 39 if(client_sock==-1) 40 { 41 ErrorMessage("accept() error"); 42 } 43 write(client_sock,message,sizeof(message)); 44 close(client_sock); 45 close(serv_sock); 46 return 0; 47 } 48 void ErrorMessage(char *message) 49 { 50 fputs(message,stderr); 51 fputc('\n',stderr); 52 exit(1); 53 }
客户端代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> void ErrorMessage(char *message); int main(int argc,char* argv[]) { int sock; struct sockaddr_in serv_addr; char message[30]; char *serv_port = "9190"; int str_len; sock = socket(PF_INET,SOCK_STREAM,0); if(sock==-1) { ErrorMessage("socket() error"); } memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); serv_addr.sin_port=htons(atoi(serv_port)); if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1) { ErrorMessage("connect() error"); } str_len = read(sock,message,sizeof(message)-1); if(str_len==-1) { ErrorMessage("read() error"); } printf("Message from server:%s\n",message); close(sock); return 0; } void ErrorMessage(char *message) { fputs(message,stderr); fputc('\n',stderr); exit(1); }
运行结果以下图所示: