c10k问题
2000年左右提出的,BIO模型下的10K个socket处理客户端和服务端数据传输慢的问题。
单线程模拟10k个客户端
package io.bio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import java.util.List;
/**
* Author: ljf
* CreatedAt: 2021/3/31 下午2:24
*/
public class Ck10Client {
public static void main(String[] args) {
List<SocketChannel> clients = new LinkedList<>();
InetSocketAddress serverAddr = new InetSocketAddress("192.168.172.3",9090);
for(int i = 10000;i<65000;i++){
try {
SocketChannel client1 = SocketChannel.open();
/**
* linux 中看到的是 :
* client1 ... port 10002
* client2 ... port 10002
*/
client1.bind(new InetSocketAddress("192.168.172.1",i));
client1.connect(serverAddr);
clients.add(client1);
SocketChannel client2 = SocketChannel.open();
client2.bind(new InetSocketAddress("192.168.8.103",i));
client2.connect(serverAddr);
clients.add(client2);
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("clients :" + clients.size());
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端和客户端通信,在内核里会有两个socket,一是服务器内核listen客户端的socket,二是客户端进来后相互之间通信的socket(先netstat -natp 看java的pid,然后lsof -p pid 就可以看到了)
1.BIO模型
package io.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* bio 慢的原因:
* 1.accept阻塞, 后 new thread 那一步会发生系统调用 clone,这里用户态系统太切换
* 2.客户端连进来后的io流也是阻塞的。
* Author: ljf
* CreatedAt: 2021/3/31 下午1:38
*/
public class SocketBIO {
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(9090, 5);
System.out.println("step1: new ServerSocket(9090,5)");
while (true) {
Socket client = server.accept();
System.out.println("step2:client \t" + client.getPort());
new Thread(new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
try {
inputStream = client.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String s = reader.readLine();
while (true) {
if (s != null) {
System.out.println(s);
} else {
inputStream.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
阻塞发生在服务端accept客户端和服务端等待客户端数据(内核RECV(6)两处。所以慢。
2.NIO模型
package io.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import java.util.List;
/**
* nio 的n 有两个意思:
* 1.是accept(5 的时候 是 NON_BLOCKINg,和 RECV(6 的时候非阻塞
* 2.java 的new io ,即新io的意思
* <p>
* Author: ljf
* CreatedAt: 2021/3/31 下午3:16
*/
public class SocketNIO {
public static void main(String[] args) {
List<SocketChannel> clients = new LinkedList<>();
try {
ServerSocketChannel ss = ServerSocketChannel.open();// 服务端开启监听,接收客户端
ss.bind(new InetSocketAddress(9090)); // 绑定本地的9090端口
ss.configureBlocking(false); // 重点,这里是用非阻塞的方式接收客户端
while (true) {
// 接收客户端连接
// Thread.sleep(1000);
// accept 调用了内核的accept,没有客户端连进来返回值,在BIO的时候一直卡着,NIO不看着,返回-1,java 返回null
// 有客户端连进来,accept 返回这个客户端的FD5,client Object
// NONBLOCKING 就是代码能往下走了,但是往下走的情况要根据客户端是否连进来有不同
SocketChannel client = ss.accept();
if (client == null) {
System.out.println("null ...");
} else {
client.configureBlocking(false); // 重点,socket(服务端的listen
// socket<连接请求三次握手后,往这里扔,我去通过accept得到连接的socket>,连接socket<往后的数据读写使用的>)
int port = client.socket().getPort();
System.out.println("client port : " + port);
clients.add(client);
}
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);// 这种是直接在服务器内存分配,还有一种是allocate(int capacity)
// (这种是在jvm里分配,即堆内存)第一种会分配比较慢,第二种是发生系统内存到堆内存的复制,也不一定快,具体看运行情况
// 遍历已经连进来的客户端能不能读写数据
for (SocketChannel s : clients) {
int num = s.read(byteBuffer); // >0 -1 0 不会阻塞
if (num > 0) {
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
String b = new String(bytes);
System.out.println(client.socket().getPort() + " : " + b);
byteBuffer.clear();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
道理在代码注释里有了,目前这个代码写法有一个毛病是clients随着死循环的增多,遍历会慢,所以会越跑越慢,最后要么报文件描述符不够用错误。