学懂IO必备的TCP、socket知识(三)

系列文章目录

学懂IO必备的操作系统知识(一)
学懂IO必备的操作系统知识(二)
学懂IO必备的TCP、socket知识(三)


前言

io分为磁盘io和网络io,前两篇文章,唠叨了一些操作系统磁盘io的知识,接下来继续唠叨网络io的知识。要想扒光网络io的衣服,看到真容,那就必须找的祖辈了。什么io模型的升级呀,BIO、NIO、AIO,socket、tcp协议等等,你要是连她这些七大姑八大姨都不认识,你好意思好了解io吗?

一、socket是什么?

1.1 socket定义

socket就是一个四元组(保证唯一),c_ip:c_port + s_ip:s_port

特点:

  • 服务器可以分配的端口个数最大为65525(两个字节存储端口,最大2的16次大小)
  • 服务端在与客户端建立了连接后,服务端不需要再为连接新分配一个端口号,一直是固定的。
  • 只要服务器的资源足够可以建立成百万级的连接

1.2 socket参数

    //server socket 监听参数:
    private static final int RECEIVE_BUFFER = 10; 
    private static final int SO_TIMEOUT = 0;
    private static final boolean REUSE_ADDR = false;
    private static final int BACK_LOG = 2;
    
    //在服务端的 client socket 监听参数:
    private static final boolean CLI_KEEPALIVE = false;
    private static final boolean CLI_OOB = false;
    private static final int CLI_REC_BUF = 20;
    private static final boolean CLI_REUSE_ADDR = false;
    private static final int CLI_SEND_BUF = 20;
    private static final boolean CLI_LINGER = true;
    private static final int CLI_TIMEOUT = 0;
    private static final boolean CLI_NO_DELAY = false;

server socket参数:

  • RECEIVE_BUFFER : receive_Q 接收缓存字节大小,TCP发送缓存区和接收缓存区,默认是87380个字节,
  • SO_TIMEOUT: 等待客户连接的超时时间,单位毫秒,当服务器等待的时间超过了超时时间,就会抛出SocketTimeOutException,它是InterruptedException的子类;如果把serverSocket.setSoTimeOut(6000)去掉,那么服务器端会阻塞,直到接收到了客户的连接,才会从accept()方法返回
  • REUSE_ADDR: 是否允许重用服务器所绑定的地址,有些操作系统是不允许重用端口的。两个serversocket在监听相同端口前,设置true,等一个serversocket 进程关闭后,另个serversocket能立刻重用相同接口,类似于容错。
  • BACK_LOG: 备胎的连接个数,如果是2,则最多有3个可以建立全连接,连接状态为完成状态ESTABLISHED,新的连接服务端会是SYN_RECV状态。见图:
    在这里插入图片描述

client socket参数

  • ReceiveBufferSize: receive_Q 大小,TC接收缓存区,默认是87380个字节,

  • SendBufferSize: send_Q 大小, TCP发送缓存区,默认是16384个字节,16K

  • tcpNoDelay: 用来控制是否开启Nagle算法,是为了提高较慢的广域网传输效率,减小小分组的报文个数,该算法要求一个TCP连接上最多只能有一个未被确认的小分组,在该小分组的确认到来之前,不能发送其他小分组。这个跟报文中的MSS有关。开启了Nagle算法与没有开启的一个效果,如图:
    在这里插入图片描述

  • OOBInline: 表示是否支持发送一个字节的TCP 紧急数据。默认 false,当接收方收到紧急数据时不作任何处理,直接丢弃

  • KeepAlive: tcp如果双方建立了连接,2H没有通信,会发送心跳检测,如果没有响应,会持续11分钟,如果还是没有响应会断开。默认false

  • SoLinger: 用来控制Socket关闭时的行为。默认情况下,当执行Socket的close()方法,该方法会立即返回,但底层的Socket实际上并不立即关闭,它会延迟一段时间知道发送完所有剩余的数据,才会真正关闭Socket,才断开连接。socket.setSoLinger(true,0):方法立即返回, 会导致socket底层立即关闭,未发送和未接收的数据会丢弃。socket.setSoLinger(true,1000); /该方法不会立即返回,而是进入阻塞状态。 只有当底层的Soket发送完所有的剩余数据或阻塞时间已经超过了1000秒,再返回,但是剩余未发送的数据被丢弃。

  • SoTimeout: 设置socket调用InputStream读数据的超时时间,以毫秒为单位,如果超过这个时候,会抛出java.net.SocketTimeoutException。

示例 服务端代码:


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * BIO  多线程的方式
 */
public class SocketIOPropertites {
    //server socket listen property:
    private static final int RECEIVE_BUFFER = 10;
    private static final int SO_TIMEOUT = 0;
    private static final boolean REUSE_ADDR = false;
    private static final int BACK_LOG = 2;
    //client socket listen property on server endpoint:
    private static final boolean CLI_KEEPALIVE = false;
    private static final boolean CLI_OOB = false;
    private static final int CLI_REC_BUF = 20;
    private static final boolean CLI_REUSE_ADDR = false;
    private static final int CLI_SEND_BUF = 20;
    private static final boolean CLI_LINGER = true;
    private static final int CLI_LINGER_N = 0;
    private static final int CLI_TIMEOUT = 0;
    private static final boolean CLI_NO_DELAY = false;
/*
    StandardSocketOptions.TCP_NODELAY
    StandardSocketOptions.SO_KEEPALIVE
    StandardSocketOptions.SO_LINGER
    StandardSocketOptions.SO_RCVBUF
    StandardSocketOptions.SO_SNDBUF
    StandardSocketOptions.SO_REUSEADDR
 */
    public static void main(String[] args) {

        ServerSocket server = null;
        try {
            server = new ServerSocket();
            server.bind(new InetSocketAddress( 9090), BACK_LOG);
            server.setReceiveBufferSize(RECEIVE_BUFFER);
            server.setReuseAddress(REUSE_ADDR);
            server.setSoTimeout(SO_TIMEOUT);//获取连接超时时间

            System.out.println("server up use 9090!");

            while (true) {
                try {
                    System.in.read();  //分水岭:

                    Socket client = server.accept();
                    System.out.println("client port: " + client.getPort());

                    client.setKeepAlive(CLI_KEEPALIVE);
                    client.setOOBInline(CLI_OOB);
                    client.setReceiveBufferSize(CLI_REC_BUF);
                    client.setReuseAddress(CLI_REUSE_ADDR);
                    client.setSendBufferSize(CLI_SEND_BUF);
                    client.setSoLinger(CLI_LINGER, CLI_LINGER_N);
                    client.setSoTimeout(CLI_TIMEOUT);//read()读取数据超时时间
                    client.setTcpNoDelay(CLI_NO_DELAY);

                    new Thread(
                            () -> {
                                while (true) {
                                    try {
                                        InputStream in = client.getInputStream();
                                        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                                        char[] data = new char[1024];
                                        int num = reader.read(data);

                                        if (num > 0) {
                                            System.out.println("client read some data is :" + num + " val :" + new String(data, 0, num));
                                        } else if (num == 0) {
                                            System.out.println("client readed nothing!");
                                            continue;
                                        } else {
                                            System.out.println("client readed -1...");
                                            client.close();
                                            break;
                                        }

                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                    ).start();

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                server.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客服端代码:


import java.io.*;
import java.net.Socket;

/**
 * @create: 2020-05-17 16:18
 */
public class SocketClient {

    public static void main(String[] args) {

        try {
            Socket client = new Socket("192.168.150.11",9090); //自己修改IP地址

            client.setSendBufferSize(20);
            client.setTcpNoDelay(true);
            OutputStream out = client.getOutputStream();

            InputStream in = System.in;
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));

            while(true){
                String line = reader.readLine();
                if(line != null ){
                    byte[] bb = line.getBytes();
                    for (byte b : bb) {
                        out.write(b);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

监听 tcp 通信 ,

shell> tcpdump -nn -i oth0 port 9090 

在这里插入图片描述

  //通过新的客户端nc发送连接 或 直接从socketclient 发送连接,可以在tcpdump 的窗口看到三次握手的信息,及后续发送的报文
  shell> nc 192.168.149.11:8080 

三次握手+报文信息:
在这里插入图片描述
报文相关

  • 窗口大小:缓存区多大,mtu(ifconfig 可看)整个数据包大小,报文中的mss为数据内容大小,双方协商一个窗口大小win(窗口剩余代表着可发送数据包的大小),每次发送不一样。

  • 拥塞: 表示对方窗口没空间接受数据了,需要阻塞一下,有空间了后,会发送一个信息。

    在这里插入图片描述

clientsocket 、serversocket 对应关系图:
在这里插入图片描述
服务端不需要再为client 的连接分配一个随机端口号,而是会分配一个fd来进行通信。

二、TCP三次握手、四次分手

2.1 TCP定义

TCP 是 TCP/IP 协议栈中的传输层协议,它通过序列确认以及包重发机制,提供可靠的数据流发送和到应用程序的虚拟连接服务。
特点
Tcp是面向连接的,可靠的传输协议(为什么说可靠呢,每次发送都有应答),通过三次握手创建通信链路后,内核级会分配资源及双方会存储四元组信息(连接信息),即使服务端没有对应处理进程。

2.2 三次握手

有个哥们写的非常详细,我就不赘述了,老话说的好,要站在巨人的肩膀上:TCP三次握手详解-深入浅出(有图实例演示)

2.3 四次分手

简化版流程
在这里插入图片描述

client 发送了断了连接请求,server端还未发起,状态如下:
在这里插入图片描述

三、io 模型

这部分,劳烦老铁们,查看:深层次详解同步IO、异步IO、阻塞IO、非阻塞IO

四、BIO

提前在这里讲下bio,以后就重点讲nio了。

BIO 多线程的机制

服务端通过创建一个serverSocket得到一个fd,再通过fd bind绑定到监听的端口上,再对外开启监听listen(例如:通过netstate -antp命令得到下面的9090 监听 ,0.0.0.0:9090 0.0.0.0:* );客服端创建clientSocket的一个fd,再通过三次握手完成连接,当完成全连接后放到一个全连接的列表中,此时通过netstate -antp命令,会发现多了一条记录 ,192.168.150.11:9080 192.168.150.12:47513 ESTABLISHED - (图1,建立了连接,内核给双方分配了资源,但是没有分配对应的进程来处理),服务端进行socket的accept之后,会从全连接列表中把全连接取出去,并对每个连接分配对应的进程及新的fd处理该连接的操作,图2。

图1
在这里插入图片描述

图2在这里插入图片描述

strace -ff -o out: 跟踪内核线程,信息输出到out文件,每个线程对应一个out前缀的文件,out.pid。在执行class文件时使用。
例子:strace -ff -o -out java TestSocket 。out.pid 主进程文件,可以看到主进程在内核执行的操作,socket(…)返回一个fd,bind(fd,…) fd绑定到端口,listen(fd,backlog) 开始进行fd 的监听。里面再开始阻塞到accept(fd ,直到客户端连接进入,会对该连接新建一个fd,并通过clone(…)开启一个新的线程。 主线程是个死循环,一直在accept(…),获取连接,开启新的线程。(对照上面的代码例子

netstat -antp 看到网络状态
nc 192.168.150.11 9090进行连接
lsof -p pid
man 2 socket :通过man 可查看帮助手册,分8类。

五、总结


所有的io模型都是会有以下几步
socket=fd 3>>bind(fd,port)>>listen(fd 3)>>accept(fd)> fd 5 ,会发生阻塞blocking >>recv(fd 5) blocking 会发生阻塞
在这里插入图片描述

socket建立连接的架构图:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值