3.1 分配给套接字的IP地址与端口号

3.1.1 网络地址

  • IPv4(Internet Protocol version 4)4字节地址组
  • IPv6(Internet Protocol version 6)16字节地址组

3.1.2 用于区分套接字的端口号

因为在浏览视频网页时,一边播放视频一边有网页,所以需要两个不同的套接字来接收。

如何区分二者提出以下一些建议和方法:开发收发数据的P2P程序,该程序用块单位分割一个文件,从多台计算机接收数据

计算机中一般配有NIC(Network Interface Card,网络接口卡)数据传输设备。通过NIC向计算机内部传输数据时会用到IP。操作系统负责把传递到内部的数据分配给套接字,所以需要端口号。通过NIC设备接收的数据内有端口号,操作系统正是参考此端口号把数据传输给相应端口的套接字。(端口号由16位构成,可分配的端口号范围是065535,其中11023知名端口,一般分配给特定的应用程序。端口号不能重复,但是TCP套接字UDP套接字不会共用端口号,所以允许重复。例如:某TCP套接字用9190号端口,其他TCP套接字不能使用,但是UDP套接字可以使用

3.2 地址信息的表示

3.2.1 表示IPv4地址的结构体

  1. struct sockaddr_in
  2. {
  3. sa_family_t sin_family; //地址族(Address Family)
  4. uint16_t sin_port; //16位TCP/UDP端口号
  5. struct in_addr sin_addr;//32位IP地址
  6. char sin_zero[8];//不使用
  7. };
  8. struct in_addr
  9. {
  10. In_addr_t s_addr;//32位IPv4地址
  11. };
  12. struct sockaddr
  13. {
  14. sa_family_t sin_family;//地址族
  15. char sa_data[14];//地址信息
  16. };

3.2.2 POSIX中定义的数据类型

数据类型名称 数据类型说明 声明的头文件
int8_t signed 8-bit int sys/types.h
uint8_t unsigned 8-bit int(unsigned char) sys/types.h
int16_t signed 16-bit int sys/types.h
uint16_t unsigned 16-bit int(unsigned short) sys/types.h
int32_t signed 32-bit int sys/types.h
uint32_t unsigned 32-bit int(unsigned long) sys/types.h
sa_family_t 地址族(address family) sys/socket.h
socklen_t 长度(length of struct) sys/socket.h
in_addr_t IP地址,声明为uint32_t netinet/in.h
in_port_t 端口号,声明为uint16_t netinet/in.h

3.2.3 结构体sockaddr_in的成员分析

3.2.3.1 成员sin_family

地址族 含义
AF_INET IPv4网络协议中使用的地址族
AF_INET6 IPv6网络协议中使用的地址族
AF_LOCAL 本地通信中采用的UNIX协议的地址族

3.2.3.2 成员sin_port

该成员保存16位端口号,以网络字节序保存。

3.2.3.3 成员sin_addr

该成员保存32位IP地址信息,同样以网络字节序保存。

3.2.3.4 成员sin_zero

无特殊含义。为了使结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员。

3.3 网络字节序与地址变换

3.3.1 字节序于网络字节序

  • 大端模式:按照人类固有的思维顺序存储
  • 小端模式:先处理低字节的数据也就是倒着存储,因为对于计算机来说处理小的数据更方便

由于不同的模式处理的顺序不同,我们需要区分模式,这样可以应对不同CPU下的处理方案。所以说,在通过网络传输数据时约定统一方式,这种约定称为网络字节序,非常简单统一为大端序。(无论要传递什么信息,都应该在传输前转换为大段模式进行存储)

3.3.2 字节序转换

相关函数如下:

unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
//htons h代表主机的字节序
//htons n代表网络的字节序
//所以htons就是把short型数据从主机转换为网络字节序
//ntohs就是把网络字节序转换成主机的顺序

程序如下:

//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;
}
zhang@zhang-virtual-machine:~/Desktop/Ctest$ gcc endian_conv.c -o conv.c
zhang@zhang-virtual-machine:~/Desktop/Ctest$ ./conv.c
Host ordered port:0x1234 
Network ordered port:0x3412 
Host ordered address:0x12345678 
Network ordered address:0x78563412 
#Intel和AMD都是小端模式

3.4 网络地址的初始化与分配

3.4.1 将字符串信息转换为网络字节序的整数型

3.4.1.1 inet_addr函数

#include<arpa/inet.h>
//改函数可以将字符串形式的IP地址转换成32位整数型数据,那么此函数在转换类型的同时进行网络字节序转换,还能无效的IP地址
int_addr_t inet_addr(const char *string);
//成功时返回32位大端序整数型值,失败时返回INADDR_NONE
//inet_addr.c
#include<stdio.h>
#include<arpa/inet.h>

int main(int argc, char *agv[])
{
    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 occureded \n");
    else
        printf("Network ordered integer addr:%#lx \n\n", conv_addr);
    return 0;
}
zhang@zhang-virtual-machine:~/Desktop/Ctest$ gcc inet_addr.c -o addr
zhang@zhang-virtual-machine:~/Desktop/Ctest$ ./addr
Network ordered integer addr:0x4030201 
Error occureded

3.4.1.2 inet_aton函数

首先强调inet_aton函数inet_addr函数在功能上完全相同,只不过利用了in_addr结构体,且使用频率更高。

#include<arpa/inet.h>
int inet_aton(const char * string, struct in_addr * addr);
//成功时返回1(true),失败时返回0(false)
//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);
}
zhang@zhang-virtual-machine:~/Desktop/Ctest$ gcc inet_aton.c -o aton
zhang@zhang-virtual-machine:~/Desktop/Ctest$ ./aton
Network ordered integer addr:0x4f7ce87f

3.4.1.3 inet_ntoa函数

改函数可以将网络字节序整数型IP地址转换成我们熟悉的字符串形式。

#include<arpa/inet.h>
char *inet_ntoa(struct in_addr adr);
//成功时返回转换的字符串地址值,失败时返回-1
//若要长期保存需要将字符串复制到其他的内存空间
//inet_ntoa.c
#include<stdio.h>
#include<string.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;
}
zhang@zhang-virtual-machine:~/Desktop/Ctest$ gcc inet_ntoa.c -o ntoa
zhang@zhang-virtual-machine:~/Desktop/Ctest$ ./ntoa
Dotted-Decimal notation1: 1.2.3.4 
Dotted-Decimal notation2: 1.1.1.1 
Dotted-Decimal notation3: 1.2.3.4

3.4.2 网络地址初始化

struct sockaddr_in addr;
char *serv_ip="127.0.0.1"; //声明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));//基于字符串的端口号初始化