傳輸控制協議
;是一種面向鏈接
的、可靠的
、基於字節流
的傳輸層通訊協議
,由 IETF 的 RFC 793 定義。
socket()
:建立一個客戶端 Socket。bind()
: 綁定一個 Socket 到一個本地地址和端口上。accept()
:服務器端接受一個新的鏈接。write()
:把數據寫入到 Socket 輸出流。read()
:從 Socket 輸入流中讀取數據。
三次握手:java
第一次握手
:客戶端發送帶有 SYN 標誌的鏈接請求報文段,而後進入 SYN_SEND 狀態,等待服務端確認。第二次握手
:服務端接受到客戶端的 SYN 報文段後,須要發送 ACK 信息對這個 SYN 報文段進行確認。同時,還要發送本身的 SYN 請求信息。服務端會將上述信息放到一個報文段(SYN+ACK 報文段)中,一併發送給客戶端,此時服務端進入 SYN_RECV 狀態。第三次握手
:客戶端接收到服務端的 SYN+ACK 報文段後,會向服務端發送 ACK 確認報文段,這個報文段發送完畢後,客戶端和服務端都進入 ESTABLEISHED 狀態,完成 TCP 三次握手。
四次揮手:算法
當被動方收到主動方的 FIN 報文通知時,它僅僅表示主動方沒有數據再發送給被動方了。但未必被動方全部的數據都完整的發送給了主動方,因此被動方不會立刻關閉 SOCKET,它可能還須要發送一些數據給主動方後,再發送 FIN 報文給主動方,告訴主動方贊成關閉鏈接,因此這裏的 ACK 報文和 FIN 報文多數狀況下都是分開發送的。服務器
原理:網絡
發送的數據包的二進制相加而後取反,目的是檢測數據在傳輸過程當中的任何變化。若是收到報文段的檢驗和有差錯,TCP 將丟棄這個報文段和不確認收到此報文段。併發
TCP 給發送的每個包進行編號,接收方對數據包進行排序,把有序數據傳送給應用層。異步
當TCP 發出一個段後,它啓動一個定時器,等待目的端確認收到這個報文段。若是不能及時收到一個確認,將重發這個報文段。socket
TCP 鏈接的每一方都有固定大小的緩衝空間,TCP 的接收端只容許發送端發送接收端緩衝區能接納的數據。當接收方來不及處理髮送方的數據,能提示發送方下降發送的速率,防止包丟失。TCP 使用的流量控制協議是可變大小的滑動窗口協議。ide
當網絡擁塞時,減小數據的發送。函數
應用數據被分割成 TCP 認爲最適合發送的數據塊。性能
TCP 的接收端會丟棄重複的數據。
排序、組裝流程
因爲數據傳輸的順序多是不必定的,因此在此中間會進行排序。
數據包丟失,進行重傳
鏈接中斷
當鏈接中斷後,須要進行重連、而後從新進行重傳。
一對多傳輸流程
public class Server { private static final int PORT = 20000; public static void main(String[] args) throws IOException { ServerSocket server = createServerSocket(); initServerSocket(server); // 綁定到本地端口上 server.bind(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 50); System.out.println("服務器準備就緒~"); System.out.println("服務器信息:" + server.getInetAddress() + " P:" + server.getLocalPort()); // 等待客戶端鏈接 for (; ; ) { // 獲得客戶端 Socket client = server.accept(); // 客戶端構建異步線程 ClientHandler clientHandler = new ClientHandler(client); // 啓動線程 clientHandler.start(); } } private static ServerSocket createServerSocket() throws IOException { // 建立基礎的ServerSocket ServerSocket serverSocket = new ServerSocket(); // 綁定到本地端口20000上,而且設置當前可容許等待連接的隊列爲50個 //serverSocket = new ServerSocket(PORT); // 等效於上面的方案,隊列設置爲50個 //serverSocket = new ServerSocket(PORT, 50); // 與上面等同 // serverSocket = new ServerSocket(PORT, 50, Inet4Address.getLocalHost()); return serverSocket; } private static void initServerSocket(ServerSocket serverSocket) throws IOException { // 是否複用未徹底關閉的地址端口 serverSocket.setReuseAddress(true); // 等效Socket#setReceiveBufferSize serverSocket.setReceiveBufferSize(64 * 1024 * 1024); // 設置serverSocket#accept超時時間 // serverSocket.setSoTimeout(2000); // 設置性能參數:短連接,延遲,帶寬的相對重要性 serverSocket.setPerformancePreferences(1, 1, 1); } /** * 客戶端消息處理 */ private static class ClientHandler extends Thread { private Socket socket; ClientHandler(Socket socket) { this.socket = socket; } @Override public void run() { super.run(); System.out.println("新客戶端鏈接:" + socket.getInetAddress() + " P:" + socket.getPort()); try { // 獲得套接字流 OutputStream outputStream = socket.getOutputStream(); InputStream inputStream = socket.getInputStream(); byte[] buffer = new byte[256]; int readCount = inputStream.read(buffer); ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, 0, readCount); // byte byte be = byteBuffer.get(); // char char c = byteBuffer.getChar(); // int int i = byteBuffer.getInt(); // bool boolean b = byteBuffer.get() == 1; // Long long l = byteBuffer.getLong(); // float float f = byteBuffer.getFloat(); // double double d = byteBuffer.getDouble(); // String int pos = byteBuffer.position(); String str = new String(buffer, pos, readCount - pos - 1); System.out.println("收到數量:" + readCount + " 數據:" + be + "\n" + c + "\n" + i + "\n" + b + "\n" + l + "\n" + f + "\n" + d + "\n" + str + "\n"); outputStream.write(buffer, 0, readCount); outputStream.close(); inputStream.close(); } catch (Exception e) { System.out.println("鏈接異常斷開"); } finally { // 鏈接關閉 try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("客戶端已退出:" + socket.getInetAddress() + " P:" + socket.getPort()); } } }
public class Client { private static final int PORT = 20000; private static final int LOCAL_PORT = 20001; public static void main(String[] args) throws IOException { Socket socket = createSocket(); initSocket(socket); // 連接到本地20000端口,超時時間3秒,超過則拋出超時異常 socket.connect(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 3000); System.out.println("已發起服務器鏈接,並進入後續流程~"); System.out.println("客戶端信息:" + socket.getLocalAddress() + " P:" + socket.getLocalPort()); System.out.println("服務器信息:" + socket.getInetAddress() + " P:" + socket.getPort()); try { // 發送接收數據 todo(socket); } catch (Exception e) { System.out.println("異常關閉"); } // 釋放資源 socket.close(); System.out.println("客戶端已退出~"); } private static Socket createSocket() throws IOException { /* // 無代理模式,等效於空構造函數 Socket socket = new Socket(Proxy.NO_PROXY); // 新建一份具備HTTP代理的套接字,傳輸數據將經過www.baidu.com:8080端口轉發 Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(Inet4Address.getByName("www.baidu.com"), 8800)); socket = new Socket(proxy); // 新建一個套接字,而且直接連接到本地20000的服務器上 socket = new Socket("localhost", PORT); // 新建一個套接字,而且直接連接到本地20000的服務器上 socket = new Socket(Inet4Address.getLocalHost(), PORT); // 新建一個套接字,而且直接連接到本地20000的服務器上,而且綁定到本地20001端口上 socket = new Socket("localhost", PORT, Inet4Address.getLocalHost(), LOCAL_PORT); socket = new Socket(Inet4Address.getLocalHost(), PORT, Inet4Address.getLocalHost(), LOCAL_PORT); */ Socket socket = new Socket(); // 綁定到本地20001端口 socket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), LOCAL_PORT)); return socket; } private static void initSocket(Socket socket) throws SocketException { // 設置讀取超時時間爲2秒 socket.setSoTimeout(2000); // 是否複用未徹底關閉的Socket地址,對於指定bind操做後的套接字有效 socket.setReuseAddress(true); // 是否開啓Nagle算法 socket.setTcpNoDelay(true); // 是否須要在長時無數據響應時發送確認數據(相似心跳包),時間大約爲2小時 socket.setKeepAlive(true); // 對於close關閉操做行爲進行怎樣的處理;默認爲false,0 // false、0:默認狀況,關閉時當即返回,底層系統接管輸出流,將緩衝區內的數據發送完成 // true、0:關閉時當即返回,緩衝區數據拋棄,直接發送RST結束命令到對方,並沒有需通過2MSL等待 // true、200:關閉時最長阻塞200毫秒,隨後按第二狀況處理 socket.setSoLinger(true, 20); // 是否讓緊急數據內斂,默認false;緊急數據經過 socket.sendUrgentData(1);發送 socket.setOOBInline(true); // 設置接收發送緩衝器大小 socket.setReceiveBufferSize(64 * 1024 * 1024); socket.setSendBufferSize(64 * 1024 * 1024); // 設置性能參數:短連接,延遲,帶寬的相對重要性 socket.setPerformancePreferences(1, 1, 0); } private static void todo(Socket client) throws IOException { // 獲得Socket輸出流 OutputStream outputStream = client.getOutputStream(); // 獲得Socket輸入流 InputStream inputStream = client.getInputStream(); byte[] buffer = new byte[256]; ByteBuffer byteBuffer = ByteBuffer.wrap(buffer); // byte byteBuffer.put((byte) 126); // char char c = 'a'; byteBuffer.putChar(c); // int int i = 2323123; byteBuffer.putInt(i); // bool boolean b = true; byteBuffer.put(b ? (byte) 1 : (byte) 0); // Long long l = 298789739; byteBuffer.putLong(l); // float float f = 12.345f; byteBuffer.putFloat(f); // double double d = 13.31241248782973; byteBuffer.putDouble(d); // String String str = "Hello你好!"; byteBuffer.put(str.getBytes()); // 發送到服務器 outputStream.write(buffer, 0, byteBuffer.position() + 1); // 接收服務器返回 int read = inputStream.read(buffer); System.out.println("收到數量:" + read); // 資源釋放 outputStream.close(); inputStream.close(); } }