1. socket 介绍:
简介:
所谓 socket(
套接字),就是对网络中不同主机上的应用进程之间进行双向通信的
端点的抽象。
一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处
的地位来讲,套接字
上联应用进程,下联
网络协议栈,是应用程序通过网络协议进行
通信的接口,
是应用程序与网络协议根进行
交互的接口。
socket 可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概
念。它是网络环境中进程间通信的 API,也是可以被命名和寻址的通信端点,使用中的每一个套接
字都有其类型和一个与之相连进程。
通信时其中一个网络应用程序将要传输的一段信息写入它所在
主机的 socket 中,该 socket 通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台
主机的 socket 中,使对方能够接收到这段信息。socket 是由
IP 地址和端口结合的,提供向应用
层进程传送数据包的机制。
socket 本身有“插座”的意思,在
Linux 环境下,用于表示进程间网络通信的
特殊文件类型。本质为
内核借助缓冲区形成的伪文件。既然是文件,那么理所当然的,我们可以
使用文件描述符引用套接
字。与管道类似的,Linux 系统将其
封装成文件的目的是为了统一接口,使得读写套接字和读写文
件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传
递。
A将数据先写入写缓冲区,然后封装后发送到B,B分用(解封装)后数据被放入套接字的读缓冲区。
// 套接字通信分两部分:
- 服务器端:被动接受连接,一般不会主动发起连接
- 客户端:主动向服务器发起连接
socket是一套通信的接口,Linux 和 Windows 都有,但是有一些细微的差别。
2. 字节序:
简介:
现代 CPU 的累加器一次都能装载(至少)
4 字节(这里考虑
32 位机),即一个整数。那么这 4
字节
在内存中排列的顺序将影响它被累加器装载成的整数的值,这就是字节序问题。在各种计算机
体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问
题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传送。
如
果不达成一致的规则,通信双方将无法进行正确的编码/译码从而导致通信失败。
字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数
据当然就无需谈顺序的问题了)。
字节序分为大端字节序(Big-Endian) 和小端字节序(Little-Endian)。
大端字节序是指一个整
数的
最高位字节(23 ~ 31 bit)存储在内存的低地址处,
低位字节(0 ~ 7 bit)存储在内存的高地
址处;小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地
址处。
字节序举例:
上图0x11是最高位,所以放在高地址处(内存地址增长方向)。
上图0x12是最高位,所以放在低地址处。
判断本地主机是大端序还是小端序代码:
/*
字节序:字节在内存中存储的顺序。
小端字节序:数据的高位字节存储在内存的高位地址,低位字节存储在内存的低位地址
大端字节序:数据的低位字节存储在内存的高位地址,高位字节存储在内存的低位地址
*/
// 通过代码检测当前主机的字节序
#include <stdio.h>
int main() {
union {
short value; // 2字节
char bytes[sizeof(short)]; // char[2]
} test;
test.value = 0x0102;
if((test.bytes[0] == 1) && (test.bytes[1] == 2)) {
printf("大端字节序\n");
} else if((test.bytes[0] == 2) && (test.bytes[1] == 1)) {
printf("小端字节序\n");
} else {
printf("未知\n");
}
return 0;
}
字节序转换函数 :
当格式化的数据在两台使用不同字节序的主机之间直接传递时,接收端必然错误的解释之。解决问题的方法是:发送端总是把要发送的数据转换成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。
网络字节顺序
是
TCP/IP
中规定好的一种数据表示格式,它与具体的
CPU
类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释,网络字节顺序采用大端排序方式。
BSD Socket
提供了封装好的转换接口,方 便程序员使用。包括从主机字节序到网络字节序的转换函数:htons、
htonl
;从网络字节序到主机字节序的转换函数:
ntohs
、
ntohl
。
h - host 主机,主机字节序
to - 转换成什么
n - network 网络字节序
s - short unsigned short
l - long unsigned int
#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); // 网络字节序 - 主机字节序
测试代码如下:
/*
网络通信时,需要将主机字节序转换成网络字节序(大端),
另外一段获取到数据以后根据情况将网络字节序转换成主机字节序。
// 转换端口
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); // 网络字节序 - 主机字节序
*/
#include <stdio.h>
#include <arpa/inet.h>
int main() {
// htons 转换端口
unsigned short a = 0x0102;
printf("a : %x\n", a);
unsigned short b = htons(a);
printf("b : %x\n", b);
printf("=======================\n");
// htonl 转换IP
char buf[4] = {192, 168, 1, 100};
int num = *(int *)buf;// 强转成int *,是4个字节,buf一共4个字节
int sum = htonl(num);
unsigned char *p = (char *)∑
printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3));
printf("=======================\n");
// ntohl
unsigned char buf1[4] = {1, 1, 168, 192};
int num1 = *(int *)buf1;
int sum1 = ntohl(num1);
unsigned char *p1 = (unsigned char *)&sum1;
printf("%d %d %d %d\n", *p1, *(p1+1), *(p1+2), *(p1+3));
// ntohs
return 0;
}
输出结果为:
再来看一下a和b的输出疑问:
gpt的解答:
你的理解基本正确,我会进一步解释一下。
在计算机中,整数在内存中以二进制形式存储,而不同的字节序会影响在内存中的表示方式。对于小端序(Little Endian)机器,最低有效字节存储在最低内存地址,而对于大端序(Big Endian)机器,最高有效字节存储在最低内存地址。
当你使用 `%x` 格式化输出时,实际上是将内存中的二进制数据格式化为十六进制字符串,这并不会改变数据在内存中的存储方式。因此,`a` 在小端序机器上在内存中的表示是 `02 01`,但是在使用 `%x` 输出时,它被格式化为 `0x0102`,这只是一种表示形式。
同样,`htons` 和 `htonl` 等函数并不改变内存中的实际存储方式,它们只是负责在不同字节序之间进行转换。这些函数的目的是确保在网络通信中数据的一致性,而不是改变内存中的存储方式。
所以,你的理解是正确的:输出的值是通过格式化方式呈现的,而不是真正改变了内存中的存储方式。