传统的基于IO的网络编程(阻塞IO):(伪代码)
ServerSocket serverSocket = …….
serverSocket.bind(8899);//用于发起连接的端口号,并不是建立好连接后数据传输的端口号
while(true){
Socket socket = severSocket.accept(); //是一个阻塞方法,如果没有客户端与服务器端发起连接,
//服务器端就停在这里直到有客户端请求建立连接
New Tread(socket);//起一个新的线程的一个好处,流程执行到这里还可以继续往下执行
//在while循环中,一个客户端连接好后,用线程处理这个连接;继续等待下一个客户端的连接
Run(){
Socket.getInputStream().
}
}
//客户端服务器端建立好连接后,服务端会根据当前的操作系统所空闲的端口号来随机进行指派。
//(每一个客户端-服务器连接都会有一个端口号)
客户端代码:
Socket socket = new Socket(“location”,8899);
Socket.connect();
telnet与nc 命令行客户端监听服务器命令程序:Telnet:打开telent客户端;set localecho:打开本地回显;telent localhost 5000:建立连接
每一个服务端向服务端发起连接请求的时候,服务器端都会起一个新的线程;问题:线程占用系统资源,且在操作系统中线程有最大值;
NIO
一个线程可以处理很多个客户端;Selector;事件:当某一个动作发生的时候。
已经和客户端发起连接的socketchannel,任何一个selectableChannel都可以将自己注册到一个Selector中,这样,这个channel就能被Selector所管理。每一个事件一旦产生之后,得到相应的selectionKey,其携带了与某一个channel关联的对象。就可以通过channe不断地获取到数据。以事件为模型;SocketChannel就是SelectableChannel的一种。
由此构成了由一个或极少数线程,来处理大量客户端连接的结构;当与客户端连接的数据一旦准备好时,selector就能立即得到通知,获取数据进行处理。
public static void main(String[] args) throws Exception {
int[] ports = new int[4];
ports[0] = 5000;
ports[1] = 5001;
ports[2] = 5002;
ports[3] = 5003;
Selector selector = Selector.open();
System.out.println(SelectorProvider.provider().getClass());//class sun.nio.ch.WindowsSelectorProvider
System.out.println(sun.nio.ch.DefaultSelectorProvider.create().getClass());//class sun.nio.ch.WindowsSelectorProvider
for (int i = 0; i < ports.length; ++i) {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//关注的是连接事件
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
InetSocketAddress address = new InetSocketAddress(ports[i]);
serverSocket.bind(address);//将所有的channel绑定完成
//进行注册,到selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("监听端口" + ports[i]);
}
//连接好后等待客户端给我们传输数据
while (true) {
int numbers = selector.select();//key的数量
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println("selectorKeys:" + selectionKeys);
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();//关注的是连接事件
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);//建立好连接
socketChannel.register(selector, SelectionKey.OP_READ);//关注的是读取事件
iterator.remove();
System.out.println("获得客户端连接:" + socketChannel);
} else if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
int byteRead = 0;
while (true) {
ByteBuffer buffer = ByteBuffer.allocate(512);
buffer.clear();
int read = socketChannel.read(buffer);//
if (read <= 0) {
break;//已经读完了
}
buffer.flip();
socketChannel.write(buffer);
byteRead += read;
}
System.out.println("读取:" + byteRead + ",来自于:" + socketChannel);
iterator.remove();
}
}
}
}
实现一个简单的通讯服务,包含多个客户端与一个服务端;客户端发送的消息可以被服务端接收且同步到其他的客户端显示。
public class NIOClient {
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector,SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8899));
// ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue();
// queue.offer("liu");
while (true) {
selector.select();
Set<SelectionKey> keySet = selector.selectedKeys();
for (SelectionKey selectionKey : keySet) {
if (selectionKey.isConnectable()) {
SocketChannel client = (SocketChannel) selectionKey.channel();
if (client.isConnectionPending()) {
client.finishConnect();
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put((LocalDateTime.now() + " 连接成功").getBytes());
buffer.flip();
client.write(buffer);//发送给服务端
ExecutorService executorService = Executors.
newSingleThreadExecutor(Executors.defaultThreadFactory());
executorService.submit(() -> {
while (true) {
try {
buffer.clear();
InputStreamReader input = new InputStreamReader(System.in);//输入流->字符输入流
BufferedReader br = new BufferedReader(input);//创建一个缓冲字符输入流
String sendMessage = br.readLine();
buffer.put(sendMessage.getBytes());//转化为字节数组,并给buffer
buffer.flip();
client.write(buffer);//写出到buffer
} catch (Exception e) {
e.printStackTrace();
}
}}
);
}
client.register(selector, SelectionKey.OP_READ);//将读事件注册到client,服务端所发到客户端的消息才能被接受
}else if(selectionKey.isReadable()){
SocketChannel client = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = client.read(buffer);
if(count > 0){
String receiveMessage = new String(buffer.array(),0,count);
System.out.println(receiveMessage);
}
}
}
keySet.clear();//最后清空
}
} catch (Exception x) {
x.printStackTrace();
}
}
}
public class NIOSever {
private static Map<String, SocketChannel> clientMap = new HashMap<>();
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(8899));
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(true){
try{
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
selectionKeys.forEach(selectionKey -> {
final SocketChannel client;
try {
if(selectionKey.isAcceptable()){//是否有新进来的连接
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
String key = "【"+ UUID.randomUUID().toString() + "】";
clientMap.put(key, client);//每一个客户端,对应的值放到Map中
}else if(selectionKey.isReadable()){//是否有新到来的数据
client = (SocketChannel)selectionKey.channel();
ByteBuffer readbuffer = ByteBuffer.allocate(1024);
int count = client.read(readbuffer);
if(count > 0){
readbuffer.flip();
Charset charset = Charset.forName("utf-8");
String receivedMessage = String.valueOf(charset.decode(readbuffer).array());//向服务端发送的内容
System.out.println(client + ":" + receivedMessage);//得到了一个客户端向服务器发出的信息
String senderkey = null;
for(Map.Entry<String, SocketChannel> entry : clientMap.entrySet()){
if(client == entry.getValue()){
senderkey = entry.getKey();//拿到发送者的key值
break;
}
}
for(Map.Entry<String, SocketChannel> entry : clientMap.entrySet()){
SocketChannel value = entry.getValue();//对于每一个客户端
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put((senderkey + ":" + receivedMessage).getBytes());//信息变为一个字节数组,放到buffer中
buffer.flip();
value.write(buffer);//写到buffer中,即写给客户端
}
}
}
}catch (IOException e) {
e.printStackTrace();
}
});
selectionKeys.clear();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
}
AIO是异步IO的缩写。虽然NIO在网络中提供了非阻塞的方法,但是NIO的IO行为还是替你干部的。对NIO来说,业务操作是在IO操作准备好时,得到通知,接着就由线程自行进行IO操作,IO操作本身还是同步的。对于AIO来说,它不是在IO准备好时再通知线程,而是在IO操作已经完成后,再给线程发出通知。因此AIO是完全不会阻塞的。业务逻辑变为了一个回调函数,等待IO操作完成后,由系统自动触发。