TCP通訊
server:package day07;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.ServerSocket;import java.net.Socket;/** * 服務端應用程式 * @author Administrator * */public class Server { //運行在服務端的Socket private ServerSocket server; /** * 構造方法,用於初始化服務端 * @throws IOException */ public Server() throws IOException{ try { /* * 建立ServerSocket時需要指定服務端連接埠 */ System.out.println("初始化服務端"); server = new ServerSocket(8088); System.out.println("服務端初始化完畢"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); //沒有建立成功。拋出異常 throw e; } } /** * 服務端開始工作的方法 */ public void start(){ try{ /* * ServerSocket的accept方法 * 用於監聽8088連接埠,等待用戶端的連結 * 該方法是一個阻塞方法,直到一個用戶端連結,否則該方法一直阻塞。 * 若一個用戶端連結了,會返回該用戶端的Socket * */ System.out.println("等待用戶端串連......"); Socket socket=server.accept(); System.out.println("用戶端串連了"); //接收來自用戶端的訊息 //InputStream輸入資料流 InputStream is = socket.getInputStream(); //變成字元流。。。 InputStreamReader isr = new InputStreamReader(is); //將字元流轉換為緩衝流輸入資料流,這樣就可以按行為單位讀取字串了 BufferedReader br = new BufferedReader(isr); String message; while((message=br.readLine())!=null){ System.out.println("用戶端:"+"IP:"+socket.getInetAddress()+":"+socket.getPort()+"::"+message); } //若readLine返回null就表示無法再讀取到資訊 //windows:當用戶端與服務端中斷連線後readLine()方法會拋出異常 }catch(Exception e){ e.printStackTrace(); } } public static void main(String[] args){ Server server = null; try { server = new Server(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println("服務端初始化失敗。"); } server.start(); }}
client:package day07;import java.io.IOException;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.Socket;import java.net.UnknownHostException;import java.util.Scanner;public class Client {//Socket,用於串連服務端的ServerSocket private Socket socket; //構造方法,初始化client public Client() { try { /* * 建立Socket對象時,就會嘗試根據給定的地址與連接埠串連伺服器。所以, * 若該對象建立成功,說明與服務端串連正常 */ socket = new Socket("127.0.0.1",8088); System.out.println("成功串連服務端"); } catch (UnknownHostException e) { //連接埠號碼異常 e.printStackTrace(); } catch (IOException e) { System.out.println("用戶端建立失敗"); e.printStackTrace(); } } //用戶端啟動方法 public void start(){ try{ /* * 可以通過Socket的getOutputStream() * 方法擷取一條輸出資料流,使用者與資訊發送至服務端 * 是一個位元組流, */ OutputStream out = socket.getOutputStream(); //轉換成字元流,寫更容易些。 OutputStreamWriter osw = new OutputStreamWriter(out); //在封裝上緩衝流 以行為單位寫字串 PrintWriter pw = new PrintWriter(osw); while(true){ Scanner scanner = new Scanner(System.in); pw.println(scanner.next()); //不寫的話就不會顯示,因為是緩衝流。並沒有輸出。自己flush一下。 pw.flush(); } }catch(Exception e){ e.printStackTrace(); } } public static void main(String[] args) { Client client = new Client(); client.start(); }}小結:
建立一個類,然後定義個socket,作為server和client,開始只是定義了是Socket類型,並沒有初始化。實現建構函式,server的建構函式寫的是連接埠,server = new ServerSocket();ip就是運行在哪個上面就是哪個了。client是傳入的伺服器的ip和連接埠 socket = new Socket(ip,連接埠)
然後都要建立個start函數用作sockt串連後的功能。
server要等待串連; accept 調用accpet是在start中的。
Socket socket=server.accept();
建立的socket是 串連用戶端的 socket
然後在主函數中建立個 server
server = new Server();調用 start函數
然後是在start中寫如何傳輸字元。socket有個getOutputStream方法,但是返回的是位元組流,那麼就要轉成字元流 就是 OutputStreamWriter ,然後在加個緩衝流,一行一行的讀寫操作 PrintWriter
server: getInputStream InputStreamReader BufferedReader
clent: getOutputStream outputStreamWriter
PrintWriter
讀取操作,若readLine返回的是null那麼就是沒有資訊在讀了。
要注意 flush() 多線程服務端
package day07;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.ServerSocket;import java.net.Socket;/** * java多線程socket * @author Administrator * */public class ThreadServer2 { private ServerSocket server; public ThreadServer2(){ try { server = new ServerSocket(8088); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void start(){ while(true){ System.out.println("伺服器等待串連....."); try { Socket socket = server.accept(); //調用線程,傳入socket hthread ht1 = new hthread(socket); ht1.start(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }//catch }//while }//start public static void main(String[] args) { //直接建立啟動伺服器 new ThreadServer2().start(); } //線程類,在start中調用線程,並傳入socket class hthread extends Thread { private Socket socket; public hthread (Socket socket){ this.socket = socket; System.out.println("用戶端串連成功。"); } public void run(){ try { //接收資料 InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String message; while((message = br.readLine())!=null){ System.out.println("用戶端:IP:"+socket.getInetAddress()+" PORT"+socket.getPort()+": "+message); }//while } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }} } }}用Runnable,然後用線程池:
package day07;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.ServerSocket;import java.net.Socket;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import day07.ThreadServer2.hthread;public class ThreadciServer { private ServerSocket server; private ExecutorService threadPool; public ThreadciServer(){ try { server = new ServerSocket(8088); //建立線程池,最多運行50個 threadPool =Executors.newFixedThreadPool(2); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void start(){ while(true){ System.out.println("伺服器等待串連....."); try { Socket socket = server.accept(); //調用線程,傳入socket Runnable ht1 = new hthread(socket); threadPool.execute(ht1); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }//catch }//while }//startpublic static void main(String[] args) { //直接建立啟動伺服器 new ThreadServer2().start();}//線程類,在start中調用線程,並傳入socketclass hthread implements Runnable { private Socket socket; public hthread (Socket socket){ this.socket = socket; System.out.println("用戶端串連成功。"); } public void run(){ try { //接收資料 InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String message; while((message = br.readLine())!=null){ System.out.println("用戶端:IP:"+socket.getInetAddress()+" PORT"+socket.getPort()+": "+message); }//while } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }} System.out.println("一個用戶端下線了...."); }}}又小結:
返回的,server的直接就定義輸出資料流,然後在run中原來是把資訊輸出在顯示屏,直接就pw.println(); 發送過去。
然後在client就複雜些。不是只用戶端發,然後伺服器收,在返回給用戶端,要無論用戶端是否發,有資訊就發給用戶端,那麼就要定義個輸入資料流。但是單獨給他建立個線程,收和發不是同步的。線程就是和伺服器的接收一樣的。在start方法中調用建立線程。
server:package day07;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import day07.ThreadciServer.hthread;public class Server_fanui { private ServerSocket server; private ExecutorService threadPool; public Server_fanui(){ try { server = new ServerSocket(8088); //建立線程池,最多運行50個 threadPool =Executors.newFixedThreadPool(50); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void start(){ while(true){ System.out.println("伺服器等待串連....."); try { Socket socket = server.accept(); //調用線程,傳入socket Runnable ht1 = new hthread(socket); threadPool.execute(ht1); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }//catch }//while }//startpublic static void main(String[] args) { //直接建立啟動伺服器 new ThreadServer2().start();}//線程類,在start中調用線程,並傳入socketclass hthread implements Runnable { private Socket socket; public hthread (Socket socket){ this.socket = socket; System.out.println("用戶端串連成功。"); } public void run(){ try { OutputStream os = socket.getOutputStream(); OutputStreamWriter osr = new OutputStreamWriter(os); PrintWriter pw = new PrintWriter(osr,true); //接收資料 InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String message; while((message = br.readLine())!=null){ // System.out.println("用戶端:IP:"+socket.getInetAddress()+" PORT"+socket.getPort()+": "+message); pw.println(message); }//while } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }} System.out.println("一個用戶端下線了...."); }}}
client:package day07;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.Socket;import java.net.UnknownHostException;import java.util.Scanner;import day07.Client.Getserver;public class Client_fanhui { //Socket,用於串連服務端的ServerSocket private Socket socket; //構造方法,初始化client public Client_fanhui() { try { /* * 建立Socket對象時,就會嘗試根據給定的地址與連接埠串連伺服器。所以, * 若該對象建立成功,說明與服務端串連正常 */ socket = new Socket("127.0.0.1",8088); System.out.println("成功串連服務端"); } catch (UnknownHostException e) { //連接埠號碼異常 e.printStackTrace(); } catch (IOException e) { System.out.println("用戶端建立失敗"); e.printStackTrace(); } } //用戶端啟動方法 public void start(){ try{ Runnable rserver = new Getserver(); Thread tserver = new Thread(rserver); tserver.start(); /* * 可以通過Socket的getOutputStream() * 方法擷取一條輸出資料流,使用者與資訊發送至服務端 * 是一個位元組流, */ OutputStream out = socket.getOutputStream(); //轉換成字元流,寫更容易些。 OutputStreamWriter osw = new OutputStreamWriter(out); //在封裝上緩衝流 以行為單位寫字串 PrintWriter pw = new PrintWriter(osw,true); while(true){ Scanner scanner = new Scanner(System.in); pw.println(scanner.next()); //不寫的話就不會顯示,因為是緩衝流。並沒有輸出。自己flush一下。 } }catch(Exception e){ e.printStackTrace(); } } public static void main(String[] args) { Client client = new Client(); client.start(); } //建立個線程,一個是原來的向伺服器發資料,這個是向伺服器接收資料。 class Getserver implements Runnable{ @Override public void run() { try{ System.out.println("6666"); InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String message=null; while((message=br.readLine())!=null){ System.out.println("伺服器說:"+message); } }catch(Exception e){ e.printStackTrace(); } }//run }//class_getserver }若建立個多人同時線上聊天的方法
把別的client即在伺服器是不同的線程的如何發給別的建立一個公用的list,來儲存所有用戶端輸出資料流。list中放的是流。所以定義的時候 List,在建構函式中初始化。
將該用戶端的輸出存在在共用集合,以便使得該用戶端也能接受伺服器轉寄的訊息
append(pw)
集合中存的是流。。。不是流裡面的字串。。。。。一個client對應一個list中的流。。。。
如果client斷開了就要把他在list中對應的流也刪除。remove(pw)
統計當前線上人數。。。list的size就是
將一個用戶端發來得訊息,變數list 中所有流,都發過去。實現群聊
但是會有多線程同時操作 共用集合list 。。。 安全執行緒問題
遍曆這個集合,是迭代器,不是安全執行緒的,遍曆與add和remove不是同步的。
要保證遍曆的時候也是同步的,三個都是同步的。
要是互斥的。那就同步鎖 也叫互斥鎖。
我們調用靜態方法的時候,不需要先產生一個執行個體,可以通過類名直接調用。
靜態方法和靜態屬性為所有執行個體共用
因為多個線程操作一個list會造成線程不安全的問題(增加和刪除,遍曆沒事)
用synchronized
若兩個線程看到的是一個synchronized 是同步鎖,不能同時訪問
但是這種就是互斥,不同的地方鎖住同一個對象,就是互斥鎖了
可以把輸出資料流放入map中,給每個輸出資料流定義個名稱,
然後可以根據@暱稱,然後去map尋找就去單獨輸出,否則就是全部輸出 Udp通訊
不可靠的傳輸協議
TCP串連上用的流。連續輸出互相寫
但是UDP不是保持長串連,發完就不管了
不需要等待對方,消耗少
主要學習的就是打包和拆包 ,打包發送過去,接收到後拆包
DatagramPacket
構建接收包
構造發送包
服務端接收
DatagramSocket
執行個體化的時候給一個連接埠號碼,還是服務端的連接埠要固定
receive接收資料
用戶端發送
兩端都是DatagramSocket
server:package day07;import java.net.DatagramPacket;import java.net.DatagramSocket;/** * UDP服務端 * @author Administrator * */public class UdpServer { public void start(){ try{ /* * 接收包的步驟 * 建立socket * 建立一個合適大小的包 * 通過socket接收資料到包中 * 拆包取資料 */ //服務端建立socket的時候要傳參數連接埠 DatagramSocket server = new DatagramSocket(8088);