JNIO與網(wǎng)絡(luò)編程

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中的重要對(duì)象儒旬,幾乎所有的I/O操作都要用到這兩個(gè)對(duì)象。Channel意為通道聪姿,他的作用類似于流對(duì)象,所有發(fā)送和接受的數(shù)據(jù)都要通過(guò)Channel沐悦。Buffer實(shí)質(zhì)上是一個(gè)容器對(duì)象威恼。

所有從Channel中讀取的數(shù)據(jù)都讀到了Buffer里,所有寫(xiě)入Channel的數(shù)據(jù)必須首先存放在Buffer里距境。

例如:

/*從文件中讀取
 *1)從 FileInputStream得到 channel
 *2)生成 Buffer 的對(duì)象
 *3)從Channel中讀入Buffer
*/
FileInputStream fin = new FileInputStream("read.txt");
FileChannel fc = fin.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
fc.read(buffer)     //所有從Channel中讀取的(比如:文件中的)數(shù)據(jù)都讀到了Buffer里
/*向文件中讀入
 *1)從 FileOutputStream得到 channel
 *2)生成 Buffer 的對(duì)象央拖,填充Buffer對(duì)象
 *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() 方法祭阀,用于在將數(shù)據(jù)填入buffer和數(shù)據(jù)取出buffer之間的切換
fc.write(buffer)     //所有從Channel中寫(xiě)入(比如:文件中)的數(shù)據(jù)都寫(xiě)到了Buffer里

Buffer中的狀態(tài)量

在上一段代碼中,我們看到在Buffer切換狀態(tài)時(shí)用到了filp()方法鲜戒,事實(shí)上Buffer還有其他用于切換狀態(tài)的函數(shù)专控,其中最常用的是filp()clear()

這兩個(gè)方法通常的用法如下:

buffer.clear(); //在buffer填入數(shù)據(jù)之前調(diào)用
int r = fcin.read(buffer);

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

buffer.flip();//在buffer填入數(shù)據(jù)之后遏餐,取出數(shù)據(jù)之前調(diào)用
fcout.write(buffer);

為什么會(huì)出現(xiàn)吧這樣的情況呢伦腐?
這是因?yàn)?code>Buffer中有幾個(gè)狀態(tài)量,position失都、limitcapacity柏蘑。這在對(duì)Buffer進(jìn)行操作時(shí),這三個(gè)狀態(tài)量不停變化粹庞,從而決定Buffer的可操作范圍咳焚。

//TODO 關(guān)于buffer的三個(gè)狀態(tài)量的解釋
//TODO advanced JNIO

Part2 網(wǎng)絡(luò)編程中用到的Channel

譯自:Java NIO指南

DatagramChannel

操作1:打開(kāi)DatagramChannelopen()

/*
*在這個(gè)例子中,我們打開(kāi)了一個(gè) DatagramChannel庞溜,并且可
*以從 9999 端口接收UDP數(shù)據(jù)報(bào)革半。
*/
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind( new InetSocketAddress(9999));

操作2:接收數(shù)據(jù) receive()

/*
*receive()方法會(huì)將收到的packet中的數(shù)據(jù)拷貝到Buffer中
*如果Buffer不足以容納接收到的數(shù)據(jù),超出容納空間的數(shù)據(jù)
*將被靜默地丟棄
*/
ByteBuffer buf = ByteBuffer.allocate( 1024 );
buf.clear();

channel.recrive(buf);

操作3:發(fā)送數(shù)據(jù)send()

/*
*這個(gè)例子中我們向“jenkov.com”的80端口發(fā)送了一個(gè)字符串
*但是强缘,我們得不到發(fā)送的數(shù)據(jù)被接受或者沒(méi)有被接收的反饋
*因?yàn)閁DP不對(duì)數(shù)據(jù)送達(dá)作出任何保證督惰。
*/
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()

/*
*這個(gè)例子中的connect()不同于TCP中的鏈接不傅,這個(gè)例子中的
*connect()并不建立真的鏈接旅掂,而是將channel有特定的地址
*綁定,但仍然不保證數(shù)據(jù)一定被送達(dá)访娶。
*/
channel.connect(new InetSocketAddress("jenkov.com", 80));
//鏈接到特定地址后可以直接使用channel的read()和write()方法
int bytesRead = channel.read(buf);
int bytesWrriten = channel.write(buf);

SocketChannel

操作1:打開(kāi)SocketChnnel

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

操作2:關(guān)閉SocketChannel

socketChannel.close();

操作3:從SocketChannel中讀取數(shù)據(jù)

ByteBuffer buf = ByteBuffer.allocate( 1024 );
//返回值表示buf接受了多少字節(jié)的數(shù)據(jù)商虐,-1表示鏈接斷開(kāi)
int bytesRead = socketChannel.read(buf);

操作4:向SocketChannel中寫(xiě)入數(shù)據(jù)

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循環(huán)內(nèi)調(diào)用,因?yàn)椴荒?//保證write()方法會(huì)向channel中寫(xiě)入多少比特,因此我們反復(fù)
//調(diào)用write()方法秘车,知道buf的內(nèi)容全部被寫(xiě)入
while(buf.hasRemaining()){
  channel.write(buf);
}

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

connect()方法:

如果 SocketChannel是非阻塞的,當(dāng)我們調(diào)用connect()并返回時(shí)叮趴,鏈接可能還沒(méi)有建立割笙。因此,我們需要調(diào)用finishConnect()方法進(jìn)行檢測(cè)眯亦。

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

write()方法:

不需要特殊變化伤溉,因?yàn)?code>write()方法已經(jīng)在循環(huán)中調(diào)用了。

read()方法:

需要注意這個(gè)方法的返回值妻率,因?yàn)榉祷刂当硎緦?shí)際實(shí)際讀到多少數(shù)據(jù)乱顾。

ServerSocketChannel

操作1:打開(kāi)SeverSocketChannel

ServerScoketChannel serverSocketChannel = ServerSocketChannel.open();

操作2:關(guān)閉ServerSocketChannel

serverSocketChannel.close();

操作3:監(jiān)聽(tīng)到來(lái)的連接

/*
*由于要監(jiān)聽(tīng)多個(gè)鏈接,所以將accept()放在while循環(huán)之中當(dāng)然在實(shí)際
*編程中會(huì)用其他條件替換while循環(huán)中的true
*/
while(true){
  SocketChannel socketChannel = serverSocketChannel.accept();
  //do something with socketChannel
}

操作4:非阻塞模式

/*
*當(dāng)沒(méi)有連接到來(lái)時(shí)宫静,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 對(duì)象的序列化

譯自:Java2Blog:Java中的序列化

為了硬盤存儲(chǔ)和網(wǎng)絡(luò)傳輸?shù)男枰呔唬覀冃枰獙?duì)Java對(duì)象序列化(Object->bytes);相反孤里,在需要使用Java對(duì)象時(shí)伏伯,我們需要進(jìn)行反序列化(bytes->Object)。

serialVersionUID的概念
serialVersionUID是在對(duì)象反序列化是用來(lái)確保版本一致性的捌袜。在進(jìn)行反序列化時(shí)舵鳞,JVM會(huì)把傳來(lái)的字節(jié)流中的serialVersionUID與本地相應(yīng)實(shí)體(類)的serialVersionUID進(jìn)行比較,如果相同就認(rèn)為是一致的琢蛤,可以進(jìn)行反序列化蜓堕,否則就會(huì)出現(xiàn)異常。
serialVersionUID的類型必須是如下形式:

ANY-ACCESS-MODIFIER static final long 

序列化過(guò)程

Serialization機(jī)制.jpg

需要進(jìn)行序列化的對(duì)象必須實(shí)現(xiàn)Serializable接口博其。這是一個(gè) maker interface套才,也就是一個(gè)空的接口。它的源碼如下:

public interface Serializable{
}

反序列化機(jī)制

Deserialization機(jī)制.jpg

其他情況

  1. 如果需要序列化的對(duì)象中有其他對(duì)象的引用慕淡,其他對(duì)象也必須實(shí)現(xiàn)Serializable接口背伴。
  2. TODO
  3. TODO
  4. TODO
  5. TODO
  6. TODO
  7. TODO

Part4 網(wǎng)絡(luò)和異步I/O

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

異步I/O是一種非阻塞的I/O。在調(diào)用異步I/O之后峰髓,通過(guò)對(duì)關(guān)心的事件進(jìn)行注冊(cè)傻寂,當(dāng)這些事件(如 :有可以讀取的數(shù)據(jù)到達(dá)、一個(gè)新的socket連接携兵,等)發(fā)生時(shí)會(huì)得到系統(tǒng)通知疾掰。異步I/O/的優(yōu)勢(shì)在于,可以監(jiān)聽(tīng)任意數(shù)量的Channels的I/O事件徐紧,而不用使用輪詢或者創(chuàng)建額外的線程静檬。

Selectors

Sector是異步I/O的核心組件炭懊。我們?cè)?code>Selector上注冊(cè)感興趣的I/O事件,在這些事件發(fā)生的時(shí)候 Selector就會(huì)通知我們拂檩。

操作1:打開(kāi)Selector

Selector selector = Selector.open();

操作2:用非阻塞方式打開(kāi)ServerSocketChannel
為了接收連接侮腹,我們需要ServerSockeChanne。為了保證異步I/O操作稻励,ServerSocketChannel要以非阻塞方式打開(kāi)父阻。

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

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

Selection Keys

操作3:注冊(cè)ServerSocketChannelSelector
使用ServerSocketChannel.register()方法。這個(gè)方法的第一個(gè)參數(shù)是Selector望抽,第二個(gè)參數(shù)是要監(jiān)測(cè)的事件(如:accept)至非。當(dāng)有監(jiān)測(cè)的事件發(fā)生時(shí),Selector將它添加到SelectionKey中糠聪。

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

操作4:監(jiān)聽(tīng)事件

//阻塞直到至少有一個(gè)事件發(fā)生
int num = selector.select();

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

操作5:檢查發(fā)生的事件的類型

//確認(rèn)事件是accept
if( ( key.readOps() ) & SelectionKey.OP_ACCEPT == SelectionKey.OP_ACCEPT){
  //Accept the new connection
  //...
}

操作6:接受一個(gè)新連接
上一步荒椭,我們已經(jīng)確認(rèn)youyige連接在等待,ServerSocket接受舰蟆,所以我們可以安全的接受這個(gè)連接而不用擔(dān)心accept()方法阻塞趣惠。

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

操作7:注冊(cè)新接收的ScoketChannel

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

操作8:刪除處理過(guò)的key

selectedKeys.remove(key);

操作9:接受到來(lái)的數(shù)據(jù)

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

Part5 并發(fā)

譯自:ORACLE: Java并發(fā)

在并發(fā)編程中有兩個(gè)基本的執(zhí)行單元:進(jìn)程和線程。在Java中身害,并發(fā)編程主要指多線程并發(fā)味悄。

Thread對(duì)象

**兩種定義方法

  1. 實(shí)現(xiàn)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;
  }
}

//待續(xù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市塌鸯,隨后出現(xiàn)的幾起案子侍瑟,更是在濱河造成了極大的恐慌,老刑警劉巖丙猬,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涨颜,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡茧球,警方通過(guò)查閱死者的電腦和手機(jī)庭瑰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)抢埋,“玉大人弹灭,你說(shuō)我怎么就攤上這事【韭ⅲ” “怎么了穷吮?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)饥努。 經(jīng)常有香客問(wèn)我捡鱼,道長(zhǎng),這世上最難降的妖魔是什么肪凛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任堰汉,我火速辦了婚禮辽社,結(jié)果婚禮上伟墙,老公的妹妹穿的比我還像新娘翘鸭。我一直安慰自己,他們只是感情好戳葵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布就乓。 她就那樣靜靜地躺著,像睡著了一般拱烁。 火紅的嫁衣襯著肌膚如雪生蚁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天戏自,我揣著相機(jī)與錄音邦投,去河邊找鬼。 笑死擅笔,一個(gè)胖子當(dāng)著我的面吹牛志衣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猛们,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼念脯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了弯淘?” 一聲冷哼從身側(cè)響起绿店,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎庐橙,沒(méi)想到半個(gè)月后假勿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡态鳖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年废登,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郁惜。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡堡距,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出兆蕉,到底是詐尸還是另有隱情羽戒,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布虎韵,位于F島的核電站易稠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏包蓝。R本人自食惡果不足惜驶社,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一企量、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧亡电,春花似錦届巩、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至或辖,卻和暖如春瘾英,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背颂暇。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工缺谴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耳鸯。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓湿蛔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親片拍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子煌集,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容