IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 【Linux】网络编程二:socket简介、字节序、socket地址及地址转换API -> 正文阅读

[系统运维]【Linux】网络编程二:socket简介、字节序、socket地址及地址转换API

参考连接: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

  • 大端字节序是指一个整数的最高位字节(2331bit)存储在内存的低地址处。低位字节(07bit)存储在内存的高地址处;采用这种机制的处理器有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;
地址大端存储的数据小端存储的数据
2000H12HCDH
2001H34HABH
2002HABH34H
2003HCDH12H

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

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_UNIXAF_UNIXUNIX本地域协议族
PF_INETAF_INETTCP/IPv4协议族
PF_INET6AF_INET6TCP/IPv6协议族

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

协议族地址值含义和长度
PF_UNIXsa_data表示文件的路径名,长度可达到108字节
PF_INETsa_data表示16bit端口号和32bit IPv4地址,共6字节
PF_INET6sa_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 sockaddr16位地址类型14字节地址数据
struct sockaddr_in16位地址类型:AF_INET16位端口号32位IP地址8字节填充
struct sockaddr_un16位地址类型:AF_UNIX/AF_LOCAL108字节路径名
struct sockaddr_in616位地址类型:AF_INET616位端口号32位 flow label128位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
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-10-22 21:56:36  更:2022-10-22 21:56:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 6:22:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码