Socket
Socket,又称“套接字”,应用程序通常通过“套接字”向网络发出请求或应答网络请求。
Socket 和 ServerSocket类位于 java.net 包中。对于一个网络连接来说,套接字是平等的,不因为在服务器端或在客户端而产生不同级别,它们的工作都是通过 SocketImpl 类及其子类完成的。
套接字之间的连接过程可以分为四个步骤:
1)服务器监听
2)客户端请求服务器
3)服务器确认
4)客户端确认
示例代码:
Server.java
public class Server {
final static int PROT = 8765;
public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket(PROT);
System.out.println(" server start .. ");
//进行阻塞
Socket socket = server.accept();
//新建一个线程执行客户端的任务
new Thread(new ServerHandler(socket)).start();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(server != null){
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
server = null;
}
}
Client.java
public class Client {
final static String ADDRESS = "127.0.0.1";
final static int PORT = 8765;
public static void main(String[] args) {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket(ADDRESS, PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
//向服务器端发送数据
out.println("接收到客户端的请求数据...");
out.println("接收到客户端的请求数据1111...");
String response = in.readLine();
System.out.println("Client: " + response);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
socket = null;
}
}
}
ServerHandler.java
public class ServerHandler implements Runnable{
private Socket socket ;
public ServerHandler(Socket socket){
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while(true){
body = in.readLine();
if(body == null) break;
System.out.println("Server :" + body);
out.println("服务器端回送响的应数据.");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
socket = null;
}
}
}
运行结果:
服务端
Server :接收到客户端的请求数据...
Server :接收到客户端的请求数据1111...
客户端
Client: 服务器端回送响的应数据.
采用线程池和任务队列实现伪异步 IO,将客户端的 socket 封装成 task 任务(实现 Runnable 接口的类),然后投递到线程池中去。
示例代码:
Server.java
public class Server {
final static int PORT = 8765;
public static void main(String[] args) {
ServerSocket server = null;
BufferedReader in = null;
PrintWriter out = null;
try {
server = new ServerSocket(PORT);
System.out.println("Server start");
Socket socket = null;
HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);
while(true){
socket = server.accept();
executorPool.execute(new ServerHandler(socket));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if(server != null){
try {
server.close();
} catch (Exception e3) {
e3.printStackTrace();
}
}
server = null;
}
}
}
HandlerExecutorPool.java
public class HandlerExecutorPool {
private ExecutorService executor;
public HandlerExecutorPool(int maxPoolSize, int queueSize){
this.executor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
maxPoolSize,
120L,
TimeUnit.SECONDS,
new ArrayBlockingQueue(queueSize));
}
public void execute(Runnable task){
this.executor.execute(task);
}
}
ServerHandler.java
public class ServerHandler implements Runnable {
private Socket socket;
public ServerHandler (Socket socket){
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while(true){
body = in.readLine();
if(body == null) break;
System.out.println("Server:" + body);
out.println("Server response");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (Exception e3) {
e3.printStackTrace();
}
}
socket = null;
}
}
}
Client.java
public class Client {
final static String ADDRESS = "127.0.0.1";
final static int PORT =8765;
public static void main(String[] args) {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket(ADDRESS, PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println("Client request");
String response = in.readLine();
System.out.println("Client:" + response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (Exception e3) {
e3.printStackTrace();
}
}
socket = null;
}
}
}
运行结果:
服务端
Server start
Server:Client request
客户端
Client:Server response
IO (BIO) 和 NIO 的区别:其本质就是阻塞和非阻塞的区别。
阻塞:应用程序在获取网络数据时,如果网络传输数据很慢,那么程序就一直等着,直到传输完毕为止。
非阻塞:应用程序直接可以获取已经准备好的数据,无需等待。
IO 为同步阻塞,NIO 为同步非阻塞。NIO 并没有实现异步,在 JDK1.7之后,升级了 NIO 库包,支持异步非阻塞通信模型,即 NIO2.0(AIO)。
同步和异步:同步和异步一般是面向操作系统与应用程序对 IO 操作的层面上来区别的。
同步时,应用程序会直接参与 IO 读写操作,并且应用程序会直接阻塞到某一个方法上,直到数据准备就绪,或者采用轮询的策略实时检查数据的就绪状态,如果就绪则获取数据。
异步时,所有的 IO 读写操作交给操作系统处理,与应用程序没有直接关系,当操作系统完成 IO 读写操作时,会给应用程序发送通知,应用程序直接拿走数据即可。
NIO 编程介绍
NIO 中三个重要概念:Buffer,Channel,Selector。
Buffer:Buffer 是一个对象,它包含一些要写入或者要读取的数据。在面向流的 IO 中,可以将数据直接写入或读取到 Stream 对象中。在 NIO 库中,所有数据都是用缓冲区处理的。缓冲区实际上是一个数组,通常是一个字节数组(ByteBuffer),也可以使用其他类型的数组。每一种 Java 基本数据类型都对应了一种缓冲区(除了 boolean 类型)。
Channel:通道与流的不同之处在于,通道是双向的,而流是单向的(一个流必须是 InputStream 或 OutputStream 的子类)。通道可以用于读、写或者二者同时进行,最重要的是可以和多路复用器结合起来,有多种的状态位,方便多路复用器去识别。
Selector:Selector 会不断地轮询注册在其上的通道,如果某个通道发生了读写操作,这个通道就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以取得就绪的 Channel 集合,从而进行后续的 IO 操作。一个 Selector 可以负责成千上万个 Channel 通道,没有上限,这也就意味着,只要一个线程负责 Selector 的轮询,就可以接入成千上万个客户端。
Selector 线程就类似一个管理者,管理了成千上万个管道,然后轮询哪个管道的数据已经准备好,通知 CPU 执行 IO 的读取或写入操作。
Selector 模式:当 IO 事件(管道)注册到 Selector 以后,Selector 会分配给每个管道一个 key 值。Selector 以轮询的方式进行查找注册的所有 IO 事件(管道),当 IO 事件(管道)准备就绪后,Selector 就会识别,通过 key 值找到相应的管道,进行相关的数据处理操作。
示例代码:
Server.java
public class Server implements Runnable{
//1 多路复用器(管理所有的通道)
private Selector seletor;
//2 建立缓冲区
private ByteBuffer readBuf = ByteBuffer.allocate(1024);
//3
private ByteBuffer writeBuf = ByteBuffer.allocate(1024);
public Server(int port){
try {
//1 打开路复用器
this.seletor = Selector.open();
//2 打开服务器通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//3 设置服务器通道为非阻塞模式
ssc.configureBlocking(false);
//4 绑定地址
ssc.bind(new InetSocketAddress(port));
//5 把服务器通道注册到多路复用器上,并且监听阻塞事件
ssc.register(this.seletor, SelectionKey.OP_ACCEPT);
System.out.println("Server start, port :" + port);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
try {
//1 必须要让多路复用器开始监听
this.seletor.select();
//2 返回多路复用器已经选择的结果集
Iterator keys = this.seletor.selectedKeys().iterator();
//3 进行遍历
while(keys.hasNext()){
//4 获取一个选择的元素
SelectionKey key = keys.next();
//5 直接从容器中移除就可以了
keys.remove();
//6 如果是有效的
if(key.isValid()){
//7 如果为阻塞状态
if(key.isAcceptable()){
this.accept(key);
}
//8 如果为可读状态
if(key.isReadable()){
this.read(key);
}
//9 写数据
if(key.isWritable()){
//this.write(key); //ssc
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void write(SelectionKey key){
//ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//ssc.register(this.seletor, SelectionKey.OP_WRITE);
}
private void read(SelectionKey key) {
try {
//1 清空缓冲区旧的数据
this.readBuf.clear();
//2 获取之前注册的socket通道对象
SocketChannel sc = (SocketChannel) key.channel();
//3 读取数据
int count = sc.read(this.readBuf);
//4 如果没有数据
if(count == -1){
key.channel().close();
key.cancel();
return;
}
//5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位)
this.readBuf.flip();
//6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据
byte[] bytes = new byte[this.readBuf.remaining()];
//7 接收缓冲区数据
this.readBuf.get(bytes);
//8 打印结果
String body = new String(bytes).trim();
System.out.println("Server : " + body);
// 9..可以写回给客户端数据
} catch (IOException e) {
e.printStackTrace();
}
}
private void accept(SelectionKey key) {
try {
//1 获取服务通道
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//2 执行阻塞方法
SocketChannel sc = ssc.accept();
//3 设置阻塞模式
sc.configureBlocking(false);
//4 注册到多路复用器上,并设置读取标识
sc.register(this.seletor, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(new Server(8765)).start();;
}
}
Client.java
public class Client {
//需要一个Selector
public static void main(String[] args) {
//创建连接的地址
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8765);
//声明连接通道
SocketChannel sc = null;
//建立缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
//打开通道
sc = SocketChannel.open();
//进行连接
sc.connect(address);
while(true){
//定义一个字节数组,然后使用系统录入功能:
byte[] bytes = new byte[1024];
System.in.read(bytes);
//把数据放到缓冲区中
buf.put(bytes);
//对缓冲区进行复位
buf.flip();
//写出数据
sc.write(buf);
//清空缓冲区数据
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(sc != null){
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
客户端(控制台输入)
hello nio
服务端
Server start, port :8765
Server : hello nio