视频学习地址BIO与NIO,多路复用之select
通用客户端
public class Client {
public static void main(String[] args) throws IOException {
Socket socket=new Socket();
socket.connect(new InetSocketAddress("127.0.0.1",9876));
Scanner scanner=new Scanner(System.in);
System.out.println("请输入内容");
while(true){
String next=scanner.next();
socket.getOutputStream().write(next.getBytes());
}
}
}
传统的BIO代码演示
public class BioServer {
public static void main(String[] args) throws IOException {
byte[] bs=new byte[1024];
ServerSocket serverSocket=new ServerSocket();
serverSocket.bind(new InetSocketAddress(9876));
//accept 专门负责通道
while(true){
System.out.println("等待建立连接");
Socket accept=serverSocket.accept(); //没有客户端进行连接的时候会进行阻塞
System.out.println("连接成功");
System.out.println("start data----");
int read=accept.getInputStream().read(bs); //建立连接后,没有数据发过来也会进行阻塞
System.out.println("end data---"+read);
}
}
}
可以发现问题:服务器端的连接都是串行处理的(因为建立连接后,如果那个连接没有发送数据过来,服务器端会进行阻塞也就是accept.getInputStream().read(bs)会阻塞,下一个客户端就没有办法获取连接,就只能等服务器端处理完上一个连接)
改进版BIO(Nonblocking Input Output)服务端代码演示
public class BioServer {
public static void main(String[] args) throws IOException {
byte[] bs=new byte[1024];
ServerSocket serverSocket=new ServerSocket();
serverSocket.bind(new InetSocketAddress(9876));
//accept 专门负责通道
while(true){
System.out.println("等待建立连接");
Socket socket=serverSocket.accept(); //没有客户端进行连接的时候会进行阻塞
System.out.println("连接成功");
Thread thread=new Thread(new ExecuteSocket(socket)); //创建新的线程进行处理连接的读写
thread.start();
}
}
}
class ExecuteSocket implements Runnable{
byte[] bs=new byte[1024];
Socket socket;
//处理每个客户端的连接--读写
public ExecuteSocket(Socket socket){
this.socket=socket;
}
@Override
public void run() {
try {
socket.getInputStream().read(bs);
} catch (IOException e) {
e.printStackTrace();
}
String centent=new String(bs);
System.out.println(centent);
}
}
引入了主从复制的思想,每创建一个连接,都会新建一个线程来处理该连接的读写请求,那么多个连接就可以并发的执行了。
这种BIO存在什么问题?会创建太多的线程,有的连接可能就只是创建连接,并不会进行数据传输,但是我们还是为这个连接创建了线程,浪费系统资源,从而提出了NIO,还是希望用单线程来解决问题
NIO(New Input Output)服务端代码演示
写在前面的话:我们知道单线程的问题(传统的BIO)是会导致所有的连接都是串行执行,不能并发执行,其实导致这个问题的关键就在于在接收连接和接收数据的时候会进行阻塞,所以我们的解决思想就是将接收连接和接收数据这两个操作改成无阻塞操作,通过调用底层的api
public class NioServer {
public static void main(String[] args) {
List<SocketChannel> list=new ArrayList<SocketChannel>();
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
try {
//ServerSocketChannel相当于ServerSocket,但由于ServerSocket没有设置成非阻塞的API,所以换成ServerSocketChannel
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(9091));
serverSocketChannel.configureBlocking(false);//设置成非阻塞
while(true){
SocketChannel socketChannel=serverSocketChannel.accept(); //无阻塞的
if(socketChannel==null){
Thread.sleep(1000);
System.out.println("没人进行连接");
//得到所有的嵌套字,循环所有的嵌套字,通过嵌套字获得数据
for(SocketChannel channel:list){
int k=channel.read(byteBuffer); //无阻塞的
System.out.println(k);
if(k!=0){
byteBuffer.flip();
System.out.println(new String(byteBuffer.array()));
}
}
}else {
socketChannel.configureBlocking(false);
list.add(socketChannel);
//得到所有的嵌套字,循环所有的嵌套字,通过嵌套字获得数据
for(SocketChannel socketChannel1:list){
int k=socketChannel.read(byteBuffer); //无阻塞的
System.out.println(k+"=============");
if(k!=0){
byteBuffer.flip();
System.out.println(new String(byteBuffer.array()));
}
}
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
NIO用单线程实现了多并发,即解决了传统的BIO的单线程串行问题,又解决了改进后的BIO多线程浪费资源的问题,但是NIO还是存在问题,我们可以看到,每次循环的时候,我们需要手动的去遍历每一个客户端有没有发送数据,那么如果C10K的话呢?(Client 有10k个),那么这个是资源浪费,所以就引出了多路复用器,不再需要我们代码区判断是否有连接,是否有数据,只需要我们把ServerSocketChannel,SocketChannel注册到多路复用器即可,那么就是内核帮我们判断了
多路复用器之select服务端代码
public class SocketMultiplexingSingleThread {
private ServerSocketChannel server=null;
private Selector selector=null;
int port = 9090;
public void initServer() throws IOException {
server=ServerSocketChannel.open();
server.configureBlocking(false);
server.bind(new InetSocketAddress(port));
selector=Selector.open();
//将server注册到selector中,监听server的accept时间
server.register(selector, SelectionKey.OP_ACCEPT);
}
public void start() throws IOException {
initServer();
System.out.println("服务器启动了");
while(true){
while(selector.select(0)>0){ //问过内核了有没有事件,内核回复有!一直没有的话 这里会进行阻塞
Set<SelectionKey> selectionkeys=selector.selectedKeys();//从多路复用器中取出有效的key
Iterator<SelectionKey> iter=selectionkeys.iterator();
while(iter.hasNext()){
SelectionKey key=iter.next();
iter.remove();//因为是set,如果不remove的话下次可能还会取到他,所以就需要remove掉
if(key.isAcceptable()){
acceptHandle(key);
}else if(key.isReadable()){
readHandle(key);
}
}
}
}
}
//有accpt事件的时候
public void acceptHandle(SelectionKey key) throws IOException {
ServerSocketChannel ssc= (ServerSocketChannel) key.channel();
SocketChannel client=ssc.accept();
client.configureBlocking(false);
ByteBuffer byteBuffer=ByteBuffer.allocate(8192);
client.register(selector,SelectionKey.OP_READ,byteBuffer); //也注册到多路复用器中,多路复用器监听client的读写文件事件
System.out.println("新客户端"+client.getRemoteAddress());
}
//有read事件的时候
public void readHandle(SelectionKey key) throws IOException {
SocketChannel client= (SocketChannel) key.channel();
ByteBuffer byteBuffer= (ByteBuffer) key.attachment();
byteBuffer.clear();
int read=0;
while(true){
read=client.read(byteBuffer);
if(read>0){
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
client.write(byteBuffer);
}
byteBuffer.clear();
}else if(read==0){
break;
}else{
client.close()
break;
}
}
public static void main(String[] args) throws IOException {
SocketMultiplexingSingleThread socketMultiplexingSingleThread=new SocketMultiplexingSingleThread();
socketMultiplexingSingleThread.start();
}
}
以上是多路复用中的select的用法,使用多路复用可以将有没有数据的判断交给内核去进行判断,而不用我们代码去进行判断,但是多路复用的底层有三种实现,分别是select,poll还有epoll,这三种是依次的优化,目的都是添加监听,但是内核实现判断哪些连接上有事件,并返回具体有事件的连接 的方法不同
视频学习:select poll epoll介绍