1.网络请求原理
网络请求过程图
2.I/O 模型基本认识
阻塞调用和非阻塞调用
- 阻塞调用是指调用结果返回前,当前线程会被挂起,调用线程只有在得到结果返回后才会返回。
- 非阻塞调用是指在不能得到结果前,该调用不会阻塞当前线程。
同步处理和异步处理
- 同步处理是指被调用方在得到最终结果后才返回给调用方;
- 异步处理是指被调用方先返回应答,然后在计算调用结果,计算完最终结果后再通知并返回给调用方。
阻塞、非阻塞和同步、异步的区别:
- 阻塞和非阻塞讨论的是调用者
- 同步和异步讨论的是被调用者
1.阻塞I/O模型
优点:程序简单,在阻塞等待数据期间进程/线程挂起,基本不会占用CPU资源
缺点:多线程实现时,会线程内存浪费,CPU调度浪费
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class TestBolckingIO {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
// ss.bind(new InetSocketAddress(ConstantCon.HOST, ConstantCon.PORT));
int idx =0;
while (true) {
final Socket socket = ss.accept();//阻塞方法
idx++;
new Thread(() -> {
handle(socket);
},"线程["+idx+"]" ).start();
}
}
static void handle(Socket socket) {
byte[] bytes = new byte[1024];
try {
// 服务端向客户端发送数据
String serverMsg = " server sss[ 线程:" + Thread.currentThread().getName() + "]" + "\r\n";
socket.getOutputStream().write(serverMsg.getBytes());//阻塞方法
socket.getOutputStream().flush();
System.out.println("接收连接:" + socket.getInetAddress() + " 端口号:"+socket.getPort());
/*
while (true) {
String serverMsg = " server sss[ 线程:" + Thread.currentThread().getName() + "]" + "\r\n";
socket.getOutputStream().write(serverMsg.getBytes());//阻塞方法
socket.getOutputStream().flush();
//Thread.sleep(1000);
}*/
//服务端接收客户端数据并处理
InputStream in = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
Integer num = 0;
String strInfo ;
while((strInfo = reader.readLine())!= null) {
num++;
System.out.println("接收IP:"+socket.getInetAddress() + ";数量="+ num +";接收内容=" + strInfo);
}
} catch (Exception e) {
// e.printStackTrace();
System.out.println(e.getMessage() + socket.getInetAddress() + socket.getPort());
}
}
}
2.非阻塞IO模型(non-blocking I/O)
优点:规避了阻塞IO模型中多线程的问题
缺点:轮询不断的访问内核,将占用大量的CPU时间,系统资源利用率低
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
public class TestNonBlockIO1 {
public static void main(String[] args) throws Exception {
LinkedList<SocketChannel> clients = new LinkedList<>();
ServerSocketChannel ss = ServerSocketChannel.open();
ss.bind(new InetSocketAddress(9090));
ss.configureBlocking(false); //重点
while(true){
Thread.sleep(1000);
SocketChannel client = ss.accept(); //不会阻塞
if(client == null ){
System.out.println("null.....");
}else{
client.configureBlocking(false);
int port = client.socket().getPort();
System.out.println("client...port: "+port);
clients.add(client);
}
ByteBuffer buffer = ByteBuffer.allocateDirect(4096); //可以在堆里 堆外
for (SocketChannel c : clients) { //串行化!!!! 多线程!!
int num = c.read(buffer); // >0 -1 0 //不会阻塞
if(num>0){
buffer.flip();
byte[] aaa = new byte[buffer.limit()];
buffer.get(aaa);
String b = new String(aaa);
System.out.println(c.socket().getPort()+" : " + b );
buffer.clear();
}
}
}
}
}
3.I/O 复用模型(I/O multiplexing)
select/poll/epoll 是针对于操作系统层面。
select/poll
时间复杂度:O(n)
优点:通过一次系统调用,把fds,传递给内核,内核进行遍历,减少系统调用的次数。
缺点:重复传递fd;每次select/poll 都要重新遍历全量的fd。一个线程中处理了多个Channel,会导致Channel处理效率的降低
epoll
时间复杂度:O(1)
优点:每次创建新连接时,将fd放到内核空间,epoll会把有变化的流通知应用程序(事件驱动)。
单线程处理
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class TestNIOSelect {
public static void main(String[] args) throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();//管道型ServerSocket
ssc.socket().bind(new InetSocketAddress(ConstantCon.HOST, ConstantCon.PORT));
ssc.configureBlocking(false);//设置非阻塞
System.out.println(" NIO single server started, listening on :" + ssc.getLocalAddress());
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);//在建立好的管道上,注册关心的事件 就绪
while(true) {
while(selector.select() >0 ) {
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();//处理的事件,必须删除
handle(key);
}
}
}
}
private static void handle(SelectionKey key) throws IOException {
if(key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);//设置非阻塞
sc.register(key.selector(), SelectionKey.OP_READ );//在建立好的管道上,注册关心的事件 可读
} else if (key.isReadable()) { //flip
SocketChannel sc = null;
sc = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(512);
buffer.clear();
int len = sc.read(buffer);
if(len != -1) {
System.out.println("[" +Thread.currentThread().getName()+"] recv :"+ new String(buffer.array(), 0, len));
}
ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient\r\n".getBytes());
sc.write(bufferToWrite);
}
}
}
多线程处理
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class SocketMultiplexingThreads {
private ServerSocketChannel server = null;
private Selector selector1 = null;
private Selector selector2 = null;
private Selector selector3 = null;
int port = 9090;
public void initServer() {
try {
server = ServerSocketChannel.open();
server.configureBlocking(false);
server.bind(new InetSocketAddress(port));
selector1 = Selector.open();
selector2 = Selector.open();
selector3 = Selector.open();
server.register(selector1, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SocketMultiplexingThreads service = new SocketMultiplexingThreads();
service.initServer();
NioThread T1 = new NioThread(service.selector1, 2);
NioThread T2 = new NioThread(service.selector2);
NioThread T3 = new NioThread(service.selector3);
T1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
T2.start();
T3.start();
System.out.println("服务器启动了。。。。。");
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class NioThread extends Thread {
Selector selector = null;
static int selectors = 0;
int id = 0;
boolean boss = false;
static BlockingQueue<SocketChannel>[] queue;
static AtomicInteger idx = new AtomicInteger();
NioThread(Selector sel, int n) { // boss
this.selector = sel;
this.selectors = n; //2
boss = true ;
queue = new LinkedBlockingQueue[selectors];
for (int i = 0; i < n; i++) {
queue[i] = new LinkedBlockingQueue<>();
}
System.out.println("Boss 启动");
}
NioThread(Selector sel) {
this.selector = sel;
id = idx.getAndIncrement() % selectors;
System.out.println("worker: " + id + " 启动");
}
@Override
public void run() {
try {
while (true) {
while (selector.select(10) > 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectionKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
if (key.isAcceptable()) {
acceptHandler(key);
} else if (key.isReadable()) {
readHandler(key);
}
}
}
if (!boss && !queue[id].isEmpty()) { //boss 不参与的 你有3个线程,boss不参与,只有work根据分配,分别注册自己的client
ByteBuffer buffer = ByteBuffer.allocate(8192);
SocketChannel client = queue[id].take();
client.register(selector, SelectionKey.OP_READ, buffer);
System.out.println("-------------------------------------------");
System.out.println("新客户端:" + client.socket().getPort() + "分配到worker:" + (id));
System.out.println("-------------------------------------------");
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void acceptHandler(SelectionKey key) {
try {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel client = ssc.accept();
client.configureBlocking(false);
int num = idx.getAndIncrement() % selectors; //0,1
queue[num].add(client);
} catch (IOException e) {
e.printStackTrace();
}
}
public void readHandler(SelectionKey key) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.clear();
int read = 0;
try {
while (true) {
read = client.read(buffer);
if (read > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
client.write(buffer);
}
buffer.clear();
} else if (read == 0) {
break;
} else {
client.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Netty实现案例
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyTestNIO {
class Handler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
int size = buf.writerIndex();
byte[] data = new byte[size];
buf.getBytes(0,data);
String dd = new String(data);
//解决换行符粘代问题
String[] strs = dd.split("\n");
for (String str : strs) {
System.out.println("触发的命令:"+str+"...");
}
ctx.writeAndFlush(msg);
// ctx.close();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
System.out.println(cause.toString());
ctx.close();
}
}
public static void main(String[] args) {
new NettyTestNIO().serverStart();
System.out.println("Netty server started !");
}
public void serverStart() {
//EventLoopGroup bossGroup = new NioEventLoopGroup(2);
NioEventLoopGroup bossGroup = new NioEventLoopGroup(2);
NioEventLoopGroup workerGroup = new NioEventLoopGroup(3);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
System.out.println("client...conn..."+ ch);
ch.pipeline().addLast(new Handler());
}
});
ChannelFuture f = b.bind(9090).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}