要想客户端和服务器能在⽹络中通信,那必须得使⽤ Socket 编程,它是进程间通信⾥⽐较特别的⽅式,
特别之处在于它是可以跨主机间通信。 Socket 的中⽂名叫作插⼝,咋⼀看还挺迷惑的。事实上,双⽅要进⾏⽹络通信前,各⾃得创建⼀个 Socket,这相当于客户端和服务器都开了⼀个“⼝⼦”,双⽅读取和发送数据的时候,都通过这个“⼝⼦”。 这样⼀看,是不是觉得很像弄了⼀根⽹线,⼀头插在客户端,⼀头插在服务端,然后进⾏通信。 创建 Socket 的时候,可以指定⽹络层使⽤的是 IPv4 还是 IPv6,传输层使⽤的是 TCP 还是 UDP。 UDP 的 Socket 编程相对简单些,这⾥我们只介绍基于 TCP 的 Socket 编程。 服务器的程序要先跑起来,然后等待客户端的连接和数据,我们先来看看服务端的 Socket 编程过程是怎样 的。 服务端⾸先调⽤ socket() 函数,创建⽹络协议为 IPv4,以及传输协议为 TCP 的 Socket ,接着调⽤ bind() 函数,给这个 Socket 绑定⼀个 IP 地址和端⼝,绑定这两个的⽬的是什么? 绑定端⼝的⽬的:当内核收到 TCP 报⽂,通过 TCP 头⾥⾯的端⼝号,来找到我们的应⽤程序,然后 把数据传递给我们。 绑定 IP 地址的⽬的:⼀台机器是可以有多个⽹卡的,每个⽹卡都有对应的 IP 地址,当绑定⼀个⽹卡 时,内核在收到该⽹卡上的包,才会发给我们; 绑定完 IP 地址和端⼝后,就可以调⽤ listen() 函数进⾏监听,此时对应 TCP 状态图中的 listen ,如果 我们要判定服务器中⼀个⽹络程序有没有启动,可以通过 netstat 命令查看对应的端⼝号是否有被监听。 服务端进⼊了监听状态后,通过调⽤ accept() 函数,来从内核获取客户端的连接,如果没有客户端连 接,则会阻塞等待客户端连接的到来。 那客户端是怎么发起连接的呢?客户端在创建好 Socket 后,调⽤ connect() 函数发起连接,该函数的参 数要指明服务端的 IP 地址和端⼝号,然后万众期待的 TCP 三次握⼿就开始了。 在 TCP 连接的过程中,服务器的内核实际上为每个 Socket 维护了两个队列: ⼀个是还没完全建⽴连接的队列,称为 TCP 半连接队列,这个队列都是没有完成三次握⼿的连接, 此时服务端处于 syn_rcvd 的状态; ⼀个是⼀件建⽴连接的队列,称为 TCP 全连接队列,这个队列都是完成了三次握⼿的连接,此时服 务端处于 established 状态; 当 TCP 全连接队列不为空后,服务端的 accept() 函数,就会从内核中的 TCP 全连接队列⾥拿出⼀个已 经完成连接的 Socket 返回应⽤程序,后续数据传输都⽤这个 Socket。 注意,监听的 Socket 和真正⽤来传数据的 Socket 是两个: ⼀个叫作监听 Socket; ⼀个叫作已连接 Socket; 连接建⽴后,客户端和服务端就开始相互传输数据了,双⽅都可以通过 read() 和 write() 函数来读写数 据。