服务与服务之间的通信,在Java领域中有很多可实现远程通讯的技术,例如:RMI、MINA、ESB、Burlap、Hessian、SOAP、EJB和JMS等。
远程通信之socket
Java BIO (blocking I/O): 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善
代码demo 1
package com.socket.demo;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* autor:lld
* 2020/6/1119:59
*/
public class ServerSocketDemo {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8080);
//通过accept去阻塞(连接组啥)并监听客户端连接
Socket socket =serverSocket.accept();
System.out.println(socket.getPort());
//获取到连接之后,拿到输入输出流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//读取客户端的数据
String clinetMessage = bufferedReader.readLine();
System.out.println("接收到客户端的数据为:"+clinetMessage);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("收到\n");
bufferedWriter.flush();
bufferedReader.close();
bufferedWriter.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
package com.socket.demo;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* autor:
* 2020/6/1119:59
*/
public class ClientSocketDemo {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost",8080);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("服务端你好\n");
bufferedWriter.flush();
//获取到连接之后,拿到输入输出流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//读取客户端的数据
String serverMessage = bufferedReader.readLine();
System.out.println("接收到服务端的数据为:"+serverMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
}
### 代码demo 1服务端保持不关闭,通过线程池来管理线程,允许多个客户端建立连接通道
package test;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server {
public Server() {
}
public static ExecutorService executorService = Executors.newCachedThreadPool();
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8080);
while(true){
Socket socket = serverSocket.accept();
executorService.execute(() -> {
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = bufferedReader.readLine();
System.out.println("接收到客户端的信息:" + s);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("村上春树1\n");
bufferedWriter.flush();
bufferedReader.close();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
});
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//TODO
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package test;
import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
public Client() {
// TODO Auto-generated constructor stub
}
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
Socket socket = new Socket("127.0.0.1", 8080);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("服务端你好\n");
bufferedWriter.flush();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = bufferedReader.readLine();
System.out.println("接收到服务端的消息:"+s);
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO (non-blocking I/O): 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。(serverSocketchannel一个支持IO重用,所以一个线程就可以处理多个客户端的连接),多路复用机制
这个机制可以通过鱼竿钓鱼的来辅助理解,假设有100个鱼竿,selector多路复用器就会去轮询,如果有鱼竿上钩了就会通知我们,去启动一个线程进行处理
DEMO1
package test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
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 NioServer {
static Selector selector;
public static void main(String[] args) {
try {
selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//selector 必须是非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8080));
//SelectionKey.OP_ACCEPT 监听的操作
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(true){
//阻塞机制
selector.select();
//获取到监听集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
iterator.remove();
//判断是否为链接事件
if(selectionKey.isAcceptable()){
handleAccept(selectionKey);
}
//判断是否为读事件
else if(selectionKey.isReadable()){
handleRead(selectionKey);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handleAccept(SelectionKey selectionKey){
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
try {
SocketChannel socketChannel = serverSocketChannel.accept();
//非阻塞
socketChannel.configureBlocking(false);
socketChannel.write(ByteBuffer.wrap("你好,我是服务器端".getBytes()));
//注册读事件
socketChannel.register(selector,SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handleRead(SelectionKey selectionKey){
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
//不一定有值
socketChannel.read(byteBuffer);
System.out.println("客户端的返回值:"+new String(byteBuffer.array()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
package test;
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.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NioClient {
static Selector selector;
public static void main(String[] args) {
try {
selector = Selector.open();
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("localhost",8080));
//注册连接事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
while(true){
//阻塞机制
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
//判断是否为连接事件
if(selectionKey.isConnectable()){
handleConnection(selectionKey);
}else if(selectionKey.isReadable()){
handleRead(selectionKey);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handleConnection(SelectionKey selectionKey) throws IOException {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
if (socketChannel.isConnectionPending()){
socketChannel.finishConnect();
}
socketChannel.configureBlocking(false);
socketChannel.write(ByteBuffer.wrap("服务端你好,我是客户端".getBytes()));
socketChannel.register(selector,SelectionKey.OP_READ);
}
private static void handleRead(SelectionKey selectionKey) throws IOException {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
socketChannel.read(byteBuffer);
System.out.println("服务端发送的消息为:"+new String(byteBuffer.array()));
}
}
AIO异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理
相当于100个鱼竿在钓鱼的过程中,selector多路复用器不去轮询,让鱼上钩之后,触发事件进行通知去启用一个线程来处理。
select / poll /epoll 理解
(1)select->时间复杂度O(n)
它仅仅知道了,有I/O事件发生了,却并不知道哪几个流(可能一个,多个,甚至全部),只能通过无差别的轮询,找到能读出数据或者写入数据的流,对他们进行操作。同时处理的流越多,无差别轮询就越长
(2)poll->时间复杂度O(n)
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,但是他没有最大连接数的限制,原因是它是基于链表来存储的
(3)epoll->时间复杂度为O(1)
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/o事件通知我们,所以我们说epoll实际上是事件驱动(每个时间关联fd)的,此时我们对这些流的操作都是有意义的。
select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写时间就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责吧数据从内核拷贝到用户空间。
epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所持有,而select则应该是POSIX所规定,一般操作系统均有实现。
使用场景
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。