第一时间收到文章更新
已获得原公众号的授权转
前言
不明白 Socket 是什么的主要原因其实就是没有实际的网络编程经验,就没有在代码里用过 Socket,背来背去还是脑袋一片浆糊,很正常,看完这篇文章肯定就清楚了(狗头)
TCP 四元组
要说 Socket,那当然不能绕过 TCP 了,各位不妨先来思考下如何确定一个 TCP 连接?
以小黑和小白为例,他们分别位于不同的小区,小黑找小白玩,需要知道小白的小区和门牌号,也就是说,小区 + 门牌号就是小白家的入口,知道了这个入口,小黑就能找到小白。反之也是同样的。
小区类比于 IP 地址,门牌号类比于端口号,
IP 地址 + 端口号
(小区 + 门牌号) 就能唯一确定一个程序。光有小区不行,光有门牌号也不行,所以这就是为什么说网络层负责建立主机到主机的通信(IP 地址),传输层负责建立端口到端口的通信(端口号)了。
这个很好记忆,你上线一个网站的时候,如果没有绑定域名的话,那么就只能通过 IP 地址 + 端口号(默认是 80,浏览器上不显示)访问。
总结下就是,
TCP 四元组
可以唯一的确定一个连接,四元组包括如下:
其中:
-
IP 地址(源地址和目的地址,32位)是在 IP 头部中
-
端口号(源端口和目的端口,16位)是在 TCP 头部中
Socket
掌握了四元组这个基本概念,我们再来解释 Socket。
上文说过,小区 + 门牌号是住宅的入口,
IP 地址 + 端口号是一个程序的入口,这个入口就是 Socket
,那么服务端和客户端之间想要进行通信,只要互相暴露出自己的入口(Socket),就能够找到彼此了。
更严谨来说,
Socket 封装了基本的通信功能,是 TCP/IP 协议的基本操作单元
。
以 Java 中的
Socket
类为例,服务端和客户端首先都需要调用构造函数创建 Socket 暴露自己的入口(绑定 IP 地址和端口,也可以调用
bind
方法进行绑定)
光暴露了入口还不行,你还得竖起耳朵听,不然别人来敲门你听不见那也没法通信啊,所以接下来服务端调用
accept()
方法,该方法将一直等待,直到客户端请求服务端的入口,再就是 TCP 三次握手建立连接的过程了。
服务端 Socket 创建一般使用
ServerSocket
类,该类提供了非常重要的 accept(建立连接) :
那客户端是如何请求服务端的入口的呢?也就是是如何发起连接的呢,客户端在创建好 Socket 后,调用
connect(host, port)
函数发起连接,该函数需要指明服务端的 IP 地址和端口号。
所以说,
TCP 三次握手其实是发生在客户端
connect
和服务端
accept
两个函数之间
。握手完了就可以通过
read()
和
write()
来通信啦。这里需要重点注意的是:
监听的 Socket 和真正用来传数据的 Socket 是两个不同的 Socket
:
看下上述的
ServerSocket.accept
方法就明白了,
accept
会返回一个 Socket 对象,后续服务端和客户端之间的数据传输都用这个 Socket:
事实上,在三次握手的过程中,内核(Kernel)为每个连接都维护了两个队列:
-
TCP 半连接队列
:这个队列存储没有完成三次握手的 Socket,此时服务端处于
syn_rcvd
的状态;
-
TCP 全连接队列
:这个队列存储已经完成了三次握手的 Socket,此时服务端处于
established
状态;
当 TCP 全连接队列不为空后,服务端的
accept()
函数,就会
从内核中的 TCP 全连接队列里拿出一个已经完成连接的 Socket
并返回,用于后续服务端和客户端的通信。
总结
综上, 基于 TCP 协议的 Socket 调用过程就结束了,下面由贴心助理 ChatGPT 总结下:
以下全是 ChatGPT 生成的结果
,没有一个字是我写的(😂),虽然是我引导了很多轮的结果,但是输入合适的 Promt 并配合上下文 ChatGPT 基本能输出 90% 想要的内容,确实太强了
文字解释
:
代码示例