声明:本文章仅基于个人粗略整理总结,如有不足之处,欢迎指出。
参考文章:NIO Socket编程实例 、nio的简单传输例子、深入浅出NIO Socket实现机制、深入浅出NIO之Channel、Buffer
++++++++++++++++++++++++++
上一篇:https://blog.csdn.net/u012767184/article/details/88762935
nio是采用的事件驱动模型,通过selector使用一个线程去监控各个连接句柄的状态,不是去轮询每个句柄,在数据就绪后,将消息通知给selector,而具体的socket句柄管理则是采用多路复用的模型,交由操作系统来完成。selector充当的是一个消息的监听者,负责监听channel在其注册的事件,这样就可以通过一个线程完成了大量连接的管理,当注册的事件发生后,再调用相应线程进行处理。避免为每个连接都使用一个线程去维持长连接,减少了长连接的开销,同时减少了上下文的切换提高了系统的吞吐量。
1、基础理论
Non-blocking IO(同步非阻塞IO)
Nio应付高并发请求,对外的web服务器,对内的微服务。
Nio里一定有线程,但线程池的数量很少
当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。
NIO工作流程
NIO分别三大块:Selectors(选择器)、Buffer(缓冲区)、Channel(通道)
一、Buffer(缓冲区)
用于与NIO通道进行交互,数据是从通道读入到缓冲区,从缓冲区写入通道中的。(是一块连续的内存块,是 NIO 数据读或写的中转地。)
1.1包含基本数据类型的buffer:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
#文件内存映射buffer : MappedByteBuffer
#直接内存区buffer :- DirectBuffer
缓冲区的4个属性:
1)容量(capacity):表示Buffer最大数据容量,缓冲区容量不能为负,并且建立后不能修改。
2)限制(limit):第一个不应该读取或者写入的数据的索引,即位于limit后的数据不可以读写。缓冲区的限制不能为负,并且不能大于其容量(capacity)。
3)位置(position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制(limit)。
4)标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark()方法指定Buffer中一个特定的position,之后可以通过调用reset()方法恢复到这个position。
1.2make与rest用法
标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark()方法指定Buffer中一个特定的position,之后可以通过调用reset()方法恢复到这个position。
public class Test002 {
public static void main(String[] args) {
//System.out.println( "Hello World!" );
ByteBuffer buf = ByteBuffer.allocate(1024);
String str = "abcd1";
buf.put(str.getBytes());
// 开启读取模式
buf.flip();
byte[] dst = new byte[buf.limit()];
buf.get(dst, 0, 2);
buf.mark();
System.out.println(new String(dst, 0, 2));
System.out.println("mark的当前位置:"+buf.position());
buf.get(dst, 2, 2);
System.out.println(new String(dst, 2, 2));
System.out.println("当前位置:"+buf.position());
buf.reset();
System.out.println("重置恢复到mark位置.."+buf.position());
}
}
Buffer的基本使用步骤:
- 调用xxBuffer.allocate(int)创建Buffer
ByteBuffer buf=ByteBuffer.allocate(48);
- 调用put方法往Buffer中写数据
Int bytesRead=inChannel.read(buf);
Buf.put();
- 调用buffer.flip()将buffer转为读模式
Buf.flip();//转为读模式,position变为0
- 读取buffer数据
Byte abyte=buf.get();
- 读写完成后,都需要clear()or buffer.compact()
buffer.clear();// 清空缓冲区,数据没有被清空依然存在,,处于被遗忘状态,不知道数据的多少position=0 limit =capacity
buffer.compact();//整理,将未读取的数据移动到头部
直接缓冲区与非直接缓冲区别
非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
直接字节缓冲区可以通过调用此类的 allocateDirect() 工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
直接缓冲耗时更短。
直接字节缓冲区还可以通过 FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer 。 Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。
字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。
// 使用直接缓冲区完成文件的复制(内存映射文件)
static public void test2() throws IOException {
long start = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("f://1.mp4"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("f://2.mp4"), StandardOpenOption.WRITE,
StandardOpenOption.READ, StandardOpenOption.CREATE);
// 内存映射文件
MappedByteBuffer inMappedByteBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedByteBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
// 直接对缓冲区进行数据的读写操作
byte[] dsf = new byte[inMappedByteBuf.limit()];
inMappedByteBuf.get(dsf);
outMappedByteBuffer.put(dsf);
inChannel.close();
outChannel.close();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
// 1.利用通道完成文件的复制(非直接缓冲区)
static public void test1() throws IOException { // 4400
long start = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("f://1.mp4");
FileOutputStream fos = new FileOutputStream("f://2.mp4");
// ①获取通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
// ②分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
while (inChannel.read(buf) != -1) {
buf.flip();// 切换为读取数据
// ③将缓冲区中的数据写入通道中
outChannel.write(buf);
buf.clear();
}
outChannel.close();
inChannel.close();
fos.close();
fis.close();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
Channel(通道)
通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。Channel 负责传输, Buffer 负责存储。客户端socket与服务端socket之间的IO传输不直接把数据交给CPU使用,而是先经过Channel通道把数据保存到Buffer,然后CPU直接从Buffer区读写数据,一次可以读写更多的内容。
通道是由 java.nio.channels 包定义的。 Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的“流”。只不过 Channel本身不能直接访问数据, Channel 只能与Buffer 进行交互。
connect:客户端连接服务端事件,对应值为SelectionKey.OP_CONNECT(8)
accept:服务端接收客户端连接事件,对应值为SelectionKey.OP_ACCEPT(16)
read:读事件,对应值为SelectionKey.OP_READ(1)
write:写事件,对应值为SelectionKey.OP_WRITE(4)
所有的io的Nio都是从一个channel开始的,Channel有点类似于流,但是和流不同的是,channel是可以双向读写的。Channel有几种类型,主要包含文件io操作和网络io:
- FileChannel (文件io)
- DatagramChannel (udp数据报)
- SocketChannel (tcp客户端)
- ServerSocketChannel (tcp服务端)
| java.nio.channels.Channel 接口: |--FileChannel |--SocketChannel |--ServerSocketChannel |--DatagramChannel
获取通道 1. Java 针对支持通道的类提供了 getChannel() 方法 本地 IO: FileInputStream/FileOutputStream RandomAccessFile
网络IO: Socket ServerSocket DatagramSocket
2. 在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open() 3. 在 JDK 1.7 中的 NIO.2 的 Files 工具类的 newByteChannel() |
@Test
// 使用直接缓冲区完成文件的复制(內存映射文件)
public void test2() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.READ, StandardOpenOption.WRITE,
StandardOpenOption.CREATE);
// 映射文件
MappedByteBuffer inMapperBuff = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMapperBuff = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
// 直接对缓冲区进行数据读写操作
byte[] dst = new byte[inMapperBuff.limit()];
inMapperBuff.get(dst);
outMapperBuff.put(dst);
outChannel.close();
inChannel.close();
}
@Test
// 1.利用通道完成文件复制(非直接缓冲区)
public void test1() throws IOException {
FileInputStream fis = new FileInputStream("1.png");
FileOutputStream fos = new FileOutputStream("2.png");
// ①获取到通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
// ②分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
while (inChannel.read(buf) != -1) {
buf.flip();// 切换到读取模式
outChannel.write(buf);
buf.clear();// 清空缓冲区
}
// 关闭连接
outChannel.close();
inChannel.close();
fos.close();
fis.close();
}
分散读取与聚集写入
分散读取(scattering Reads):将通道中的数据分散到多个缓冲区中
聚集写入(gathering Writes):将多个缓冲区的数据聚集到通道中
RandomAccessFile raf1 = new RandomAccessFile("test.txt", "rw"); // 1.获取通道 FileChannel channel = raf1.getChannel(); // 2.分配指定大小的指定缓冲区 ByteBuffer buf1 = ByteBuffer.allocate(100); ByteBuffer buf2 = ByteBuffer.allocate(1024); // 3.分散读取 ByteBuffer[] bufs = { buf1, buf2 }; channel.read(bufs); for (ByteBuffer byteBuffer : bufs) { // 切换为读取模式 byteBuffer.flip(); } System.out.println(new String(bufs[0].array(), 0, bufs[0].limit())); System.out.println("------------------分算读取线分割--------------------"); System.out.println(new String(bufs[1].array(), 0, bufs[1].limit())); // 聚集写入 RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw"); FileChannel channel2 = raf2.getChannel(); channel2.write(bufs); |
Selectors(选择器)
用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。一个Selector选择器可以检测多个SelectableChannel
用cpu核心数量的线程,充分利用cpu资源,又减少线程切换
Selector的用法
选择KEY
1、SelectionKey.OP_CONNECT 可连接
2、SelectionKey.OP_ACCEPT 可接受
3、SelectionKey.OP_READ 可读
4、SelectionKey.OP_WRITE 可写
如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
2、代码示例
基本思路:非阻塞读取数据,并将每次的selectKey根据ip地址存储在hashmap里,及时更新,便于写数据的时候根据泵站id获取ip地址,发送到对应的DTU设备上。每次重新启动线程连接时,清除之前不必要的ip地址数据库存储
NIO服务端连接过程
ServerSocket serverSocket = null;
ServerSocketChannel sChannel;
System.out.println("服务器已经启动……");
// 1、创建通道://工厂方法创建ServerSocketChannel
sChannel = ServerSocketChannel.open();
// 2、切换读取模式:非阻塞
sChannel.configureBlocking(false);// jdk1.7以上
// 3、绑定连接
sChannel.bind(new InetSocketAddress(8080));
// 4、获取选择器
Selector selector = Selector.open();
// 5、将通道注册到选择器“并监听接收事件”
sChannel.register(selector, SelectionKey.OP_ACCEPT);
// 6、轮询 获取选择“已经准备就绪的事件”
while (selector.select() > 0) {
// 7.获取当前选择器 有注册已经监听到事件
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
// 8.获取准备就绪事件
SelectionKey sk = it.next();
// 9.判断事件准备就绪
if (sk.isAcceptable()) {
// 10. “若” 接受就绪,获取客户端连接
SocketChannel socketChannel = sChannel.accept();
// 11. 设置为阻塞费事
socketChannel.configureBlocking(false);// 异步非阻塞IO
// 12.将该通道注册到服务器上
socketChannel.register(selector, SelectionKey.OP_READ);
}else if(sk.isReadable()){
//13.获取当前选择“就绪状态”的通道
SocketChannel socketChannel= (SocketChannel) sk.channel();
//14.读取数据
int len=0;
ByteBuffer byteBuffer= ByteBuffer.allocate(1024);
while ((len=socketChannel.read(byteBuffer))>0) {
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(),0,len));
byteBuffer.clear();
}
}
it.remove();
}
}
package com.irs.util.protocol;
import java.io.IOException;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
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.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
import java.util.Queue;
import org.apache.commons.lang3.StringUtils;
import com.irs.mapper.collect.ColStationSetMapper;
import com.irs.pojo.collect.ColStationSet;
import com.irs.pojo.collect.ColStationSetExample;
import com.irs.pojo.collect.ColStationSetExample.Criteria;
import com.irs.service.collect.ColStationSetService;
import com.irs.service.collect.DataProcessing;
public class PlcSocket extends Observable{
/*监听端口*/
int port = 151;
/*缓冲区大小*/
ByteBuffer buffer = ByteBuffer.allocate(1024);
/*其它相关定义*/
Selector selector;
ServerSocketChannel channel;
ServerSocket socket;
public static Queue<byte[]> queue = new LinkedList<byte[]>();// 消息队列
public static List<ColStationSet> colStitionsetList=null;//
public static HashMap<String, SelectionKey> socketList = new HashMap<>();//hashmap存储SelectionKey
private ColStationSetService colStationSetService;
private DataProcessing dataProcessing;
/*启动*/
public void Start() throws Exception {
/*初始化一个Selector*/
selector = Selector.open();
/*打开通道*/
channel = ServerSocketChannel.open();
/*非阻塞模式*/
channel.configureBlocking(false);
/*本机IP*/
//InetAddress ip = InetAddress.getByName("127.0.0.1");
InetAddress ip = InetAddress.getLocalHost();
/*绑定IP和端口*/
InetSocketAddress address = new InetSocketAddress(ip,port);
socket = channel.socket();
socket.bind(address);
/*启动监听*/
System.out.println("TCP服务器开始监听...");
Listen();
}
/*停止*/
public void Stop() throws Exception {
channel.close();
selector.close();
}
/*监听*/
public void Listen() throws Exception {
/*注册接收事件*/
channel.register(selector,SelectionKey.OP_ACCEPT);
/*无限循环*/
colStationSetService=new ColStationSetService();
//清除ip记录表的数据
colStationSetService.deldata();
while (true) {
selector.select();
/*轮询事件*/
Iterator iter = selector.selectedKeys().iterator();
dataProcessing=new DataProcessing();
while (iter.hasNext()) {
SelectionKey key = (SelectionKey)iter.next();
iter.remove();
/*事件分类处理*/
if (key.isAcceptable()) {// 连接就绪
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
String ip=sc.getRemoteAddress().toString();
ip=ip.substring(1);
System.out.println("新终端已连接:"+ sc.getRemoteAddress());
colStitionsetList= colStationSetService.selColStationSetByStationid(null,ip);
socketList.put(ip, key);
if (colStitionsetList!=null&& colStitionsetList.size()==0) {
ColStationSet colStationSet=new ColStationSet();
colStationSet.setIpAdress(ip);
colStationSet.setCreatetime(new Date());
colStationSetService.insColStationSet(colStationSet);
}
} else if (key.isReadable()) {// 读就绪
SocketChannel sc = (SocketChannel)key.channel();
String ip=sc.getRemoteAddress().toString();
ip=ip.substring(1);
socketList.put(ip, key);
int recvCount = sc.read(buffer);
byte[] bytelist=buffer.array();
bytelist=changeMessage(bytelist,recvCount);
if(bytelist.length>=161){
String list= CRC16M.getBufHexStr(bytelist);
reverse(bytelist, list);
}else{
queue.offer(bytelist);
if(bytelist.length>21){
System.arraycopy(bytelist, 21, bytelist, 0, bytelist.length-21);
}else{
System.arraycopy(bytelist, 1, bytelist, 0, bytelist.length-1);
}
}
// buffer.clear();
/*if (recvCount > 0) {*/
/*if(queue.size()!=0){*/
byte[] arr= queue.poll();//数据在Queue的存储,并且每次取出一条数据
/*System.out.println("queue:"+queue.size());
System.out.println(sc.getRemoteAddress() + "发来数据: "+ CRC16M.getBufHexStr(arr)); */
colStitionsetList= colStationSetService.selColStationSetByStationid(null,ip);
queue.poll();
dataProcessing.dealByteData(arr,colStitionsetList);
buffer.flip();
/* }*/
/* }*//*else {//recvCount<0
// sc.close();
}*/
buffer.clear();
}else if (key.isWritable()) {
}
}
}
}
/**
*
* @Title: write
* @Description: 根据泵站向指定ip发数据
* @Author:
* @Version: V1.00 (版本号)
* @CreateDate: 2018年11月13日 上午8:40:16
* @Parameters: @param station
* @Parameters: @throws IOException
* @Return void
* @Throws
*/
public void write(String station,String data) throws IOException{
colStationSetService=new ColStationSetService();
colStitionsetList= colStationSetService.selColStationSetByStationid(station,null);
String ipAdreess=colStitionsetList.get(0).getIpAdress();
SelectionKey key=socketList.get(ipAdreess);
ByteBuffer respBb = ByteBuffer.wrap(data.getBytes());
if (respBb.hasRemaining()) {
SocketChannel sc = (SocketChannel) key.channel();
sc.write(respBb);
}
}
public List<ColStationSet> selColStationSetByStationid(String stationid,String ipAdrsess) {
ColStationSetExample colStationSetExample = new ColStationSetExample();
ColStationSetMapper colStationSetMapper = null;
Criteria criteria = colStationSetExample.createCriteria();
List<ColStationSet> list = null;
if (StringUtils.isNotBlank(stationid)) {
criteria.andStationIdEqualTo(stationid);
}
if(StringUtils.isNotBlank(ipAdrsess)){
criteria.andIpAdressEqualTo(ipAdrsess);
}
list = colStationSetMapper.selectByExample(colStationSetExample);
return list;
}
//获取EE和FF的下标
public int getpotion(String list,String a,int num){
int start;
start =list.indexOf(a)+num;
String list2=list.substring(start+1);
if(list2.indexOf(a)==0){
String list3=list.substring(start+2);
if(list3.indexOf(a)==0){
String list4=list.substring(start+2);
if(list4.indexOf(a)==0){
return start;}
}
}
return -1;
}
/**
* 分类采集数值
* @param bytelist
* @param list
*/
public void reverse(byte[]bytelist, String list){
int start = -1;
if (StringUtils.isNotBlank(list)) {
start = list.indexOf("EEEE");
}
if (start != -1) {
if (start != 0) {
String f = list.substring(start - 1).intern();
if ("F".equals(f)) {
start = start + 1;
}
}
int end = 0;
if (start >= 0) {
/* end = getpotion(list, "F", 0) + 4; */
end = list.indexOf("FFFF");
end = end + 4;
byte[] queueon = new byte[1024 * 1024];//1mb
if (end > 3) {
if (end > start) {
if (end % 2 != 0) {// i为偶数
end += 1;
}
int c = (end - start) / 2;
if (bytelist.length - c > 0 && c - start >= 0) {
System.arraycopy(bytelist, start, queueon, 0, c - start);
queueon = changeMessage(queueon, c);
queue.offer(queueon);
queueon=null;
} else {
queue.offer(bytelist);
}
list = list.substring(end).intern();
if (bytelist.length - c > 0) {
byte[] a = new byte[bytelist.length - c + 1];
System.arraycopy(bytelist, 0, a, 0, bytelist.length - c - 1);
if (StringUtils.isNotBlank(list)) {
reverse(a, list);
}
a=null;
}
}
} else {// end
if (bytelist.length >= 21) {
String a1 = list.substring(0, 1);
if (!"FE".equals(a1)) {// 若第一节点为FE
System.arraycopy(bytelist, 0, queueon, 0, 21);
queueon = changeMessage(queueon, 21);
queue.offer(queueon);
queueon=null;
byte[] a = new byte[bytelist.length - 21];
System.arraycopy(bytelist, 0, a, 0, bytelist.length - 21);
if (StringUtils.isNotBlank(list)) {
reverse(a, list);
}
a=null;
}
} else {
getfe(bytelist, list);
}
}
} // -1
getfe(bytelist, list);
}
}
/**
* 删除空字节
*
* @param message
* @param length
*/
public byte[] changeMessage(byte[] message, int length) {
setChanged();
if(length>0){
byte[] temp = new byte[length];
System.arraycopy(message, 0, temp, 0, length);
notifyObservers(temp);
return temp;
}else{
return message;
}
}
}
相关数据库存储
-- ----------------------------
-- Table structure for `col_station_set`
-- ----------------------------
DROP TABLE IF EXISTS `col_station_set`;
CREATE TABLE `col_station_set` (
`id` int(255) NOT NULL auto_increment,
`station_id` varchar(255) default NULL COMMENT '数据采集站点信息的id',
`ip_adress` varchar(100) default NULL COMMENT 'ip地址',
`createtime` datetime default NULL COMMENT '得到ip地址的时间',
`updatetime` datetime default NULL,
`createuser` varchar(255) default NULL,
`updateuser` varchar(255) default NULL,
`ty_companyid` varchar(255) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
3、客户端
package com.netty;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Scanner;
public class NioClient {
//nio 异步非阻塞
public static void main(String[] args) throws IOException {
System.out.println("客户端已经启动....");
// 1.创建通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8080));
// 2.切换异步非阻塞
sChannel.configureBlocking(false);
// 3.指定缓冲区大小
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
Scanner scanner= new Scanner(System.in);
while (scanner.hasNext()) {
String str=scanner.next();
byteBuffer.put((new Date().toString()+"\n"+str).getBytes());
// 4.切换读取模式
byteBuffer.flip();
sChannel.write(byteBuffer);
byteBuffer.clear();
}
sChannel.close();
}
}
4、其他
网络调试助手
提取码:8ydc