前言
相信大家对于TCP已经非常熟悉了,学习过计算机网络的同学对于它的连接和断开流程应该已经烂熟于心了吧。
那么Socket是什么?
Socket是应用层与TCP/IP协议簇通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
两种套接字
1. TCP协议栈维护者两个socket缓冲区:send buffer和recv buffer
要通过TCP连接发送出去的数据都先拷贝到send buffer中。这个过程可能是从用户空间进程的app buffer拷入,也可能是从内核的kernel buffer拷入。拷贝的过程是通过send()函数完成的,因此也被称为写数据。由于也可以使用write()函数写入数据,所以send buffer也被称为write buffer。不过,send()函数比write()函数更有效率。
最终数据是通过网卡流出的,所以需要将send buffer中的数据拷贝到网卡中。由于一端是内存,一端是网卡设备,可以直接使用DMA的方式进行拷贝,无需CPU的参与。也就是说,send buffer中的数据通过DMA的方式拷贝到网卡中,并通过网络传输给TCP连接的另一端:接收端。
当通过TCP连接接收数据时,数据肯定是先通过网卡流入的。然后同样通过DMA的方式拷贝到recv buffer中。再通过recv()函数将数据从recv buffer拷入到用户空间进程的app buffer中。
流程如下图:
2. 监听套接字和已连接套接字
1)监听套接字
在服务进程读取配置文件时,从配置文件中解析出要监听的地址、端口,然后通过socket()函数创建的,然后再通过bind()函数将这个监听套接字绑定到对应的地址和端口上。随后,进程/线程就可以通过listen()函数来监听这个端口(严格的来说监控这个监听套接字)
在Java中socket变成的代码实现:
// 返回的server可以认为是监听套接字
ServerSocket server = new ServerSocket(8888);
->
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
setImpl();
if (port < 0 || port > 0xFFFF)
throw new IllegalArgumentException(
"Port value out of range: " + port);
if (backlog < 1)
backlog = 50;
try {
bind(new InetSocketAddress(bindAddr, port), backlog