NIO与Netty-1-BIO
文章目录
-
BIO是同步阻塞IO,在服务端获取等待连接时需要阻塞,且每个连接的socket去获取客户端传来的数据也需要阻塞,因此需要为每个连接都需要建立一个线程来处理,所以线程过多性能较低。
-
示例
-
//一个BIO的服务器 public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(6666); System.out.println("服务器启动了"); while (true) { Socket socket = serverSocket.accept();//此方法无连接会阻塞 System.out.println("连接到一个客户端"); Thread connect=new Thread(new Runnable() { //创建一个线程来处理此连接 public void run() { handlerConnect(socket);//连接处理函数 } }); connect.run(); } } public static void handlerConnect(Socket socket) { try { byte[] bytes = new byte[1024]; //通过socket 获取输入流 InputStream inputStream = socket.getInputStream(); //循环的读取客户端发送的数据 while (true) { int read = inputStream.read(bytes);//此方法读取客户端传来的信息会阻塞 if(read != -1) { System.out.println(new String(bytes, 0, read)); //输出客户端发送的数据 } else { break; } } }catch (Exception e) { e.printStackTrace(); }finally { System.out.println("关闭和client的连接"); try { socket.close(); }catch (Exception e) { e.printStackTrace(); } } }
-
-
BIO阻塞模型
- BIO存在一个负责监听建立连接事件的线程,在有新连接时便创建一个线程来处理此连接。
- 因此连接数将等于处理连接的线程数。
- 因为是阻塞式IO,单线程情况下,处理者线程可能阻塞在其中一个套接字的read上,导致另一个套接字即使准备好了数据也无法处理,这个时候解决的方法就只能是针对每一个套接字,都新建一个线程处理其数据读取。
-
ServerSocket源码解读
-
ServerSocket创建流程
-
//以ServerSocket(int port)构造器为例 ServerSocket serverSocket = new ServerSocket(6666);
-
//ServerSocket(int port)构造方法 public ServerSocket(int port) throws IOException { this(port, 50, null); //调用ServerSocket(int port, int backlog, InetAddress bindAddr) } public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException { setImpl();//*设置serverSocket实例的SocketImpl属性 /**代码省略-port、baklog校验**/ try { bind(new InetSocketAddress(bindAddr, port), backlog);//*绑定端口 } /**代码省略-异常处理**/ }
-
ServerSocket.setImpl()
-
private void setImpl() { if (factory != null) {//此时factory为null /**代码省略-利用工厂创建impl**/ } else {//执行此代码块 impl = new SocksSocketImpl();//创建一个新的SocketImpl设置上去 } if (impl != null) impl.setServerSocket(this);//将刚刚创建的impl与此serverSocket绑定 }
-
-
ServerSocket.bind(SocketAddress endpoint, int backlog)
-
public void bind(SocketAddress endpoint, int backlog) throws IOException { /**代码省略-校验抛异常**/ InetSocketAddress epoint = (InetSocketAddress) endpoint; /**代码省略-endpoint校验**/ try { SecurityManager security = System.getSecurityManager(); if (security != null) security.checkListen(epoint.getPort()); //为此ServerSocket的SocketImpl绑定监听的地址和端口 getImpl().bind(epoint.getAddress(), epoint.getPort()); //为impl设置为监听模式 getImpl().listen(backlog); bound = true; } /**代码省略-异常处理**/ }
-
-
注意尽管调用了setImpl但第一次调用getImpl()方法时还会对impl进行一些设置,例如获取文件描述符fd。getImpl->createImpl->impl.create->获取文件描述符。
-
impl的bind与listen分别在jvm层调用了操作系统的**bind()和listen()**函数。
-
-
ServerSocket.accept()解读
-
//通过此方法建立连接,并返回连接对应的socket Socket socket = serverSocket.accept();
-
此方法为阻塞的,若没有连接到来则一直阻塞,有连接到来才返回新建立的socket。
-
//ServerSocket.accept() public Socket accept() throws IOException { /**代码省略-校验抛异常**/ Socket s = new Socket((SocketImpl) null);//创建新的socket对象 implAccept(s);//!!!!!!!!!!!! return s; }
-
ServerSocket.implAccept(Socket s)
-
protected final void implAccept(Socket s) throws IOException { SocketImpl si = null; try { if (s.impl == null)//此时s.impl为null s.setImpl();//为s设置一个新的impl else { s.impl.reset(); } si = s.impl; s.impl = null; si.address = new InetAddress(); si.fd = new FileDescriptor(); getImpl().accept(si);//通过s的新impl去执行具体的accpet逻辑 SocketCleanable.register(si.fd); /**代码省略-权限检查**/ } /**代码省略-异常处理**/ s.impl = si; s.postAccept(); }
-
getImpl().accept(SocketImpl s)
-
protected void accept(SocketImpl s) throws IOException { acquireFD(); try { socketAccept(s);//具体的accept方法 } finally { releaseFD(); } }
-
PlainSocketImpl.socketAccept(SocketImpl s)
-
void socketAccept(SocketImpl s) throws IOException { int nativefd = checkAndReturnNativeFD(); /**代码省略**/ int newfd = -1; InetSocketAddress[] isaa = new InetSocketAddress[1]; if (timeout <= 0) {//无超时时间的accept newfd = accept0(nativefd, isaa); //调用jvm中的accept方法 //jvm调用操作系统的accept方法获取连接 //返回新连接对应的文件描述符 } else {//含有超时时间的accept configureBlocking(nativefd, false); try { waitForNewConnection(nativefd, timeout); newfd = accept0(nativefd, isaa); //调用jvm中的accept方法 if (newfd != -1) { configureBlocking(newfd, true); } } finally { configureBlocking(nativefd, true); } } /* 将s的fd设置为获取到的连接的文件描述符 */ fdAccess.set(s.fd, newfd); /* 设置建立的新连接的端口号,ip地址等 */ InetSocketAddress isa = isaa[0]; s.port = isa.getPort(); s.address = isa.getAddress(); s.localport = localport; /**代码省略**/ }
-
accept0方法调用的jvm本地方法最终是通过调用操作系统的**accept()**函数实现的。
-
-
-
-
-
-
-
Socket中InputStream解读
-
每个连接的Socket是由ServerSocket.accpet()返回来的,若想读取连接中客户端发送的数据,则需要获取InputStream,并调用其read方法。
-
InputStream获取流程
-
protected synchronized InputStream getInputStream() throws IOException { synchronized (fdLock) { /**代码省略-校验抛异常**/ if (socketInputStream == null) socketInputStream = new SocketInputStream(this); } return socketInputStream; }
-
new SocketInputStream(this)
-
SocketInputStream(AbstractPlainSocketImpl impl) throws IOException { super(impl.getFileDescriptor());//设置文件描述符 this.impl = impl;//设置此流对应的impl socket = impl.getSocket();//设置对应的socket }
-
-
-
-
SocketInputStream.read(byte b[])
-
public int read(byte b[]) throws IOException { return read(b, 0, b.length); } public int read(byte b[], int off, int length) throws IOException { return read(b, off, length, impl.getTimeout()); } int read(byte b[], int off, int length, int timeout) throws IOException { int n; if (eof) {//连接已经结束 return -1; } /**代码省略-校验抛异常**/ FileDescriptor fd = impl.acquireFD();//获取对应的impl的文件描述符 try { n = socketRead(fd, b, off, length, timeout);//真正的read函数 if (n > 0) { return n; } } catch (ConnectionResetException rstExc) { impl.setConnectionReset(); } finally { impl.releaseFD(); } /**代码省略-校验抛异常**/ eof = true; return -1; }
-
socketRead(fd, b, off, length, timeout)
-
private int socketRead(FileDescriptor fd, byte b[], int off, int len, int timeout) throws IOException { return socketRead0(fd, b, off, len, timeout);//调用jvm本地方法 }
-
调用的jvm本地方法最终也是通过操作系统的**recv()**函数来实现read的。
-
-
-
-
-
对应的操作系统方法
-
java的socket编程其实底层也是对应的c/c++对应的那套操作系统的socket编程方法。
-
作用 java方法 操作系统方法 创建socket获得fd impl.socketCreate(boolean isServer) socket() 将host,port绑定到socket上 impl.bind(InetAddress host, int port) bind() 将socket设置为监听状态 impl.listen(int backlog) listen() 服务端socket获取连接 impl.accept(SocketImpl s) accept() 连接socket读取收到的内容 socketInputStream.read(byte b[]) recv() -
操作系统方法详解
-
socket() https://baike.baidu.com/item/socket%28%29 bind() https://baike.baidu.com/item/bind%28%29 listen() https://baike.baidu.com/item/listen%28%29 accept() https://baike.baidu.com/item/accept%28%29 recv() https://baike.baidu.com/item/recv%28%29
-
-