目录
一、原理
二、BIO
1、三次握手
2、测试用例
2.1、服务端测试代码
2.2、客户端代码
2.3、开启服务端
2.4、启动客户端
一、原理
IO即Input/Output 输入输出,诺依曼结构将计算机分成5个基础部分:运算器、控制器、存储器、输入、输出。
应用程序发起一次IO *** 作,包含两个阶段:
IO调用:应用程序进程向 *** 作系统内核发起调用。
IO执行: *** 作系统内核完成IO *** 作。
IO又分成BIO(阻塞IO)、NIO(非阻塞IO)。下面我们先梳理下BIO。
二、BIOBIO是在Jdk1.4及以前有的,它的主要问题就是存在阻塞的情况。当然鉴于真正的IO执行是在OS中,所以阻塞不阻塞还是 *** 作系统内核提供的API决定的。
BIO:blocking IO主要问题就是阻塞,取决于系统内核提供的API。
客户端三次握手完成后,服务端的accept系统调用,会clone一个专用线程。然后再去接收其他客户端的连接。
1、三次握手TCP协议建立IO连接都会经历三次握手阶段。
客户端发送一个带SYN标志的TCP报文到服务器。
服务器回应回应客户端,带上ACK标志和SYN标志,响应客户端。
客户端再次回应服务端一个ACK标志的TCP报文,建立连接。
2、测试用例 2.1、服务端测试代码public class BIOServerTest {
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; //如果有很多连接过来的时候,线程不够了,等待不处理的个数。
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; // TCP下的优化算法,在发送数据比较小的情况下,可以利用缓冲的概念
public static void main(String[] args){
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(9090), BACK_LOG);
serverSocket.setReuseAddress(REUSE_ADDR);
serverSocket.setSoTimeout(SO_TIMEOUT);
}
catch (IOException e){
e.printStackTrace();
}
System.out.println("server up use 9090");
while (true){
try{
System.in.read();
Socket socket = serverSocket.accept();
System.out.println("client port:"+ socket.getPort());
socket.setKeepAlive(CLI_KEEPALIVE);
socket.setOOBInline(CLI_OOB);
socket.setReceiveBufferSize(CLI_REC_BUF);
socket.setReuseAddress(CLI_REUSE_ADDR);
socket.setSendBufferSize(CLI_SEND_BUF);
socket.setSoLinger(CLI_LINGER, CLI_LINGER_N);
socket.setSoTimeout(CLI_TIMEOUT);
socket.setTcpNoDelay(CLI_NO_DELAY);
}
catch (Exception e){
e.printStackTrace();
}
}
}
}
2.2、客户端代码
public class BIOClientTest {
public static void main(String[] args){
try
{
Socket client = new Socket("192.168.150.11", 9090);
client.setSendBufferSize(20); //发送的缓冲区
client.setTcpNoDelay(true); //优化方式发送数据
client.setOOBInline(false); //首字节快速发送,关闭
OutputStream out = client.getOutputStream();
InputStream inputStream = System.in;
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
while (true){
String line = reader.readLine();
if(line != null){
byte[] bb = line.getBytes();
for (byte b : bb){
out.write(b);
}
}
}
}
catch (Exception e)
{
}
}
}
-
setSendBufferSize
发送数据缓冲区的大小,例如:客户端发送22个字节数据,那么服务端会先写入前20个,再写入后面两个。
- setTcpNoDelay
优化方式是否开启。
false是开启,有delay的现象,并且delay的数据可能大于setSendBufferSize设置的数值。
- setOOBInline
true是开启,客户端每次传数据,首个字节不缓存,快速先传递给服务端。
false是不开启,不首发第一个字节给服务端。
2.3、开启服务端服务端开启数据包抓取
[root@ywxtdb ~]# tcpdump -nn -i ens33 port 9090
启动服务端程序
[root@ywxtdb IO]# javac BIOServerTest.java
[root@ywxtdb IO]# java BIOServerTest
server up use 9090
查看内核里面的socket建立的过程,此时9090端口已经开始监听
[root@ywxtdb ~]# netstat -natp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp6 0 0 ::1:631 :::* LISTEN 1542/cupsd
tcp6 0 0 ::1:25 :::* LISTEN 1844/master
tcp6 0 0 ::1:6010 :::* LISTEN 2442/sshd: root@pts
tcp6 0 0 ::1:6011 :::* LISTEN 2442/sshd: root@pts
tcp6 0 0 ::1:6012 :::* LISTEN 2442/sshd: root@pts
tcp6 0 0 :::9090 :::* LISTEN 5013/java
得到服务端jsp进程号
[root@ywxtdb ~]# jps
5013 BIOServerTest
5434 Jps
查看服务端进程,他里面的文件描述符是否已经有了,以及分配情况
[root@ywxtdb ~]# lsof -p 5013
2.4、启动客户端
登录客户端服务器,执行如下命令
[root@localhost IO]# javac BIOClientTest.java
[root@localhost IO]# java BIOClientTest
- 查看服务器端抓包情况。
[root@ywxtdb ~]# tcpdump -nn -i ens33 port 9090
如下Flags三次握手
第一次130连接上128服务端
第二次128响应了130客户端
第三次130回复了128服务端
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
11:08:42.643253 IP 192.168.1.130.40504 > 192.168.1.128.9090: Flags [S], seq 1639954253, win 29200, options [mss 1460,sackOK,TS val 2438787 ecr 0,nop,wscale 7], length 0
11:08:42.643375 IP 192.168.1.128.9090 > 192.168.1.130.40504: Flags [S.], seq 1333909161, ack 1639954254, win 28960, options [mss 1460,sackOK,TS val 2879177 ecr 2438787,nop,wscale 7], length 0
11:08:42.643666 IP 192.168.1.130.40504 > 192.168.1.128.9090: Flags [.], ack 1, win 229, options [nop,nop,TS val 2438788 ecr 2879177], length 0
握手数据分析
mss:数据内容大小,数据包大小 - ip-端口(40k) =1460。ens33网卡定义的MTU(数据包大小)为1500。
win:定义一个窗口大小,协商服务端、客户端网卡,开一个窗口。大小根据实际值而定。
注意:如果客户端请求的数据量超过1500和窗口的阈值,内核缓存区会开始丢失后面的数据。
- 查看服务端网络情况
此时多了一条客户端的socket,但还没被java进程接收。
[root@ywxtdb ~]# netstat -natp
- 执行客户端输入
客户端输入1111
[root@localhost IO]# java BIOClientTest
1111
查看服务端抓包情况,可以看见通讯都能正常交互。
查看网络情况,虽然这个socket没有被接受,但内核中已经记录了有4个字节的请求。
查看java程序的文件描述符是否已经有了,可以看见。目前还没分配,还是listen状态。
可以知道,程序不是映射socket的,而是在等文件描述符。
- 执行服务端输入
[root@ywxtdb IO]# java BIOServerTest
server up use 9090
client port:40502
查看网路情况,发现这个socket已经被接受。
查看java进程情况,7u文件描述符已经取到。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)