TCP協議提供可靠的數據傳輸服務是經過創建TCP鏈接實現的。一條「TCP鏈接」鏈接的兩端是Internet上分別在兩臺主機運行的兩個進程,一個是發送進程,一個是接收進程,每一個進程用一個Socket(IP地址和端口)惟一肯定。一對Socket惟一標識一條TCP鏈接。TCP鏈接是全雙工和點對點的,全雙工指數據可雙向傳輸,點對點是指每條TCP鏈接只有兩個端點。
使用一對socket進行tcp鏈接,鏈接成功後,就能夠經過socket發送或接收字節流數據,完成點到點、全雙工的通訊。segmentfault
服務端經過建立指定端口號的ServerSocket
對象,調用accept()方法等待客戶端鏈接請求,等待期間當前進程處於堵塞狀態。當服務端接收到客戶端的鏈接請求時,進程繼續運行,並創建一條TCP鏈接,accept()完成並返回一個Socket對象,經過該Socket對象與客戶端的Socket對象實現實時的數據通訊。服務器
ServerSocket serverSocket = new ServerSocket(7000); Socket socket = serverSocket.accept();
客戶端建立Socket對象,指定服務器主機的IP和端口,發出TCP鏈接請求,待服務器接受鏈接請求後Socket對象建立成功,此時能夠經過此Socket對象與服務端Socket對象實時通訊。socket
Socket socket = new Socket("127.0.0.1", 7000);
從控制檯獲取輸入的字符串,字符串數據將從PrintWriter"流入"OutputStreamWriter在"流入"socket的OutputStream中,經過TCP創建的鏈路,流到與之對應的Socket的InputStream中。注意,Socket老是成對的。tcp
PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); message = scanner.nextLine(); writer.println(message); writer.flush();
在另外一端的Socket中,即可以經過InputStream把發送的數據讀取出來,此時數據的流向爲InputStream->InputStreamReader->BufferedReader->控制檯。spa
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = ""; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); }
整個的數據流向爲:客戶端控制檯->PrintWriter->OutputStreamWriter->客戶端Socket的OutputStream->TCP鏈路->服務端Socekt的InputStream->InputStreamReader->BufferedReader->服務端控制檯。經過socket即可以輕易的將數據從客戶端發送到服務端了。線程
瞭解了Socekt通訊後,咱們能夠使用Socket仿照QQ羣聊效果:當某個用戶發送消息時,全部用戶都接收到此消息並顯示到控制檯上。3d
客戶端須要兩個進程,一個進程不停的從控制檯中讀取數據,讀取到用戶輸入時即向服務端發送讀取到的數據,把它稱爲寫進程。另外一個進程接收服務端發送的數據,並把它顯示到控制檯上,稱爲讀進程。code
public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); Socket socket = new Socket("127.0.0.1", 7000); // 獲取輸入流和輸出流 PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 開啓兩個線程分別讀寫 // 寫線程 new Thread(() -> { // 循環從控制檯讀取數據 String message = ""; while (!message.equals("exit")) { message = scanner.nextLine(); writer.println(message); writer.flush(); } writer.close(); }).start(); // 讀線程 new Thread(() -> { // 不停從input流獲取數據 String line = ""; try { while ((line = bufferedReader.readLine()) != null) { System.out.format("%50s", line); System.out.println(); } bufferedReader.close(); } catch (IOException e) { } }).start(); }
爲了響應多個客戶端鏈接,須要在循環中不停的調用accept()方法,每當獲取到一個新TCP鏈接時,把獲取到的Socket對象存入set中,對每一個Socket對象都開啓一個線程,主要的任務是不停的從InputStream中讀取數據,即接收發送客戶端數據。獲取到數據後,再發送給全部已鏈接的客戶端Socket(除了發送數據的客戶端Socket),即遍歷set發送數據。orm
public class Server { public static int userCount = 0; public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(7000); Set<Socket> socketSet = new HashSet<>(); while (true) { Socket socket = serverSocket.accept(); socketSet.add(socket); userCount++; // 開啓一個新線程 Thread thread = new Thread(() -> { // 不停從input流獲取數據 BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String userName = Thread.currentThread().getName(); String line = ""; System.out.println("start Read"); while ((line = bufferedReader.readLine()) != null) { System.out.println(userName + "消息:" + line); for (Socket tem : socketSet) { if (!tem.equals(socket)) { PrintWriter writer = new PrintWriter(new OutputStreamWriter(tem.getOutputStream())); writer.println(line + ":" + userName); writer.flush(); } } } bufferedReader.close(); } catch (IOException e) { } System.out.println("finish Read"); }); thread.setName("用戶" + userCount); thread.start(); } } }
在終端運行的效果圖:
server