Java網絡編程html
一、Socket編程java
Socket(套接字):封裝着如端口號,ip地址,計算機名等信息的類。經過Socket咱們能夠和遠程計算機通訊。spring
網絡通訊模型編程
C/S Client/Server緩存
客戶端通航運行在用戶的計算機上,客戶端是一個特有的程序,實現特有的功能,鏈接服務器進行通訊,誰發起鏈接誰是用戶。安全
服務器端一般是等待客戶端鏈接,提供功能服務並與之通訊。服務器
B/S網絡
固定了客戶端和通訊協議和C/S結構。多線程
通訊協議:計算機通訊的實質就是相互收發字節。那麼按照必定的格式收發字節就是通訊協議。併發
/** * 建立客戶端Socket * Socket客戶端類 * 構造的時候就會根據給定的服務端ip地址和服務端的端口號嘗試鏈接 */ try { System.out.println("開始鏈接"); Socket socket = new Socket("172.16.3.33", 8088); System.out.println("與服務端鏈接成功!"); /** * 經過socket能夠獲取一組與服務器通訊的輸入輸出流 * 咱們對其包裝就能夠方便進行讀寫信息了。 * 經過socket拿到的是兩個低級流(字節流) */ InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); /** * 向服務器發送字符,將字符輸出流轉換成緩衝字符輸出流 */ PrintWriter pw = new PrintWriter(out); pw.println("你好服務器!"); pw.flush(); /** * 收取服務器發送的字符串,包裝爲緩衝字符輸入流 */ BufferedReader reader = new BufferedReader(new InputStreamReader(in)); //讀取服務器發回的信息 String info = reader.readLine(); System.out.println("服務端:"+info); } catch (IOException e) { //e.printStackTrace(); } /** * 服務端 * 服務器端打開socket等待客戶端的鏈接 * 服務器端的Socket名稱是ServerSocket * 建立ServerSocket須要指定服務端口號 */ public static void main(String[] args) { try { System.out.println("dddd"); ServerSocket server = new ServerSocket(8050); /** * 監聽端口 * 等待客戶端鏈接 * 等待客戶端鏈接的方法accept() * 該方法是一個阻塞方法,知道有客戶端鏈接上該方法纔會返回,返回的就是當前客戶端的套接字。 * 從中咱們能夠知道客戶端的ip等信息。 * * accept()方法能夠重複調用 */ System.out.println("啓動完畢,等待鏈接"); Socket client = server.accept(); System.out.println("有一個客戶端和我鏈接了"); /** * 經過socket獲取輸入流讀取客戶端的信息 */ InputStream in = client.getInputStream(); OutputStream out = client.getOutputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); System.out.println("客戶端:"+reader.readLine()); //向客戶端發信息 PrintWriter pw = new PrintWriter(out); pw.println("你好客戶端"); pw.flush(); } catch (IOException e) { //e.printStackTrace(); } }
二、多線程Socket
Server端多線程:
服務器端無限循環接受客戶端的訪問,每鏈接都能茶聖一對新的Socket的實例。
爲每一個客戶端鏈接建立一個獨立線程,處理客戶請求。
public static void main(String[] args) { try { ServerSocket server = new ServerSocket(8088); System.out.println("啓動完畢,等待鏈接"); while (true) { Socket client = server.accept(); if(client==null)continue; System.out.println("與客戶端鏈接成功"+client.getInetAddress().getHostAddress()); Handler handler = new Handler(client);//交給線程去處理 Thread t = new Thread(handler); t.start(); } } catch (IOException e) { } } /** * 與客戶端通訊線程 * 負責與某個特定的socket的客戶端進行通訊 * 每一個線程實例負責一個客戶端的通訊 */ private static class Handler implements Runnable { /** * 當前線程要通訊的客戶端socket */ private Socket client; public Handler(Socket client) { this.client = client; } public void run(){ try { /** 經過socket獲取客戶端信息 */ InputStream in = this.client.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); /** 死循環讀取客戶端信息 */ while (true) { if (reader.readLine() == null) return; System.out.println("客戶端"+ this.client.getInetAddress().getHostAddress() + ":" + reader.readLine()); } } catch (Exception e) { } } }
三、線程池
上面這種頻繁的建立線程和銷燬線程是很是消耗資源和性能的。
能夠建立一些空的線程,將它們保存起來,當有任務須要併發執行時,咱們取出一個空的線程來運行這個任務,當任務運行完畢後,在將線程收回,等待下次分配任務。
這樣自始自終咱們都只使用了開始建立的那些線程,並能夠重複利用來節省性能開銷。
JDK提供了線程池的管理器ExecutorService
經過Executors類的靜態方法建立幾個不一樣實現的線程池。
Executors.newCachedThreadPool();建立一個緩存線程池,當有任務的時候會檢查線程池中是否有空線程,如有就使用它,若沒有就建立新的線程。若長久沒有使用的線程會自動回收。
Executors.newFixedThreadPool(int threads);建立一個可重用的,具備固定線程數的線程池。
Executors.newScheduledExecutor();建立只有一條線程的線程池,它能夠在指定延遲後執行線程任務。
Executors.newSingleThreadExecutor();建立一個只有單線程的線程池,至關於Exceutors.newFixedThreadPool方法時傳入參數1。裏邊維護着一個任務隊列。
/** * 建立一個線程池,具備50個 */ ExecutorService threadPool =Executors.newFixedThreadPool(50); try { ServerSocket server = new ServerSocket(8088); System.out.println("啓動完畢,等待鏈接"); /** 將轉發消息線程啓動 */ SendMessageHandler sHandler = new SendMessageHandler(); Thread sendTh = new Thread(sHandler); sendTh.start(); while (true) { Socket client = server.accept(); System.out.println("與客戶端鏈接成功"+client.getInetAddress().getHostAddress()); Handler handler = new Handler(client);//交給線程去處理 /** * 將須要併發的任務(Runnable)交給線程池 * 讓其分配空線程去運行該任務 * 若線程都在工做,那麼登載,直到有空線程爲止。 */ threadPool.execute(handler); //Thread t = new Thread(handler); //t.start(); } } catch (IOException e) { }
四、雙端隊列
內部由兩個隊列實現,他們交替進行存取工做。這樣至少能夠保證2個線程同時進行存取操做。可是對於兩個線程同時進行存或者取,仍是要求同步的。
可是比單隊列實現線程安全仍是要快的。
BlockingDeque雙端隊列
實現:
1)ArrayBlockingDeque該類實現的構造方法要求咱們傳入一個整數,表明當前隊列的長度。因此這個是一個固定大小的雙端隊列,存取原則爲FIFO先入先出的原則。
當元素調用offer存入了隊列時,若隊列已滿,那麼能夠設置延時等待,當超過了延時等待會返回存入失敗。
2)LinkedBlockingDeque變長雙端隊列。長度不定,隨着元素數量而增長,最大能夠達到Integer.MAX_VALUE,重載構造方法能夠傳入一個整數,使之變爲一個定長的隊列。
3)PriorityBlockingDeque 這個和LinkedBlockingDeque類似,只不過是進去了天然排序後獲取。
4)SynchronousQueue特殊的雙端隊列 存取步驟有要求,必須存一次取一次,交替進行。
例:
服務端
/** * 建立一個靜態集合保護全部客戶端的數輸入流 * 注意:由於這個集合被多個線程使用,所喲一集合要是安全的。 */ static Vector<PrintWriter> allOut = new Vector<PrintWriter>(); /** 消息隊列 */ static BlockingDeque<String> msgQueue = new LinkedBlockingDeque<String>(); public static void main(String[] args) { /** * 建立一個線程池,具備50個 */ ExecutorService threadPool =Executors.newFixedThreadPool(50); try { ServerSocket server = new ServerSocket(8088); System.out.println("啓動完畢,等待鏈接"); /** 將轉發消息線程啓動 */ SendMessageHandler sHandler = new SendMessageHandler(); Thread sendTh = new Thread(sHandler); sendTh.start(); while (true) { Socket client = server.accept(); System.out.println("與客戶端鏈接成功"+client.getInetAddress().getHostAddress()); Handler handler = new Handler(client);//交給線程去處理 /** * 將須要併發的任務(Runnable)交給線程池 * 讓其分配空線程去運行該任務 * 若線程都在工做,那麼登載,直到有空線程爲止。 */ threadPool.execute(handler); //Thread t = new Thread(handler); //t.start(); } } catch (IOException e) { } } /** * 與客戶端通訊線程 * 負責與某個特定的socket的客戶端進行通訊 * 每一個線程實例負責一個客戶端的通訊 */ private static class Handler implements Runnable { /** * 當前線程要通訊的客戶端socket */ private Socket client; public Handler(Socket client) { this.client = client; } public void run(){ PrintWriter pw = null; try { /** 經過socket獲取客戶端信息 */ InputStream in = this.client.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); /** * 將當前客戶端的輸出流保存到共享集合 */ pw = new PrintWriter(client.getOutputStream()); allOut.add(pw); /** 死循環讀取客戶端信息 */ while (true) { String str = reader.readLine(); /** * 將信息放入到消息隊列 */ if("q".equals(str)){ client.close(); } msgQueue.offer(str); /** * arg1:存放的元素 * arg2:延時時間 * arg3:延時的時間單位 * * 下面方法是向隊列中存放元素,設置5秒超時,若超時了尚未放入隊列這返回false */ boolean tf = msgQueue.offer(str, 5, TimeUnit.SECONDS); } } catch (Exception e) { e.printStackTrace(); /** * 拋出異常,咱們應該將這個客戶端的輸出流從共享集合中刪除 * 告訴其餘線程不要再發信息了。 */ allOut.remove(pw); } } } /** * 轉發消息 * 循環消息隊列,將每個消息經過全部客戶端的輸入流發送給客戶端 * 作到廣播的效果。 */ public static class SendMessageHandler implements Runnable{ public void run() { while (true) { /** 循環將消息發送給每個客戶端 每50ms做一次*/ String str = null; while ((str=msgQueue.poll())!=null) {//and msgQueue.poll()!=null /** 獲取全部客戶端的輸出流,將字符串輸出 */ for (PrintWriter pw : allOut) { pw.println(str); pw.flush(); } } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } }
客戶端:
public static void main(String[] args) { /** * 建立客戶端Socket * Socket客戶端類 * 構造的時候就會根據給定的服務端ip地址和服務端的端口號嘗試鏈接 */ try { System.out.println("開始鏈接"); Socket socket = new Socket("172.16.3.14", 8088); System.out.println("與服務端鏈接成功!"); //啓動消息線程 Thread readerMsgTh = new Thread(new ReadMessageHandler(socket.getInputStream())); readerMsgTh.start(); OutputStream out = socket.getOutputStream(); PrintWriter pw = new PrintWriter(out); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); while (true) { pw.println(reader.readLine()); pw.flush(); } } catch (IOException e) { //e.printStackTrace(); } } /** * 該線程用於從服務器讀取信息。 */ public static class ReadMessageHandler implements Runnable{ private InputStream in;//從服務端讀取信息 public ReadMessageHandler(InputStream in) { this.in = in; } public void run() { try { //將字節輸入流轉換爲緩衝字符輸入流 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); while (true) { System.out.println(reader.readLine()); } } catch (Exception e) { e.printStackTrace(); } } }
http://www.cnblogs.com/springcsc/archive/2009/12/03/1616413.html這裏有一篇文章講的比較細,把地址記錄下來,供查詢,感謝做者。