二:套接字协议、类型、地址族与数据序列

套接字协议与数据传输

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

下面给出参数的详细说明。

协议族(Protocol Family)

头文件sys/socket.h声明的协议族
名称协议族
PF_INET(最常用)IPv4互联网协议族
PF_INET6IPv6互联网协议族
PF_LOCAL本地通信的UNIX协议族
PF_PACKET底层套接字的协议族
PF_IPXIPX Novell协议族

套接字类型(Type)

套接字类型决定套接字的数据传输方式,即在传输层采用TCP,还是UDP。协议族是在网络层。

头文件sys/socket.h声明的协议族
名称套接字类型特点
SCOK_STREAM面向连接的套接字可靠、有序、基于字节的
SOCK_DGRAM面向消息的套接字快速、不可靠、数据有边界(无序)

协议的最终选择

大部分情况可以向第三个参数传0,除非遇到以下情况:

“同一协议族中存在多个数据传输方式相同的协议”

目前,PF_INET协议族中,SOCK_STREAM数据传输方式的协议,满足的只有IPPROTO_TCP
同理,满足PF_INETSOCK_DGRAM的只有IPPROTO_UDP

地址族与数据序列

地址信息的表示 socketaddr_in

数据传输目标地址同时包含IP地址和端口号,只有这样,数据才会被传输到最终的目的应用程序。

所以对于IPv4来讲,定义了以下结构体表示地址信息给bind函数。

struct socketaddr_in {
	sa_family_t		sin_family;       //地址族
	uint16_t		sin_port; 		  //16位TCP/UDP端口号
	struct in_addr	sin_addr;         //32位IP地址
	char			sin_zero;		  //不使用
}

struct in_addr {
	int_addr_t		s_addr;      //32位IP地址
}

结构体sockaddr_in的成员分析

  • 成员sin_family
头文件sys/socket.h声明的协议族
名称协议族
PF_INET(最常用)IPv4互联网协议族
PF_INET6IPv6互联网协议族
PF_LOCAL本地通信的UNIX协议族
PF_PACKET底层套接字的协议族
PF_IPXIPX Novell协议族
  • 成员sin_port
    该成员保存16位端口号,重点:使用网络字节序
  • 成员sin_addr
    32为IP地址,也是网络字节序
  • 成员sin_zero
    没啥用,为了与sockaddr结构体保持一致而插入的成员,必须为0。

网络字节序与地址变换

CPU 向内存保存数据的方式有两种:

  • 大端序:高位字节存放到低位地址。网络字节序为大端序。
  • 小端序:高位字节存放到高位地址。目前主流的 Intel 和AMD系列 CPU 按小端序方式保存数据。

在使用网络发送数据时要先把数据转化成大端序,接收时也要先转换为主机字节序。
链接: Linux一句命令之判断大小端序

字节序转换
htons 中的 h 代表主机字节序,n 代表网络字节序。
s 代表 short 类型,处理 2 字节数据,用于端口号转换;l 代表 long 类型(Linux 中 long 占用 4 字节),处理 4 字节数据,用于 IP 地址转换。

'short 类型,用于端口号的转换'
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
'long 类型,用于 IP 地址的转换'
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

网络地址分配与初始化

sockaddr_in 中保存地址信息的成员是 32 位整型,而一般我们描述 IP 地址时用的是字符串格式的点分十进制表示法,因此需要将字符串形式的 IP 地址转换为 32 位整型数据。
有两个函数可以完成以上功能:inet_addr 函数和 inet_aton 函数。

  • inet_addr函数
    inet_addr 函数在转换类型的同时也会完成网络字节序的转换,它还可以检测无效的 IP 地址。
#include <arpa/inet.h>

 // 功能:将字符串形式的 IP 地址转换为 32 位整型数据并返回。
 // 返回值:成功时返回 32 位大端序整型值,失败时返回 INADDR_NONE。
in_addr_t inet_addr(const char* string);  
  • inet_aton函数
    inet_aton 函数和 inet_addr 函数的功能相同,也是将字符串形式的 IP 地址转换为 32 位网络字节序整数,但是它利用了 in_addr 结构体,使用频率更高。
    inet_aton 需要传递一个 in_addr 类型结构体的指针,它会将转换结果直接放入该指针所指的 in_addr 结构体中。
#include <arpa/inet.h>

 // 功能:将字符串形式的 IP 地址转换为 32 位网络字节序整数并存储到 addr 中。
 // 返回值:成功时返回 1,失败时返回 0
int inet_aton(const char* string, struct in_addr* addr);  
  • inet_ntoa函数
    inet_ntoa 函数与 inet_aton 函数相反,它将网络字节序的整数型 IP 地址转换为字符串形式。
#include <arpa/inet.h>

// 功能:将网络字节序的整数型 IP 地址转换为字符串形式
// 返回值:成功时返回转换的字符串地址值,失败时返回 -1
char* inet_ntoa(struct in_addr adr);  

该函数使用时要小心:返回值类型为 char 指针,返回字符串地址意味着字符串已保存到内存空,但该函数是在内部申请了内存并保存了字符串,因此如果再次调用 inet_ntoa 函数,也有可能覆盖之前保存的字符串信息。因此要将返回的字符串信息复制到其他内存空间。

网络地址初始化

struct sockaddr_in addr;
char *serv_ip = "211.217.168.13";          // 声明 IP 地址字符串
char *serv_port = "9190";                  // 声明端口号字符串
memset(&addr, 0, sizeof(addr));            // 结构体变量 addr 的所有成员初始化为 0,主要是为了将 sockaddr_in 的成员 sin_zero 初始化为 0。
addr.sin_family = AF_INET;                 // 指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); // 基于字符串的 IP 地址初始化
addr.sin_port = htons(atoi(serv_port));    // 基于字符串的端口号初始化

bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))

服务器端和客户端都要进行网络地址信息的初始化,但目的不同:

  1. 服务器端要将声明的 sockaddr_in 结构体变量初始化为自己的 IP 地址和端口号,用于在 bind 函数中与自己的套接字相绑定。
  2. 客户端也要将声明的 sockaddr_in 结构体变量初始化为服务器端的 IP 地址和端口号,用于在 connect 函数中向服务器发起连接请求。

INADDR_ANY

可以用常数 INADDR_ANY 来获取服务器端的 IP 地址

 // INADDR_ANY 相当于主机字节序的 32 位整型 IP 
addr.sin_addr.s_addr = htonl(INADDR_ANY); 地址

使用 INADDRY_ANY,如果同一个计算机具有多个 IP 地址,那么可以从不同 IP 地址(的同一端口号)接收数据,因此服务器端中优先使用 INADDR_ANY,而客户端不应该采用。

demo

  • 练习网络字节序与主机字节序之间的转换
#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);         // 打印结果:0x1234
    printf("Network ordered port: %#x \n", net_port);       // 打印结果:0x3412
    printf("Host ordered address: %#x \n", host_addr);      // 打印结果:0x12345678
    printf("Network ordered address: %#x \n", net_addr);    // 打印结果:0x78563412

    return 0;
}
  • 了解 inet_addr 函数的用法
#include <arpa/inet.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    char *addr1 = "1.2.3.4";    
    char *addr2 = "1.2.3.256";   // 错误的 IP 地址:一个字节能表示的最大整数是 255

    unsigned long conv_addr = inet_addr(addr1);  // 将 "1.2.3.4" 转换为 0x4030201 并返回
    if (conv_addr == INADDR_NONE)
        printf("Error occured! \n");
    else
        printf("Network ordered integer addr: %#lx \n", conv_addr);  

    conv_addr = inet_addr(addr2);   // 因为 IP 地址无效而返回 INADDR_NONE
    if (conv_addr == INADDR_NONE)
        printf("Error occured! \n");
    else
        printf("Network ordered integer addr: %#lx \n\n", conv_addr);

    return 0;
}
  • 了解 inet_aton 函数
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>

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))      // 注意 inet_aton 的用法
        printf("Conversion error");
    else
        printf("Network ordered integer addr: %#x \n", addr_inet.sin_addr);

    return 0;
}
  • 了解 inet_ntoa 函数的用法
#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;
}
  • 在 Windows 系统中练习网络字节序与主机字节序之间的转换的程序。
#include <WinSock2.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    WSADATA wsa_data;
    unsigned short host_port = 0x1234;
    unsigned short net_port;
    unsigned long host_addr = 0x12345678;
    unsigned long net_addr;

    if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0)
        printf("WSAStarup() error!");

    net_port = htons(host_port);
    net_addr = htonl(host_addr);

    printf("Host ordered port: %#x \n", host_port);      // 打印结果:0x1234
    printf("Network ordered port: %#x \n", net_port);    // 打印结果:0x3412
    printf("Host ordered address: %#x \n", host_addr);   // 打印结果:0x12345678
    printf("Network ordered address: %#x \n", net_addr); // 打印结果:0x78563412

    WSACleanup();
    return 0;
}
  • 了解 inet_addr 函数和 inet_ntoa 函数在 windows 系统中的用法。
    注意:windows 中没有 inet_aton 函数。
    虽然 Windows 中的 in_addr 结构体和之前不一样了,但是一样可以使用 inet_ntoa 函数。
#pragma execution_character_set("utf-8")

#include <WinSock2.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    WSADATA wsa_data;
    if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0)
        printf("WSAStartup() error");

    // inet_addr 函数调用示例
    {
        char* addr = "127.212.124.78";
        unsigned long conv_addr = inet_addr(addr);
        if (conv_addr == INADDR_NONE)
            printf("Error occured!\n");
        else
            printf("Network ordered interger addr: %#lx \n", conv_addr);
    }

    // inet_ntoa 函数调用示例
    {
        struct sockaddr_in addr;
        char* str_ptr;
        char str_arr[20];

        addr.sin_addr.S_un.S_addr = htonl(0x1020304);
        str_ptr = inet_ntoa(addr.sin_addr);
        strcpy(str_arr, str_ptr);
        printf("Dotted-Decimal notation3 %s \n", str_arr);
    }

    WSACleanup();
    return 0;
}
  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值