最近学习java系统间通信BIO、NIO写了两个例子,如果有错误麻烦指正!
1.BIO
客户端代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SocketClient implements Runnable {
public static void main(String[] args) {
ExecutorService executor=Executors.newCachedThreadPool();
for(int i=0;i<=10;i++){
executor.execute(new SocketClient(i));
}
}
private int i;
public SocketClient(int i){
this.i=i;
}
@Override
public void run() {
try {
Socket socket=new Socket("127.0.0.1",8080);
socket.setSoTimeout(500);
System.out.println("client:"+i+"准备发送请求!!");
PrintWriter write=new PrintWriter(socket.getOutputStream());
BufferedReader read=new BufferedReader(new InputStreamReader(socket.getInputStream()));
write.println("Socket:"+i+"\rexit");
write.flush();
System.out.println("发送完毕!Socket:"+i);
String readline=read.readLine();
while(readline!=null){
System.out.println("接收到服务端发送消息:"+readline);
readline=read.readLine();
}
System.out.println("客户端"+i+"准备关闭!!");
socket.close();
write.close();
read.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
服务器端代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
public class SocketService implements Runnable{
public static void main(String[] args) {
try {
SocketAddress address = new InetSocketAddress("127.0.0.1", 8080);
ServerSocket sever=new ServerSocket();
sever.bind(address);
System.out.println("创建服务端完毕!!");
while(true){
Socket client= sever.accept();
new Thread(new SocketService(client)).start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private Socket socket;
public SocketService(Socket socket) {
this.socket=socket;
}
@Override
public void run() {
try{
socket.setSoTimeout(500);
System.out.println("接收到socket请求"+System.currentTimeMillis());
PrintWriter write=new PrintWriter(socket.getOutputStream());
BufferedReader read=new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readline=read.readLine();
while(readline!=null){
write.println("服务端返回消息:"+readline);
write.flush();
System.out.println("接收到客户端消息:"+readline);
readline=read.readLine();
if(readline.equals("exit")) break;
}
System.out.println("处理完毕!准备关闭流!!");
socket.close();
write.close();
read.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
在写BIO客户端的时候没遇到啥问题服务器端倒是遇到两个问题。
1.服务器端如何判断流读取完毕?
服务器端的socket.getInputStream() 得到IO对象read,read会阻塞读取,如果客户端的socket发送流完毕,但是没有关闭自身socket请求时。那么服务器端接收完毕客户端的流以后,将会继续循阻塞读取流。因为只有socket关闭以后read.readLine() 才会等于null,
迫不得已只能在客户端发送流时候自定义结尾符号exit,至于客户端的readline!=null为啥不需要结尾符,因为服务器端在处理完毕请求以后关闭了socket。
while(readline!=null){
write.println("服务端返回消息:"+readline);
write.flush();
System.out.println("接收到客户端消息:"+readline);
readline=read.readLine();
if(readline.equals("exit")) break;
}
2.服务器端因为请求超时,线程直接停止了。
服务器设置了超时请求时间socket.setSoTimeout(500) 如果处理客户端请求和主线程放在同一个线程,一旦请求超时当前线程将被停止。当然不可能为了一个请求的超时而导致服务器的停止(还有其它的异常也会导致线程停止),所以需要开辟一个新的线程处理客户端请求。
NIO
NIO与BIO的优势就在于 服务端可以使用单线程处理客户端的多线程并发请求,
不像BIO需要为每一个请求开辟一个线程,增加了服务器的负担。
客户端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import sun.nio.ch.DirectBuffer;
public class SocketClient implements Runnable {
public static void main(String[] args) {
ExecutorService executor=Executors.newCachedThreadPool();
for(int i=0;i<=3;i++){
executor.execute(new SocketClient(i));
}
// new Thread(new SocketClient(1)).start();
}
int i;
public SocketClient(int i) {
this.i=i;
}
@Override
public void run() {
try {
SocketChannel channel=SocketChannel.open();
SocketAddress address=new InetSocketAddress("127.0.0.1", 8080);
channel.configureBlocking(false);
channel.connect(address);
Selector selector=Selector.open();
channel.register(selector, SelectionKey.OP_CONNECT);
while(true){
int status=selector.select(500);//将会阻塞感兴趣的IO发生
//selectNow不会阻塞,调用后立即返回状态 需要在循环中不停获取
//status=selector.selectNow();
if(status>0){
System.out.println("channel:"+i+"获得服务器响应,响应状态"+status);
Set<SelectionKey> keys=selector.selectedKeys();
Iterator<SelectionKey> its=keys.iterator();
while(its.hasNext()){
SelectionKey key=its.next();
if(key.isConnectable()){
SocketChannel sc=(SocketChannel)key.channel();
sc.finishConnect();//阻塞到客户端 与服务器端建立连接
sc.configureBlocking(false);
System.out.println("channel:"+i+"连接建立完毕!!");
ByteBuffer bf=ByteBuffer.wrap(("channel:"+i).getBytes("utf-8"));
int size=sc.write(bf);
if(size<=0){
key.interestOps(SelectionKey.OP_WRITE); //注册写事件
sc.write(bf);
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);//取消注册写事件
}
sc.register(selector,SelectionKey.OP_READ);
}else if(key.isReadable()){
SocketChannel sc=(SocketChannel)key.channel();
ByteBuffer buffer=ByteBuffer.allocateDirect(1024);
String msg="";
while(sc.read(buffer)>0){
buffer.flip();
msg+=Charset.forName("utf-8").decode(buffer).toString();
}
((DirectBuffer)buffer).cleaner().clean();
System.out.println("客户端"+i+"接收到服务器消息"+msg);
}
}
}
status=-1;//因为设置了500毫秒的超时 时间
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程执行完毕!");
}
}
服务器端
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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
import sun.nio.ch.DirectBuffer;
public class SocketService{
public static void main(String[] args) {
try{
ServerSocketChannel server=ServerSocketChannel.open();
server.configureBlocking(false);
server.socket().bind(new InetSocketAddress("127.0.0.1", 8080));
Selector selector=Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动完毕!!");
while(true){
int status=selector.select();//不建议设置超时 时间
if(status>0){
Iterator<SelectionKey> keys=selector.selectedKeys().iterator();
while(keys.hasNext()){
SelectionKey key=keys.next();
keys.remove();
if(key.isAcceptable()){
System.out.println("可接受客户端请求:"+status);
ServerSocketChannel ssc=(ServerSocketChannel)key.channel();
SocketChannel channel=ssc.accept();
ByteBuffer bf=ByteBuffer.wrap(channel.getRemoteAddress().toString().getBytes("utf-8"));
int size=channel.write(bf);
if(size<=0){
key.interestOps(SelectionKey.OP_WRITE); //注册写事件
channel.write(bf);
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);//取消注册写事件
}
channel.configureBlocking(false);
channel.register(selector,SelectionKey.OP_READ);
}else if(key.isReadable()){
System.out.println("可读客户端流:"+status);
SocketChannel sc=(SocketChannel)key.channel();
ByteBuffer buffer=ByteBuffer.allocateDirect(1024);
String msg="";
while(sc.read(buffer)>0){
buffer.flip();
msg+=Charset.forName("utf-8").decode(buffer).toString();
}
((DirectBuffer)buffer).cleaner().clean();
System.out.println("服务端接收到客户端"+sc.getRemoteAddress()+"消息"+msg);
}
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
在编写NIO程序的时候一个问题
1.channel.register(selector, SelectionKey.OP_CONNECT) 一个NIO程序可以有多个信道却只有一个选择器,同时向一个选择器多次注册那么selector.select() 将触发哪一个注册事件呢?
在AbstractSelectableChannel.class里头找到了答案,该类是channelsocket的父类意味着每一个channel都有一个AbstractSelectableChannel对象,同一个channel在同一个selector注册感兴趣事件,都会覆盖之前的在selector上注册的事件。不同的channel对于同一个selector注册将会触发addKey(k) 事件,如果两个不同的channel对同一个selector进行注册那么selector.select() 将会得到拥有两个selectionKey的Set集合。顺便说一下selector不会重复处理同一个事件,比如我客户端注册了读感兴趣事件,服务端向客户端第一次发送了HAHA字符串,客户端通过selector.select() 获取到selectionKey对象后,以后将无法再获取到HAHA,服务器端再次发送XIXI客户端成功接收。
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
synchronized (regLock) {
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);//查找selector是否存在
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
// New registration
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);//添加一个selectionkey
}
return k;
}
}