JNIO与网络编程

The NIO library was introduced with JDK 1.4. NIO was created to allow Java programmers to implement high-speed I/O without having to write custom native code. NIO moves the most time-consuming I/O activites ( namely, filling and draining buffers) back into the operating system, thus allowing for a great increase in speed. The most important distinction between the original I/O library and NIO is how data is packaged and transmitted. Original I/O deals with data in stream, whereas NIO deals with data in blocks

摘自:IBM:Getting started with new I/O (NIO)

Part1 Buffer 和 Channel

参考:IBM:Getting started with new I/O (NIO)

Channel 和 Buffer 的基本使用

BufferChannel 是NIO中的重要对象,几乎所有的I/O操作都要用到这两个对象。Channel意为通道,他的作用类似于流对象,所有发送和接受的数据都要通过ChannelBuffer实质上是一个容器对象。

所有从Channel中读取的数据都读到了Buffer里,所有写入Channel的数据必须首先存放在Buffer里。

例如:

/*从文件中读取
 *1)从 FileInputStream得到 channel
 *2)生成 Buffer 的对象
 *3)从Channel中读入Buffer
*/
FileInputStream fin = new FileInputStream("read.txt");
FileChannel fc = fin.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
fc.read(buffer)     //所有从Channel中读取的(比如:文件中的)数据都读到了Buffer里
/*向文件中读入
 *1)从 FileOutputStream得到 channel
 *2)生成 Buffer 的对象,填充Buffer对象
 *3)从Buffer中读入Channel
*/
FileOutputStream fout = new FileOutputStream("read.txt");
FileChannel fc = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );

for(int i = 0; i < message.length; i++){
  buffer.put(message[i]);  
}
buffer.flip();      //flip() 方法,用于在将数据填入buffer和数据取出buffer之间的切换
fc.write(buffer)     //所有从Channel中写入(比如:文件中)的数据都写到了Buffer里

Buffer中的状态量

在上一段代码中,我们看到在Buffer切换状态时用到了filp()方法,事实上Buffer还有其他用于切换状态的函数,其中最常用的是filp()clear()

这两个方法通常的用法如下:

buffer.clear(); //在buffer填入数据之前调用
int r = fcin.read(buffer);

if(r == -1){
  break;
}

buffer.flip();//在buffer填入数据之后,取出数据之前调用
fcout.write(buffer);

为什么会出现吧这样的情况呢?
这是因为Buffer中有几个状态量,positionlimitcapacity。这在对Buffer进行操作时,这三个状态量不停变化,从而决定Buffer的可操作范围。

//TODO 关于buffer的三个状态量的解释
//TODO advanced JNIO

Part2 网络编程中用到的Channel

译自:Java NIO指南

DatagramChannel

操作1:打开DatagramChannelopen()

/*
*在这个例子中,我们打开了一个 DatagramChannel,并且可
*以从 9999 端口接收UDP数据报。
*/
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind( new InetSocketAddress(9999));

操作2:接收数据 receive()

/*
*receive()方法会将收到的packet中的数据拷贝到Buffer中
*如果Buffer不足以容纳接收到的数据,超出容纳空间的数据
*将被静默地丢弃
*/
ByteBuffer buf = ByteBuffer.allocate( 1024 );
buf.clear();

channel.recrive(buf);

操作3:发送数据send()

/*
*这个例子中我们向“jenkov.com”的80端口发送了一个字符串
*但是,我们得不到发送的数据被接受或者没有被接收的反馈
*因为UDP不对数据送达作出任何保证。
*/
String newData = "New String to write to file..."
                             +System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate( 1024 );
buf.clear();
buf.put(newData.getBytes());
buf.flip();

int bytesSent =  channel.send(buf, 
                 new InetSocketAddress("jenkov.com", 80));

操作4:链接到特定地址connect()

/*
*这个例子中的connect()不同于TCP中的链接,这个例子中的
*connect()并不建立真的链接,而是将channel有特定的地址
*绑定,但仍然不保证数据一定被送达。
*/
channel.connect(new InetSocketAddress("jenkov.com", 80));
//链接到特定地址后可以直接使用channel的read()和write()方法
int bytesRead = channel.read(buf);
int bytesWrriten = channel.write(buf);

SocketChannel

操作1:打开SocketChnnel

SocketChannel socketChannel = socketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenvok.com", 80));

操作2:关闭SocketChannel

socketChannel.close();

操作3:从SocketChannel中读取数据

ByteBuffer buf = ByteBuffer.allocate( 1024 );
//返回值表示buf接受了多少字节的数据,-1表示链接断开
int bytesRead = socketChannel.read(buf);

操作4:向SocketChannel中写入数据

String newData = "New String to write to file..."
                             +System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate( 1024 );
buf.clear();
buf.put(newData.getBytes());
buf.flip();

//注意:SocketChannel.write()在while循环内调用,因为不能
//保证write()方法会向channel中写入多少比特,因此我们反复
//调用write()方法,知道buf的内容全部被写入
while(buf.hasRemaining()){
  channel.write(buf);
}

操作5:非阻塞模式下的connect()write()read()方法

connect()方法:

如果 SocketChannel是非阻塞的,当我们调用connect()并返回时,链接可能还没有建立。因此,我们需要调用finishConnect()方法进行检测。

socketChannel.configureBlocking(false);
socketChannel.connect(
                          new InetSocketAddress("http://jenlov.com",80));
while(! socketChannel.finishConnectt()){
  //wait, or do something else...
}

write()方法:

不需要特殊变化,因为write()方法已经在循环中调用了。

read()方法:

需要注意这个方法的返回值,因为返回值表示实际实际读到多少数据。

ServerSocketChannel

操作1:打开SeverSocketChannel

ServerScoketChannel serverSocketChannel = ServerSocketChannel.open();

操作2:关闭ServerSocketChannel

serverSocketChannel.close();

操作3:监听到来的连接

/*
*由于要监听多个链接,所以将accept()放在while循环之中当然在实际
*编程中会用其他条件替换while循环中的true
*/
while(true){
  SocketChannel socketChannel = serverSocketChannel.accept();
  //do something with socketChannel
}

操作4:非阻塞模式

/*
*当没有连接到来时,accept()返回null
*/
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket.bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);

while(true){
  SocektChannel socketChannel = serverSocketChannel.accept();
  if(socketChannel != null){
    //do something with socketChannel...
  }
}

Part3 对象的序列化

译自:Java2Blog:Java中的序列化

为了硬盘存储和网络传输的需要,我们需要对Java对象序列化(Object->bytes);相反,在需要使用Java对象时,我们需要进行反序列化(bytes->Object)。

serialVersionUID的概念
serialVersionUID是在对象反序列化是用来确保版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现异常。
serialVersionUID的类型必须是如下形式:

ANY-ACCESS-MODIFIER static final long 

序列化过程

Serialization机制.jpg

需要进行序列化的对象必须实现Serializable接口。这是一个 maker interface,也就是一个空的接口。它的源码如下:

public interface Serializable{
}

反序列化机制

Deserialization机制.jpg

其他情况

  1. 如果需要序列化的对象中有其他对象的引用,其他对象也必须实现Serializable接口。
  2. TODO
  3. TODO
  4. TODO
  5. TODO
  6. TODO
  7. TODO

Part4 网络和异步I/O

译自:IBM:Getting started with new I/O (NIO)

异步I/O是一种非阻塞的I/O。在调用异步I/O之后,通过对关心的事件进行注册,当这些事件(如 :有可以读取的数据到达、一个新的socket连接,等)发生时会得到系统通知。异步I/O/的优势在于,可以监听任意数量的Channels的I/O事件,而不用使用轮询或者创建额外的线程。

Selectors

Sector是异步I/O的核心组件。我们在Selector上注册感兴趣的I/O事件,在这些事件发生的时候 Selector就会通知我们。

操作1:打开Selector

Selector selector = Selector.open();

操作2:用非阻塞方式打开ServerSocketChannel
为了接收连接,我们需要ServerSockeChanne。为了保证异步I/O操作,ServerSocketChannel要以非阻塞方式打开。

SeverSocketChannel ssc = SeverSocketChannel.open();
ssc.configureBlocking( false );

ServerSocket ss = ssc.socket();
InetSocketAddress address = new InetSocketAddress( port[i] );
ss.bind( address );

Selection Keys

操作3:注册ServerSocketChannelSelector
使用ServerSocketChannel.register()方法。这个方法的第一个参数是Selector,第二个参数是要监测的事件(如:accept)。当有监测的事件发生时,Selector将它添加到SelectionKey中。

SelectionKey key = ssc.register( selector, SelectionKey.PO_ACCEPT);

操作4:监听事件

//阻塞直到至少有一个事件发生
int num = selector.select();

Set selectedKeys = selector.seectedKeys();
for(SelectionKye key: selectedKeys){
  //deal with I/O event
}

操作5:检查发生的事件的类型

//确认事件是accept
if( ( key.readOps() ) & SelectionKey.OP_ACCEPT == SelectionKey.OP_ACCEPT){
  //Accept the new connection
  //...
}

操作6:接受一个新连接
上一步,我们已经确认youyige连接在等待,ServerSocket接受,所以我们可以安全的接受这个连接而不用担心accept()方法阻塞。

ServerSocketChannel ssc = key.channel();
SocketChannel sc = ssc.accept();

操作7:注册新接收的ScoketChannel

sc.configureBloking( false );
SelectionKey newkey = sc.register( selector, Selection.OP_READ);

操作8:删除处理过的key

selectedKeys.remove(key);

操作9:接受到来的数据

if( (key.readyOps() &  Selectionkey.OP_READ) == SelectionKey.OP_READ){
  SocketChannel sc = (SocketChannel)key.channel();
}

Part5 并发

译自:ORACLE: Java并发

在并发编程中有两个基本的执行单元:进程和线程。在Java中,并发编程主要指多线程并发。

Thread对象

**两种定义方法

  1. 实现Runnable接口
  2. 继承Thread类
public class HelloRunnable implements Runnable{
  public void run(){
    System.out.println("Hello from a thread!");
  }
  public static void main(String args[]){
    (new Thread(new HelloRunnable())).start();
  }

}
public class HelloThread exends Thread{
  public void run(){
    System.out.println("Hello from a thread!");
  }
  public static void main(String args[]){
    (new HelloThread()).start;
  }
}

//待续

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容