0
点赞
收藏
分享

微信扫一扫

4.java实现socket编程之NIO实现tcp通信


NIO原生api实现客户端与服务端通信

本文相关的代码是用原生的api实现nio通信的例子,功能为简单的读写。

客户端代码

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Client {

public static void main(String[] args) {
//创建连接的地址
InetSocketAddress InetSocketAddress = new InetSocketAddress("127.0.0.1", 8000);
//声明连接通道
SocketChannel socketChannel = null;
//建立向数据的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
//打开通道
socketChannel = SocketChannel.open();
//进行连接
socketChannel.connect(InetSocketAddress);
while (true){
//定义字节数组,从控制台输入数据
byte[] bytes = new byte[1024];
System.in.read(bytes);
//数据放入缓冲区
byteBuffer.put(bytes);
//复位buffer
byteBuffer.flip();
//写出数据
socketChannel.write(byteBuffer);
//清空数据
byteBuffer.clear();
//线程休眠,等待数据返回,实验证明,不加休眠也没事。
//Thread.sleep(1000);
//读取反馈的数据
readReturn(socketChannel);
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (socketChannel != null) {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

private static void readReturn(SocketChannel socketChannel) throws IOException {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
System.out.println("ByteBuffer.allocate(1024)执行了");
// 读取数据,非阻塞好处在于,触发事件的时候,数据就都准备好了。
int count = socketChannel.read(readBuffer);
System.out.println("socketChannel.read(readBuffer)执行了");
//这个count通过实验没有出现调用的情况。但是这种测试证明了一点:
//客户端的写法,会出现阻塞在read的情况。nio的非阻塞主要是体现在服务器端。
if(count == -1){
//socketChannel.close(); 不能关闭,关闭之后,上面轮询可能出错
System.out.println("读取不到数据,通道关闭了!");
return;
}
//若有数据则进行读取,需要先切换到读取的模式
readBuffer.flip();
//创建byte数组,接受缓冲区的数据
byte[] bytes = new byte[readBuffer.remaining()];
//接收数据
readBuffer.get(bytes);
//打印结果
String body = new String(bytes).trim();
System.out.println("client accpt: " + body);
}
}

服务端代码

package nio;

import java.io.IOException;
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.util.Iterator;

public class Server implements Runnable{
//多路复用器(管理所有的通道)
private Selector selector;
//建立读取数据的缓冲区
private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//建立写入数据的缓冲区
private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
private SocketChannel channel;
//读写不共用通道,不冲突,全双工通信。

/**
* 功能描述: 构造方法
*
* @auther: 李泽
* @date: 2019/3/4 10:57
*/
public Server(int port) {
try {
//打开多路复用器
this.selector = Selector.open();
//打开服务器通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//设置服务器通道为非阻塞模式
serverSocketChannel.configureBlocking(false);
//绑定地址
serverSocketChannel.bind(new InetSocketAddress(port));
//把服务器通道注册到多路复用器上,并且监听阻塞事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//输出启动文字
System.out.println("server start...");
}catch (Exception e){
e.printStackTrace();
}
}

@Override
public void run() {
while (true){
try {
//多路复用器开始监听
this.selector.select();
//返回多路复用器已经选择的结果集。
Iterator<SelectionKey> selectionKeyIterator = this.selector.selectedKeys().iterator();
//遍历
while (selectionKeyIterator.hasNext()){
//获取一个选择的元素
SelectionKey key = selectionKeyIterator.next();
//选择之后从Iterator容器中移除。
selectionKeyIterator.remove();
//若为有效的,则根据状态处理。
if (key.isValid()){
//若为阻塞状态
if (key.isAcceptable()){
this.accept(key);
}
//8 如果为可读状态
if(key.isReadable()){
this.read(key);
}
//9 写数据
if(key.isWritable()){
this.write(key); //ssc
}
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 功能描述: 建立连接之后,serversocketchannel主动写数据。
*
* @auther: 李泽
* @date: 2019/3/4 11:18
*/
private void write(SelectionKey key) {
//ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//ssc.register(this.seletor, SelectionKey.OP_WRITE);
}

/**
* 功能描述:serversocketchannel读取数据,可以在此写好反馈。
*
* @auther: 李泽
* @date: 2019/3/4 11:18
*/
private void read(SelectionKey key) {
try {
//清空缓冲区的旧的数据
this.readBuffer.clear();
//获取之前注册的socket通道对象,这个对象就可以理解为与客户端通信的管道
SocketChannel channel = (SocketChannel) key.channel();
// 读取数据,非阻塞好处在于,触发事件的时候,数据就都准备好了。
int count = channel.read(readBuffer);
//若没有数据,关闭通道,移除注册的key
if(count == -1){
key.channel().close();
key.cancel();
return;
}
//若有数据则进行读取,需要先切换到读取的模式
this.readBuffer.flip();
//创建byte数组,接受缓冲区的数据
byte[] bytes = new byte[this.readBuffer.remaining()];
//接收数据
this.readBuffer.get(bytes);
//打印结果
String body = new String(bytes).trim();
System.out.println("Server accpt: " + body);
//读取完之后返回数据
AfterRead(channel);

}catch (Exception e){
e.printStackTrace();
}
}
/**
* 功能描述: 读取完之后返回数据的抽象方法
*
* @auther: 李泽
* @date: 2019/3/4 12:03
*/
private void AfterRead(SocketChannel channel) throws IOException, InterruptedException {
//尝试模拟网络延迟,实验证明也不会触发读取时的通道内数据为空的逻辑。
Thread.sleep(5000);
//数据放入缓冲区
writeBuffer.put("你好!".getBytes());
//复位buffer
writeBuffer.flip();
//写出数据
channel.write(writeBuffer);
//清空数据
writeBuffer.clear();
}

/**
* 功能描述: 实现监听状态的处理方法,一般是针对serversocketchannel对象的。
* 拿出监听状态的服务器端channel,该状态的channel已经监听到channel,准备好读取数据了
* 将这个监听到的channel转换为等待读取的状态,注册到多路复用器上。
*
* @auther: 李泽
* @date: 2019/3/4 11:18
*/
private void accept(SelectionKey key) {
try {
//获取服务器通道
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
//执行阻塞方法,获得与客户端的通道。
SocketChannel socketChannel = serverSocketChannel.accept();
//设置阻塞模式-非阻塞
socketChannel.configureBlocking(false);
//注册到多路复用器上,并设置读取标识
socketChannel.register(selector,SelectionKey.OP_READ);
//TODO 测试第二个客户端启动会不会还能连接上,应该是不能的。因为没有重新注册监听事件。如果还能则说明
//TODO ssc拿到channel之后,会自动回归监听状态。
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {

new Thread(new Server(8000)).start();;
}

}


举报

相关推荐

0 条评论