- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.ClosedChannelException;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.ServerSocketChannel;
- import java.nio.channels.SocketChannel;
- import java.util.Iterator;
- /**
- * 服务端
- */
- public class SocketServer {
- /**
- * 服务器默认绑定端口
- */
- public static final int DEFAULT_PORT = 9999;
- /**
- * 选择器
- */
- public Selector selector;
- public SocketServer(String ip, int port) {
- ServerSocketChannel ssc = null;
- try {
- int _port = DEFAULT_PORT;
- if (port > 0)
- _port = port;
- /* 获取通道 */
- ssc = ServerSocketChannel.open();
- /* 配置非阻塞 */
- ssc.configureBlocking(false);
- /**
- * 配置绑定端口 ServerSocketChannel没有bind()方法,
- * 因此有必要取出对等的ServerSocket并使用它来绑定到一
- * 个端口以开始监听连接
- */
- ssc.socket().bind(new InetSocketAddress(ip, _port));
- /* 获取选择器 */
- this.selector = Selector.open();
- /* 将通道注册到选择器 */
- ssc.register(this.selector, SelectionKey.OP_ACCEPT);
- }catch(ClosedChannelException e1){
- System.out.println("关闭的通道,无法注册到选择器");
- e1.printStackTrace();
- } catch (IOException e2) {
- try {
- if(ssc != null) ssc.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- System.out.println("服务器绑定端口冲突");
- e2.printStackTrace();
- }
- }
- /**
- * 轮询选择器
- * @throws Exception
- */
- public void pollSelect() throws Exception {
- /* (阻塞)轮询选择器,直到有事件 */
- while (this.selector.select()>0) {
- /* 获取事件通知列表 */
- Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();
- while (it.hasNext()) {
- SelectionKey selectKey = it.next();
- it.remove();
- try {
- process(selectKey);
- } catch (Exception e) {
- e.printStackTrace();
- continue;
- }
- }
- }
- }
- /**
- * 事件处理
- * @param selectKey
- */
- public void process(SelectionKey selectKey) throws Exception{
- if (selectKey.isAcceptable()) { /* 客户端连接事件 */
- accept(selectKey);
- } else if (selectKey.isReadable()) { /* 可读事件 */
- read(selectKey);
- }
- }
- /**
- * 连接事件
- * @param selectKey
- */
- public void accept(SelectionKey selectKey) throws Exception {
- ServerSocketChannel ssc = null;
- try {
- ssc = (ServerSocketChannel) selectKey
- .channel();
- SocketChannel sc = ssc.accept();
- sc.configureBlocking(false);
- /* 发送信息 */
- sc.write(ByteBuffer.wrap(new String("Hello World!")
- .getBytes()));
- /* 注册读事件 */
- sc.register(this.selector, SelectionKey.OP_READ);
- } catch (ClosedChannelException e) {
- if(ssc!=null)
- ssc.close();
- throw new IOException("关闭的通道,无法注册到选择器");
- } catch (IOException e) {
- if(ssc!=null)
- ssc.close();
- throw new IOException("连接服务或配置失败!");
- }
- }
- /**
- * 可读事件
- * @param selectKey
- */
- public void read(SelectionKey selectKey) throws Exception{
- SocketChannel channel = null;
- try {
- // 服务器可读取消息:得到事件发生的Socket通道
- channel = (SocketChannel) selectKey.channel();
- // 创建读取的缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(100);
- channel.read(buffer);
- byte[] data = buffer.array();
- String msg = new String(data).trim();
- System.out.println("客户端:" + msg);
- // ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
- // 将消息回送给客户端
- // channel.write(outBuffer);
- } catch (Exception e) {
- if(channel != null)
- channel.close();
- throw new Exception("客户端将通道关闭,无法从通道读入缓冲或将缓冲数据写回通道!");
- }
- }
- public static void main(String[] args) {
- SocketServer ss = null;
- try {
- ss = new SocketServer("localhost", 9999);
- ss.pollSelect();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.ClosedChannelException;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.SocketChannel;
- import java.util.Iterator;
- /**
- * 客户端
- */
- public class SocketClient {
- public Selector selector;
- public SocketClient(String ip, int port){
- SocketChannel channel = null;
- try {
- //channel = SocketChannel.open(new InetSocketAddress(ip,port));
- channel = SocketChannel.open();
- // 设置通道为非阻塞
- channel.configureBlocking(false);
- // 获得一个通道管理器
- this.selector = Selector.open();
- // 客户端连接服务器,其实方法执行并没有实现连接
- channel.connect(new InetSocketAddress(ip, port));
- /**while(!channel.finishConnect()){
- System.out.println("尝试连接....");
- }*/
- // 注册连接事件。
- channel.register(this.selector, SelectionKey.OP_CONNECT);
- } catch(ClosedChannelException e1){
- System.out.println("关闭的通道,无法注册到选择器");
- e1.printStackTrace();
- } catch (IOException e2) {
- System.out.println("连接异常!");
- try {
- if(channel != null) channel.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- e2.printStackTrace();
- }
- }
- /**
- * 轮询选择器
- * @throws IOException
- */
- public void pollSelect() throws Exception {
- /* (阻塞)轮询选择器,直到有事件 */
- while ( this.selector.select() > 0 ) {
- /* 获取事件通知列表 */
- Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
- while (ite.hasNext()) {
- SelectionKey selectKey = (SelectionKey) ite.next();
- // 删除已选的key,以防重复处理
- ite.remove();
- process(selectKey);
- }
- }
- }
- /**
- * 处理事件
- * @param selectKey
- */
- public void process(SelectionKey selectKey) throws Exception{
- if (selectKey.isConnectable()) {
- connect(selectKey);
- } else if (selectKey.isReadable()) {
- read(selectKey);
- }
- }
- /**
- * 连接事件
- * @param selectKey
- * @throws Exception
- */
- public void connect(SelectionKey selectKey) throws Exception{
- try {
- SocketChannel channel = (SocketChannel) selectKey
- .channel();
- /* 如果正在连接,则完成连接 */
- if(channel.isConnectionPending()){
- /**
- * connect()方法尚未被调用,调用finishConnect()方法,
- * 那么将产生NoConnectionPendingException
- */
- channel.finishConnect();
- }
- /**
- * 在非阻塞模式下调用connect()方法之后,SocketChannel又被切换回了阻塞模式;那么如果
- * 有必要的话,调用线程会阻塞直到连接建立完成,finishConnect()方法接着就会返回true
- * 值。
- */
- /* 设置成非阻塞 */
- channel.configureBlocking(false);
- /* 给服务端发送信息 */
- channel.write(ByteBuffer.wrap(new String("编号001客户端连接成功!").getBytes()));
- /* 注册读事件 */
- channel.register(this.selector, SelectionKey.OP_READ);
- } catch (ClosedChannelException e) {
- throw new IOException("关闭的通道,无法注册到选择器");
- } catch (IOException e) {
- throw new IOException("连接服务或配置失败!");
- }
- }
- /**
- * 读事件
- * @param selectKey
- * @throws Exception
- */
- public void read(SelectionKey selectKey) throws Exception{
- try {
- // 服务器可读通道
- SocketChannel channel = (SocketChannel) selectKey.channel();
- // 创建读取的缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(100);
- channel.read(buffer);
- byte[] data = buffer.array();
- String msg = new String(data).trim();
- System.out.println(msg);
- ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
- // 将消息回送给服务端
- //channel.write(outBuffer);
- } catch (Exception e) {
- throw new IOException("服务端将通道关闭,无法从通道读入缓冲或将缓冲数据写回通道!");
- }
- }
- public static void main(String[] args) {
- SocketClient sc = null;
- try {
- sc = new SocketClient("localhost", 9999);
- sc. pollSelect();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
之二:
引用
继上节利用JAVA NIO实现简单数据传,本节实现自定义对象传输,为了实现接收方构建完整对象,自定义对象实现Serializable接口,以便完成对象序列化与反序化。
下一节我们将采用线程池来管理读写,期待...
- import java.io.Serializable;
- /**
- * 传输对象,利用ObjectOutputStream、ObjectInputStream
- * 传输,所以对象需要可序列化
- * @author oy
- *
- */
- public class User implements Serializable{
- private static final long serialVersionUID = 588789688009378496L;
- private int age;
- private String name;
- /* Ignore this attribution */
- private transient String school;
- public User(String name, int age, String school){
- this.name = name;
- this.age = age;
- this.school = school;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getSchool() {
- return school;
- }
- public void setSchool(String school) {
- this.school = school;
- }
- @Override
- public String toString() {
- return "User [age=" + age + ", name=" + name + ", school=" + school
- + "]";
- }
- }
- /* 发送对象信息 */
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(baos);
- oos.writeObject(new User("Test", 30, "xx大学"));
- channel.write(ByteBuffer.wrap(baos.toByteArray()));
- /* 接收对象信息 */
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- channel.read(buffer);
- byte[] data = buffer.array();
- ByteArrayInputStream bais = new ByteArrayInputStream(data);
- ObjectInputStream ois = new ObjectInputStream(bais);
- User user = (User)ois.readObject();
之三:
本节采用JDK1.5之后java.util.concurrent包的API服务端实现线程
池读取信息,可以接采用Executors工具快速创建线程池,也可以ExecutorService子类自
定义创建。
客端连接服务端发送信息后关闭连接SOCKET短连接(HTTP为短连接),若采用SOCKET长
连接,需要增加"心跳检测",本节暂未实现长连接。
因Selector轮询可读事件时,存在重读问题,解决办法是在读的代码块中加下述代码
selectKey.cancel()或selectKey.interestOps(selectKey.interestOps() &
(~SelectionKey.OP_READ))
池读取信息,可以接采用Executors工具快速创建线程池,也可以ExecutorService子类自
定义创建。
客端连接服务端发送信息后关闭连接SOCKET短连接(HTTP为短连接),若采用SOCKET长
连接,需要增加"心跳检测",本节暂未实现长连接。
因Selector轮询可读事件时,存在重读问题,解决办法是在读的代码块中加下述代码
selectKey.cancel()或selectKey.interestOps(selectKey.interestOps() &
(~SelectionKey.OP_READ))
- 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.nio.channels.SocketChannel;
- import java.util.Iterator;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.LinkedBlockingQueue;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- /**
- * 服务端
- */
- public class SocketServer {
- /**
- * 服务器默认绑定端口
- */
- public static final int DEFAULT_PORT = 9999;
- /**
- * 选择器
- */
- private Selector selector;
- /**
- * 读取线程池
- */
- private ExecutorService pool;
- public SocketServer(String ip, int port) {
- ServerSocketChannel ssc = null;
- try {
- int _port = DEFAULT_PORT;
- if (port > 0)
- _port = port;
- /* 获取通道 */
- ssc = ServerSocketChannel.open();
- /* 配置非阻塞 */
- ssc.configureBlocking(false);
- /**
- * 配置绑定端口 ServerSocketChannel没有bind()方法,
- * 因此有必要取出对等的ServerSocket并使用它来绑定到一
- * 个端口以开始监听连接
- */
- ssc.socket().bind(new InetSocketAddress(ip, _port));
- /* 获取选择器 */
- this.selector = Selector.open();
- /* 将通道注册到选择器 */
- ssc.register(this.selector, SelectionKey.OP_ACCEPT);
- /**
- * 可以使用Executors,快速创建线程池,但是如果综合考虑
- * CPU、内存等资及并发情况,可以采用自定方式创建池(
- * 拒绝处理等问题
- * ),此处简单自定义创建读取池
- * */
- pool = new ThreadPoolExecutor(1, 2, 2000L,
- TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(5));
- } catch (IOException e2) {
- System.out.println("服务器启动失败!");
- e2.printStackTrace();
- try {
- if(ssc != null) ssc.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 轮询选择器
- * @throws Exception
- */
- public void pollSelect() throws Exception {
- /* (阻塞)轮询选择器,直到有事件 */
- while (true) {
- int readyChannels = 0;
- /*选择器是否关闭*/
- if(this.selector.isOpen()){
- readyChannels = this.selector.select();
- }
- if(readyChannels == 0) continue;
- /* 获取事件通知列表 */
- Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();
- while (it.hasNext()) {
- SelectionKey selectKey = it.next();
- it.remove();
- try {
- process(selectKey);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
- /**
- * 事件处理
- * @param selectKey
- */
- public void process(SelectionKey selectKey) throws Exception{
- /* 客户端连接事件 */
- if (selectKey.isAcceptable()) {
- accept(selectKey);
- } else if (selectKey.isReadable()) { /* 可读事件 */
- read(selectKey);
- /**
- * 解决重复读
- * 或设置不关注读事件来解决重复
- * selectKey.interestOps(selectKey.interestOps() & (~SelectionKey.OP_READ));
- * */
- selectKey.cancel();
- }
- }
- /**
- * 连接事件
- * @param selectKey
- */
- public void accept(SelectionKey selectKey) throws Exception {
- SocketChannel sc = null;
- try {
- ServerSocketChannel ssc = (ServerSocketChannel) selectKey
- .channel();
- sc = ssc.accept();
- sc.configureBlocking(false);
- System.out.println("客户端:"
- + sc.socket().getInetAddress().getHostAddress()+"端口"+sc.socket().getPort()
- + " 已连接");
- sc.register(this.selector, SelectionKey.OP_READ);
- } catch (Exception e) {
- if(sc!=null)
- sc.close();
- throw new IOException("客户端已断开连接!");
- }
- }
- /**
- * 可读事件
- * @param selectKey
- */
- public void read(SelectionKey selectKey) throws Exception{
- SocketChannel sc = (SocketChannel) selectKey
- .channel();
- /*提交线程池处理*/
- pool.submit(new SocketServerReadThread(sc));
- }
- public static void main(String[] args) {
- SocketServer ss = null;
- try {
- ss = new SocketServer("localhost", 9999);
- ss.pollSelect();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- import java.io.ByteArrayInputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.nio.ByteBuffer;
- import java.nio.channels.SocketChannel;
- /**
- * 从线程中读信息
- * @author oy
- *
- */
- public class SocketServerReadThread implements Runnable{
- private SocketChannel channel;
- public SocketServerReadThread(SocketChannel channel){
- this.channel = channel;
- }
- @Override
- public void run() {
- try {
- // 创建读取的缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- channel.read(buffer);
- byte[] data = buffer.array();
- ByteArrayInputStream bais = new ByteArrayInputStream(data);
- ObjectInputStream ois = new ObjectInputStream(bais);
- User user = (User)ois.readObject();
- System.out.println("客户端:"
- + channel.socket().getInetAddress().getHostAddress()+"端口"+channel.socket().getPort() + " 消息: " + user.toString());
- } catch (Exception e) {
- System.out.println("客户端已断开连接!");
- try {
- if(channel != null)
- channel.close();
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- }
- }
- }