一、网络通信编程基本常识
1、什么是Socket?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,一般由操作系统提供。
2、短连接
短连接是指socket建立连接之后传输数据确定接收完后关闭连接
3、长连接
长连接是指建立socket连接后不管是否使用都保持连接
4、什么时候用长连接,短连接?
http1.1网上都开始向长连接演化,web网站成千上万的客户端使用短连接会更节省资源消耗,短连接频繁的通信会造成socket错误,而socket频繁的创建也会耗费系统资源
二、Java原生网络编程
1、原生JDK网络编程BIO
面向流的阻塞IO,
服务端(这种来一个客户端就创建一个线程的方式,如果客户端连接数量过多,内存资源消耗过多,造成系统崩溃)
public class Server {public static void main(String[] args) throws IOException {// 创建ServerSocket接收客户端连接对象ServerSocket serverSocket = new ServerSocket();// 绑定服务器监听端口serverSocket.bind(new InetSocketAddress(8888));System.out.println("start server...");while (true) {new Thread(new ServerTask(serverSocket.accept())).start();}}private static class ServerTask implements Runnable {private Socket socket = null;public ServerTask(Socket socket) {this.socket = socket;}@Overridepublic void run() {try(//实例化与客户端通信的输入输出流ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())){//接收客户端的输出,也就是服务器的输入String userName = inputStream.readUTF();System.out.println("Accept client message:"+userName);//服务器的输出,也就是客户端的输入outputStream.writeUTF("Hello,"+userName);outputStream.flush();}catch(Exception e){e.printStackTrace();}finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}
}// 单线程实现服务端连接(单线程服务端无法同时支持多个连接同时处理)
class ServerSingle {public static void main(String[] args) throws IOException {//服务端启动必备ServerSocket serverSocket = new ServerSocket();//表示服务端在哪个端口上监听serverSocket.bind(new InetSocketAddress(8888));System.out.println("Start Server ....");int connectCount = 0;try {while(true){Socket socket = serverSocket.accept();System.out.println("accept client socket ....total =" + ( ++connectCount));//实例化与客户端通信的输入输出流try(ObjectInputStream inputStream =new ObjectInputStream(socket.getInputStream());ObjectOutputStream outputStream =new ObjectOutputStream(socket.getOutputStream())){//接收客户端的输出,也就是服务器的输入String userName = inputStream.readUTF();System.out.println("Accept client message:"+userName);//服务器的输出,也就是客户端的输入outputStream.writeUTF("Hello,"+userName);outputStream.flush();}catch(Exception e){e.printStackTrace();}finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}} finally {serverSocket.close();}}
}// 线程池的方式启动服务端(受线程数量影响,如果传输的数据过大 比如文件,占用时间过长的话,其它连接只能等待)
class ServerPool {private static ExecutorService executorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());public static void main(String[] args) throws IOException {//服务端启动必备ServerSocket serverSocket = new ServerSocket();//表示服务端在哪个端口上监听serverSocket.bind(new InetSocketAddress(10001));System.out.println("Start Server ....");try{while(true){executorService.execute(new ServerTask(serverSocket.accept()));}}finally {serverSocket.close();}}//每个和客户端的通信都会打包成一个任务,交个一个线程来执行private static class ServerTask implements Runnable{private Socket socket = null;public ServerTask(Socket socket){this.socket = socket;}@Overridepublic void run() {//实例化与客户端通信的输入输出流try(ObjectInputStream inputStream =new ObjectInputStream(socket.getInputStream());ObjectOutputStream outputStream =new ObjectOutputStream(socket.getOutputStream())){//接收客户端的输出,也就是服务器的输入String userName = inputStream.readUTF();System.out.println("Accept client message:"+userName);//服务器的输出,也就是客户端的输入outputStream.writeUTF("Hello,"+userName);outputStream.flush();}catch(Exception e){e.printStackTrace();}finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}}
客户端
public class Client {public static void main(String[] args) throws IOException {Socket socket = null;ObjectOutputStream objectOutputStream = null;ObjectInputStream objectInputStream = null;InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);try {socket = new Socket();socket.connect(inetSocketAddress);System.out.println("connect server success");objectOutputStream = new ObjectOutputStream(socket.getOutputStream());objectInputStream = new ObjectInputStream(socket.getInputStream());System.out.println("ready send message");objectOutputStream.writeUTF("gaorufeng");objectOutputStream.flush();// 接收并打印服务器传输的数据System.out.println(objectInputStream.readUTF());} finally {if (socket!=null) socket.close();if (objectOutputStream!=null) objectOutputStream.close();if (objectInputStream!=null) objectInputStream.close();}}
}
BIO阻塞的两个地方:
1、服务端执行serverSocket.accept()方法后会进入阻塞,直到有客户端请求进来
2、服务器如果是单线程运行(一个线程处理多个客户端请求会进入阻塞),如果数据还没传输完毕,其它客户端socket是无法与服务端建立tcp连接的,虽然socket显示连接成功,但是tcp连接还没建立
BIO适合少量连接的使用场景
BIO多线程通信模型:
2、原生JDK网络编程NIO
面向缓冲的非阻塞IO
1)NIO的Reactor模式
BIO和NIO最大的区别就是线程阻塞和不阻塞,NIO就是基于Reactor模式实现的,所谓Reactor模式,就是往一个组件注册感兴趣的事件并监听,触发对应的事件进行处理,而不是阻塞
2)NIO三大核心组件
- Selector选择器:Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器(Selectors),然后使用一个单独的线程来操作这个选择器,进而“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道(应用程序将向 Selector 对象注册需要它关注的 Channel,以及具体的某一个 Channel会对哪些 IO 事件感兴趣。Selector 中也会维护一个“已经注册的Channel”的容器)。
- Channel管道:被建立的一个应用程序和操作系统交互事件、传递内容的渠道(注意是连接到操作系统)。那么既然是和操作系统进行内容的传递,那么说明应用程序可以通过通道读取数据,也可以通过通道向操作系统写数据,而且可以同时进行读写。
- buffer缓冲区:JDK NIO是面向缓冲的。Buffer就是这个缓冲,用于和 NIO 通道进行交互。 数据是从通道读入缓冲区,从缓冲区写入到通道中的。以写为例,应用程序都是将数据写入 缓冲,再通过通道把缓冲的数据发送出去,读也是一样,数据总是先从通道读到缓冲,应用程序再读缓冲的数据。
public class NioSelectorServer {public static void main(String[] args) throws IOException, InterruptedException {// 创建NIO ServerSocketChannelServerSocketChannel serverSocket = ServerSocketChannel.open();serverSocket.socket().bind(new InetSocketAddress(9000));// 设置ServerSocketChannel为非阻塞serverSocket.configureBlocking(false);// 打开Selector处理Channel,即创建epollSelector selector = Selector.open();// 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣serverSocket.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服务启动成功");while (true) {// 阻塞等待需要处理的事件发生selector.select();// 获取selector中注册的全部事件的 SelectionKey 实例Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();// 遍历SelectionKey对事件进行处理while (iterator.hasNext()) {SelectionKey key = iterator.next();// 如果是OP_ACCEPT事件,则进行连接获取和事件注册if (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel socketChannel = server.accept();socketChannel.configureBlocking(false);// 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件socketChannel.register(selector, SelectionKey.OP_READ);System.out.println("客户端连接成功");} else if (key.isReadable()) { // 如果是OP_READ事件,则进行读取和打印SocketChannel socketChannel = (SocketChannel) key.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(128);int len = socketChannel.read(byteBuffer);// 如果有数据,把数据打印出来if (len > 0) {System.out.println("接收到消息:" + new String(byteBuffer.array()));} else if (len == -1) { // 如果客户端断开连接,关闭SocketSystem.out.println("客户端断开连接");socketChannel.close();}}//从事件集合里删除本次处理的key,防止下次select重复处理iterator.remove();}}}
}