【套接字-Socket】学习笔记
1. 套接字(Socket)的概念
TCP/IP网络模型有四层:网络接口层、网络互连层、传输层和应用层。
当应用层(用户)通过传输层进行数据通信时,TCP协议或者UDP协议可能会需要同时为多个进程提供并发服务,也就是说多个连接或者进程需要通过同一个协议端口进行数据的传输。
因此,计算机操作系统为应用程序与TCP/IP协议的交互提供了称为套接字(Socket)的接口,可以区分不同进程间的网络通信和连接。
套接字对于程序员来说是一套网络通信的接口。网络通信的主体分为两部分:客户端和服务器端。在网络通信时,我们需要注意以下三个概念:IP、端口和通信数据。
2. TCP协议介绍
TCP协议是传输层协议,它具有以下三点特性:
- 面向连接:它是一个双向连接,三次握手完成连接,四次挥手断开连接;
- 安全:在TCP通信过程中,会对发送的每一个数据包进行校验,如果发现丢失会进行自动重传;
- 流式传输:发送端和接收端处理数据的速度,数据的量都可以不一致。
面向连接就是我们所谓的三次握手,而对于程序员来说,socket已经帮我们封装好了,只需要在客户端调用connect()
函数,就可以进行三次握手。我们首先来了解一下TCP协议的格式和三次握手的具体流程。
TCP头部
上图是一TCP头部的各个字段:
- 源端口号和目的端口号,字段都是长16位(bit),2个字节(byte)
- 序号:
(1)TCP是面向字节流的,在一个TCP连接中传送的字节流中的每一个字节都要按顺序编号。
(2)首部中的序号字段是指本报文段所发送的数据的第一个字节的序号,是随机生成的(ISN)。每次发送数据时,序号值=ISN+数据在整个字节流中的偏移量
。假设A->B且ISN=1024,第一段数据的512byte已经发送到B了,那么第二段数据发送的序号为1024+512。
(3)序号是循环使用的,当序号增加到最大值时,下一个序号就又回到了0。
(4)序号可以用于解决网络包乱序的问题。 - 确认序号,表示收到的下一个报文段的第一个数据字节的序号,它的值是收到的序号值+1。
- 首部长,标识首部有多少个4字节*首部长,最大15,即60字节。
- 8个标志位
(1)CWR:与后面的ECE标志都用于IP首部的ECN字段,ECE标志为1时,则通知对方已将拥塞窗口缩小。
(2)ECE:若其值为1则会通知对方,从对方到这边的网络有阻塞。在收到数据包的IP首部中ECN为1时将TCP首部中的ECE设为1。
(3)URG:该位设为 1,表示包中有需要紧急处理的数据,对于需要紧急处理的数据,与后面的紧急指针有关。
(4)ACK:该位设为 1,确认应答的字段有效,TCP规定除了最初建立连接时的 SYN 包之外该位必须设为 1。
(5)PSH:该位设为 1,表示需要将收到的数据立刻传给上层应用协议,若设为 0,则先将数据进行缓存。
(6)RST:该位设为 1,表示 TCP 连接出现异常必须强制断开连接。
(7)SYN:用于建立连接,该位设为 1,表示希望建立连接,并在其序列号的字段进行序列号初值设定。
(8)FIN:该位设为 1,表示今后不再有数据发送,希望断开连接。 - 窗口大小,表示从确认序号所指位置开始能够接收的数据大小,TCP不允许发送超过该窗口大小的数据。
三次握手
第一次握手
- 客户端:向服务器端发起连接请求,其报文中的SYN字段设置为1,生成了随即序号x,seq=x。
- 服务器端:接收客户端发送的请求数据,解析tcp协议,校验SYN标志位是否为1,并得到序号x。
第二次握手
服务器端:给客户回复数据
- 回复ACK,将tcp协议的ACK标志位设为1,表示同意客户端建立连接的请求
- 回复ACK=x+1确认号。
3. TCP通信流程
4. Socket编程中的细节
字节序
目前在各种体系的计算机中所采用的字节存储机制主要有:Big-Endian(大端)和Little-Endian(小端)。
- 小端:数据的低位字节存储到内存的低地址位,数据的高位字节存储到内存的高地址位。计算机中的数据存储默认使用小端。
- 大端:数据的低位字节存储到内存的高地址位,数据的高位字节存储到内存的低地址位。套接字通信过程中的临时数据都是大端存储的,包括接受/发送的数据、IP地址、端口。
在Socket中已经提供了封装好的数据转换接口,包括从主机字节序到网络字节序的转换函数htons
、htonl
;从网络字节序到主机字节序的转换函数ntohs
、ntohl
。
#include <arpa/inet.h>
// u:unsigned
// 16: 16位, 32:32位
// h: host, 主机字节序
// n: net, 网络字节序
// s: short
// l: int
// 这套api主要用于 网络通信过程中 IP 和 端口 的 转换
// 将一个短整形从主机字节序 -> 网络字节序
uint16_t htons(uint16_t hostshort);
// 将一个整形从主机字节序 -> 网络字节序
uint32_t htonl(uint32_t hostlong);
// 将一个短整形从网络字节序 -> 主机字节序
uint16_t ntohs(uint16_t netshort)
// 将一个整形从网络字节序 -> 主机字节序
uint32_t ntohl(uint32_t netlong);
IP地址转换
在使用过程中IP地址是通过字符串来描述,也需要进行大小端的转换:
主机字节序 -> 网络字节序
// 主机字节序的IP地址是字符串, 网络字节序IP地址是整形
int inet_pton(int af, const char *src, void *dst);
int af
是IP协议,AF_INET
是IPV4格式的IP地址,AF_INET6
是IPV6格式的IP地址;const char *src
是要转换的点分十进制IP地址;void *dst
转换后得到的大端整形IP。
网络字节序 -> 主机字节序
#include <arpa/inet.h>
// 将大端的整形数, 转换为小端的点分十进制的IP地址
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
const char *src
是要转换的大端整形IP地址;void *dst
是转换后的小端点分十进制IP地址;socklen_t size
是指dst
中最多能存储多少个字节。