基于 JAVA NIO 的socket通信

来源:互联网 发布:电视剧全集获取源码 编辑:程序博客网 时间:2024/05/19 14:55

转载自http://blog.csdn.net/z69183787/article/details/52945585

基于NIO创建的客户端,只是发送数据,基于NIO创建的服务端只是接受数据

一个简单入门案例,贴代码:

1.客户端

package cn.itcast.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;public class TcpNioClient extends Thread {private InetSocketAddress address;private SocketChannel channel;private String msg;public TcpNioClient(String hostname,int port,String msg){address = new InetSocketAddress(hostname, port);this.msg = msg;System.out.println("------客户端发送数据----"+msg);}@Overridepublic void run() {try {channel = SocketChannel.open();channel.configureBlocking(false);channel.connect(address);if(channel.finishConnect()){//两个buffer,区分用,异步的ByteBuffer buffer = ByteBuffer.allocate(1024);if(buffer.hasRemaining()){channel.write(ByteBuffer.wrap(msg.getBytes()));}//程序会在这里阻塞,因为服务端没有写回数据while(true){buffer.clear();int readnum = channel.read(buffer);if(readnum>0){buffer.flip();System.out.println("客户端 ---receive data:"+new String(buffer.array(), 0, readnum));channel.close();break;}}}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {for(int i=0;i<5;i++){TcpNioClient client = new TcpNioClient("127.0.0.1", 23000, "hello world"+i);client.start();}}}

2. 服务端

package cn.itcast.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;import javax.rmi.CORBA.Tie;public class NioTcpServer extends Thread {private final InetSocketAddress socketAddress;private Handler handler = new ServerHandler();public NioTcpServer(String localname,int port){socketAddress = new InetSocketAddress(localname, port);}@Overridepublic void run() {try {ServerSocketChannel serverChannel = ServerSocketChannel.open();serverChannel.bind(socketAddress);serverChannel.configureBlocking(false);Selector select = Selector.open();serverChannel.register(select, SelectionKey.OP_ACCEPT);while(true){                                     //选择信道,开始操作                               int num =select.select();if(num>0){Iterator<SelectionKey> iterator = select.selectedKeys().iterator();while(iterator.hasNext()){SelectionKey ky = iterator.next();if(ky.isAcceptable()){handler.handleAccept(ky);}else if(ky.isReadable()){handler.readMsg(ky);}else if(ky.isWritable()){handler.writerMsg(ky);}//将键从键集中移除,新连接会阻塞,无法连接上服务器iterator.remove();}}}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {NioTcpServer tcp = new NioTcpServer("127.0.0.1", 23000);tcp.start();}}

3.服务端,对不同兴趣集操作信道的接口

package cn.itcast.nio;import java.io.IOException;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;public interface Handler {void readMsg(SelectionKey key) throws IOException;void handleAccept(SelectionKey key) throws IOException;void writerMsg(SelectionKey key) throws IOException;}

package cn.itcast.nio;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.SelectableChannel;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import javax.print.attribute.standard.Severity;public class ServerHandler implements Handler {@Overridepublic void handleAccept(SelectionKey key) throws IOException {//接受请求的连接,并获取新的连接,并将新的连接注册到信道上,兴趣集为读ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel acceptChannel = serverChannel.accept();if(acceptChannel!=null){acceptChannel.configureBlocking(false);//再注册前,信道必须设置为非阻塞的acceptChannel.register(key.selector(), SelectionKey.OP_READ);}}@Overridepublic void readMsg(SelectionKey key) throws IOException {SocketChannel channel = (SocketChannel)key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);buffer.clear();int readNum ;if((readNum=channel.read(buffer))>0){buffer.flip();System.out.println("服务端 receive :"+readNum+"bytes");System.out.println("服务端 接受到的数据 data:"+new String(buffer.array(), 0, readNum));buffer.flip();channel.write(buffer);}channel.close();}@Overridepublic void writerMsg(SelectionKey key) throws IOException {//获取附件信息Object obj =  key.attachment();SocketChannel sc = (SocketChannel) key.channel();if(obj!=null){ByteBuffer buffer = ByteBuffer.wrap(obj.toString().getBytes());sc.write(buffer);key.interestOps(SelectionKey.OP_READ);buffer.compact();}}}
几点需要注意的:

1. 在注册信道前,将信道配置为非阻塞,否则会报异常IllegalBlockingModeException

2. 写数据,读数据时候,注意要操作使用clear,flip方法操作缓冲区的limit,position

3. 由于是异步的,读取数据时候需要判断,返回的int的返回值,写数据时候,要判断缓冲区是否有数据

上述实现,NioTcpServer服务线程启动后,监听指定端口,等待客户端请求的到来,然后NioTcpClient客户端进程启动并发送请求数据,服务端接收到请求数据后,响应客户端(将请求的数据作为响应数据写回到客户端通道SocketChannel,并等待客户端处理)。

实际上,客户端和服务端可以采用同样轮询的非阻塞模式来实现,为简单实现在这个例子中我们把客户端角色简化了,而实际上它可能在另一个系统通信中充当服务端角色。

另外,上面对于不同事件是采用非线程的方式来处理,只是简单地调用处理的方法。在实际中,如果存在大量连接、读写请求,可以考虑使用线程池来更大程度地并发处理,提高服务端处理的速度和吞吐量,提升系统性能。