絡(luò)原理+代碼實(shí)戰(zhàn):Java Socket編程從零到一》)
計(jì)算機(jī)網(wǎng)絡(luò)的前世今生隨著時(shí)代的發(fā)展越來越需要計(jì)算機(jī)之間互相通信共享軟件數(shù)據(jù)即以多個(gè)計(jì)算機(jī)互聯(lián)完成業(yè)務(wù)就有了網(wǎng)絡(luò)互聯(lián)數(shù)據(jù)共享的本質(zhì)是網(wǎng)絡(luò)運(yùn)輸計(jì)算機(jī)之間通過網(wǎng)絡(luò)來傳輸數(shù)據(jù)也稱為網(wǎng)絡(luò)通信根據(jù)網(wǎng)絡(luò)互聯(lián)的規(guī)模不同分為局域網(wǎng)和廣域網(wǎng)1.局域網(wǎng)與廣域網(wǎng)局域網(wǎng)內(nèi)的主機(jī)之間能方便的進(jìn)行網(wǎng)絡(luò)通信又稱內(nèi)網(wǎng)。局域網(wǎng)組建網(wǎng)絡(luò)的方式有以下幾種:基于網(wǎng)線直連基于集線器組建基于交換機(jī)組建基于交換機(jī)和路由器組建通過路由器將多個(gè)局域網(wǎng)連接起來在物理上組成了很大范圍的網(wǎng)絡(luò)。廣域網(wǎng)內(nèi)部的局域網(wǎng)都屬于其子網(wǎng)。網(wǎng)絡(luò)通信基礎(chǔ)IP與端口網(wǎng)絡(luò)世界的“門牌號(hào)”與“收貨人”簡(jiǎn)單來說IP地址是用來定義主機(jī)的網(wǎng)絡(luò)地址。相當(dāng)于發(fā)送快遞的地址。格式是一個(gè)32位的二進(jìn)制數(shù)通常用“點(diǎn)分十進(jìn)制”的方式表示即a.b.c.dabcd都是0~255之間的十進(jìn)制。端口號(hào)是用來定義主機(jī)中的進(jìn)程相當(dāng)于收貨人協(xié)議協(xié)議是所有網(wǎng)絡(luò)設(shè)備都必須遵守的一組約定最終體現(xiàn)為數(shù)據(jù)包的格式。因此進(jìn)行一次網(wǎng)絡(luò)通信需要涉及到五個(gè)非常關(guān)鍵的信息------五元組源IP源端口號(hào)目的IP目的端口號(hào)協(xié)議號(hào)協(xié)議分層當(dāng)我們?cè)O(shè)計(jì)完一個(gè)協(xié)議時(shí)會(huì)使得這個(gè)協(xié)議非常復(fù)雜非常強(qiáng)大。把一個(gè)大的協(xié)議拆分成若干個(gè)小的協(xié)議就是協(xié)議分層。分層。只有相鄰兩層協(xié)議之間可以互相交互。上層協(xié)議可以調(diào)用下層協(xié)議下層協(xié)議可以給上層協(xié)議提供服務(wù)。即協(xié)議之間的交互不能跨層進(jìn)行OSI七層模型OSI七層模型是一個(gè)邏輯上的定義和規(guī)范把網(wǎng)絡(luò)協(xié)議在邏輯上劃分為7層。由于體系太復(fù)雜了實(shí)際真實(shí)的網(wǎng)絡(luò)分層方式是更簡(jiǎn)化的TCP/IP五層或四層協(xié)議模型TCP/IP五層/四層協(xié)議模型應(yīng)用層負(fù)責(zé)應(yīng)用程序之間的溝通傳輸層兩個(gè)任意設(shè)備之間進(jìn)行通信只考慮起點(diǎn)和終點(diǎn)網(wǎng)絡(luò)層兩個(gè)任意設(shè)備之間進(jìn)行通信考慮中間過程數(shù)據(jù)鏈路層兩個(gè)相鄰設(shè)備之間通信物理層規(guī)定了網(wǎng)絡(luò)通信中一些硬件設(shè)施符合的要求網(wǎng)絡(luò)設(shè)備所在的分層對(duì)于一臺(tái)主機(jī)操作系統(tǒng)實(shí)現(xiàn)了從物理層到應(yīng)用層也就是TCP/IP五層模型的下四層對(duì)于一臺(tái)路由器工作過程涉及到從物理層到網(wǎng)絡(luò)層對(duì)于交換機(jī)工作過程涉及到從物理層到數(shù)據(jù)鏈路層這是網(wǎng)絡(luò)分層對(duì)應(yīng)的協(xié)議類型網(wǎng)絡(luò)編程即網(wǎng)上的主機(jī)以編程的形式進(jìn)行網(wǎng)絡(luò)通信。對(duì)于應(yīng)用層來說傳輸層給應(yīng)用層提供的一組api是socket api。這也是傳輸層的兩大協(xié)議。傳輸層雙雄TCP與UDP對(duì)比傳輸層的兩個(gè)核心協(xié)議TCP有連接可靠傳輸面向字節(jié)流全雙工UDP無連接不可靠傳輸面向數(shù)據(jù)報(bào)全雙工UDP數(shù)據(jù)報(bào)套接字主要有兩大類DatagramSocket和DatagramPacket。通過下方的回顯服務(wù)器來體會(huì)一下~public class UdpEchoServer { //首先需要一個(gè)“管道” private DatagramSocket socketnull; public UdpEchoServer(int port) throws SocketException { //因?yàn)榉?wù)器相當(dāng)于110啥的“公共電話”他需要有一個(gè)專門的端口號(hào)。 socketnew DatagramSocket(port); } public void start() throws IOException { System.out.println(服務(wù)器啟動(dòng)); while(true){ //1.讀取請(qǐng)求并分析 DatagramPacket requestPacketnew DatagramPacket(new byte[4096],4096); socket.receive(requestPacket); //解析,把0和1解析成能看得懂的(要全部解析從0到最后) String requestnew String(requestPacket.getData(),0, requestPacket.getLength()); //2.計(jì)算響應(yīng) String responseprocess(request); //3.把響應(yīng)返回給客戶端,順便轉(zhuǎn)化為0和1(相當(dāng)于警察找人民不知道人民在哪需要地址) DatagramPacket responsePacketnew DatagramPacket(response.getBytes(),response.getBytes().length, requestPacket.getSocketAddress()); socket.send(responsePacket); //4.打印一個(gè)日志 System.out.printf([%s:%d] req: %s, resp: %s\n, requestPacket.getAddress().toString(), requestPacket.getPort(), request, response); } } private String process(String request) { return request; } public static void main(String[] args) throws IOException { UdpEchoServer server new UdpEchoServer(9090); server.start(); } }public class UdpEchoClient { private DatagramSocket socketnull; //因?yàn)榭蛻舳耸请S機(jī)的但每個(gè)客戶端都需要訪問目標(biāo)地址于是把服務(wù)器的地址和端口設(shè)置為成員變量 private String serverIp; private int serverPort; public UdpEchoClient(String serverIp,int serverPort) throws SocketException { this.serverIp serverIp; this.serverPort serverPort; socketnew DatagramSocket(); } public void start() throws IOException { Scanner scannernew Scanner(System.in); while(true){ //1.從控制臺(tái)讀取用戶的信息 System.out.println(請(qǐng)輸入你要發(fā)送的內(nèi)容); if(!scanner.hasNext()){ break; } String requestscanner.next(); //2.把請(qǐng)求發(fā)送給服務(wù)器(不僅要把內(nèi)容翻譯成機(jī)器能看懂的語言getBytes也要把地址Ip翻譯成機(jī)器能看得懂的InetAddress.getByName(serverIp),serverPort) DatagramPacket requestPacketnew DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIp),serverPort); socket.send(requestPacket); //3.等待接收服務(wù)器傳回來的響應(yīng) DatagramPacket responsePacketnew DatagramPacket(new byte[4096],4096); socket.receive(responsePacket); //4.解析一下看看發(fā)的是啥 String responsenew String(responsePacket.getData(),0, responsePacket.getLength()); System.out.println(response); } } public static void main(String[] args) throws IOException { UdpEchoClient clientnew UdpEchoClient(127.0.0.1,9090); client.start(); } }TCP流套接字也有兩大類ServerSocket和Socket。ServerSocket 主要是創(chuàng)建TCP服務(wù)端的而Socket是為客戶端提供的public class TcpEchoServer{ private ServerSocket serverSocketnull; public TcpEchoServer(int port) throws IOException { serverSocketnew ServerSocket(port); } public void start() throws IOException { System.out.println(啟動(dòng)服務(wù)器); ExecutorService executorServiceExecutors.newCachedThreadPool(); while(true){ //對(duì)于tcp來說需要先處理與服務(wù)器的鏈接 //通過讀寫clientSocket來和客戶端進(jìn)行通信 /** * accept是接收客戶端發(fā)來的也就建立鏈接了如果沒有accept就會(huì)堵塞 */ while(true){ Socket clientSocketserverSocket.accept(); executorService.submit(()-{ processConnection(clientSocket); }); } } } //處理一個(gè)客戶端的鏈接可能包含多個(gè)請(qǐng)求 private void processConnection(Socket clientSocket) { System.out.printf([%s:%d] 客戶端上線!\n, clientSocket.getInetAddress(), clientSocket.getPort()); try(InputStream inputStreamclientSocket.getInputStream(); OutputStream outputStreamclientSocket.getOutputStream()){ /** * PrintWriter 是“打包快遞的人”把你寫的字符串打包成字節(jié)發(fā)到網(wǎng)絡(luò)上(發(fā)送) */ Scanner scannernew Scanner(inputStream); PrintWriter writernew PrintWriter(outputStream); while(true){ //1.讀取請(qǐng)求并解析 /** * byte[] requestnew byte[1024];inputStream.read(request);這個(gè)相當(dāng)于提前準(zhǔn)備一個(gè)容器接住“流” 也可以接住scanner來完成 */ if(!scanner.hasNext()){ System.out.printf([%s:%d] 客戶端下線!\n, clientSocket.getInetAddress(), clientSocket.getPort()); break; } String request scanner.next(); //2.根據(jù)請(qǐng)求計(jì)算響應(yīng) String responseprocess(request); //3.返回響應(yīng)到客戶端 writer.println(response); /** * flush() 的作用就是強(qiáng)制把緩沖區(qū)里的數(shù)據(jù)立刻推出去不要等 */ writer.flush(); // 打印日志 System.out.printf([%s:%d] req: %s, resp: %s\n, clientSocket.getInetAddress(), clientSocket.getPort(), request, response); } } catch (IOException e) { throw new RuntimeException(e); } } private String process(String request) { return request; } public static void main(String[] args) throws IOException { TcpEchoServer server new TcpEchoServer(9090); server.start(); } }public class TcpEchoClient { /** * UDP 不需要連接目標(biāo)無連接所以構(gòu)造時(shí)不需要目標(biāo)地址 * TCP 是有連接的所以它在構(gòu)造方法里就撥號(hào)連上了目標(biāo)必須傳 IP 和端口 */ private Socket socketnull; public TcpEchoClient(String serverIp,int serverPort) throws IOException { /** * 這一步就相當(dāng)于是客戶端的鏈接 */ socketnew Socket(serverIp,serverPort); } public void start(){ Scanner scannernew Scanner(System.in); try(InputStream inputStreamsocket.getInputStream(); OutputStream outputStreamsocket.getOutputStream()) { Scanner scanner1new Scanner(inputStream); PrintWriter writenew PrintWriter(outputStream); while(true){ //1.從控制臺(tái)讀取用戶輸入 String requestscanner.next(); //2.發(fā)送給服務(wù)器 write.println(request); // 加上刷新緩沖區(qū)操作, 才是真正發(fā)送數(shù)據(jù) write.flush(); //3.讀取服務(wù)器返回的響應(yīng) String responsescanner1.next(); //4.打印到控制臺(tái) System.out.println(response); } } catch (IOException e) { throw new RuntimeException(e); } } public static void main(String[] args) throws IOException { TcpEchoClient client new TcpEchoClient(127.0.0.1, 9090); client.start(); } }