【C++】Winsock套接字编程,struct sockaddr、sockaddr_in,主机网络字节序

2023-12-04 01:32

本文主要是介绍【C++】Winsock套接字编程,struct sockaddr、sockaddr_in,主机网络字节序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前一篇关于windows平台下winsock套接字编程的相关api

struct sockaddr与struct sockaddr_in
在bind函数,connect函数里提到了套接字编程网络地址信息结构体const struct sockaddr和const struct sockaddr_in

关于const struct sockaddr是通用的套接字网络地址信息结构体,而const struct sockaddr_in主要针对于TCP/IP协议体系的网络地址信息存储(sockaddr_in更适合操作)
二者的内存占用一样,所以在调用对应api时候,可以直接将sockaddr_in进行类型强转为sockaddr即可

看一下二者的内部结构
struct sockaddr
{
unsigned short int sa_family; //协议族
unsigned char sa_data[14]; //地址信息
}

struct sockaddr_in
{
short sin_family; //协议族
unsigned short int  sin_port; //端口号(windows平台部分编译环境下,可能将此类型typdef为in_port_t)
struct in_addr sin_addr; //intenet地址信息,详细内容之后讨论
unsigned char sin_zero[len]; //充填部分
}
我们着重看一下struct sockaddr_in这个结构体
  • short sin_family,对于TCP/IP协议,赋值AF_INET即可
  • unsigned short int sin_port,通信的端口号(网络字节序赋值,关于主机字节序和网络字节序的区别,之后讨论),我在vs2015下,unsigned short int类型被typedef为in_port_t。
  •     struct in_addr sin_addr,保存internet地址信息,也就是我们常说的IP地址,这是一个结构体,来看一下它的内容
struct in_addr
{
union
{
struct 
{
uchar s_b,s_b2,s_b3,s_b4;
}S_un_b;
struct
{
ushort s_w1,s_w2;
}S_un_w;
u_long S_addr; //网络字节序
}S_un;
}    
struct in_addr结构体中,使用联合union,三种方式来保存IP地址信息;
关于IP地址,这是一个4字节的无符号整型,此结构体也就对应了三种保存方式:1.S_un_b单字节保存为;2.S_un_w,双字节保存;3.S_addr,4字节保存;
我们常用S_addr4字节直接保存IP地址信息
  • unsigned char sin_zero[len]用来充填对齐,使sockaddr_in与sockaddr内存对齐,不用关注。

网络字节序与主机字节序
在之前,一直提到对应的端口port与网络地址IP地址,必须使用网络字节序来操作。
什么是网络字节序,还有对应的主机字节序是什么?
首先,我们先看一下目前计算机当中存在的两种字节序
little_endian,big_endian

对于一个数,在计算机内存存储有两种方式:
一,将数字的低位存在内存的起始地址上,称为little_endian
二,将数字的高位存在内存的起始地址上,称为big_endian

由于目前计算机工艺的区别,不同计算机采用了不同的存储序,我们可以通过一些方式查询到自己计算机的字节序(本文不再赘述,可自行查找)

为了保证计算机在网络通信时,不会因为两种顺序造成寻址失败,规定将 big_endian称为网络字节序

PS:主机字节序与little_endian、big_endian没有直接关系。并不是说little_endian就是主机字节序。主机字节序是根据本台计算机的硬件决定的,主机字节序既可能是little_endian,也可能是big_endian。

所以,我们在操作sockaddr_in里sin_port和sin_addr时,一定要将网络字节序的端口号和IP地址操作进去
(关于网络字节序与主机字节序的转换函数,之后介绍)

举例:
对于点分十进制IP地址“192.168.3.31”,这是一个4字节的数据
对应的二进制:1100 0000     1010 1000     0000 0011     0001 1111
对应十进制  3232236319‬
如果转换到网络字节序(big_endian)下,那么高位就会存放在起始位置上(4字节数据颠倒存放)
那么对应的二进制:0001 1111     0000 0011     1010 1000     1100 0000
对应十进制: ‭520333504‬

转换函数:(h-host主机,n-net网络,l-long4字节,s-short2字节)
主机字节序转换为网络字节序:
u_long htonl(u_long hostlong); //4字节
u_short htons(u_short hostshort);  //2字节
网络字节序转换为主机字节序:
u_long ntohl(u_long netlong); //4字节
u_long ntohs(u_short netshort); //2字节

对于sockaddr,sockaddr_in结构体中的 端口号sin_port,需要使用2字节的转换函数来转换
对于in_addr结构体中的S_addr,需要使用4字节转换函数来转换

另一种IP地址的转换函数:
    对于上面提到的转换函数,使用起来必然是很困难的,因为我们日常中常见的IP地址是点分十进制“192.168.1.1”这样的形式。
    如果我们需要使用上面的函数,则需要自己先手工将点分十进制转换成普通十进制二进制才能使用。这样很麻烦。
    所以,我们有其他直接将点分十进制char类型转换成网络字节序的函数
    unsigned long inet_addr inet_addr(const char *cp);
    我们可以直接将const char *cp="192.168.1.1"这样形式的IP地址使用该函数转换为网络字节序。
    
    但是,我使用的是windows10,VS2015环境,如果使用这个函数,则会编译报错。
    会提示使用inet_pton()函数来进行转换
    查了相关资料,inet_addr函数相对较旧,不支持IPv6,所以VS2015已经不支持,VS2010目测支持。而linux平台下,win平台下使用codeblock附加对应库函数,也可以使用。
    但是为了保证VS2015的正常运行,所以我还是改用了支持IPv6的inet_pton()函数。
    
    函数int inet_pton(int af, const char *src, void *dst); 附加头函数#include<WS2tcpip.h>
    参数:af-协议地址族 TCP/IP协议使用AF_INET
               src-字符串形式的点分十进制IP地址
               对应的in_addr结构体指针,必须使用void类型强转

这篇关于【C++】Winsock套接字编程,struct sockaddr、sockaddr_in,主机网络字节序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/451619

相关文章

Win32下C++实现快速获取硬盘分区信息

《Win32下C++实现快速获取硬盘分区信息》这篇文章主要为大家详细介绍了Win32下C++如何实现快速获取硬盘分区信息,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 实现代码CDiskDriveUtils.h#pragma once #include <wtypesbase

C++ Primer 标准库vector示例详解

《C++Primer标准库vector示例详解》该文章主要介绍了C++标准库中的vector类型,包括其定义、初始化、成员函数以及常见操作,文章详细解释了如何使用vector来存储和操作对象集合,... 目录3.3标准库Vector定义和初始化vector对象通列表初始化vector对象创建指定数量的元素值

C++实现回文串判断的两种高效方法

《C++实现回文串判断的两种高效方法》文章介绍了两种判断回文串的方法:解法一通过创建新字符串来处理,解法二在原字符串上直接筛选判断,两种方法都使用了双指针法,文中通过代码示例讲解的非常详细,需要的朋友... 目录一、问题描述示例二、解法一:将字母数字连接到新的 string思路代码实现代码解释复杂度分析三、

C++一个数组赋值给另一个数组方式

《C++一个数组赋值给另一个数组方式》文章介绍了三种在C++中将一个数组赋值给另一个数组的方法:使用循环逐个元素赋值、使用标准库函数std::copy或std::memcpy以及使用标准库容器,每种方... 目录C++一个数组赋值给另一个数组循环遍历赋值使用标准库中的函数 std::copy 或 std::

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.