【Linux】网络编程二:socket简介、字节序、socket地址及地址转换API

2023-11-08

参考连接:https://www.nowcoder.com/study/live/504/2/16.

【Linux】网络编程一:网络结构模式、MAC/IP/端口、网络模型、协议及网络通信过程简单介绍
【Linux】网络编程二:socket简介、字节序、socket地址及地址转换API
【Linux】网络编程三:TCP通信和UDP通信介绍及代码编写



六, 网络通信

6.1 Socket介绍

Socket,套接字,是对网络中不同主机上的应用程序之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,套接字提供了应用层程序利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用程序,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议进行交互的接口。

socket可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑概念。它是网络环境中进程间通信的API,也是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连的进程,通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的socket中,该socket通过与网卡(NIC)相连的传输介质将这段信息送到另外一台主机的socket中,使对方能够接收到这段信息。

socket是由IP地址和端口结合的,提供向应用层进程传送数据包的机制。

在Linux环境下,socket用于表示进程间网络通信的特殊文件类型。本质是内核借助缓冲区形成的伪文件。既然是文件,就可以使用文件描述符引用套接字。与管道类似,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致,区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。

套接字通信分为两部分:

  • 服务器端:客户端主动向服务器发送连接,服务器被动接受连接,服务器一般不会主动发送连接。
  • 客户端:主动向服务器发起连接。

socket是一套通信的接口,Linux、Windows都有套接字socket,但有差别。

6.2 字节序

6.2.1 字节序简介

现代CPU的累加器一次能装载至少4字节(32位机),即一个整数。这个4个字节在内存中排列的顺序将影响它被累加器装载成的整数的值,这就是字节序的问题。

在各种计算机体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字)应该以什么样的顺序进行传递。如果不达成一致的规则,通信双方将无法进行正确的编码/译码从而导致通信失败。

字节序,就是字节的顺序,是大于一个字节类型的数据在内存中的存放顺序。

字节序分为大端字节序Big-Endian小端字节序Little-Endian

  • 大端字节序是指一个整数的最高位字节存储在内存的低地址处。低位字节存储在内存的高地址处;采用这种机制的处理器有IBM3700系列、PDP-10系列、Mortolora位处理器和绝大多数的RISC处理器。

  • 小端字节序则是指整数的最高位字节存储在内存的高地址处,低位字节存储在内存的低地址处。采用这种机制的处理器有PDP-11、VAX、Intel系列位处理器和一些网络通信设备。

示例:存储0x1234ABCD到内存2000H开始的四个字节中
Big-Endian存储,从2000H开始,依次为12H 34H ABH CDH;
Little-Endian存储,从2000H开始,依次为CDH ABH 34H 12H;
地址 大端存储的数据 小端存储的数据
2000H 12H CDH
2001H 34H ABH
2002H ABH 34H
2003H CDH 12H

大部分计算机采用小端字节序。

6.2.2 如何判断本机的字节序

通过代码检测主机的字节序:

/**
 * @file byteorder.c
 * @author Zoya (2314902703@qq.com)
 * @brief 通过代码检测当前主机的字节序
 * @version 0.1
 * @date 2022-10-08
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <stdio.h>

int main()
{
    union
    {
        short value;               // 2字节
        char bytes[sizeof(short)]; // 2字节数组
    } test;

    test.value = 0x0102;
    if (0x01 == test.bytes[0] && 0x02 == test.bytes[1])
    {
        printf("大端字节序\n");
    }
    else if (0x02 == test.bytes[0] && 0x01 == test.bytes[1])
    {
        printf("小端字节序\n");
    }
    else
    {
        printf("未知\n");
    }
    return 0;
}

运行程序,本机测试得到结果:

小端字节序
6.2.3 字节序转换函数

当格式化的数据在两台使用不同字节序的主机之间直接传递时,接收端必然错误的解释。解决的方式是:发送端总是把发送的数据转换成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收的数据进行转换。

网络字节序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统无关,从而可以保证数据在不同主机之间传输时能够被正确解释,网络字节序采用大端排序方式

BSD Socket提供了封装好的转换接口,包括:

  • 从主机字节序到网络字节序的转换函数:htonshtonl
  • 从网络字节序到主机字节序的转换函数:ntohsntohl
#include <arpa/inet.h>
// 转换端口
uint16_t htons(uint16_t hostshort);  // 主机字节序 -> 网络字节序
uint16_t ntohs(uint16_t netshort);  // 网络字节序 -> 主机字节序
// 转换IP
uint32_t htonl(uint32_t hostlong);  // 主机字节序 -> 网络字节序
uint32_t ntohl(uint32_t netlong);  // 网络字节序 -> 主机字节序

使用示例:

/**
 * @file transbyte.c
 * @author zoya (2314902703@qqn.com)
 * @brief
 * @version 0.1
 * @date 2022-10-09
 *
 * 网络通信时,发送端需要将主机字节序转换成网络字节序(大端)
 * 另外一端收到数据后根据情况将网络字节序转换
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    // host to net prot
    unsigned short a = 0x0102;

    unsigned short b = htons(a); // host port to net port

    printf("a : 0x%x\n", a);
    printf("b : 0x%x\n", b);

    printf("*******************************\n");

    // host to net long ip
    unsigned char buf[4] = {192, 168, 1, 100};
    unsigned int num = *(int *)buf;
    unsigned int sum = htonl(num);
    unsigned char *p = (char *)&sum;
    printf("%d %d %d %d \n", *p, *(p + 1), *(p + 2), *(p + 3));

    printf("*******************************\n");

    // net to host port
    unsigned short netport = 0x0201;
    unsigned short hostport = ntohs(netport);
    printf("net port : 0x%x\n", netport);
    printf("host port : 0x%x\n", hostport);

    printf("*******************************\n");

    // net to host ip
    unsigned char netbuf[4] = {1, 1, 168, 192};
    unsigned int netnum = *(int *)netbuf;
    unsigned int hostnum = ntohl(netnum);
    unsigned char *phostnum = (unsigned char *)&hostnum;
    printf("%d %d %d %d\n",
           *phostnum, *(phostnum + 1), *(phostnum + 2), *(phostnum + 3));

    return 0;
}

运行结果:

a : 0x102
b : 0x201
*******************************
100 1 168 192 
*******************************
net port : 0x201
host port : 0x102
*******************************
192 168 1 1

6.3 socket地址

socket地址是一个结构体,封装了端口号和ip等信息。

客户端要访问服务器,要知道服务器的ip和port。

socket地址分为:通用socket地址、专用socket地址。

6.3.1 通用socket地址

socket网络编程接口中表示socket地址的是结构体sockaddr,其定义如下:

#include <bits/socket.h>
struct sockaddr {
	sa_family_t sa_family;
	char sa_data[14];
}
typedef unsigned short int sa_family_t;

参数sa_family成员是地址族类型(sa_family_t)的变量,地址族类型通常于协议族类型对应,常见的协议族(protocol family,也称domain)和对应的地址族如下所示,PF_*AF_*可以混用:

协议族 地址族 描述
PF_UNIX AF_UNIX UNIX本地域协议族
PF_INET AF_INET TCP/IPv4协议族
PF_INET6 AF_INET6 TCP/IPv6协议族

参数sa_data用于存放socket地址值,不同协议族的地址值具有不同的含义和长度:

协议族 地址值含义和长度
PF_UNIX sa_data表示文件的路径名,长度可达到108字节
PF_INET sa_data表示16bit端口号和32bit IPv4地址,共6字节
PF_INET6 sa_data表示16bit端口号,32bit流表示,128bit IPv6地址,32bit范围ID,共26字节

14字节的sa_data不能容纳多数协议族的地址值,因此,Linux定义了新的通用的socket地址结构体socketaddr_storage,该结构体不仅提供了足够大的空间用于存放地址值,而且是内存对齐的。

#include <bits/socket.h>
struct sockaddr_storage
{
	sa_family_t sa_family;  // 协议族
    unsiged long int __ss_align;  // 用来做内存对齐的
    char __ss_padding[128 - sizeof(__ss_align)];  // 存储socket地址信息
}
typedef unsigned short int sa_family_t;
6.3.2 专用socket地址

很多网络编程函数诞生早于IPv4协议,当时使用的是struct sockaddr结构体,为了向前兼容,现在的sockaddr退化成了(void*)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

结构体类型 成员
struct sockaddr 16位地址类型 14字节地址数据
struct sockaddr_in 16位地址类型:AF_INET 16位端口号 32位IP地址 8字节填充
struct sockaddr_un 16位地址类型:AF_UNIX/AF_LOCAL 108字节路径名
struct sockaddr_in6 16位地址类型:AF_INET6 16位端口号 32位 flow label 128位IP地址 32位 scope ID

TCP/IP协议族有sockaddr_insockaddr_in6两个专用的socket地址结构体,它们分别用于IPv4和IPv6:

#include <netinet/in.h>
struct sockaddr_in
{
    sa_family_t sin_family;  // __SOCKADDR_COMMON(sin_),地址族协议
    in_port_t sin_port;  // port number 端口号,2字节
    struct in_addr sin_addr;  // internet address IP地址 4字节
    unsigned char sin_zero[sizoef(struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - sizeof(struct in_addr)];  // 填充部分
}
struct in_addr
{
    in_addr_t s_addr;
}
struct sockaddr_in6
{
    sa_family_t sin6_family;
    in_port_t sin6_port;  // Transport layer port
    uint32_t sin6_flowinfo;  // IPv6 flow information
    struct in6_addr sin6_addr;  // IPv6 地址
    uint32_t sin6_scope_id;  // IPv6 scope-id
}
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
#define __SOCKADDR_COMMON_SZIE (sizeof(unsigned short int))

所有专用socket地址以及sockaddr_storage类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制转换),因为所有的socket接口API使用的地址参数类型都是sockaddr

6.4 IP地址转换

IP地址转换就是将字符串ip转换为整数或者主机与网络字节序转换。通常用可读性好的字符串表示IP地址,比如用点分十进制字符串表示IPv4地址,以及用十六进制字符串表示IPv6地址。

下面3个函数可用于点分十进制字符串表示的IPv4地址和用网络字节序整数表示的IPv4地址之间的转换:

#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);  //  转换成int类型整数(网络字节序)
int inet_aton(cosnt char *cp,struct in_addr *inp);  // address to network; cp:点分十进制字符串; inp: 保存转换后的IP(网络字节序); 返回值:1表示成功,0-表示非法
char *inet_ntoa(struct in_addr in);  // 网络字节序的整数转换为字符串

下面是更新的函数也可以完成上面3个函数同样的功能,并且同时适用IPv4地址和IPv6地址,建议使用下面的两个函数。

#include <arpa/inet.h>
// p表示点分十进制的IP字符串,n表示网络字节序的整数

int inet_pton(int af,const char *src,void *dst); // 从点分十进制字符串类型IP 转换为 网络字节序地址,
				// af是使用的地址协议族,AF_INET或AF_INET6;
				// 返回值:1表示成功,0表示非法的参数,-1表示错误
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);  // 将网络字节序的整数 转换为 点分十进制的字符串;
																			// src -> dst,结果保存在dst中;
						// size:指定dst可用的字节数;
						// 返回值:返回转换后的字符串,与dst是用一个值(dst必须设置为非空指针)

示例:

/**
 * @file iptrans.c
 * @author zoya (2314902703@qq.com)
 * @brief  IP地址转换函数
 * @version 0.1
 * @date 2022-10-09
 *
 * @copyright Copyright (c) 2022
 *
 * inet_pton()  // 字符串 转换为 网络IP
 * inet_ntop()  // 网络IP 转换为 字符串
 */

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    // 将点分十进制的字符串 转换为 网络字节序的整数
    char buf[] = "192.168.1.100";

    unsigned int netnum = 0;
    inet_pton(AF_INET, buf, &netnum);
    printf("ip : %s \n", buf);
    unsigned char *p = (unsigned char *)&netnum;
    printf("netnum %d : %d %d %d %d\n", netnum, *p, *(p + 1), *(p + 2), *(p + 3));

    // 将网络字节序的整数 转换 为点分十进制的字符串
    netnum += 1;
    char pbuf[16] = "";
    const char *str = inet_ntop(AF_INET, (void *)&netnum, pbuf, 16);
    printf("netnum %d : %d %d %d %d\n", netnum, *p, *(p + 1), *(p + 2), *(p + 3));
    printf("ip : %s, %s\n", pbuf, str);

    return 0;
}

运行结果:

ip : 192.168.1.100 
netnum 1677830336 : 192 168 1 100
netnum 1677830337 : 193 168 1 100
ip : 193.168.1.100, 193.168.1.100
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux】网络编程二:socket简介、字节序、socket地址及地址转换API 的相关文章

随机推荐

  • Android学习之完整的注册登录Demo(APP端+服务器端)

    因比赛或者项目需要 写了几个小打小闹的APP 有的处于 单机 状态 有的处于 半联网 状态 觉得学习有点操之过急 所以先缓一缓 梳理一下之前所学的知识 将之前做的一些小玩意儿 整理出来写成博客 第一篇便是大部分APP都具有的注册登录系统 代
  • 15款最好用的思维导图(心智图 )工具

    原文地址 http www linuxidc com Linux 2015 01 111807 htm 思维导图也叫心智图 是一项流行的全脑式学习方法 用来表示词 思路 任务或其他与围绕着一个中央关键词或想法项目的示意图 通过径向 图形和非
  • 【控制】基于PID实现水箱控制系统matlab代码

    1 内容介绍 计算机控制技术 课程是电气信息类专业的主干课程之一 实验教学是该课程教学的重要组成部分 本实验项目以水箱液位为控制参量 设计了包括系统硬件 基于Matlab的控制界面 PID控制程序等内容的 计算机控制技术 实验方案 通过教学
  • 二叉树:总结篇(需要掌握的二叉树技能都在这里了)

    二叉树 总结篇 需要掌握的二叉树技能都在这里了 二叉树的理论基础 二叉树的遍历方式 1 深度优先遍历 2 广度优先遍历 求二叉树的属性 1 是否对称 2 求最大深度 3 求最小深度 4 求有多少个节点 5 是否平衡 6 找所有路径 7 递归
  • 剑指offer_第20题_包含min函数的栈_Python

    题目描述 定义栈的数据结构 并在该类型中实现一个能够得到栈中所含最小元素的min函数 时间复杂度应为O 1 理解 什么是栈 算法复杂度 解题思路 思路1 class Solution def init self self stack sel
  • ant.design的input框同时绑定onBlur和onPressEnter事件且方法为同一个方法后的结果

    ant design的input框同时绑定onBlur和onPressEnter事件且方法为同一个方法后的结果 前几天在做项目时碰到了这么一件事 就是在input上面同时绑定onBlur事件和onPressEnter 而且这两个事件还同时使
  • Python爬虫实战案例——第二例

    某某美剧剧集下载 从搜索片名开始 本篇文章主要是为大家提供某些电影网站的较常规的下载电影的分析思路与代码思路 通过爬虫下载电影 我们会从搜索某部影片的关键字开始直到成功下载某一部电影 地址 aHR0cHM6Ly93d3cuOTltZWlqd
  • esp32 SPIFFS的使用

    读取方法 include FS h include SPIFFS h include AutoFile h void File Init SPIFFS begin true 挂载 时间较长 void contentWrite String
  • 目标检测 - 主流算法介绍 - 从RCNN到DETR

    目标检测是计算机视觉的一个非常重要的核心方向 它的主要任务目标定位和目标分类 在深度学习介入该领域之前 传统的目标检测思路包括区域选择 手动特征提取 分类器分类 由于手动提取特征的方法往往很难满足目标的多样化特征 传统方法始终没能很好的解决
  • Inno Setup 添加管理员权限

    1 在 Setup 节点添加 PrivilegesRequired admin 2 在Inno Setup的安装目录下有个SetupLdr e32文件 用exescope打开 然后在十六进制区域编辑 在右边ASCII显示区域无法编辑 修改到
  • 关于新版vscode的ul>li*3等,不提示快捷方式解决方法

    到文件首选项的设置里搜索emmet 然后找到Emmet Use Inline Completions 勾选上就可以了 快捷方式 如ul gt li 3 加上tab键 注意按enter键无效
  • MKL——常用函数说明

    Intel MKL 全称 Intel Math Kernel Library 提供经过高度优化和大量线程化处理的数学例程 面向性能要求极高的科学 工程及金融等领域的应用 MKL是一款商用函数库 提供C Fortran 和 Fortran 9
  • 以太坊的企业系统集成

    最流行的开源Java集成库 Apache Camel现在支持以太坊的JSON RPC API 以太坊生态系统 以太坊是一个开源 公共 区块链平台 用于运行智能合约 它提供了一个去中心化的图灵完备虚拟机 可以执行脚本和加密货币 用于补偿参与者
  • Android 根目录下和应用目录下的build.gradle的详解,以及groovy语法的讲解

    博主前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住也分享一下给大家 点击跳转到网站 前言 Gradle的作用 打apk包 打插件包 自动化构建 多渠道打包 自动化签名 后台java打包 生成文件 使用的是groovy语法
  • 数据中台-数据安全管理-11

    文章目录 数据安全管理 11 1 数据安全面临的调整 11 1 1 数据安全问题带来的4大损害 11 1 2 法律和政策背景 11 1 3 数据安全的4大技术挑战 1 平台安全 2 服务安全 3 数据本身的安全 4 APT攻击防御 11 1
  • 华为OD机试 - 阿里巴巴找黄金宝箱(I)(Java)

    题目描述 一贫如洗的樵夫阿里巴巴在去砍柴的路上 无意中发现了强盗集团的藏宝地 藏宝地有编号从0 N的箱子 每个箱子上面贴有一个数字 箱子中可能有一个黄金宝箱 黄金宝箱满足排在它之前的所有箱子数字和等于排在它之后的所有箱子数字之和 第一个箱子
  • Verilog实现呼吸灯效果

    呼吸灯的效果采用PWM调波的形式 即快速的改变每个周期的占空比 一个周期内高电平时间占一个周期时间的比值 来实现点亮到熄灭的效果 示意如下图 而关于整个波形图 用50MHz的晶振 从0开始计数到49则为1us 而1ms是1us的1000倍
  • elasticsearch update 无结果

    这个一直不能用 for id in data front res es update index index doc type doc type id id body doc flag 0 print id res break total
  • Android 块设备驱动之二

    将块设备添到系统 添加磁盘和分区到系统中 add diskgendisk函数 register disk add partition 块设备操作 读写操作 请求操作 提交读写请求 将块设备添到系统 调用void blk register r
  • 【Linux】网络编程二:socket简介、字节序、socket地址及地址转换API

    参考连接 https www nowcoder com study live 504 2 16 Linux 网络编程一 网络结构模式 MAC IP 端口 网络模型 协议及网络通信过程简单介绍 Linux 网络编程二 socket简介 字节序