1.使用java實現服務器與客戶端之間的對話,客戶端與服務器java
服務器端代碼linux
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.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Chat { // 運行在服務端的Socket private ServerSocket server; // 線程池,用於管理客戶端鏈接的交互線程 private ExecutorService threadPool; // 保存全部客戶端輸出流的集合 private HashMap<String, PrintWriter> allout; /** * 構造方法,用於初始化服務端 * * @throws IOException */ public Chat() throws IOException { try { /* * 建立ServerSocket時須要指定服務端口 */ System.out.println("初始化服務端"); server = new ServerSocket(8088); // 初始化線程池 threadPool = Executors.newFixedThreadPool(50); // 初始化存放全部客戶端輸出流的集合 allout = new HashMap<String, PrintWriter>(); System.out.println("服務端初始化完畢"); } catch (IOException e) { e.printStackTrace(); throw e; } } /** * 服務端開始工做的方法 */ public void start() { try { /* * ServerSocket的accept方法 用於監聽8088端口,等待客戶端的鏈接 該方法是一個阻塞方法,直到一個 * 客戶端鏈接,不然該方法一直阻塞。 若一個客戶端鏈接了,會返回該客戶端的 Socket */ while (true) { System.out.println("等待客戶端鏈接..."); Socket socket = server.accept(); /* * 當一個客戶端鏈接後,啓動啓動一個線程 ClientHandler,將該客戶端的Socket 傳入,使得該線程與該 * 客戶端交互 這樣咱們能再次進入循環,接收下一個客戶端的鏈接 */ Runnable handler = new ClientHandler(socket); // Thread t = new Thread(handler) ; // t.start(); threadPool.execute(handler); } } catch (Exception e) { e.printStackTrace(); } } /** * 將給定的輸出流共享集合 * * @param p */ public synchronized void addout(String nickName, PrintWriter p) { allout.put(nickName, p); } /** * 將給定的輸出流從共享集合中刪除 * * @param p */ public synchronized void removeOut(String nickName) { allout.remove(nickName); } /** * 將給定的消息轉發給客戶端 * * @param message */ public synchronized void sendMessage(String message) { Set<Entry<String, PrintWriter>> set = allout.entrySet(); for (Entry<String, PrintWriter> e : set) { e.getValue().println(message); } } public static void main(String[] args) { Chat server; try { server = new Chat(); server.start(); } catch (IOException e) { e.printStackTrace(); System.out.println("服務端初始化失敗"); } } /* * 服務器中的一個線程,用於與某個客戶端 交互 使用線程的目的是使得服務端能夠處理多客戶端了。 */ class ClientHandler implements Runnable { // 當前線程處理的客戶端的Socket、 private Socket socket; // 當前客戶端的ip private String ip; // 當前客戶端的暱稱 private String nickname; public ClientHandler(Socket socket) { this.socket = socket; InetAddress address = socket.getInetAddress(); // 獲取遠端計算機的IP地址 ip = address.getHostAddress(); // address.getCanonicalHostName() // 獲取客戶端的端口號 int port = socket.getPort(); System.out.println(ip + ":" + port + " 客戶端鏈接了"); // 改成了使用暱稱了,因此不在這裏通知了 // //通知其餘用戶,該用戶上線了 // sendMessage("["+ip+"]上線了"); } /* * 該線程將當前Socket中的輸入流獲取 用來循環讀取客戶端發送過來的消息 (non-Javadoc) * * @see java.lang.Runnable#run() */ public void run() { /* * 定義在try語句外的目的是,爲了在finally中也能夠引用到 */ PrintWriter pw = null; try { /* * 爲了讓服務端與客戶端發送信息 咱們須要經過socket 獲取輸出流。 */ OutputStream out = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8"); pw = new PrintWriter(osw, true); /* * 將客戶端的輸出流存入共享集合 以即是的客戶端也能接收服務端轉發的消息 */ // addout(pw); // Scanner sc = new Scanner(System.in) ; // // System.out.println(sc.nextLine()); /* * 經過socket獲取遠端的地址信息 對於服務端而言,遠端就是客戶端了 */ /* * 經過剛剛連上的客戶端的Socket獲取 輸入流,來讀取客戶端發送過來的信息 */ InputStream in = socket.getInputStream(); /* * 將字節輸入流包裝爲字符輸出流,這樣 能夠指定編碼集來讀取每個字符 */ InputStreamReader isr = new InputStreamReader(in, "UTF-8"); /* * 將字符流轉換爲緩衝字符輸入流 這樣就能夠以行爲單位讀取字符串了 */ BufferedReader br = new BufferedReader(isr); /* * 當建立好當前客戶端的輸入流後 讀取的第一個字符串,應當是暱稱 */ nickname = br.readLine(); addout(nickname, pw); // 通知全部客戶端,當前用戶上線了 sendMessage("[" + nickname + "]上線了"); String message = null; // 讀取客戶端發送過來的一行字符串 /* * 讀取客戶端發送過來的信息這裏 windows與linux存在必定的差別: linux:當客戶端與服務端斷開鏈接後 * 咱們經過輸入流會讀取到null 但這是合乎邏輯的,由於緩衝流的 readLine()方法若返回null就 * 表示沒法經過該留再讀取到信息。 參考以前服務文本文件的判斷。 * * windows:當客戶端與服務端斷開鏈接後 readLine()方法會拋出異常。 */ while ((message = br.readLine()) != null) { message = hexiestr(message); // System.out.println( // "客戶端說:" + message); // pw.println("服務端說:"+message) ; /* * 當讀取客戶端發送過來的一條消息後,將該消息轉發給全部客戶端 */ // for (PrintWriter o : allout) { // o.println(message); // } String siliao = siliaostr(message); if (allout.containsKey(siliao)) { message = message.substring(message.indexOf(":") + 1); allout.get(siliao).println(nickname + "對你說:" + message); } else { sendMessage(nickname + "說:" + message); } } } catch (Exception e) { // 在windows中的客戶頓, // 報錯一般是由於客戶端斷開了鏈接 } finally { /* * 首先將該客戶端的輸出流從共享集合中刪除 */ removeOut(nickname); // 輸出當前在線人數 System.out.println("當前在線人數爲:" + allout.size()); // 通知其餘用戶,該用戶下線了 sendMessage("[" + nickname + "]下線了"); /* * 不管是Linux 用戶仍是windows用戶,當予服務端斷開鏈接後、、、 咱們都應該在服務端也與客戶端斷開鏈接 */ try { socket.close(); } catch (IOException e) { e.printStackTrace(); System.out.println("一個客戶端下線了.."); } } } } public String siliaostr(String str) { int fir = str.indexOf("\\") + 1; int las = str.indexOf(":"); if (fir <= 0 || las <= 0) { return str; } else { String sub = str.substring(fir, las); return sub; } } public String hexiestr(String str) { String s = str.replaceAll("(sb|cao|ca)", "**"); return s; } }
客戶端代碼c++
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.util.Scanner; public class Client { //Socket,用於鏈接服務端的ServerSocket private Socket socket; private Scanner scanner; /* * 客戶端構造方法,用於初始化客戶端 */ public Client() throws IOException{ try { /* * 建立Socket對象時, * 就會嘗試根據給定的地址與端口鏈接服務端。 * 因此,若該對象建立成功,說明與服務端鏈接正常。 */ System.out.println("正在鏈接服務端。。"); socket=new Socket("127.0.0.1",8088); System.out.println("成功鏈接服務端"); }catch (IOException e) { throw e; } } /* * 客戶端啓動方法 */ public void start(){ try { //建立並啓動線程,來接收服務端的消息 Runnable runn=new GetServerInfoHandler(); Thread t=new Thread(runn); t.start(); /* * 能夠經過Socket的getOutputStream() * 方法得到一條輸出流,用於將信息發送至服務端 */ OutputStream out=socket.getOutputStream(); /* * 使用字符流來根據指定的編碼集將字符串轉換爲字節後, * 在經過out發送給服務端 */ OutputStreamWriter osw=new OutputStreamWriter(out,"UTF-8"); /* * 將字符流包裝爲緩衝字符流,就能夠按行爲單位寫出字符串 */ PrintWriter pw=new PrintWriter(osw,true); /* * 建立一個Scanner,用於接收用戶輸入的字符串 */ scanner = new Scanner(System.in); //輸出歡迎用語 System.out.println("歡迎來到Linus聊天室"); while(true){ //首先輸入暱稱 System.out.println("請輸入暱稱"); String nickname=scanner.nextLine(); if(nickname.trim().length()>0){ pw.println(nickname); break; } System.out.println("暱稱不能爲空"); } while(true){ String str=scanner.nextLine(); pw.println(str); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String [] args){ try { Client client=new Client(); client.start(); } catch (Exception e) { e.printStackTrace(); System.out.println("客戶端初始化失敗"); } } /* * 該線程的做用是循環接受服務端發送過來的信息,並輸出待控制檯 */ class GetServerInfoHandler implements Runnable{ public void run() { try { /* * 經過Socket獲取輸入流 */ InputStream in=socket.getInputStream(); //將輸入流轉化爲字符輸入流,指定編碼 InputStreamReader isr=new InputStreamReader(in,"UTF-8"); //將字符輸入流轉換爲緩衝流 BufferedReader br=new BufferedReader(isr); String message=null; //循環讀取服務端發送的每個字符串 while((message=br.readLine())!=null){ //將服務端發送的字符串輸出到控制檯 System.out.println(message); } } catch (Exception e) { } } } }
程序執行結果以下:windows
2.分析socket大體系統調用流程api
(1)創建一個服務器ServerSocket,並同時定義好ServerSocket的監聽端口;
(2)ServerSocket 調用accept()方法,使之處於阻塞。
(3)建立一個客戶機Socket,並設置好服務器的IP和端口。
(4)客戶機發出鏈接請求,創建鏈接。
(5)分別取得服務器和客戶端ServerSocket 和Socket的InputStream和OutputStream.
(6) 利用Socket和ServerSocket進行數據通訊。服務器
其運行抽象圖和運行流程圖以下:socket
3.java中socket中api與linux中socket的api的對比this
new SeverSocket()與Linux中socket()bind()listen()的功能同樣編碼
SeverSocket.accept()與 Linux中accept()的功能同樣spa
in.readLine()與 Linux中recv()的功能同樣
java中經過調用本地方法來使用一些操做系統底層的功能,一般使用的都是c或c++代碼來實現的,具備跨平臺做用;linux中的socket API則只能在其系統上使用