服务器端
package com.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
public class NioServer {
Selector selector = null;
ServerSocketChannel serverSocketChannel = null;
private NioserverHandler handler;
public NioServer() throws IOException {
selector = Selector.open();
// 打开服务器套接字通道
serverSocketChannel = ServerSocketChannel.open();
// 调整通道的阻塞模式非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().setReuseAddress(true);
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public NioServer(NioserverHandler handler) throws IOException {
this();
this.handler = handler;
while(selector.select() > 0) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while(it.hasNext()) {
SelectionKey s = it.next();
it.remove();
this.handler.excute((ServerSocketChannel)s.channel());
}
}
}
public static void main(String[] args) throws IOException {
new NioServer(new NioserverHandler());
}
}
服务器端操作
package com.nio;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <p>
* 这样的写法倾向于mina过度
* </p>
* @author niming
*
*/
public class NioserverHandler {
private final static Logger logger = Logger.getLogger(NioserverHandler.class.getName());
private final static String DIRECTORY = "D:/NioRequest";
/**
* 这里边我们处理接收和发送
*
* @param serverSocketChannel
*/
public void excute(ServerSocketChannel serverSocketChannel) {
SocketChannel socketChannel = null;
try {
socketChannel = serverSocketChannel.accept(); // 等待客户端连接
RequestObject requestObject = receiveData(socketChannel);// 接数据
logger.log(Level.INFO,requestObject.toString());
writeFile(DIRECTORY,requestObject); // 写文件
String response = "File " + new String(requestObject.getFileName()) + " SUCCESS......";
responseData(socketChannel, response);
logger.log(Level.INFO, response);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* <p>读取通道中的数据到Object里去</p>
*
* @param socketChannel
* @return
* @throws IOException
*/
public RequestObject receiveData(SocketChannel socketChannel) throws IOException {
// 文件名长度
int nameLength = 0;
// 文件名
byte[] fileName = null; // 如果文件以字节数组发来,我们就无从得知文件名,所以文件名一起发过个
// 文件长度
int contentLength = 0;
// 文件内容
byte[] contents = null;
// 由于我们解析时前4个字节是文件名长度
int capacity = 4;
ByteBuffer buf = ByteBuffer.allocate(capacity);
int size = 0;
byte[] bytes = null;
// 拿到文件名的长度
size = socketChannel.read(buf);
if (size >= 0) {
buf.flip();
capacity = buf.getInt();
buf.clear();
nameLength = capacity;
}
// 拿文件名,相信文件名一次能够读完,如果你文件名超过1K 你有病了
buf = ByteBuffer.allocate(capacity);
size = socketChannel.read(buf);
if (size >= 0) {
buf.flip();
bytes = new byte[size];
buf.get(bytes);
fileName = bytes;
buf.clear();
}
// 拿到文件长度
capacity = 4;
buf = ByteBuffer.allocate(capacity);
size = socketChannel.read(buf);
if (size >= 0) {
buf.flip();
// 文件长度是可要可不要的,如果你要做校验可以留下
contentLength = buf.getInt();
buf.clear();
}
// 用于接收buffer中的字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 文件可能会很大
capacity = 1024;
buf = ByteBuffer.allocate(capacity);
while((size = socketChannel.read(buf)) >= 0){
buf.flip();
bytes = new byte[size];
buf.get(bytes);
baos.write(bytes);
buf.clear();
}
contents = baos.toByteArray();
RequestObject requestObject = new RequestObject(nameLength, fileName, contentLength, contents);
return requestObject;
}
/**
* 把接收的数据写到本地文件里
*
* @param drc 本地问价目录
* @param requestObject
* @throws UnsupportedEncodingException
* @throws IOException
*/
public static void writeFile(String drc,RequestObject requestObject) throws UnsupportedEncodingException {
File file = new File(drc + File.separator + new String(requestObject.getFileName(),"utf-8"));
FileOutputStream os = null;
try {
os = new FileOutputStream(file);
os.write(requestObject.getContents());
os.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (os != null)
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 我们接受数据成功后要给客户端一个反馈
* 如果是java的客户端我们可以直接给对象,如果不是最好还是给文本或字节数组
* @param socketChannel
* @param response
*/
private void responseData(SocketChannel socketChannel, String response) {
ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());
try {
socketChannel.write(buffer);
buffer.clear();
// 确认要发送的东西发送完了关闭output 不然它端接收时socketChannel.read(Buffer)
// 很可能造成阻塞 ,可以把这个(L)注释掉,会发现客户端一直等待接收数据
socketChannel.socket().shutdownOutput();// (L)
} catch (IOException e) {
e.printStackTrace();
}
}
}
接收文件的封装类
package com.nio;
import java.io.Serializable;
import java.util.Arrays;
/**
* <p>
* 此类用于我们接收client端发来信息
* 使用字节数组可以兼容字符串,文件,图片
* </p>
* @author niming
*
*/
public class RequestObject implements Serializable {
private static final long serialVersionUID = -765860549787565035L;
/** 文件名长度 */
private int nameLength;
/** 文件名 */
private byte[] fileName;
/** 文件长度 */
private int contentLength;
/** 文件内容 */
private byte[] contents;
public RequestObject() {}
public RequestObject(int nameLength, byte[] fileName, int contentLength,
byte[] contents) {
this.nameLength = nameLength;
this.fileName = fileName;
this.contentLength = contentLength;
this.contents = contents;
}
public int getNameLength() {
return nameLength;
}
public void setNameLength(int nameLength) {
this.nameLength = nameLength;
}
public byte[] getFileName() {
return fileName;
}
public void setFileName(byte[] fileName) {
this.fileName = fileName;
}
public int getContentLength() {
return contentLength;
}
public void setContentLength(int contentLength) {
this.contentLength = contentLength;
}
public byte[] getContents() {
return contents;
}
public void setContents(byte[] contents) {
this.contents = contents;
}
@Override
public String toString() {
return "[ nameLength : " + nameLength
+ " ,fileName : " + Arrays.toString(fileName)
+ " ,contentLength : " + contentLength
+ " ,contents : " +contentLength+"]";
}
}
客户端socket
package com.nio;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
public class NioClient {
private final static Logger logger = Logger.getLogger(NioClient.class.getName());
public static void main(String[] args) {
//for(int i =0; i< 10 ;i++)
new Thread(new MyRunnable(new NioClientHandler())).start();
}
private static final class MyRunnable implements Runnable {
private static final String FILEPATH = "F:\\ThinkinJava4.zip";
private NioClientHandler handler;
private MyRunnable(NioClientHandler handler) {
this.handler = handler;
}
public void run() {
SocketChannel socketChannel = null;
try {
// 和ServerSocket连接与传统的socket类似
// 无非就是换成了套接字通道
socketChannel = SocketChannel.open();
SocketAddress socketAddress = new InetSocketAddress(
InetAddress.getLocalHost(), 9999);
socketChannel.connect(socketAddress);
long start = System.currentTimeMillis();
// 发送数据
handler.sendData(socketChannel, FILEPATH, "ThinkinJava4.zip");
// 接收服务器反馈的信息
String response = handler.receiveData(socketChannel);
long end = System.currentTimeMillis();
logger.log(Level.INFO, response + " 用时 : " + (end-start) + "ms");
} catch (IOException e) {
logger.log(Level.SEVERE, null, e);
} finally {
try {
socketChannel.close();
} catch (Exception ex) {
}
}
}
}
}
客户端操作封装类
package com.nio;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NioClientHandler {
/**
* 向服务器发送数据
*
* @param socketChannel
* @param filepath
* @param filename
* @throws IOException
*/
public void sendData(SocketChannel socketChannel,String filepath, String filename) throws IOException {
byte[] bytes = makeFileToByte(filepath);
// ByteBuffer的缓冲区长度,4字节的文件名长度+文件名+4字节文件长度+文件
ByteBuffer buffer = ByteBuffer.allocate(8 + filename.getBytes().length
+ bytes.length);
buffer.putInt(filename.length());// 文件名长度
buffer.put(filename.getBytes()); // 文件名
buffer.putInt(bytes.length); // 文件长度
buffer.put(ByteBuffer.wrap(bytes));// 文件
buffer.flip();// 把缓冲区的定位指向开始0的位置 清除已有标记
socketChannel.write(buffer);
buffer.clear();
// 关闭输出流防止接收时阻塞, 就是告诉接收方本次的内容已经发完了,你不用等了
socketChannel.socket().shutdownOutput();
}
/**
* 接收服务器相应的信息
*
* @param socketChannel
* @return
* @throws IOException
*/
public String receiveData(SocketChannel socketChannel)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String response = "";
try {
ByteBuffer buffer = ByteBuffer.allocate(1024);
byte[] bytes;
int count = 0;
while ((count = socketChannel.read(buffer)) >= 0) {
buffer.flip();
bytes = new byte[count];
buffer.get(bytes);
baos.write(bytes);
buffer.clear();
}
bytes = baos.toByteArray();
response = new String(bytes,"UTF-8");
socketChannel.socket().shutdownInput();
} finally {
try {
baos.close();
} catch (Exception ex) {
}
}
return response;
}
private byte[] makeFileToByte(String fileFath) throws IOException {
File file = new File(fileFath);
FileInputStream fis = new FileInputStream(file);
int length = (int) file.length();
byte[] bytes = new byte[length];
int temp = 0;
int index = 0;
while(true){
index = fis.read(bytes,temp,length - temp);
if(index <= 0 )
break;
temp += index;
}
fis.close();
return bytes;
}
}
虽然Nio属于非阻塞Io,提高了不少的效率,但是针对多线程我们还是要实现自己的控制,再次推荐另外一个基于Nio的实现框架mina,后续学习