網絡編程套接字(socket api)
了解了網絡的一些概念,接下來就要進行網絡中的跨主機通信,了解網絡中的一些API,這里談到的API都是針對傳輸層進行的,這是因為我們編寫的代碼是在應用層,而傳輸層就是到了計算機內核中操作系統中已經實現好了的,編寫網絡編程代碼就是調用系統API,將數據傳輸到傳輸層,傳輸層->物理層就由操作系統進行。
將應用層數據傳輸到傳輸層的這些API就叫做socket api(網絡編程套接字),要將數據傳輸到傳輸層就要了解傳輸層中涉及到的協議:TCP,UDP,這兩個協議涉及到兩組不同的socket api,要分別進行討論。
TCP:有連接,可靠傳輸,面向字節流,全雙工。
UDP:無連接,不可靠傳輸,面向數據包,全雙工。
連接:有無連接就是通信雙方是否保存對方信息。
可靠/不可靠傳輸:可靠傳輸就是會在發出數據報后盡可能的保證能到達對端,不可靠就是在發出數據包后不管了。
全雙工:一個信道,可以雙向傳輸。
UDP socket
DatagramSocket
網卡:網卡等硬件設備就是通過文件進行封裝的,通過網絡發送數據,就是往網卡里接收數據;通過網絡接收信息,就是往網卡里讀取數據,封裝網卡的文件,就是socket文件。
而創建一個DatagramSocket對象就是在操作系統中創建一個socket文件,通過這個對象寫入數據,就是通過網卡發送數據;通過對象讀取數據,就是通過網卡接收數據。
DatagramSocket中的一些方法receive(接收一個數據報),send(發送一個數據報),close。
DatagramPacket
datagrampacket代表著一個UDP中傳輸數據的基本單位,也就是一個數據報。
編寫一個簡單的網絡程序
一個網絡程序一般是由客戶端和服務器構成的,這里只考慮一個客戶端和一個服務器,并且服務器返回的響應就是請求的回顯服務器(echo server)。
客戶端(cilent):
1.從控制臺讀取請求。
?2.通過網絡將請求發送給服務器。
3.從服務器讀取返回的結果。
4.將返回的結果顯示到控制臺
服務器(server):
1.接收客戶端發來的請求。
2.根據請求處理響應。
3.將響應發送回客戶端。
代碼實現
服務器編寫思路:
首先要創建一個socket文件對象(DatagramSocket),接下來就要通過網卡進行接收請求,那么就要創建一個數據報對象DatagramPacket,將網卡里的數據讀寫到數據報中,再對數據報中的進行內容進行解析。
public class Server {private DatagramSocket datagramSocket = null;public Server(int port) throws SocketException {//這里添加的port指的是服務器的端口號,源端口號和目的端口號是相對的//要指定的是空閑的端口,如果指定已經使用過的端口,就會拋出異常datagramSocket = new DatagramSocket(port);}public void start() throws IOException {//啟動服務器//1.接送客戶端請求//1.1 創建一個空的數據報DatagramPacket datagramPacket = new DatagramPacket(new byte[4069],4069);//1.2 將請求讀到一個空的數據報中,receive方法會將網卡內容讀到數據報中,當沒讀到東西時就會阻塞datagramSocket.receive(datagramPacket);//1.3 將數據報中二進制的內容轉換成字符串String request = new String(datagramPacket.getData(),0,datagramPacket.getLength());//2.根據請求處理響應String torequest = prcess(request);//3.將響應返回給客戶端//3.1 將響應構造成數據報,但是UDP是無連接,所以需要將客戶端的信息保存到數據報中DatagramPacket datagramPacket1 = new DatagramPacket(torequest.getBytes(),torequest.getBytes().length,datagramPacket.getSocketAddress());datagramSocket.send(datagramPacket1);}private String prcess(String request) {return request;}}
客戶端實現思路:
客戶端不用知道自己的端口,需要知道服務器的IP和端口。再將輸入的內容打包成一個數據報,發送給服務器,再將響應的數據報解析成字符串,最后打印。
public class Cilent {private String ServerIP;private int ServerPort;private DatagramSocket datagramSocket = null;public Cilent(String serverIP, int serverPort) throws SocketException {ServerIP = serverIP;ServerPort = serverPort;datagramSocket = new DatagramSocket();}public void start() throws IOException {Scanner scanner = new Scanner(System.in);//1. 從控制臺上讀取請求String re = scanner.next();//2. 將請求發送給服務器DatagramPacket datagramPacket = new DatagramPacket(re.getBytes(),re.getBytes().length,InetAddress.getByName(ServerIP),ServerPort);datagramSocket.send(datagramPacket);//3. 讀取服務器返回的響應DatagramPacket datagramPacket1 = new DatagramPacket(new byte[4096],4096);datagramSocket.receive(datagramPacket1);String s = new String(datagramPacket1.getData(),0,datagramPacket1.getLength());//4. 將響應顯示到控制臺System.out.println(s);}
}
客戶端和服務器代碼的區別:
1.客戶端不用知道自己的端口,操作系統會隨機分配一個空閑的端口,是因為客戶端是發送請求的一方,要知道服務器的IP和端口才能發送,而服務器只要知道自己的端口,返回響應時接收的數據報里有客戶端的信息。
?TCP協議
兩個核心的類:ServerSocket和Socket。
ServerSocket:
打開它相當于打開一個socket文件,但是它不是負責“發送”“接收”,而是建立TCP連接,這個對象是專門給服務器使用的。
Socket:
打開它相當于打開socket文件,這個類雙方都會使用,用來發送接收數據。
使用TCP協議編寫一個簡單網絡程序
客戶端:
1.用戶從控制臺輸入
2.將請求發送給服務器
3.接送服務器返回的響應
4.將響應返回到控制臺
服務器:
1.接收客戶端的請求
2.根據請求次數處理響應
3.返回響應
編寫服務器思路:TCP協議是要進行連接的,所以不可以一上來就進行讀寫數據,要先進行從內核中將連接拿取,連接后就會返回一個Socket文件,之后就是對socket文件進行處理。
要拿到Socket中的流對象進行讀寫操作,讀出數據后就要進行處理相應(這里讀數據可以用scanner從流對象中進行讀取,當讀到的數據為空時就返回跳出循環,斷開連接),處理完成后就要通過PrintWriter返回響應。
public class server {private ServerSocket serverSocket = null;public server(int port) throws IOException {this.serverSocket = new ServerSocket(port);}private void start() throws IOException {System.out.println("服務器啟動");while(true){//通過accent建立連接//ServerSocket是建立連接,Socket負責進行數據通信,后面的都是針對Socket進行數據的傳輸//Serversocket 和 Socket 之間的關系類似前者將你介紹到總部的銷售,后者是給你介紹的銷售Socket socket = serverSocket.accept();processa(socket);}}private void processa(Socket socket) {try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()){Scanner scanner = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while (true){//循環處理,一個客戶端可能和服務器有多輪響應//讀取并解析響應if(!scanner.hasNext()){break;}String request = scanner.next();//根據請求計算響應String response = process(request);//返回響應printWriter.print(response);}}catch (IOException e){e.printStackTrace();}}private String process(String response){return response;}
}
注意事項:一個端口被同時使用于TCP和UDP協議是不會發生沖突的。
編寫客戶端思路:
TCP是有連接,所以要一上來就進行連接,然后就是進行發送和接收,但是發送時要注意有緩沖區(buffer),發送的printin其實并沒有進行發送,而是把數據填寫到緩沖區中,這是因為CPU讀寫內存比外設要快的多,所以在讀寫入網卡時就會等緩沖區的達到一定時,才會打包發送,但是可以通過flush進行提前喚醒。
public class EcohCilent {public Socket socket = null;public EcohCilent(String serverip, int serverPort) throws IOException {//有連接,先上來要建立連接,是由操作系統進行操作的,但是我們要指定服務器的端口和ipsocket = new Socket(serverip, serverPort);}public void start() {Scanner scanner = new Scanner(System.in);System.out.println("客戶端啟動!");try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {//接收服務器時創建的scanned,用于讀取服務器返回的內容while (true) {Scanner scanner1 = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);//輸入請求System.out.println(">");String request = scanner.next();//進行打包發送到服務器writer.println(request);writer.flush();//接收返回的請求if (!scanner1.hasNext()) {System.out.println("服務器斷開連接");break;}String rerequest = scanner1.next();System.out.println(rerequest);}}catch(IOException e){e.printStackTrace();}}
注意事項:
1.在TCP客戶端中要頻繁創建Socket,可能就會導致文件資源泄漏,所以要及時關閉。而UDP中不用進行關閉是因為它不需要頻繁創建socket文件。
2.在TCP中當有多個客戶端發送請求時就會出現問題。這是因為上面的代碼有兩層循環,當進入第二層循環時會發生阻塞,這是就要通過線程的方式處理。