IO那些事04-网络IO之TCP

传统的BIO模型下的,服务端代码:

/**
 * 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;
    //当线程不够处理到达的socket请求时,等待队列数量,超出数量拒绝
    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);

        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("server up use 9090!");
        try {
            while (true) {

                 System.in.read();  //分水岭:

                Socket client = server.accept();  //阻塞的,没有 -1  一直卡着不动  accept(4,
                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);
                client.setTcpNoDelay(CLI_NO_DELAY);

                //client.read   //阻塞   没有  -1 0
                new Thread(
                        () -> {
                            try {
                                InputStream in = client.getInputStream();
                                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                                char[] data = new char[1024];
                                while (true) {

                                    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...");
                                        System.in.read();
                                        client.close();
                                        break;
                                    }
                                }

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

                        }
                ).start();

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

    }
}

客户端代码:

public class SocketClient {

    public static void main(String[] args) {

        try {
            Socket client = new Socket("192.168.150.11",9090);

            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();
        }
    }
}

上面就是一段很普通的BIO下的服务端和客户端的java代码实现。
一般来说,我们直观上会觉得一段连接的建立,是发生在服务端调用了accpet方法之后,此时客户端发来了连接请求,此时才被建立连接,那假如客户端的请求到来了,但服务器却还没来得及调用accpet,此时客户的socket请求被放在哪了呢?

首先开启tcp对9090端口号的监听:
在这里插入图片描述

启动服务端:
在这里插入图片描述

此时通过netstat查看,发现确实开启了9090的端口号:
在这里插入图片描述

通过jps查看server的进程号:
在这里插入图片描述

通过lsof查看进程的文件描述符详细情况:
在这里插入图片描述

发现开启了一个监听的文件描述符。

此时服务端的代码被System.in阻塞,因此没有到调用accpet方法的阶段,那这时候,客户端发来请求呢?
启动客户端:
在这里插入图片描述

此时去观察刚刚监听tcp9090端口号的shell的打印:
在这里插入图片描述

发现这里监听到了客户端11与服务端12建立三次握手的信息。

此时再通过netstat查看,发现也确实多了一条关于这个连接的socket描述:
在这里插入图片描述

只不过这个socket并未分配给任何的进程,此时只是存储于内核本身中,但确实说明了,此时双方都已经完成了握手,也在各自方进行了分配资源。

不仅如此,如果此时通过客户端进行发送信息给服务端,会在TCP的监听处发现监听信息:
在这里插入图片描述

并且此时,观察服务端这边,虽然连接没有分配给任何进程,但自身的接受队列中已经积压了客户端发送的内容:
在这里插入图片描述

接着,我们服务端代码继续执行,解除system.in 的阻塞,执行accept,此时在看netstat:
在这里插入图片描述

发现,这时候这个socket资源就已经分配给我们启动的服务端java进程了。
并且,服务端的java进程的文件描述符,也已经多了关于这个socket连接的文件描述符:
在这里插入图片描述

TCP是什么

通过上面的过程,我们做个小结:
TCP是基于三次连接的,完成后会各自分配资源。 这句话也得到了很好的印证。
在这里插入图片描述

socket是什么?

是一个四元组(ClientIP:ClientPort,ServerIP:ServerPort) ,由四个维度来唯一确定一个socket。
且socket的概念是内核级别的,而非进程,也就是说不是必须要java端调用一个accpet才行,而是内核去首先接手。
在这里插入图片描述

每一个socket最终可能会被分配到不同的进程中,然后以不同的文件描述符进行表示。

TCP通信模型图

在这里插入图片描述

TCP参数设置

BACK_LOG

服务端TCP设置参数:

private static final int BACK_LOG = 2;

上面我们可以看到,socket最初都是被内核先接纳的,如果没有分配给进程,都是存放在内核的,但也不能无休止的一直堆积下去,不然会撑爆内核了。
那内核可以存多少个未分配的socket呢?通过这个参数指定,如果是2的话,就是最多有3个socket可以被暂存,如果超过3个了呢?会被拒绝,换言之,TCP握手的时候不给予回应:
在这里插入图片描述

前三个成功建立连接,状态是ESTABLISHED,而第四个连接到来,是SYN_RECV,就是虽然请求来了,但是服务端没有给予回应。

SO_TIMEOUT

private static final int SO_TIMEOUT = 0;

服务端accept超时时间。

CLI_SEND_BUF

private static final int CLI_SEND_BUF = 20;

socket包发送的缓冲区大小

CLI_NO_DELAY

private static final boolean CLI_NO_DELAY = false;

是否尽可能进行延时发送优化,开启优化,发送的包大小将可能会超过缓冲区大小。

CLI_OOB

   private static final boolean CLI_OOB = false;

是否积极的预先发送确认数据(少量1个字节)。

CLI_KEEPALIVE

private static final boolean CLI_KEEPALIVE = false;
在这里插入代码片

在这里插入图片描述

开启后,传输控制层会周期性双方互相发送一些心跳包,来保证确认存活。

三次握手的细节

在这里插入图片描述

可以发现每一次握手请求都带会有seq,win。
每一端自身都维护一个序列号seq 回复的请求的ack会基于对方发送过来的seq进行+1操作。
那win是什么?
窗口的概念,数据的传输最终都会作为一个数据包进行发送,而这个包多大呢?也就是一次发多少呢?
通过ifconfig可以查看网卡的MTU大小。
MTU 传输包总大小(包含请求头+数据)
MSS 传输包的数据大小
而在两端进行通信的时候,会带上自己的剩余缓存窗口大小,而发送方下次在发送数据包的时候,会根据上次收到的窗口大小来决定本次要发生多大的数据包合适,这样是为了在提高传输效率的同时避免拥塞。(拥塞:就是接收方因为可用接受空间不足,此时发送方需要阻塞等待接收方一点点接收完数据,而接收方满了的话,就需要剔除空间中的数据,剔除规则就是将后面接到数据进行丢弃,只收到了前面的数据,也就是会发生数据丢失。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值