套接字通信
套接字通信
1.基本概念
1.1 局域网和广域网
局域网:局域网将一定区域内的各种计算机、外部设备和数据库连接起来形成计算机通信的私有网络。、
- 局域网的主要特点是传输速度快、延迟低、成本较低且安全性高。
广域网:又称广域网、外网、公网。是连接不同地区局域网或城域网计算机通信的远程公共网络。
- 广域网相对于局域网来说,传输速度较慢,延迟较高。
1.2 IP和端口
IP: 是分配给网络上每个设备的唯一标识符,用于在网络中进行通信。
IPv4
- IPv4使用32位整数来表示一个IP地址,这相当于4个字节。通常,这个32位的地址被分为四部分,每部分8位(1字节),并以点分十进制格式表示。
- 最小的IPv4地址是
0.0.0.0
,而最大的是255.255.255.255
- 鉴于IPv4采用32位地址,理论上总共可以提供 个不同的地址
IPv6
- IPv6使用128位整数来表示一个IP地址,等同于16个字节。
- Pv6地址写作8组,每组4个十六进制数字,各组之间用冒号分隔,例如
2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b
。 - 由于IPv6采用128位地址长度,所以它可以提供的地址总数为
端口: 用于定位主机上的特定进程。
端口号是一个16位的无符号整数(
unsigned short
),其取值范围是0到65535()只有那些涉及网络通信的进程才需要绑定端口。如果一个进程不需要与网络上的其他设备进行通信,那么它就不必绑定端口。
一个端口可以被重复使用吗?
在任何给定时间,一个具体的端口只能由一个进程独占使用。这意味着在同一时刻,多个进程不能同时监听同一个端口以接收数据。然而,一旦某个进程完成了对特定端口的使用并释放了它,该端口就可以被新的进程重新绑定和使用。此外,在一些情况下,如TCP连接结束后,可能会有一段等待时间(TIME_WAIT状态),在此期间端口暂时不可用,以确保网络上的数据包不会被错误地路由到新建立的连接上。
1.3 网络分层模型
应用层:直接为用户提供服务,负责处理特定的应用程序细节。
表示层:将数据转换为兼容格式进行传输,包括加密、压缩等操作。
- 会话层:管理不同机器上进程之间的对话,控制对话连接的建立和终止。
- 传输层:确保端到端的数据可靠传输,提供错误检测和恢复功能。主要协议有TCP和UDP。
- 网络层:负责数据包的路由选择和转发,决定数据如何从源地址到达目的地址。IP协议工作在此层。
- 数据链路层:在不可靠的物理连接上提供可靠的数据传输,处理错误检测和纠正。以太网协议工作于此层。
- 物理层:定义了硬件设备的标准,如电压水平、线缆类型、针脚布局等,以及如何通过物理媒介传输比特流。
2.socket编程
2.1 字节序
字节序: 大于一个字节类型的数据在内存中的存放顺序,也就是说对于单字符来说是没有字节序问题的,字符串是单字符的集合,因此字符串也没有字节序问题。
主机字节序 (小端): 数据的低位字节存储到内存的低地址位, 数据的高位字节存储到内存的高地址位
网络字节序 (大端): 数据的低位字节存储到内存的高地址位, 数据的高位字节存储到内存的低地址位
注:套接字通信过程中操作的数据都是大端存储的,包括:接收/发送的数据、IP地址、端口。
2.2 大小端转换
主机字节序的IP地址转换为网络字节序:
1 | int inet_pton(int af, const char *src, void *dst); |
af
: 地址族(IP地址的家族包括ipv4和ipv6)协议
AF_INET
: ipv4格式的ip地址AF_INET6
: ipv6格式的ip地址
src
: 传入参数
dst
: 传出参数, 函数调用完成, 转换得到的大端整形IP被写入到这块内存中
返回值:成功返回1,失败返回0或者-1
将大端的整形数, 转换为小端的点分十进制的IP地址 :
1 | const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); |
af
: 地址族(IP地址的家族包括ipv4和ipv6)协议
AF_INET
: ipv4格式的ip
地址AF_INET6
: ipv6格式的ip
地址
src
: 传入参数, 这个指针指向的内存中存储了大端的整形IP地址
dst
: 传出参数, 存储转换得到的小端的点分十进制的IP地址
size
: 修饰dst
参数的, 标记dst
指向的内存中最多可以存储多少个字节
返回值:
- 成功: 指针指向第三个参数对应的内存地址, 通过返回值也可以直接取出转换得到的IP字符串
- 失败:
NULL
2.3 sockaddr
sockaddr
: 一个通用的、协议无关的套接字地址结构。
1 | struct sockaddr { |
sockaddr_in
: 是专门针对 IPv4 协议设计的套接字地址结构。
1 | typedef unsigned short uint16_t; |
2.4 套接字函数
1 | // 创建一个套接字 |
domain
: 使用的地址族协议
- AF_INET: 使用IPv4格式的ip地址
- AF_INET6: 使用IPv6格式的ip地址
type
:
- SOCK_STREAM: 使用流式的传输协议
- SOCK_DGRAM: 使用报式(报文)的传输协议
protocol
: 一般写0即可, 使用默认的协议
- SOCK_STREAM: 流式传输默认使用的是tcp
- SOCK_DGRAM: 报式传输默认使用的udp
函数的返回值是一个文件描述符,通过这个文件描述符可以操作内核中的某一块内存,网络通信是基于这个文件描述符来完成的。
2.5 bind
1 | // 将文件描述符和本地的IP与端口进行绑定 |
参数:
sockfd
: 监听的文件描述符, 通过socket()
调用得到的返回值addr
: 传入参数, 要绑定的IP和端口信息需要初始化到这个结构体中,IP和端口要转换为网络字节序addrlen
: 参数addr
指向的内存大小,sizeof(struct sockaddr)
3.TCP通信流程
在tcp的服务器端, 有两类文件描述符:
监听的文件描述符
- 只需要有一个
- 不负责和客户端通信, 负责检测客户端的连接请求, 检测到之后调用accept就可以建立新的连接
通信的文件描述符
- 负责和建立连接的客户端通信
- 如果有N个客户端和服务器建立了新的连接, 通信的文件描述符就有N个,每个客户端和服务器都对应一个通信的文件描述符
基于tcp的服务器端通信代码:
1 | // server.c |
基于tcp通信的客户端通信代码:
1 | // client.c |
4.补充
4.1 什么是 Socket?
Socket 可以看作是一个应用程序与网络之间的接口,它定义了如何进行数据发送和接收的方式。
4.2 Socket 的类型有哪些?
- 流式套接字(SOCK_STREAM):基于 TCP 协议,面向连接,可靠。
- 数据报套接字(SOCK_DGRAM):基于 UDP 协议,无连接,不可靠。
4.3 如何创建一个 Socket?
创建一个 Socket需要指定所使用的地址族和协议类型
1 | int sockfd = socket(AF_INET, SOCK_STREAM, 0); |
AF_INET
:IPv4 地址族。SOCK_STREAM
:流式套接字。- 第三个参数通常为 0,表示使用默认协议。
4.4 bind 函数的作用是什么?
将 Socket 绑定到指定的 IP 地址和端口号。
4.5 listen 函数的作用是什么?
将 Socket 设置为监听状态(它并不会阻塞程序,而是告诉操作系统:“我现在准备好了,可以开始接受客户端的连接请求了。”)
4.6 accept 函数的作用是什么?
进入阻塞状态等待接受客户端的连接请求,并返回一个新的 Socket 文件描述符用于通信。
4.7 connect 函数的作用是什么?
客户端调用该函数向服务器发起连接请求。
4.8 服务器中的两个文件描述符各自的作用?
服务器上分别有用于监听的文件描述符和用于通信的文件描述符,其各自有两个缓冲区,读缓冲区和写缓冲区。
监听的文件描述符所对应的读缓冲区用于接收客户端的连接请求,当调用accept()时会检测监听的文件描述符的读缓冲区是否有请求数据,如果有请求数据accept()会解除阻塞和对应的客户端建立连接。
通信的文件描述符的读缓冲区用于存储客户端所发送的通信数据,调用read()(没有数据则阻塞)可以把数据从读缓冲区读出来,write()把数据再写入写缓冲区(缓冲区满则阻塞),然后内核会将写缓冲区的数据发送给对应的客户端的读缓冲区。