Java IO 与 NIO 服务器&客户端通信小栗子

本篇包含了入门小栗子以及一些问题的思考

BIO

package com.demo.bio;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner; /**
* 问题:开启多个客户端,只有服务端发送足够条数的消息,客户端才会收到
*/
public class Server { public static void main(String[] args) throws Exception {
new Server().startServer();
} public void startServer() throws IOException {
ServerSocket serverSocket = new ServerSocket(9999); while (true){
Socket client = serverSocket.accept();
System.err.println("Client:" + client.getInetAddress().getHostAddress());
OutputStream out = client.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"), true);
writer.println("Hello!We are already connected!say 'bye' to close"); new Thread(new SocketReadThread(client)).start();
new Thread(new SocketWriteThread(client)).start();
} }
} /**
* 读线程
*/
class SocketReadThread implements Runnable{ private Socket socket; public SocketReadThread(Socket socket) {
this.socket = socket;
} @Override
public void run() {
try {
InputStream in = socket.getInputStream();
Scanner scanner = new Scanner(in, "UTF-8");
boolean bye = false;
while (!bye && scanner.hasNextLine()){
String line = scanner.nextLine();
System.out.println("Client Msg[" + socket + "]:" + line);
if(line.trim().equals("bye")){
bye = true;
}
}
in.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
} }
} /**
* 写线程
*/
class SocketWriteThread implements Runnable{ private Socket socket; public SocketWriteThread(Socket socket) {
this.socket = socket;
} @Override
public void run() {
try {
OutputStream out = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"), true);
Scanner scanIn = new Scanner(System.in);
while (true){
String line = scanIn.nextLine();
writer.println(line);
if (socket.isClosed()){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} }
}
package com.demo.bio;

import java.io.*;
import java.net.Socket;
import java.util.Scanner; /**
* 客户端
*/
public class Client { public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 9999);
OutputStream out = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"), true); new Thread(new SocketReceiveThread(socket)).start();
Scanner scanIn = new Scanner(System.in);
while (!socket.isClosed()){
String line = scanIn.nextLine();
writer.println(line);
if(line.trim().equals("bye")){
socket.close();
}
}
} }
class SocketReceiveThread implements Runnable{ private Socket socket; public SocketReceiveThread(Socket socket) {
this.socket = socket;
} @Override
public void run() {
try {
InputStream in = socket.getInputStream();
Scanner scanner = new Scanner(in, "UTF-8");
boolean bye = false;
while (!bye && scanner.hasNextLine()){
String line = scanner.nextLine();
System.out.println("Server Msg:" + line);
if(line.trim().equals("bye")){
bye = true;
}
}
scanner.close();
} catch (IOException e) {
e.printStackTrace();
} }
}

BIO没什么难的,同步阻塞。上面实现的主要就是服务器和客户端你一句我一句,巴拉巴拉巴拉

NIO

我要实现一个客户端服务器通信的例子,我的第一个版本

package com.demo.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set; /**
* 问题:启动服务器,没有启动客户端的时候,阻塞在selector.select();直到有客户端连接才会向下走。
* 启动客户端:获取到客户端的消息,并读取显示;然后写一条数据给客户端;然后进入了写操作模块,等待写入,阻塞。
* 这个时候,客户端已经经过了读取操作,并且没有读到数据,也进入了写操作模块,等待写入,阻塞。这就解释了为什么客户端收不到服务器的第一条消息。
* 客户端写入:客户端输入数据,发送给服务器,离开写操作模块,进入下一轮循环,然后进入读操作模块,读取到服务器的第一条消息并显示。
* 服务器接收:此时服务器并没有收到客户端的消息,因为此时还在写操作模块阻塞,所以想要读取到数据,就要向客户端发送数据,以离开写操作模块,进入下一轮循环。
* 这就解释了:为什么要先写入才能读取的数据。
*/
public class Server { private boolean isFirst = true;
private ServerSocketChannel ssc = null;
private Selector selector = null; public Server(int port) throws IOException {
ssc = ServerSocketChannel.open();
selector = Selector.open();
InetSocketAddress inetAddress = new InetSocketAddress(InetAddress.getLocalHost(), port); ssc.socket().bind(inetAddress);
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
listener(selector);
} private void listener(Selector selector) throws IOException{
while(true){
System.out.println("等待客户端连接...");
selector.select();
System.out.println("捕获客户端连接...");
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator(); while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//连接事件
if(key.isAcceptable()){
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
channel.accept().configureBlocking(false).register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//System.out.println(channel.toString() + "-已连接");
}
//读数据
if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer bf = ByteBuffer.allocate(1024);
channel.read(bf);
System.out.println("来自客户端数据:" + new String(bf.array()));
// 只有第一次通信返回消息
if(isFirst){
isFirst = false;
ByteBuffer bst = ByteBuffer.wrap("Hi!".getBytes());
channel.write(bst);
}
}
//写数据
if(key.isWritable()){
Scanner scanner = new Scanner(System.in);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String msg = sdf.format(new Date()) + "\t" + scanner.nextLine();
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer bst = ByteBuffer.wrap(msg.getBytes());
channel.write(bst);
// key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);// 取消写就绪,否则会一直触发写就绪
}
iterator.remove(); } }
} public static void main(String[] args) {
try {
Server server = new Server(9999);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
package com.demo.nio;

import java.io.IOException;
import java.net.InetAddress;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set; public class Client { private SocketChannel sc = null;
private Selector selector = null; public Client(int port) throws IOException {
sc = SocketChannel.open();
selector = Selector.open();
sc.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);
ByteBuffer bf = ByteBuffer.wrap("Hello".getBytes());
sc.write(bf); listener(selector);
} private void listener(Selector selector) throws IOException{
while(true){
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator(); while(iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer dst = ByteBuffer.allocate(1024);
channel.read(dst);
System.out.println("来自服务器:" + new String(dst.array()));
}
if(key.isWritable()){
Scanner scanner = new Scanner(System.in);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String msg = sdf.format(new Date()) + "\t" + scanner.nextLine();
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer bst = ByteBuffer.wrap(msg.getBytes());
channel.write(bst);
}
iterator.remove();
} }
} public static void main(String[] args) {
try {
Client client = new Client(9999);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }

上面例子的问题在注释里已经详细描述了,不信可以运行一下,下面是修正版,把写操作放在一个独立的线程里

package com.demo.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set; /**
* 修正版
*/
public class ServerRevision { private boolean isFirst = true;
private ServerSocketChannel ssc = null;
private Selector selector = null; public ServerRevision(int port) throws IOException {
ssc = ServerSocketChannel.open();
selector = Selector.open();
InetSocketAddress inetAddress = new InetSocketAddress(InetAddress.getLocalHost(), port); ssc.socket().bind(inetAddress);
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
listener(selector);
} private void listener(Selector selector) throws IOException{
while(true){
System.out.println("等待客户端连接...");
selector.select();
System.out.println("捕获客户端连接...");
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator(); while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//连接事件
if(key.isAcceptable()){
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
channel.accept().configureBlocking(false).register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//System.out.println(channel.toString() + "-已连接");
}
//读数据
if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer bf = ByteBuffer.allocate(1024);
channel.read(bf);
System.out.println("来自客户端数据:" + new String(bf.array()));
// 只有第一次通信返回消息
if(isFirst){
isFirst = false;
ByteBuffer bst = ByteBuffer.wrap("Hi!".getBytes());
channel.write(bst);
}
}
//写数据
if(key.isWritable()){
System.out.println("[服务器]写就绪...");
new Thread(new DealWrite(key)).start();
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);// 取消写就绪,否则会一直触发写就绪
}
iterator.remove(); } }
} public static void main(String[] args) {
try {
ServerRevision server = new ServerRevision(9999);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} } class DealWrite implements Runnable{ private SelectionKey key; public DealWrite(SelectionKey key) {
this.key = key;
} @Override
public void run() {
while (true){
Scanner scanner = new Scanner(System.in);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String msg = sdf.format(new Date()) + "\t" + scanner.nextLine();
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer bst = ByteBuffer.wrap(msg.getBytes());
try {
channel.write(bst);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.demo.nio;

import java.io.IOException;
import java.net.InetAddress;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set; /**
* 修正版
*/
public class ClientRevision { private SocketChannel sc = null;
private Selector selector = null; public ClientRevision(int port) throws IOException {
sc = SocketChannel.open();
selector = Selector.open();
sc.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);
ByteBuffer bf = ByteBuffer.wrap("Hello".getBytes());
sc.write(bf); listener(selector);
} private void listener(Selector selector) throws IOException{
while(true){
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator(); while(iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer dst = ByteBuffer.allocate(1024);
channel.read(dst);
System.out.println("来自服务器:" + new String(dst.array()));
}
if(key.isWritable()){
System.out.println("[客户端]写就绪...");
new Thread(new DealWrite(key)).start();
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);// 取消写就绪,否则会一直触发写就绪
}
iterator.remove();
} }
} public static void main(String[] args) {
try {
ClientRevision client = new ClientRevision(9999);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }

目前只是测试了服务器-客户端一对一的通信,不知道一个服务器对多个客户端会出什么bug

NIO稍微有些复杂吧,不过核心的就三个Selector、Channel、Buffer,NIO是同步非阻塞的。

上一篇:SpringBoot+Shiro+Redis共享Session入门小栗子


下一篇:SpringBoot介绍,快速入门小例子,目录结构,不同的启动方式,SpringBoot常用注解