网络编程- Scoket-BIO
上图时对socket做一个简单介绍, 具体的实现这里就说了。主要将一下java网络编程几大IO模型实现、原理、演变,IO模型分别为BIO、OS级别的NIO(NONBLOCKING IO)、NIO java jdk1.4升级的NEW IO。
BIO模型
前提知识:想要彻底的了解IO模型及其原理和演变,你需要了解一下操作系统底层相关的一些系统调用知识,我上篇文章介绍过了,参考学习,有错误的望指出。系统调用|内核程序
想必大家都知道IO操作肯定需要系统调用的,因为网络传输势必会用到网卡硬件,这就需要内核程序的参与,接下来详细介绍下整个BIO实现以及原理模型。
大家先有个简单网络模型:
客户端和服务器端建立链接请求的时候肯定是调用了系统调用,那么系统调用又是怎么做的呢?
一下主要讲解服务器端
先抓包看下内核:
以上3和系统调用就是我们写建立连接的时候系统调用做的事。
系统调用通过这三个函数帮我们建立起服务器链接,并开启监听,监听客户端链接事件。并通过系统调用函数 accept() 方法接受客户端链接,因为一般需要链接多个客户端,所以需要用 while 无线循环去接受客户端。
但是accept方法是阻塞的,所以必须要等有至少一个客户端链接进来的时候才会继续下一步。(重点)
当有客户端链接进来的时候,服务器端需要对连接进行轮询去获取客户端发来的信息,通过 recv函数获取客户端数据。
以上是我们在服务器端建立链接,我们所需要的系统调用函数。
因为accpet 和 recv 都是阻塞函数,所以会产生一个问题:
如果客户端一直不发信息过来,系统会一直阻塞在那,遇到多个客户端链接时就会一直阻塞等待了,因为你的代码就一直阻塞在那了。
那么怎么解决呢,所以在我们java程序中我们会为每一个链接创建一个新的线程,去专门处理自己的链接发送的消息。
代码演示:
服务器端:
package com.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 基于TCP协议 网络通讯编程(网络中客户端于服务器io流传输)
* Created by Administrator on 2021/5/16.
* Socket 服务器端
* 1.
*/
public class SocketServerC {
public static void main(String[] args) throws IOException {
// 1. 创建server服务器,指定端口号,形成ip+端口构成唯一能识别的标识符套接字
ServerSocket serverSocket =new ServerSocket(5154);
System.out.println("============等待连接============");
// 监听客户端连接,该方法线程阻塞,如果没有客户端连接,就阻塞一直等待。
// 优化:1.让服务器一直处理链接状态不要,链接到了就执行完关闭链接while循环
// 2. 因为下面很多地方线程阻塞,多个线程链接服务器时,产生效率问题,链接成功之后:使用多线程处理每一个链接
while(true) {
final Socket accept = serverSocket.accept(); // 阻塞, 系统调用accept函数
System.out.println("============成功连接了============" + accept.getInetAddress());
new Thread(new Runnable() {
public void run() {
try {
// 连接成功之后,读取客户端传来的网络IO对象,输入流
InputStream is = accept.getInputStream();
// 读取数据流,打印数据
// 定义一个缓存数组
byte[] bytes = new byte[1024];
int len = 0;
// 系统调用
while (-1 != (len =is.read(bytes))){ // 阻塞--一直读取流通道,获取流数据,直到发现结束符才会跳出(File文件操作不会堵塞,因为读到最后会遇到EOF的)
// 打印客户端发送过来的信息
System.out.println("客户端:" + new String(bytes, 0, len));
// 设置跳出条件,len长度小于bytes长度,这样就没办法持续读取流通道数据了,因为跳出了
if(len<bytes.length){
break;
}
}
// 向客户端发送信息
OutputStream os = accept.getOutputStream();
os.write("我收到你的信息了".getBytes());
}catch (IOException e){
System.out.println(e);
}
}
}).start();
}
}
}
客户端:
package com.socket;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* * 基于TCP协议 网络通讯编程(网络中客户端于服务器io流传输)
* Created by Administrator on 2021/5/16.
* Socket 客户端
*/
public class SocketClientC {
public static void SocketClientCC(Socket socket) throws IOException, InterruptedException {
// 通过ip+端口号 套接字 建立tcp链接
System.out.println("连接成功");
// 向服务器端发送信息
OutputStream os = socket.getOutputStream();
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
os.write(("服务我来链接你了..."+i+"").getBytes());
}
//获取服务器端发送过来的io输入流
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
// 打印
System.out.println("服务器端:"+new String(bytes,0,len));
//socket.close();
}
public static void main(String[] args) throws IOException, InterruptedException {
Socket socket = new Socket("127.0.0.1",5154);
SocketClientCC(socket);
}
}
以上的实现方式就是传统的IO模式 即 BIO,同步阻塞IO模型.
总结:
- BIO 每线程对应每一个链接
- 使用多线程可以接受很多了链接了
缺点:
如果线程数很大时,需要创建很多的线程去实现读写。
- 线程内存浪费。
- cpu调度消耗
根本原因: accept 和 recv 阻塞(java中:accept 和 read 阻塞)
既然问题找到了,就肯定需要解决,既然链接多了需要很多线程不行,原因就是因为你阻塞了,那要是accept 和 read不阻塞了,那是不是就一个线程就行了。这样问题是不是就解决了。
上述的解决思想就是NIO ( NONBLOCKING IO ) 的实现思想,切记此处说的NIO 是站在操作系统层面的非阻塞同步IO模型(不是用多路复用器),而不是jdk提供的NEW NIO,此包用Selector对多路复用器的封装。下一篇讲解!