TCP/IP网络编程 学习笔记_3 --给套接字分配IP地址和端口号

时间:2023-02-13 10:22:01

IP地址和端口号

1,IP地址:为使计算机连接到网络并收发数据,必须为其分配IP地址。IP地址分为两类:IPv4(4字节地址族)和IPv6(16字节地址族)。它们主要区别就是在表示IP地址所用的字节数,IPv6就是为了应对IPv4地址耗尽而提出来的。现在主要使用的还是IPv4,IPv4标准的4字节IP地址是由网络地址和主机(指计算机)地址组成,且分为A,B,C,D,E等类型。
TCP/IP网络编程 学习笔记_3 --给套接字分配IP地址和端口号
这个数据传输过程大致是这样:先向IP地址的网络地址传输数据(即向构成网络的路由器或交换机传递数据),然后,由接收数据的路由器根据数据中的主机地址向目标主机传递数据。
注释:本地计算机要与外网进行数据交换,需要一种物理设备作为中介转换数据,这个设备便是路由器或交换机。它们实际上也是一种计算机,只不过是为特殊目的而设计运行的,所以,如果在我们使用的计算机上安装适当的软件,也可以将其用作交换机。另外,交换机比路由器功能要简单一些,而实际用途差不多。

2,网络地址分类与主机地址边界:只需通过IP地址的第一个字节就可判别网络地址占用的字节数。有两种方式,如下:
一:
A类首字节范围:0-127
B类首字节范围:128-191
C类首字节范围:192-223

二:
A类地址首位以0开始
B类地址的前2位以10开始
C类地址的前3位以110开始

3,端口号:IP用于区分计算机,只要有IP地址就能向目标主机传输数据,但仅凭这些无法把数据传输给最终的应用程序。因此,端口就是用来指定是与哪个应用程序进行通信。端口号由16位构成,可分配的端口号范围是0-65535,但0-1023一般已经分配给特定的应用程序了。另外,虽然端口号不能重复(即一个端口号不能分配给多个同一类型套接字程序),但可以分配给不同类型套接字程序,因为它们不会共用端口号。如tcp套接字分配9190,则其他tcp套接字就不能再使用该端口号,但UDP套接字可以使用。

地址信息的表示

从上面的讲解可以知道,一个地址信息应该包括地址族+IP地址+端口号,下面来看看程序里表示它们的结构体:

//标准结构体
struct sockaddr
{
sa_family_t sin_family;//地址族
char sa_data[14];//地址信息(IP地址和端口号)
}
//扩展结构体
struct sockaddr_in
{
sa_family_t sin_family; //地址族
uint16_t sin_port; //端口号
struct in_addr sin_addr; //IP地址
char sin_sero[8]; //不使用
}

struct in_addr
{
In_addr_t s_addr; //4字节IPv4地址
}

说明:函数bind()或connect()第二个参数要求传入sockaddr结构体类型数据,但从上面可以看出这种结构体初始化数据不方便,所以就有了对sockaddr结构体的扩展sockaddr_in。sockaddr不仅仅只为IPv4设计,所以它要手动指定地址族,而它的扩展sockaddr_in则仅为IPv4设计的,不过它还是需要指定地址族(虽然已经知道),而且还需要加空数组,就是为了和sockaddr结构保持一致。

网络字节序

1,不同的CPU,在向内存保存数据的方式也可能不一样,分为2种:大端序和小端序。大端序是指高地址先存,而小端序是指低地址先存。像Intel和AMD系列的CPU就是采用小端序标准。如:0x12345678,大端序下传输顺序一致为0x12345678,而小端序下传输顺序相反为0x78563412。所以这里就可以看出,如果服务端和客服端CPU标准不同,那么传递的数据就不一致了。为了解决这个问题,就约定了一个统一的方式:统一为大端序,这种约定我们称为网络字节序
注释:其实我们只需要在向sockaddr_in结构体变量填充数据时自己处理字节序问题,其它网络数据传输就不需要我们管了,系统自动帮我们转换了。

2,字节序转换接口:

  • unsigned short htons(unsigned short);//把short类型数据从主机字节序转化为网络字节序。
  • unsigned short ntohs(unsigned short);
  • unsigned long htonl(unsigned long);
  • unsigned long ntohl(unsigned long);
    注释:h代表主机字节序,n代表网络字节序,s代表short,l代表long。

网络地址的初始化与分配

1,上面讲了网络地址的基础理论,那么实际代码中应该怎么实现呢?实现之前还需要再补充几个概念问题:

  • in_addr_t inet_addr(const chat *string);
    将点分十进制IP地址(127.0.0.1)转化为32位整型IP地址,同时还满足网络字节序。

  • int inet_aton(const char *string, struct in_addr *addr);//Windows上没有这个
    这个和上面那个功能一样,不过它多了一个参数,能把转化好的IP地址同时用in_addr类型变量保存。即网络地址初始化时,可以省去手动再单独给serv_addr.sin_addr.s_addr 赋值这一步。

  • char * inet_ntoa(struct in_addr adr);
    和上面功能相反,是把32位整型数据IP转化为点分字符串IP。

  • INADDR_ANY:这个常量可以自动获取运行服务端计算机IP地址,不必亲自输入。一般用于服务端,而客服端一般不采用。

  • 127.0.0.1是回送地址,指的是计算机自身IP地址。像服务端与客服端在同一计算机上,就可以使用这个IP地址。否则客服端就要写服务端计算机地址。

2,代码实现:

  • 服务端网络地址分配(接收连接)
struct sockaddr_in serv_addr;
...
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(9190);

if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
...
  • 客服端连接服务端分配的地址
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(9190);

if(connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("connect() error");
...

注释:网络地址信息初始化主要是针对服务端的,客服端只是去连接服务端,所以客服端IP地址和端口号填的是对应服务端的。