Java面试15|网络

来源:互联网 发布:学软装要会什么软件 编辑:程序博客网 时间:2024/06/07 03:31

1、查看网络的统计信息:

netstat -s 结果中显示统计信息,保护收发包,建立连接的数量

netstat -at 列出所有TCP端口

netstat -au 列出所有的UDP端口

netstat -aut 目前正在运行的TCP/UDP服务

netstat常用的参数如下:

-s或–statistice 显示网络工作信息统计表。

-t或–tcp 显示TCP传输协议的连线状况。

-u或–udp 显示UDP传输协议的连线状况。

-a或–all 显示所有连接中的Socket。

-n或–numeric 直接使用IP地址,而不通过域名服务器。

 

1
netstat -n | awk '/^tcp/ {++S[$NF]} END{for(a in S) print a, S[a]}' 

 $NF表示倒数第一个参数,某次运行的结果如下:

 

 

TIME_WAIT状态从上图可知由3种状态可以转换而来,根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方 socket将进入TIME_WAIT状态。TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),在Windows下默认为4分钟,即240秒。TIME_WAIT状态下的socket不能被回收使用. 具体现象是对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接,将导致服务器端存在大量的处于TIME_WAIT状态的socket, 甚至比处于Established状态下的socket多的多,严重影响服务器的处理能力,甚至耗尽可用的socket,停止服务。

通过调试Linux内核参数可以进行一些优化:

(1)net.ipv4.tcp_fin_timeout,默认60s,减小fin_timeout,减少TIME_WAIT连接数量。 

(2)net.ipv4.tcp_tw_reuse = 1表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

(3)net.ipv4.tcp_tw_recycle = 1表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

 

2、TCP(Transmission Control Protocol)三次握手与四次分手

TCP在不可靠的传输信道上提供了可靠传输的抽象,隐藏了我们的应用程序大部分的复杂性功能:丢包重传,按序传送,拥塞控制和避免,数据完整性,其他特性。当您使用TCP流,TCP协议保证您的所有字节发送与接收的数据是相同的,他们会以相同的顺序到达对端。TCP设计为一个顺序发送协议,而不是一个定时发送协议。 

所谓三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。 
三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时,将触发三次握手。
 

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

TCP协议的连接是全双工连接,一个TCP连接存在双向的读写通道。 
简单说来是 “先关读,后关写”,一共需要四个阶段。以客户机发起关闭连接为例:
1.服务器读通道关闭
2.客户机写通道关闭
3.客户机读通道关闭
4.服务器写通道关闭
关闭行为是在发起方数据发送完毕之后,给对方发出一个FIN(finish)数据段。直到接收到对方发送的FIN,且对方收到了接收确认ACK之后,双方的数据通信完全结束,过程中每次接收都需要返回确认数据段ACK。
详细过程:
第一阶段  

(1)客户机发送完数据之后,向服务器发送一个FIN数据段,序列号为i;
(2)服务器收到FIN(i)后,返回确认段ACK,序列号为i+1,关闭服务器读通道;
(3)客户机收到ACK(i+1)后,关闭客户机写通道;
(此时,客户机仍能通过读通道读取服务器的数据,服务器仍能通过写通道写数据)
第二阶段 

(1)服务器发送完数据之后,向客户机发送一个FIN数据段,序列号为j;
(2)客户机收到FIN(j)后,返回确认段ACK,序列号为j+1,关闭客户机读通道;
(3)服务器收到ACK(j+1)后,关闭服务器写通道。
这是标准的TCP关闭两个阶段,服务器和客户机都可以发起关闭,完全对称。

 

 

  

绿色箭头为client,而蓝色的为server。

 
参考:
(1)http://www.cnblogs.com/Jessy/p/3535612.html 
(2)http://www.oschina.net/question/2011290_2199294?fromerr=o3YIYWOP
(3)http://itindex.net/detail/51010-httpclient-%E5%8E%9F%E7%90%86-%E6%97%B6%E5%BA%8F%E5%9B%BE

 

3、TCP协议的流量控制和拥塞控制

 

 

4、Java NIO

是一种同步非阻塞的I/O模型,也是I/O多路复用的基础。具体说就是Selector会不断轮询注册在其上的Channel,如果某个Channel上有新的TCP连接,读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectorKey可以获取就绪Channel的集合,进行后续I/O操作。

NIO单线程轮询事件,找到可以进行读写的网络描述符进行读写。除了事件的轮询是阻塞的(没有可干的事情必须要阻塞),剩余的I/O操作都是纯CPU操作,没有必要开启多线程。

并且由于线程的节约,连接数大的时候因为线程切换带来的问题也随之解决,进而为处理海量连接提供了可能。

单线程处理I/O的效率确实非常高,没有线程切换,只是拼命的读、写、选择事件。但现在的服务器,一般都是多核处理器,如果能够利用多核心进行I/O,无疑对效率会有更大的提高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
public class MultiplexerTimeServer implements Runnable {
 
    private Selector selector;
 
    private ServerSocketChannel servChannel;
 
    private volatile boolean stop;
 
    /**
     * 初始化多路复用器、绑定监听端口
     */
    public MultiplexerTimeServer(int port) {
        try {
            selector = Selector.open();
             
            // Channel主要用来读写网络上的数据的。打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道
            servChannel = ServerSocketChannel.open();
             
            // 设置为非阻塞模式
            servChannel.configureBlocking(false);
             
            // 绑定监听端口 8080
            servChannel.socket().bind(new InetSocketAddress(port), 1024);
             
            /*
             * Selector会不断地轮询在其上的Channel,如果某个Channel上面有新的TCP
             * 连接接入、读和写事件,这个Channel就处于就绪状态
             *
             * 注册到Reacotr线程的多路复用器Selector上,监听ACCEPT事件
             */
            servChannel.register(selector, SelectionKey.OP_ACCEPT);
             
            System.out.println("The time server is start in port : " + port);
        catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
 
    public void stop() {
        this.stop = true;
    }
 
    public void run() {
        while (!stop) {
            try {
                // This method performs a blocking selection operation.
                // It returns only after at least one channel is selected,(只有在至少有一个事件就绪后才会进行返回,所以是阻塞的)
                // this selector's wakeup method is invoked, the current thread is interrupted,
                // or the given timeout period expires, whichever comes first.
                selector.select(1000); // 阻塞等待,休眠时间为1s
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                /*
                 * 当有处于就绪状态的Channel时,selector将返回就绪状态的Channel的SelectionKey
                 * 集合,通过对就绪状态的Channel集合进行迭代,可以进行网络的异步读写操作
                 */
                Iterator<SelectionKey> it = selectedKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    try {
                        // 事件分发器,单线程选择就绪的事件。
                        // I/O处理器,包括connect、read、write等,这种纯CPU操作,一般开启CPU核心个线程就可以。
                        // 业务线程,在处理完I/O后,业务一般还会有自己的业务逻辑,有的还会有其他的阻塞I/O,如DB操作,RPC等。只要有阻塞,就需要单独的线程。
                        handleInput(key); // 所以在这里其实最好是用其它的线程来处理,而不要影响了事件分发器线程
                    catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null)
                                key.channel().close();
                        }
                    }
                }
            catch (Throwable t) {
                t.printStackTrace();
            }
        }
 
        // 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
        if (selector != null)
            try {
                selector.close();
            catch (IOException e) {
                e.printStackTrace();
            }
    }
 
    private void handleInput(SelectionKey key) throws IOException {
 
        if (key.isValid()) {
             
            // 处理新接入的请求消息
            if (key.isAcceptable()) {
                // 接受一个新的客户端接入请求
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel sc = ssc.accept();
                // 设置客户端为异步非阻塞
                sc.configureBlocking(false);
                // Add the new connection to the selector
                sc.register(selector, SelectionKey.OP_READ);
            }
            if (key.isReadable()) {
                /*
                 * 读取数据
                 * 读取到的字节数,返回值有以下有三种结果:
                 * (1)大于0,读取到字节,对其进行解编码
                 * (2)等于0,没有读取到字节,南纺股份正常场景,忽略
                 * (3)-1 ,链路已经关闭,需要关闭SocketChannel,释放资源
                 */
                SocketChannel sc = (SocketChannel) key.channel();
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                // 由于设置了SocketChannel为异步非阻塞的,所以它的read是非阻塞的
                int readBytes = sc.read(readBuffer);
                if (readBytes > 0) {
                    /*
                     * 将缓冲区当前的limit设置为position,position为0,用于后续对缓冲区的读取操作。
                     * 然后根据缓冲区可读的字节个数创建字节数组,调用get()操作将缓冲区可读的字节数
                     * 组复制到新创建的字节数组中
                     */
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String body = new String(bytes, "UTF-8");
                    System.out.println("The time server receive order : " + body);
                    String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ?
                            new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
                    doWrite(sc, currentTime);
                else if (readBytes < 0) {
                    // 对端链路关闭
                    key.cancel();
                    sc.close();
                else{
                    // 读到0字节,忽略
                }
                     
            }
        }
    }
 
    private void doWrite(SocketChannel channel, String response)throws IOException {
        /*
         * 由于SocketChannel是异步非阻塞的,并不能保证一次能够把所有需要发送的数据发送,此时会出现写半包问题。
         * 需要注册写操作???,不断轮询Selector将没有发送完的bytebuffer发送完毕。可以通过byteBuffer的hasRemain()
         * 方法判断是否发送完毕。
         */
        if (response != null && response.trim().length() > 0) {
            // 将应答消息异步发送给客户端
            byte[] bytes = response.getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
            // 将缓冲区中的字节数据发送
            writeBuffer.put(bytes);
            writeBuffer.flip(); // 缓存区复位
            channel.write(writeBuffer);
        }
    }
}

 Selector轮询是阻塞的,而真正的I/O是异步非阻塞的。

对于NIO来说,缓存可以使用DirectByteBuffer和HeapByteBuffer。如果使用了DirectByteBuffer,一般来说可以减少一次系统空间到用户空间的拷贝。但Buffer创建和销毁的成本更高,更不宜维护,一般用来读取大文件时使用。

 

参考文章:http://blog.csdn.net/szzt_lingpeng/article/details/50612018  

 

5、NIO中, 如果不显式的调用 system.gc() 那会出现什么问题?

DirectBuffer的GC规则与堆对象的回收规则是一样的,只有垃圾对象才会被回收,而判定是否为垃圾对象依然是根据引用树中的存活节点来判定。

如果DirectByteBuffer的空间够用,那么System.gc()是不会触发FullGC的。 也就是说在空间不够用时,显示调用才能进行回收,如果不显式调用,那只能是抛出内存异常了。

在垃圾收集时,虽然虚拟机会对DirectMemory进行回收,但是DirectMemory却不像新生代和老年代那样,发现空间不足了就通知收集器进行垃圾回收,它只能等待老年代满了后FullGC,然后“顺便地”帮它清理掉内存中废弃的对象。否则,只能等到抛出内存溢出异常时,在catch块里调用System.gc()。

参考:http://blog.csdn.net/donsonzhang/article/details/46666353 

 

6、https 的工作原理,和 http 的区别 

 

关于https可以参考:http://www.cnblogs.com/xinzhao/p/4949344.html

 

7、描述HTTP协议(HTTP请求和响应报文的格式)

HTTP协议采用“请求-应答”模式,当使用普通模式(非KeepAlive模式)时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议);当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。 

(1)在HTTP 1.0中Keep-Alive默认是关闭的,需要在HTTP头中加入“Connection: Keep-Alive” ,才能启用Keep-Alive

(2)HTTP 1.1中Keep-Alive默认启用,加入“Connection: close”可关闭。目前大部分浏览器都是用HTTP 1.1协议,也就是说默认都会发起Keep-Alive的连接请求了,所以是否能完成一个完整的Keep- Alive连接就看Web服务器的设置情况。

1,请求行

由3部分组成,分别为:请求方法、URL(见备注1)以及协议版本,之间由空格分隔

请求方法包括GET、HEAD、PUT、POST、TRACE、OPTIONS、DELETE以及扩展方法,当然并不是所有的服务器都实现了所有的方法,部分方法即便支持,处于安全性的考虑也是不可用的

协议版本的格式为:HTTP/主版本号.次版本号,常用的有HTTP/1.0和HTTP/1.1

2,请求头部

请求头部为请求报文添加了一些附加信息,由"名/值"对组成,每行一对,名和值之间使用冒号分隔

常见请求头如下:

请求头

说明

Host

接受请求的服务器地址,可以是IP:端口号,也可以是域名

User-Agent

发送请求的应用程序名称

Connection

指定与连接相关的属性,如Connection:Keep-Alive

Accept-Charset

通知服务端可以发送的编码格式

Accept-Encoding

通知服务端可以发送的数据压缩格式

Accept-Language

通知服务端可以发送的语言

请求头部的最后会有一个空行,表示请求头部结束,接下来为请求正文,这一行非常重要,必不可少

HTTP响应报文格式:

HTTP响应报文主要由状态行、响应头部、响应正文3部分组成

1,状态行

由3部分组成,分别为:协议版本,状态码,状态码描述,之间由空格分隔。常见的状态码如下:

 

状态码

说明

200                   

响应成功

302

跳转,跳转地址通过响应头中的Location属性指定(JSP中Forward和Redirect之间的区别)

400

客户端请求有语法错误,不能被服务器识别

403

服务器接收到请求,但是拒绝提供服务(认证失败)

404

请求资源不存在

500

服务器内部错误

   

2,响应头部

与请求头部类似,为响应报文添加了一些附加信息。常见响应头部如下:

响应头

说明

Server

服务器应用程序软件的名称和版本

Content-Type

响应正文的类型(是图片还是二进制字符串)

Content-Length

响应正文长度

Content-Charset

响应正文使用的编码

Content-Encoding

响应正文使用的数据压缩格式

Content-Language

响应正文使用的语言