BIO的服务端程序
package com.dingyf.net.bio;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BioServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
ExecutorService executorService = Executors.newCachedThreadPool();
while (true) {
/**
* @class ServerSocket
* This class implements server sockets.
* A server socket waits for requests to come in over the network.
* It performs some operation based on that request, and then possibly returns a result to the requester.
*
* @class Socket
* This class implements client sockets (also called just "sockets").
* A socket is an endpoint for communication between two machines.
*
* @method accept
* Listens for a connection to be made to this socket and accepts it.
* The method blocks until a connection is made.
*/
/**
* 在这里接收到了一个连接请求的Socket
* [1] ServerSocket serverSocket = new ServerSocket(8080);
* ServerSocket已经绑定了端口号,所以它只能接收对固定端口号的请求
* 对比NIO中的Selector,一个Selector上可以注册多个ServerSocketChannel
* 其实这没有什么意义,因为一段服务器代码不需要暴露多个端口号
* [2] Socket socket = serverSocket.accept();
* Set<SelectionKey> selectionKeys = selector.selectedKeys();
* ServerSocket一次只能接收一个请求,对比Selector.selectedKeys()方法一次则可以接收多个请求
*/
Socket socket = serverSocket.accept();
TaskHandler handler = new TaskHandler(socket);
/**
* 在这里将前面接收到的请求抛给线程池去执行
* todo 这里也可以不把接收到的Socket交给线程池去执行,而是自己执行,但是因为一次只能接收一个请求,?所以可能会使客户端的请求得不到连接
* todo 这里如果当前线程自己执行的后果就是后续请求由于服务器线程在执行计算而无法得到连接;那么nio即使一次接收多个请求,不也存在同样的问题吗,Selector接收的请求虽然多了,但是计算量也相应增大了,所以后续的请求不也应当得不到连接吗(得不到连接也就是执行不到selector.select();)
* todo nio是怎么处理请求的,如果不是交给线程池的话
* todo 目前来看,我看到的bio和nio编码方式上的区别只有两点:
* todo 1. nio一次可以接收多个请求
* todo 2. nio的读写不需要获取流对象,直接通过channel的读写方法
*/
executorService.submit(handler);
}
}
static class TaskHandler implements Runnable{
private Socket socket;
private Scanner scanner;
private BufferedWriter writer;
public TaskHandler(Socket socket) {
this.socket = socket;
try {
this.scanner = new Scanner(socket.getInputStream());
this.writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
boolean flag = true;
while (flag){
String line = scanner.nextLine();
System.out.println("Thread ID:" + Thread.currentThread().getId()+" Read from client - " + line);//打印客户端传来的消息
if(line != null){
String writeMsg;
if(line.equalsIgnoreCase("bye")){
flag = false;
writeMsg = "[Exit] " + line + "\n";
}else {
writeMsg = "[Echo] " + line + "\n";
}
try {
writer.write(writeMsg);//向客户端发送消息
writer.flush();
// writer.write("------------\n");//向客户端发送消息
// writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
scanner.close();
try {
writer.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BIO的客户端程序
package com.dingyf.net.bio;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Random;
import java.util.Scanner;
public class BioClient {
public static void main(String[] args) throws IOException {
/**
* 1. Socket socket = serverSocket.accept();
* 2. Socket socket = new Socket(InetAddress.getByName("localhost"), 8080);
* BioServer启动时,ServerSocket.accept()方法阻塞,当BioClient中new Socket()的方法执行后,Server线程被唤醒
*/
Socket socket = new Socket(InetAddress.getByName("localhost"), 8080);
Scanner scanner = new Scanner(socket.getInputStream());
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while (true){
String input = getInput();
writer.write(input + "\n");//向服务端发送消息
writer.flush();//一定记得要flush
// socket.getOutputStream().write((input + "\n").getBytes());
/**
* 阻塞指的就是io的读操作,如果流中没有内容,bio会一直等待,直到有数据才返回
*/
String line = scanner.nextLine();
System.out.println("Thread ID:" + Thread.currentThread().getId()+" Read response from server - " + line);//打印服务端传来的消息
if(input.equalsIgnoreCase("bye")){
break;
}
}
scanner.close();
writer.close();
socket.close();
}
private static String getInput(){
int i = new Random().nextInt(100);
if(i == 50){
return "bye";
}
return "This is Client Msg " + i;
}
}
NIO的服务端程序
package com.dingyf.net.nio;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NioServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
ExecutorService executorService = Executors.newFixedThreadPool(2);
while (true) {
/**
* @method select
* Selects a set of keys whose corresponding channels are ready for I/O operations.
* This method performs a blocking selection operation.
* It returns only after at least one channel is selected,
* this selector's wakeup method is invoked,
* or the current thread is interrupted, whichever comes first.
*/
/**
* todo select方法也是阻塞的,所以nio的非阻塞指的不会是这里
*/
int select = selector.select();//①
if(select <= 0){
break;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
/**
* Tips.
* ① int select = selector.select();
* 如果这里不remove,在下一个while(true)的循环中,①处将返回0
*/
iterator.remove();
/**
* @method isAcceptable
* Tests whether this key's channel is ready to accept a new socket connection.
*/
if (selectionKey.isAcceptable()) {
// ServerSocketChannel serverSocketChannel2 = (ServerSocketChannel) selectionKey.channel();
// SocketChannel channel = serverSocketChannel2.accept();
SocketChannel channel = serverSocketChannel.accept();
if (channel != null) {
TaskHandler handler = new TaskHandler(channel);
executorService.submit(handler);
}
}
}
}
}
static class TaskHandler implements Runnable {
private SocketChannel channel;
public TaskHandler(SocketChannel channel) {
this.channel = channel;
}
@Override
public void run() {
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
try {
boolean flag = true;
while (flag) {
byteBuffer.clear();
int read = channel.read(byteBuffer);//读取
/**
* Tips.
* ① String line = new String(byteBuffer.array());
* ② byteBuffer.put(writeMsg.getBytes());
* 这里如果用代码①来读取,代码会在②处无法执行
*/
String line = new String(byteBuffer.array(), 0, read);
System.out.println("Thread ID:" + Thread.currentThread().getId() + " Read from client - " + line);//打印客户端传来的消息
if (line != null) {
String writeMsg;
if (line.equalsIgnoreCase("bye\n")) {
flag = false;
writeMsg = "[Exit] " + line;
} else {
writeMsg = "[Echo] " + line;
}
/**
* 假设在此处打了断点,记为: NioServer.Breakpoint_1
*/
byteBuffer.clear();
byteBuffer.put(writeMsg.getBytes());//②
byteBuffer.flip();
channel.write(byteBuffer);//写入
}
}
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
NIO的客户端程序
package com.dingyf.net.nio;
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.Random;
public class NioClient {
public static void main(String[] args) throws IOException {
SocketChannel channel = SocketChannel.open();
channel.connect(new InetSocketAddress("localhost", 8080));
// channel.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
while (true) {
String input = getInput();
byteBuffer.clear();
byteBuffer.put((input + "\n").getBytes());
byteBuffer.flip();
channel.write(byteBuffer);//向服务端发送消息
byteBuffer.clear();
/**
* ① channel.write(byteBuffer);
* ② channel.configureBlocking(false);
* ③ int read = channel.read(byteBuffer);
* 假设在该行打了断点,记为: NioClient.Breakpoint_1
* Debug启动该客户端,则断点停在NioClient.Breakpoint_1,F8之后,该线程将阻塞,
* 这时我们进入服务端的Debug Tab,断点跳到NioServer.Breakpoint_1,我们F8直到①处,再次F8,
* 回到客户端的Debug Tab,断点来到NioClient.Breakpoint_1的下一行
*
* 而如果我们添加了②,将客户端的SocketChannel设置为非阻塞,结果将不一样
* 在NioClient.Breakpoint_1处F8之后,断点将来到下一行,只是此时③处返回的读到的数据长度为0
* 如果我们将断点NioServer.Breakpoint_1放行通过①,那么③将能够读到数据,并返回数据长度
*
* 也就是说,nio的非阻塞描述的是:
* 从通道中读取数据时,如果此时通道中没有数据,read方法将继续执行,只是此时read方法返回的数据长度为0,而ByteBuffer中也不会被填充内容;
* 相应的,如果是bio或者未将nio的channel设置为非阻塞的情况下,从流(bio)或者通道(nio)中读取数据时,如果此时流或者通道中没有数据,此时read方法将阻塞,直到被调用方向流或通道中写入数据才会返回
*
* 对于nio的非阻塞,有一点值得注意:
* 调用方需要不断轮询请求read方法,才能够在被调用方向channel中写入数据后将其取出
* 所以应当设置单独的线程管理这些channel,
*/
int read = channel.read(byteBuffer);
String line = new String(byteBuffer.array(), 0, read);
// String line = new String(byteBuffer.array());
System.out.println("Thread ID:" + Thread.currentThread().getId() + " Read response from server - " + line);//打印服务端传来的消息
if (input.equalsIgnoreCase("bye")) {
break;
}
}
channel.close();
}
private static String getInput() {
int i = new Random().nextInt(100);
if (i == 50) {
return "bye";
}
return "This is Client Msg " + i;
}
}