BIO的连接建立及文件描述符分配
首先准备两台linux:node01和node02,用来做client和server
需要用到的几个命令:
lsof -p 可以看到某一个进程里的文件描述符是否已经有了,以及什么时候才能看到这个文件描述符
netstat -antp 查看内核里面socket的建立过程
tcpdump 抓取网络通信数据包
BIO一些参数介绍:
//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;
//使用netstat -antp时可以看到Recv-Q
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;
//client读取的时候是阻塞的,可以加一个超时
private static final int CLI_TIMEOUT = 0;
//TCP的一个优化算法,在发送数据比较少的时候可以利用缓冲
private static final boolean CLI_NO_DELAY = false;
服务端代码:测试的时候依靠System.in.read();来监测连接的状态
/**
* 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();
//监听9090端口,
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!");
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);
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) {//读到了client的数据
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 {//client断开连接了 System.out.println("client readed -1...");
client.close();
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
).start();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
client代码
public class SocketClient {
public static void main(String[] args) {
try {
Socket client = new Socket("192.168.100.1",9090);
//缓冲区20个字节
client.setSendBufferSize(20);
client.setTcpNoDelay(true);
OutputStream out = client.getOutputStream();
InputStream in = System.in;
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
while(true){
//注意这一步,此时是阻塞的,client还没有发送数据,在此步骤观察client和server的连接情况
String line = reader.readLine();
if(line != null ){
byte[] bb = line.getBytes();
for (byte b : bb) {
//读到数据就给服务端发送,注意:没有flush,依靠kernel
out.write(b);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
首先先看下服务端目前监听的端口号,用于和操作后进行对比,可以看到监听的都是22,25
[root@optimize-node01 ~]# netstat -antp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 6690/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 6822/master
tcp 0 0 10.0.0.5:22 10.0.0.1:7687 ESTABLISHED 7478/sshd: root@pts
tcp 0 52 10.0.0.5:22 10.0.0.1:8031 ESTABLISHED 7555/sshd: root@pts
tcp6 0 0 :::22 :::* LISTEN 6690/sshd
tcp6 0 0 ::1:25
服务端新开启一个窗口监测tcp抓包详情
[root@optimize-node01 opt]# tcpdump -nn -i ens33 port 9090
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
tcpdump
tcpdump -i 网卡
tcpdump -nn 数字的方式显示IP和端口。一个n是ip
tcpdump -c x 抓包数量,x为数字
tcpdump port xx 抓指定端口的包,xx为端口号
tcpdump tcp and port xx 指定协议和端口,xx为端口号,and可以省略不写
tcpdump host xx.xx.xx.xx 指定来源IP或目标IP的包 xx.xx.xx.xx为IP地址。
tcpdump -w xx.txt 把抓的包写入一个文件,xx.txt为文件名
tcpdump -s0 -w xx.txt 抓包时防止包截断,s0的0为数字0,抓一个完整的包必须加s0。
tcpdump -r xx.txt 用户查看-w抓的包,xx.txt为文件名
-w抓的包实际是包的内容,非简单的流向。如果访问一张图片,用-w可以把这张图片抓出来。只看流向的话,可以使用重定向。
开启服务端程序:
[root@optimize-node01 netio]# javac SocketIOPropertites.java && java SocketIOPropertites
server up use 9090!
再次查看网络连接,并与之前的网络连接进行比较
[root@optimize-node01 ~]# netstat -antp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 6690/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 6822/master
tcp 0 52 10.0.0.5:22 10.0.0.1:9230 ESTABLISHED 7599/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:7687 ESTABLISHED 7478/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:8031 ESTABLISHED 7555/sshd: root@pts
tcp6 0 0 :::22 :::* LISTEN 6690/sshd
tcp6 0 0 ::1:25 :::* LISTEN 6822/master
[root@optimize-node01 ~]# netstat -antp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 6690/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 6822/master
tcp 0 52 10.0.0.5:22 10.0.0.1:9230 ESTABLISHED 7599/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:7687 ESTABLISHED 7478/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:8031 ESTABLISHED 7555/sshd: root@pts
tcp6 0 0 :::22 :::* LISTEN 6690/sshd
tcp6 0 0 ::1:25 :::* LISTEN 6822/master
tcp6 0 0 :::9090 :::* LISTEN 7634/java
可以看到服务端监听了9090端口,并把这个监听分配给了7634的java进程
所有client的连接分为两类:第一类三次握手是走LISTEN状态的这条9090连接;第二类三次握手之后肯定会分配文件描述符交给其他的线程,这个可以在后续步骤看到
有了9090这个监听之后client就可以连接进来了
查看抓包窗口,此时是任何事情也没有发生的
[root@optimize-node01 opt]# tcpdump -nn -i ens33 port 9090
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
使用jps看下java进程,并使用lsof -p pid查看进程使用的文件描述符
[root@optimize-node01 ~]# jps
7634 SocketIOPropertites
7652 Jps
[root@optimize-node01 ~]# lsof -p 7634
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 7634 root cwd DIR 253,0 17 51428137 /opt/io/netio
java 7634 root rtd DIR 253,0 256 64 /
java 7634 root txt REG 253,0 8984 19770 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/bin/java
java 7634 root mem REG 253,0 106075056 50566106 /usr/lib/locale/locale-archive
java 7634 root mem REG 253,0 123192 33637617 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libnet.so
java 7634 root mem REG 253,0 100064 33637618 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libnio.so
java 7634 root mem REG 253,0 73844502 17289271 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/rt.jar
java 7634 root mem REG 253,0 44248 33637625 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libzip.so
java 7634 root mem REG 253,0 61624 42280 /usr/lib64/libnss_files-2.17.so
java 7634 root mem REG 253,0 219000 33637607 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libjava.so
java 7634 root mem REG 253,0 66440 33637624 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libverify.so
java 7634 root mem REG 253,0 43776 42292 /usr/lib64/librt-2.17.so
java 7634 root mem REG 253,0 88776 84 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
java 7634 root mem REG 253,0 1137016 42270 /usr/lib64/libm-2.17.so
java 7634 root mem REG 253,0 991616 42323 /usr/lib64/libstdc++.so.6.0.19
java 7634 root mem REG 253,0 17265304 17289251 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/server/libjvm.so
java 7634 root mem REG 253,0 2151672 42262 /usr/lib64/libc-2.17.so
java 7634 root mem REG 253,0 19288 42268 /usr/lib64/libdl-2.17.so
java 7634 root mem REG 253,0 71136 19781 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/jli/libjli.so
java 7634 root mem REG 253,0 90248 42321 /usr/lib64/libz.so.1.2.7
java 7634 root mem REG 253,0 141968 42288 /usr/lib64/libpthread-2.17.so
java 7634 root mem REG 253,0 163400 42255 /usr/lib64/ld-2.17.so
java 7634 root mem REG 253,0 32768 17314831 /tmp/hsperfdata_root/7634
java 7634 root 0u CHR 136,1 0t0 4 /dev/pts/1
java 7634 root 1u CHR 136,1 0t0 4 /dev/pts/1
java 7634 root 2u CHR 136,1 0t0 4 /dev/pts/1
java 7634 root 3r REG 253,0 73844502 17289271 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/rt.jar
java 7634 root 4u unix 0xffff903e39160000 0t0 39936 socket
java 7634 root 5u IPv6 63490 0t0 TCP *:websm (LISTEN)
得到的服务端进程id是7634
可以看到7634分配了一个文件描述符5在LISTEN状态
到此为止关注两点:server开启了9090端口监听,分配了一个文件描述符5
客户端开启demo程序
[root@optimize-node02 netio]# javac SocketClient.java && java SocketClient
client程序启动后且被System.in.read();阻塞住
此时去观察server端的tcpdump情况
[root@optimize-node01 opt]# tcpdump -nn -i ens33 port 9090
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
06:03:39.345640 IP 10.0.0.6.34396 > 10.0.0.5.9090: Flags [S], seq 796655830, win 29200, options [mss 1460,sackOK,TS val 19727488 ecr 0,nop,wscale 7], length 0
06:03:39.345672 IP 10.0.0.5.9090 > 10.0.0.6.34396: Flags [S.], seq 3217094740, ack 796655831, win 1152, options [mss 1460,sackOK,TS val 19604751 ecr 19727488,nop,wscale 0], length 0
06:03:39.345904 IP 10.0.0.6.34396 > 10.0.0.5.9090: Flags [.], ack 1, win 229, options [nop,nop,TS val 19727489 ecr 19604751], length 0
观察到建立3次握手的情况
1.10.0.0.6.34396向10.0.0.5.9090发送了标志位为SYN的数据包
2.10.0.0.5.9090 给 10.0.0.6.34396 回了一个标志位SYN和ACK的数据包
3.10.0.0.6.34396 又给 10.0.0.5.9090 回了一个标志位为ACK的数据包
至此3次握手建立完成
再去看server的netstat -antp监控
[root@optimize-node01 ~]# netstat -antp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 6690/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 6822/master
tcp 0 52 10.0.0.5:22 10.0.0.1:9230 ESTABLISHED 7599/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:7687 ESTABLISHED 7478/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:8031 ESTABLISHED 7555/sshd: root@pts
tcp6 0 0 :::22 :::* LISTEN 6690/sshd
tcp6 0 0 ::1:25 :::* LISTEN 6822/master
tcp6 1 0 :::9090 :::* LISTEN 7634/java
tcp6 0 0 10.0.0.5:9090 10.0.0.6:34396 ESTABLISHED -
可以看到曾经的监控只有9090的LISTEN
而且此时server还没有执行accept()
现在多了9090的ESTABLISHED,已经和client建立起了连接,且没有分配给任何进程使用
此时在client发送数据看看,发送1111
[root@optimize-node02 netio]# javac com/bjmashibing/system/io/SocketClient.java && java com/bjmashibing/system/io/SocketClient
1111
再来看server的tcpdump
[root@optimize-node01 opt]# tcpdump -nn -i ens33 port 9090
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
06:03:39.345640 IP 10.0.0.6.34396 > 10.0.0.5.9090: Flags [S], seq 796655830, win 29200, options [mss 1460,sackOK,TS val 19727488 ecr 0,nop,wscale 7], length 0
06:03:39.345672 IP 10.0.0.5.9090 > 10.0.0.6.34396: Flags [S.], seq 3217094740, ack 796655831, win 1152, options [mss 1460,sackOK,TS val 19604751 ecr 19727488,nop,wscale 0], length 0
06:03:39.345904 IP 10.0.0.6.34396 > 10.0.0.5.9090: Flags [.], ack 1, win 229, options [nop,nop,TS val 19727489 ecr 19604751], length 0
06:21:16.659497 IP 10.0.0.6.34396 > 10.0.0.5.9090: Flags [P.], seq 1:2, ack 1, win 229, options [nop,nop,TS val 20784800 ecr 19604751], length 1
06:21:16.659520 IP 10.0.0.5.9090 > 10.0.0.6.34396: Flags [.], ack 2, win 1151, options [nop,nop,TS val 20662065 ecr 20784800], length 0
06:21:16.659562 IP 10.0.0.6.34396 > 10.0.0.5.9090: Flags [P.], seq 2:3, ack 1, win 229, options [nop,nop,TS val 20784800 ecr 19604751], length 1
06:21:16.659786 IP 10.0.0.6.34396 > 10.0.0.5.9090: Flags [P.], seq 3:4, ack 1, win 229, options [nop,nop,TS val 20784800 ecr 20662065], length 1
06:21:16.670546 IP 10.0.0.6.34396 > 10.0.0.5.9090: Flags [P.], seq 3:4, ack 1, win 229, options [nop,nop,TS val 20784811 ecr 20662065], length 1
06:21:16.670565 IP 10.0.0.5.9090 > 10.0.0.6.34396: Flags [.], ack 4, win 1149, options [nop,nop,TS val 20662076 ecr 20784800,nop,nop,sack 1 {3:4}], length 0
06:21:16.670866 IP 10.0.0.6.34396 > 10.0.0.5.9090: Flags [P.], seq 4:5, ack 1, win 229, options [nop,nop,TS val 20784812 ecr 20662076], length 1
06:21:16.710345 IP 10.0.0.5.9090 > 10.0.0.6.34396: Flags [.], ack 5, win 1148, options [nop,nop,TS val 20662116 ecr 20784812], length 0
多了3次握手下面的数据包接收
再看看server的natstat -antp
[root@optimize-node01 ~]# netstat -antp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 6690/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 6822/master
tcp 0 52 10.0.0.5:22 10.0.0.1:9230 ESTABLISHED 7599/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:7687 ESTABLISHED 7478/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:8031 ESTABLISHED 7555/sshd: root@pts
tcp6 0 0 :::22 :::* LISTEN 6690/sshd
tcp6 0 0 ::1:25 :::* LISTEN 6822/master
tcp6 1 0 :::9090 :::* LISTEN 7634/java
tcp6 4 0 10.0.0.5:9090 10.0.0.6:34396 ESTABLISHED -
可以看到虽然此时还没有accept();但是已经接收到client发送过来的数据,10.0.0.5:9090 -> 10.0.0.6:34396 这条连接的Recv-Q为4,这4个字节已经被kernel收到了,kernel已经完成了资源的初步使用,所以说tcp是面向连接的(因为此时只是建立了连接,这个连接在server端并没有分配给任何进程使用)
查看java程序的文件描述符
[root@optimize-node01 ~]# lsof -p 7634
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 7634 root cwd DIR 253,0 17 51428137 /opt/io/netio
java 7634 root rtd DIR 253,0 256 64 /
java 7634 root txt REG 253,0 8984 19770 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/bin/java
java 7634 root mem REG 253,0 106075056 50566106 /usr/lib/locale/locale-archive
java 7634 root mem REG 253,0 123192 33637617 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libnet.so
java 7634 root mem REG 253,0 100064 33637618 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libnio.so
java 7634 root mem REG 253,0 73844502 17289271 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/rt.jar
java 7634 root mem REG 253,0 44248 33637625 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libzip.so
java 7634 root mem REG 253,0 61624 42280 /usr/lib64/libnss_files-2.17.so
java 7634 root mem REG 253,0 219000 33637607 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libjava.so
java 7634 root mem REG 253,0 66440 33637624 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libverify.so
java 7634 root mem REG 253,0 43776 42292 /usr/lib64/librt-2.17.so
java 7634 root mem REG 253,0 88776 84 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
java 7634 root mem REG 253,0 1137016 42270 /usr/lib64/libm-2.17.so
java 7634 root mem REG 253,0 991616 42323 /usr/lib64/libstdc++.so.6.0.19
java 7634 root mem REG 253,0 17265304 17289251 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/server/libjvm.so
java 7634 root mem REG 253,0 2151672 42262 /usr/lib64/libc-2.17.so
java 7634 root mem REG 253,0 19288 42268 /usr/lib64/libdl-2.17.so
java 7634 root mem REG 253,0 71136 19781 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/jli/libjli.so
java 7634 root mem REG 253,0 90248 42321 /usr/lib64/libz.so.1.2.7
java 7634 root mem REG 253,0 141968 42288 /usr/lib64/libpthread-2.17.so
java 7634 root mem REG 253,0 163400 42255 /usr/lib64/ld-2.17.so
java 7634 root mem REG 253,0 32768 17314831 /tmp/hsperfdata_root/7634
java 7634 root 0u CHR 136,1 0t0 4 /dev/pts/1
java 7634 root 1u CHR 136,1 0t0 4 /dev/pts/1
java 7634 root 2u CHR 136,1 0t0 4 /dev/pts/1
java 7634 root 3r REG 253,0 73844502 17289271 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/rt.jar
java 7634 root 4u unix 0xffff903e39160000 0t0 39936 socket
java 7634 root 5u IPv6 63490 0t0 TCP *:websm (LISTEN)
可以发现在内核里的socket没有分配给任何进程,java进程里的文件描述符5还是指向LISTEN状态,也就是说程序其实不是通过socket要使用socket,程序其实是在等一个东西,等一个文件描述符,但是server还没有调用accept();还没有返回文件描述符,所以只要让我们的server跳过System.in.read();阻塞,继续执行就会分配一个文件描述符。
此时在server端按下回车,跳过阻塞
[root@optimize-node01 netio]# javac SocketIOPropertites.java && java SocketIOPropertites
server up use 9090!
client port: 34396
client read some data is :4 val :1111
看到已经接收到了client,并且kernel里缓冲的那4各字节也读到了
关键还要看server的监控
[root@optimize-node01 ~]# netstat -antp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 6690/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 6822/master
tcp 0 0 10.0.0.5:22 10.0.0.1:9230 ESTABLISHED 7599/sshd: root@pts
tcp 0 180 10.0.0.5:22 10.0.0.1:7687 ESTABLISHED 7478/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:11023 ESTABLISHED 7717/sshd: root@pts
tcp 0 52 10.0.0.5:22 10.0.0.1:11030 ESTABLISHED 7771/sshd: root@pts
tcp 0 2224 10.0.0.5:22 10.0.0.1:8031 ESTABLISHED 7555/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:11028 ESTABLISHED 7751/sshd: root@pts
tcp6 0 0 :::22 :::* LISTEN 6690/sshd
tcp6 0 0 ::1:25 :::* LISTEN 6822/master
tcp6 0 0 10.0.0.5:9090 10.0.0.6:34396 ESTABLISHED 7838/java
可以看到刚才没有分配的那个socket 10.0.0.5:9090 ->10.0.0.6:34396此时已经分配给了进程号为7838的java进程
且看lsof -p 7838
[root@optimize-node01 ~]# lsof -p 7838
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 7838 root cwd DIR 253,0 17 51428137 /opt/io/netio
java 7838 root rtd DIR 253,0 256 64 /
java 7838 root txt REG 253,0 8984 19770 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/bin/java
java 7838 root mem REG 253,0 106075056 50566106 /usr/lib/locale/locale-archive
java 7838 root mem REG 253,0 123192 33637617 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libnet.so
java 7838 root mem REG 253,0 100064 33637618 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libnio.so
java 7838 root mem REG 253,0 73844502 17289271 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/rt.jar
java 7838 root mem REG 253,0 44248 33637625 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libzip.so
java 7838 root mem REG 253,0 61624 42280 /usr/lib64/libnss_files-2.17.so
java 7838 root mem REG 253,0 219000 33637607 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libjava.so
java 7838 root mem REG 253,0 66440 33637624 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/libverify.so
java 7838 root mem REG 253,0 43776 42292 /usr/lib64/librt-2.17.so
java 7838 root mem REG 253,0 88776 84 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
java 7838 root mem REG 253,0 1137016 42270 /usr/lib64/libm-2.17.so
java 7838 root mem REG 253,0 991616 42323 /usr/lib64/libstdc++.so.6.0.19
java 7838 root mem REG 253,0 17265304 17289251 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/server/libjvm.so
java 7838 root mem REG 253,0 2151672 42262 /usr/lib64/libc-2.17.so
java 7838 root mem REG 253,0 19288 42268 /usr/lib64/libdl-2.17.so
java 7838 root mem REG 253,0 71136 19781 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/amd64/jli/libjli.so
java 7838 root mem REG 253,0 90248 42321 /usr/lib64/libz.so.1.2.7
java 7838 root mem REG 253,0 141968 42288 /usr/lib64/libpthread-2.17.so
java 7838 root mem REG 253,0 163400 42255 /usr/lib64/ld-2.17.so
java 7838 root mem REG 253,0 32768 17314831 /tmp/hsperfdata_root/7838
java 7838 root 0u CHR 136,3 0t0 6 /dev/pts/3
java 7838 root 1u CHR 136,3 0t0 6 /dev/pts/3
java 7838 root 2u CHR 136,3 0t0 6 /dev/pts/3
java 7838 root 3r REG 253,0 73844502 17289271 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre/lib/rt.jar
java 7838 root 4u unix 0xffff903e1ef9fc00 0t0 59015 socket
java 7838 root 6u IPv6 59021 0t0 TCP bogon:websm->bogon:34396 (ESTABLISHED)
也得到了一个新的文件描述符
6u IPv6 59021 0t0 TCP bogon:websm->bogon:34396
总结一下:
- 1.TCP是面向连接的,可靠的传输协议
- 连接要通过3次握手,抓包抓到了
- 内核要开辟资源,资源就是所谓的连接,上面例子中Recv-Q为4的时候已经看到了
- 2.Socket
- socket是一个四元组(CIP CPORT + SIP SPORT)
- socket是在内核级的,是在传输层
- 即便你不调用accept,内核也会完成连接的建立和数据的接收过程
- 面试题:
- 一个client连接到server,server要不要为client分配一个随机port?
- 不需要,不管是服务端还是客户端,只要用四元组作为唯一标识就可以了,不用再多加一个随机端口号
- 一个client连接到server,server要不要为client分配一个随机port?
- 3.四元组是唯一的,四元组是内核级的,将四元组分配给某个进程后,进程分配给四元组的文件描述符fd也是唯一的,这个fd就是流的抽象
- 4.只要内存资源够,可以建立很多连接,这个连接就是socket四元组,服务端能够监听的端口就是65535个,也就是我们之前使用netstat -antp命令时看到的处于LISTEN状态的连接;只要socket四元组不同,就可以建立连接
演示backlog
将上面演示所用的server和client关闭,恢复到初始状态
- 1.开启server
[root@optimize-node01 netio]# javac SocketIOPropertites.java && java SocketIOPrortites
server up use 9090!
- 2.查看server监听端口
[root@optimize-node01 ~]# netstat -antp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 6690/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 6822/master
tcp 0 0 10.0.0.5:22 10.0.0.1:9230 ESTABLISHED 7599/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:11023 ESTABLISHED 7717/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:11030 ESTABLISHED 7771/sshd: root@pts
tcp 0 52 10.0.0.5:22 10.0.0.1:11028 ESTABLISHED 7751/sshd: root@pts
tcp6 0 0 :::22 :::* LISTEN 6690/sshd
tcp6 0 0 ::1:25 :::* LISTEN 6822/master
tcp6 0 0 :::9090 :::* LISTEN 7912/java
此时只有9090端口的LISTEN
- 3.开启server tcpdump抓包
[root@optimize-node01 ~]# tcpdump -nn -i ens33 port 9090
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
- 4.开启client
[root@optimize-node02 netio]# javac SocketClient.java && java SocketClient
- 5.我们的server设置的BACK_LOG是2
## 第一次三次握手的包收到了
[root@optimize-node01 ~]# tcpdump -nn -i ens33 port 9090
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
09:33:46.852061 IP 10.0.0.6.34398 > 10.0.0.5.9090: Flags [S], seq 1640085439, win 29200, options [mss 1460,sackOK,TS val 26528106 ecr 0,nop,wscale 7], length 0
09:33:46.852085 IP 10.0.0.5.9090 > 10.0.0.6.34398: Flags [S.], seq 2795791007, ack 1640085440, win 1152, options [mss 1460,sackOK,TS val 26405372 ecr 26528106,nop,wscale 0], length 0
09:33:46.852353 IP 10.0.0.6.34398 > 10.0.0.5.9090: Flags [.], ack 1, win 229, options [nop,nop,TS val 26528106 ecr 26405372], length 0
##再启动一个client,再次查看tcpdump
[root@optimize-node01 ~]# tcpdump -nn -i ens33 port 9090
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
09:33:46.852061 IP 10.0.0.6.34398 > 10.0.0.5.9090: Flags [S], seq 1640085439, win 29200, options [mss 1460,sackOK,TS val 26528106 ecr 0,nop,wscale 7], length 0
09:33:46.852085 IP 10.0.0.5.9090 > 10.0.0.6.34398: Flags [S.], seq 2795791007, ack 1640085440, win 1152, options [mss 1460,sackOK,TS val 26405372 ecr 26528106,nop,wscale 0], length 0
09:33:46.852353 IP 10.0.0.6.34398 > 10.0.0.5.9090: Flags [.], ack 1, win 229, options [nop,nop,TS val 26528106 ecr 26405372], length 0
## 第二次启动client对应的握手包
09:38:24.140244 IP 10.0.0.6.34400 > 10.0.0.5.9090: Flags [S], seq 1863433434, win 29200, options [mss 1460,sackOK,TS val 26805394 ecr 0,nop,wscale 7], length 0
09:38:24.140266 IP 10.0.0.5.9090 > 10.0.0.6.34400: Flags [S.], seq 1708229072, ack 1863433435, win 1152, options [mss 1460,sackOK,TS val 26682660 ecr 26805394,nop,wscale 0], length 0
09:38:24.140555 IP 10.0.0.6.34400 > 10.0.0.5.9090: Flags [.], ack 1, win 229, options [nop,nop,TS val 26805394 ecr 26682660], length 0
## 第三次启动client对应的握手包
09:39:42.697724 IP 10.0.0.6.34402 > 10.0.0.5.9090: Flags [S], seq 1369513206, win 29200, options [mss 1460,sackOK,TS val 26883950 ecr 0,nop,wscale 7], length 0
09:39:42.697748 IP 10.0.0.5.9090 > 10.0.0.6.34402: Flags [S.], seq 3522848273, ack 1369513207, win 1152, options [mss 1460,sackOK,TS val 26761218 ecr 26883950,nop,wscale 0], length 0
09:39:42.697981 IP 10.0.0.6.34402 > 10.0.0.5.9090: Flags [.], ack 1, win 229, options [nop,nop,TS val 26883952 ecr 26761218], length 0
## 第四次启动client对应的握手包并且一直在刷新
09:43:51.035253 IP 10.0.0.6.34406 > 10.0.0.5.9090: Flags [F.], seq 1, ack 1, win 229, options [nop,nop,TS val 27132288 ecr 26972844], length 0
09:43:56.488116 IP 10.0.0.6.34408 > 10.0.0.5.9090: Flags [S], seq 2158248665, win 29200, options [mss 1460,sackOK,TS val 27137740 ecr 0,nop,wscale 7], length 0
09:43:56.488141 IP 10.0.0.5.9090 > 10.0.0.6.34408: Flags [S.], seq 2953317749, ack 2158248666, win 1152, options [mss 1460,sackOK,TS val 27015008 ecr 27137740,nop,wscale 0], length 0
09:43:56.488484 IP 10.0.0.6.34408 > 10.0.0.5.9090: Flags [.], ack 1, win 229, options [nop,nop,TS val 27137742 ecr 27015008], length 0
09:43:57.484337 IP 10.0.0.6.34406 > 10.0.0.5.9090: Flags [F.], seq 1, ack 1, win 229, options [nop,nop,TS val 27138737 ecr 26972844], length 0
09:43:57.544723 IP 10.0.0.5.9090 > 10.0.0.6.34408: Flags [S.], seq 2953317749, ack 2158248666, win 1152, options [mss 1460,sackOK,TS val 27016065 ecr 27137742,nop,wscale 0], length 0
09:43:57.545678 IP 10.0.0.6.34408 > 10.0.0.5.9090: Flags [.], ack 1, win 229, options [nop,nop,TS val 27138798 ecr 27015008], length 0
09:43:59.545761 IP 10.0.0.5.9090 > 10.0.0.6.34408: Flags [S.], seq 2953317749, ack 2158248666, win 1152, options [mss 1460,sackOK,TS val 27018066 ecr 27138798,nop,wscale 0], length 0
09:43:59.546058 IP 10.0.0.6.34408 > 10.0.0.5.9090: Flags [.], ack 1, win 229, options [nop,nop,TS val 27140799 ecr 27015008], length 0
此时再来看看server的netstat -antp
[root@optimize-node01 ~]# netstat -antp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 6690/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 6822/master
tcp 0 0 10.0.0.5:9090 10.0.0.6:34408 SYN_RECV -
tcp 0 0 10.0.0.5:22 10.0.0.1:9230 ESTABLISHED 7599/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:11023 ESTABLISHED 7717/sshd: root@pts
tcp 0 52 10.0.0.5:22 10.0.0.1:11030 ESTABLISHED 7771/sshd: root@pts
tcp 0 0 10.0.0.5:22 10.0.0.1:11028 ESTABLISHED 7751/sshd: root@pts
tcp6 0 0 :::22 :::* LISTEN 6690/sshd
tcp6 0 0 ::1:25 :::* LISTEN 6822/master
tcp6 3 0 :::9090 :::* LISTEN 7912/java
tcp6 0 0 10.0.0.5:9090 10.0.0.6:34398 ESTABLISHED -
tcp6 0 0 10.0.0.5:9090 10.0.0.6:34402 ESTABLISHED -
tcp6 0 0 10.0.0.5:9090 10.0.0.6:34400 ESTABLISHED -
有三个ESTABLISHED状态的连接(已经建立完3次握手且分配好了资源)
有一个SYN_RECV状态的连接,这个状态代表:我收到了对方的握手信息,但是我没有回复握手信息或者是我回复的握手信息发丢了