Java網絡程式設計
- TCP和UDP的差別
- 網絡程式設計中的關鍵類和接口
- BIO程式設計
- NIO程式設計(應用反應器模式)
- AIO程式設計
- BIO,NIO,AIO的差別
TCP和UDP的差別
- UDP
- 每個資料報中都給出了完整的位址資訊,是以無需要建立發送方和接收方的連接配接
- UDP傳輸資料時是有大小限制的,每個被傳輸的資料報必須限定在64KB之内。
- UDP是一個不可靠的協定,發送方所發送的資料報并不一定以相同的次序到達接收方
- UDP操作簡單,而且僅需要較少的監護,是以通常用于區域網路高可靠性的分散系統中client/server應用程式。例如視訊會議系統,并不要求音頻視訊資料絕對的正确,隻要保證連貫性就可以了,這種情況下顯然使用UDP會更合理一些。飛秋聊天、淩波桌面共享、網絡視訊會議
- TCP
- 面向連接配接的協定,在socket之間進行資料傳輸之前必然要建立連接配接,是以在TCP中需要連接配接時間。
- TCP傳輸資料無大小限制,一旦連接配接建立起來,雙方的socket就可以按統一的格式傳輸大的資料。
- TCP是一個可靠的協定,它確定接收方完全正确地擷取發送方所發送的全部資料。
- TCP在網絡通信上有極強的生命力,例如遠端連接配接(Telnet)和檔案傳輸(FTP)都需要不定長度的資料被可靠地傳輸。但是可靠的傳輸是要付出代價的,對資料内容正确性的檢驗必然占用計算機的處理時間和網絡的帶寬,是以TCP傳輸的效率不如UDP高。
網絡程式設計中的關鍵類和接口
- InteAddress類 --> 是一個代表IP位址的封裝,沒有構造方法,可以通過兩個靜态方法獲得它的對象
InetAddress ip = InetAddress.getByName("www.baidu.com"); InetAddress local = InetAddress.getByAddress(new byte[]{127,0,0,1});
- URL --> 是統一資源定位符的簡稱,它表示Internet上某一資源的位址
- URLConnection --> 對象可以向所代表的URL連接配接發送請求和讀取URL的資源
- URLDecoder和URLEncoder -->用于編解碼
String keyWord = URLDecoder.decode("%E6%9D%8E%E5%88%9A+j2ee", "UTF-8"); String urlStr = URLEncoder.encode( "ROR靈活開發最佳指南" , "GBK");
- Get方式請求資源
String urlName = url + "?" + param; URL realUrl = new URL(urlName); URLConnection conn = realUrl.openConnection();//打開和URL之間的連接配接 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); conn.connect();//建立實際的連接配接
- Post方式請求資源
URL realUrl = new URL(url); URLConnection conn = realUrl.openConnection();//打開和URL之間的連接配接 conn.setRequestProperty("accept", "*/*");//設定通用的請求屬性 conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); //發送POST請求必須設定如下兩行 conn.setDoOutput(true);//可以發送資料 conn.setDoInput(true);//可以接收資料 out = new PrintWriter(conn.getOutputStream());//擷取URLConnection對象對應的輸出流 out.print(param);//發送請求參數
BIO程式設計
- TCP
- 服務端
ServerSocket serverSocket =new ServerSocket(10086);//1024-65535的某個端口 Socket socket = serverSocket.accept(); InputStream is = socket.getInputStream(); InputStreamReader isr =new InputStreamReader(is); BufferedReader br =new BufferedReader(isr); String info =null; while((info=br.readLine())!=null){ System.out.println("Hello,我是伺服器,用戶端說:"+info); } socket.shutdownInput();//關閉輸入流 OutputStream os = socket.getOutputStream(); PrintWriter pw = new PrintWriter(os); pw.write("Hello World!"); pw.flush(); //5、關閉資源 XXX.close();
- 用戶端
Socket socket =new Socket("127.0.0.1",10086); OutputStream os = socket.getOutputStream();//位元組輸出流 PrintWriter pw =new PrintWriter(os);//将輸出流包裝成列印流 pw.write("使用者名:admin;密碼:admin"); pw.flush(); socket.shutdownOutput(); InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String info = null; while((info=br.readLine())!=null){ System.out.println("Hello,我是用戶端,伺服器說:"+info); } //4、關閉資源 XXX.close();
- 服務端
- UDP
- 服務端
DatagramSocket server = new DatagramSocket(5050); byte[] recvBuf = new byte[100]; DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length); server.receive(recvPacket); String recvStr = new String(recvPacket.getData(), 0, recvPacket.getLength()); System.out.println("Hello World!" + recvStr); int port = recvPacket.getPort(); InetAddress addr = recvPacket.getAddress(); String sendStr = "Hello ! I'm Server"; byte[] sendBuf; sendBuf = sendStr.getBytes(); DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, addr, port); server.send(sendPacket); server.close();
- 用戶端
DatagramSocket client = new DatagramSocket(); String sendStr = "Hello! I'm Client"; byte[] sendBuf; sendBuf = sendStr.getBytes(); InetAddress addr = InetAddress.getByName("127.0.0.1"); int port = 5050; DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, addr, port); client.send(sendPacket); byte[] recvBuf = new byte[100]; DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length); client.receive(recvPacket); String recvStr = new String(recvPacket.getData(), 0, recvPacket.getLength()); System.out.println("收到:" + recvStr); client.close();
- 服務端
NIO程式設計(應用反應器模式)
- 核心類和接口
- Channel(通道):NIO把它支援的I/O對象抽象為Channel。它模拟了通信連接配接,類似于最早I/O中的流(Stream),使用者可以通過它讀取和寫入資料。目前常用的類有SocketChannel、ServerSocketChannel、DatagramChannel、FileChannel等。
- Buffer(緩沖區): Buffer是一塊連續的記憶體區域,一般作為Channel收發資料的載體出現。所有資料都通過Buffer對象來處理。
- Selector(選擇器):Selector類提供了監控一個和多個通道目前狀态的機制。隻要Channel向Selector注冊了某種特定的事件,Selector就會監聽這些事件是否會發生,一旦發生某個事件,便會通知對應的Channel。使用選擇器,借助單一線程,就可對數量龐大的活動I/O通道實施監控和維護。
- 在通道上可以注冊我們感興趣的事件。總共有以下四種事件:
- 服務端接收用戶端連接配接事件 SelectionKey.OP_ACCEPT(16)
- 用戶端連接配接服務端事件 SelectionKey.OP_CONNECT(8)
- 讀事件 SelectionKey.OP_READ(1)
- 寫事件 SelectionKey.OP_WRITE(4)
- 使用NIO程式設計,大體上可以分為下面三個步驟
- 向Selector對象注冊感興趣的事件
- 從Selector中擷取感興趣的事件
- 根據不同的事件進行相應的處理
- NIO執行個體代碼
- 服務端
public class NIOServer { private Selector selector; // 通道管理器 public void initServer(int port) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 獲得一個ServerSocket通道 serverChannel.configureBlocking(false); // 設定通道為非阻塞 serverChannel.socket().bind(new InetSocketAddress(port)); // 将該通道對應的ServerSocket綁定到port端口 this.selector = Selector.open(); // 獲得一個通道管理器 // 将通道管理器和該通道綁定,并為該通道注冊SelectionKey.OP_ACCEPT事件,注冊該事件後, // 當該事件到達時,selector.select()會傳回,如果該事件沒到達selector.select()會一直阻塞。 serverChannel.register(selector, SelectionKey.OP_ACCEPT); } public void listen() throws IOException { System.out.println("服務端啟動成功!"); while (true) { // 輪詢通路selector selector.select(); // 當注冊的事件到達時,方法傳回;否則,該方法會一直阻塞 Iterator ite = this.selector.selectedKeys().iterator(); // 獲得selector中選中的項的疊代器,選中的項為注冊的事件 while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); ite.remove(); // 删除已選的key,以防重複處理 if (key.isAcceptable()) { // 用戶端請求連接配接事件 ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel channel = server.accept(); // 獲得和用戶端連接配接的通道 channel.configureBlocking(false); // 設定成非阻塞 channel.write(ByteBuffer.wrap(new String("向用戶端發送了一條資訊").getBytes( "UTF-8"))); channel.register(this.selector, SelectionKey.OP_READ); } else if (key.isReadable()) { read(key); } } } } public void read(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel) key.channel(); // 伺服器可讀取消息:得到事件發生的Socket通道 ByteBuffer buffer = ByteBuffer.allocate(64); // 建立讀取的緩沖區 channel.read(buffer); byte[] data = buffer.array(); String msg = new String(data, "UTF-8").trim(); System.out.println("服務端收到資訊:" + msg); ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes("UTF-8")); channel.write(outBuffer); // 将消息回送給用戶端 } public static void main(String[] args) throws IOException { NIOServer server = new NIOServer(); server.initServer(8000); server.listen(); } }
- 用戶端
public class NIOClient { private Selector selector; // 通道管理器 public void initClient(String ip, int port) throws IOException { SocketChannel channel = SocketChannel.open(); // 獲得一個Socket通道 channel.configureBlocking(false); // 設定通道為非阻塞 this.selector = Selector.open(); // 獲得一個通道管理器 channel.connect(new InetSocketAddress(ip, port)); // 用channel.finishConnect();才能完成連接配接 channel.register(selector, SelectionKey.OP_CONNECT); } public void listen() throws IOException { while (true) { // 輪詢通路selector selector.select(); Iterator ite = this.selector.selectedKeys().iterator(); // 獲得selector中選中的項的疊代器 while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); ite.remove(); // 删除已選的key,以防重複處理 if (key.isConnectable()) { // 連接配接事件發生 SocketChannel channel = (SocketChannel) key.channel(); if (channel.isConnectionPending()) { // 如果正在連接配接,則完成連接配接 channel.finishConnect(); } channel.configureBlocking(false); // 設定成非阻塞 channel.write(ByteBuffer.wrap(new String("向服務端發送了一條資訊").getBytes( "UTF-8"))); channel.register(this.selector, SelectionKey.OP_READ); } else if (key.isReadable()) { read(key); } } } } public void read(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel) key.channel(); // 伺服器可讀取消息:得到事件發生的Socket通道 ByteBuffer buffer = ByteBuffer.allocate(64); // 建立讀取的緩沖區 channel.read(buffer); byte[] data = buffer.array(); String msg = new String(data, "UTF-8").trim(); System.out.println("用戶端收到資訊:" + msg); } public static void main(String[] args) throws IOException { NIOClient client = new NIOClient(); client.initClient("localhost", 8000); client.listen(); } }
- 服務端
AIO程式設計
- 核心類或接口
- Callable: Callable與Runnable的功能大緻相似,Callable中有一個call()函數,但是call()函數有傳回值
- Future: Future就是對于具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、擷取結果、設定結果操作。get方法會阻塞,直到任務傳回結果
- RunnableFuture: 它繼承了Runnbale和Futrue這兩個接口
- FutureTask:
- 它實作了RunnableFuture,另外它還可以包裝Runnable和Callable,由構造函數注入依賴
- 它既可以通過Thread包裝來直接執行,也可以送出給ExecuteService來執行
- 它還可以直接通過get()函數擷取執行結果,該函數會阻塞,直到結果傳回
- 使用執行個體:
Future<?> result = mExecutor.submit(new Runnable() { @Override public void run() { fibc(20); } }); Future<Integer> result2 = mExecutor.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { return fibc(20); } }); FutureTask<Integer> futureTask = new FutureTask<Integer>( new Callable<Integer>() { @Override public Integer call() throws Exception { return fibc(20); } }); mExecutor.submit(futureTask) ;//送出futureTask
- 使用執行個體:
- 異步Socket :
- AsynchronousChannel:所有AIO Channel的父類。
- AsynchronousByteChannel:支援Byte讀寫的Channel
- AsynchronousDatagramChannel:支援資料包(datagram)讀寫的Channel
- AsynchronousFileChannel:支援檔案讀寫的Channel
- AsynchronousServerSocketChannel: 支援資料流讀寫的伺服器端Channel
- AsynchronousSocketChannel:支援資料流讀寫的用戶端Channel
- AsynchronousChannelGroup:支援資源共享的Channel分組
- CompletionHandler: 異步IO操作結果的回調接口,用于定義在IO操作完成後所作的回調工作。AIO的API允許兩種方式來處理異步操作的結果:傳回的Future模式或者注冊CompletionHandler,我更推薦用CompletionHandler的方式
- 異步Socket方法總結:
- accept(): 接受一個連接配接,傳回一個Future,可通過Future擷取到Socket的狀态,和資料
- accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler):接受連接配接,并為連接配接綁定一個CompletionHandler處理Socket連接配接
- bind(SocketAddress local): 把ServerSocket綁定到本地端口上,等待連接配接
- bind(SocketAddress local, int backlog): 功能和上面一個方法一樣,添加了backlog參數指定隊列中挂起的連接配接的最大個數
- open(): 開啟一個異步Socket通道
- open(AsynchronousChannelGroup group): 開啟一個異步Socket通道,并把通道加入到指定的組做資源管理
- provider(): 傳回這個Channel的建立者
- setOption(SocketOption name, T value): 配置Socket參數的方法
-
AIO原理
所謂AIO,異步IO,其主要是針對程序在調用IO擷取外部資料時,是否阻塞調用程序而言的。一個程序的IO調用步驟大緻如下:
- 程序向作業系統請求資料
- 作業系統把外部資料加載到核心的緩沖區中
- 作業系統把核心的緩沖區拷貝到程序的緩沖區
- 程序獲得資料完成自己的功能
- AIO代碼執行個體
- 服務端
public class AIOServer { public static void main(String[] args) throws Exception { AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool( 10)); AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(channelGroup); server.bind(new InetSocketAddress(5000)); server.accept(server, new CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel>() { @Override public void completed(AsynchronousSocketChannel result, AsynchronousServerSocketChannel server) { server.accept(server, this); ByteBuffer buffer = ByteBuffer.allocate(1024); Future<Integer> future = result.read(buffer); try { future.get(); buffer.flip(); byte[] dst = new byte[buffer.limit()]; buffer.get(dst, 0, buffer.limit()); System.out.println("server************* : " + new String(dst)); buffer.clear(); buffer.put("client received, haha".getBytes()); buffer.flip(); result.write(buffer); buffer.clear(); result.read(buffer).get(); buffer.flip(); byte[] dst2 = new byte[buffer.limit()]; buffer.get(dst2, 0, buffer.limit()); System.out.println("*********server received : " + new String(dst2)); } catch (Exception e) { e.printStackTrace(); } } @Override public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) { } }); Thread.sleep(100000); } }
- 用戶端
public class AIOClient { public static void main(String[] args) throws Exception { AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open(); Future future = clientChannel.connect(new InetSocketAddress( "127.0.0.1", 5000)); Object o = future.get(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); clientChannel.write(byteBuffer.wrap("somchai ....".getBytes())); ByteBuffer readBuffer = ByteBuffer.allocate(1024); MyObj myObj = new MyObj(clientChannel, readBuffer); clientChannel.read(readBuffer, myObj, new CompletionHandler<Integer, MyObj>() { @Override public void completed(Integer result, MyObj attachment) { attachment.myBuffer.flip(); byte[] dst = new byte[attachment.myBuffer.limit()]; attachment.myBuffer.get(dst, 0, attachment.myBuffer.limit()); System.out.println("client***inner************** b : " + new String(dst)); ByteBuffer writeBuffer = ByteBuffer.allocate(1024); writeBuffer.put(new String(" I am client ...xixi").getBytes()); writeBuffer.flip(); attachment.clientChannel.write(writeBuffer); } @Override public void failed(Throwable exc, MyObj attachment) { } }); Thread.sleep(10000); } } class MyObj { AsynchronousSocketChannel clientChannel = null; ByteBuffer myBuffer = null; public MyObj(AsynchronousSocketChannel clientChannel, ByteBuffer myBuffer) { this.clientChannel = clientChannel; this.myBuffer = myBuffer; } }
- 服務端
BIO,NIO,AIO的差別
- 一個IO操作其實分成了兩個步驟:發起IO請求和實際的IO操作
- 阻塞IO和非阻塞IO的差別在于第一步,發起IO請求是否會被阻塞,如果阻塞直到完成那麼就是傳統的阻塞IO,如果不阻塞,那麼就是非阻塞IO
- 同步IO和異步IO的差別就在于第二個步驟是否阻塞,如果請求程序被IO讀寫阻塞,那麼就是同步IO
- 什麼是同步、異步
- 同步: 指使用者程序發起了實際的IO操作之後,等待或輪詢的檢視IO操作是否完成
- 異步: 指使用者程序發起了實際的IO操作之後,便開始做自己的事情,而當IO操作已經完成的時候會得到IO完成的通知
- 什麼是阻塞和非阻塞
- 阻塞: 讀取或者寫入函數将一直等待
- 非阻塞: 讀取或者寫入函數會立即傳回一個狀态值
- 同步、異步和阻塞、非阻塞的組合
- 同步阻塞(BIO): 使用者程序在發起一個IO操作以後,必須等待IO操作的完成,隻有當真正完成了IO操作以後,使用者程序才能運作
- 同步非阻塞(NIO): 使用者程序發起一個IO操作以後,可傳回做其它事情,但是使用者程序需要時不時的詢問IO操作是否就緒,這就要求使用者程序不停的去詢問,進而引入不必要的CPU資源浪費
- 異步非阻塞(AIO): 使用者程序發起一個IO操作後便立即傳回,等IO操作真正的完成以後,應用程式會得到IO操作完成的通知,此時使用者程序隻需要對資料進行處理就好了,不需要進行實際的IO讀寫操作,因為真正的IO讀取或者寫入操作已經由核心完成了