網絡編程套接字~~~~~
好久沒更新啦,藍橋杯爆掉了,從今天開始爆更嗷;
1,網絡編程基礎
為啥要有網絡編程呢,我們進行網絡通信就是為了獲取豐富的網絡資源,說實話真的很神奇,想想我們躺在床上,通過網絡,訪問到世界上成千上萬的人做好的網絡資源,我們甚至能看到一輩子都看不到的景色,這些資源本質上都是二進制資源或者是文本資源,我們通過網絡可以讓很多人來訪問,這個就是網絡編程,當年的網絡就是個看報的,誰能想到發展到這樣呢?
網絡編程的概念:網絡上的主機,通過不同的進程,以編程的方式實現網絡通信;
同一個主機下的不同進程實現網絡通信也是網絡編程;
網絡編程的基本概念:
接收端:數據接收端進程,目標主機;
發送端:數據發送端進程,源主機;
收發端:發送接收兩端;
請求:請求數據的發送;
響應:響應數據的發送;
就像我們去餐廳點一份蛋炒飯就是請求,廚師給我們拿來蛋炒飯就是回應;
服務端:提供服務的一方,返回服務資源;
客戶端:獲取服務的一方;
2,Socket套接字
Socket就是套接字,啥是套接字,這名起的,Socket套接字是由系統提供用于網絡通信的技術,是基于TCP/IP協議的網絡通信的基本單元,基于Socket套接字的網絡程序開發就是網絡編程;
Socket套接字主要針對傳輸層協議劃分為三類:
我們這里就學兩個核心協議
1,TCP,有連接,可靠傳輸,面向字節流,全雙工;
2,UDP,無連接,不可靠傳輸,面向數據報,全雙工;
這里的有連接和無連接都是抽象的概念,虛擬上的連接;
TCP協議保存了對端的信息,A和B通信,A和B先建立連接,A保存了B的信息,B也保存了A的信息,他們彼此都知道誰與自己建立的連接,而UDP不保存對方的信息;
可靠傳輸和不可靠傳輸是什么呢?
網絡上,數據的傳輸是會發生丟包的情況的,可能會受到外界的干擾,那么可靠傳輸就能避免這樣的丟包嗎,事實上是不能的,可靠傳輸只是保證盡可能提高傳輸成功的概率,如果出現了丟包,也能感知到,不可靠傳輸的話就是把數據發送之后就不管了,那么這樣的話我們就使用可靠傳輸就好了呀,還要UDP干嘛呢,凡是必有代價,UDP的話速度會更快;
面向字節流和面向數據報呢?
面向字節流:讀寫數據的時候,以字節為單位;
面向數據報:讀寫數據的時候,以一個數據報為單位;
全雙工和半雙工呢?
全雙工:一個通信聯絡,能讀也能寫;
半雙工:只支持單向通信,要么讀,要么寫;
我們使用Socket api來進行網絡編程,我們之前提到計算機中的“文件”有狹義和廣義的概念,硬件設備可以抽象成文件,統一管理;電腦上的網卡就是Socket文件;
3,UDP數據報套接字編程
我們先來介紹UDP需要的API:
DatagramSocket
UDP的Socket用于接收和發送UDP數據報;
構造方法:
方法簽名 | 方法說明 |
DatagramSocket() | 創建一個UDP數據報套接字的Socket,綁定到本機任意一個端口,一般用于客戶端 |
DatagramSocket(int port) | 創建一個UDP數據報套接字的Socket,綁定到本機指定的端口,一般用于服務端 |
方法:
方法簽名 | 方法說明 |
void receive(DatagramPacket p) | 從此套接字接收數據報,如果沒有收到數據報就會阻塞等待; |
void send(DatagramPacket p) | 從此套接字發送數據報,不會阻塞等待,直接發送; |
void close() | 關閉此數據報套接字 |
DatagramPacket
datagramPacket是UDP socket發送和接收的數據報;
構造方法:
方法簽名 | 方法說明 |
DatagramPacket(byte[] bytes, int length) | 構造一個DatagramPacket用來接收數據報,接收的數據呢保存在字節數組中,接收指定的長度 |
DatagramPacket(byte[] bytes, int offset, int length, SocketAddress address) | 構造一個DatagramPacket用來發送數據報,發送的數據為字節數組,從0開始,到指定長度,address指定制定目的主機的IP和端口號 |
方法:
方法簽名 | 方法說明 |
InetAddress? getAddress() | 從接收的數據報中,獲取發送端主機IP;或者從發送的數據報中,獲取接收端主機IP; |
int getPort() | 從接收的數據報中,獲取接收端主機的端口號;或者從發送的數據報中,或缺發送端主機的端口號; |
byte[] getData() | 獲取數據報中的數據;? |
getSocketAddress() | 直接獲取到當前數據報的IP和端口號; |
?
講完方法了,我們來模擬一個回顯服務器,這里就是模擬,回顯服務器就是自問自答;
我們來用UDP模擬一下:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;public class UdpEchoServer {private DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);//指定端口號,讓服務器來使用}public void start() throws IOException {//啟動服務器while(true){//循環一次,就意味著處理了一次請求//1讀取請求并解析DatagramPacket packet = new DatagramPacket(new byte[4090],4090);socket.receive(packet);String request = new String(packet.getData(),0,packet.getLength());//2根據請求計算響應String response = process(request);//3把響應返回給客戶端DatagramPacket packet1 = new DatagramPacket(response.getBytes(),response.getBytes().length,packet.getSocketAddress());socket.send(packet1);//4打印日志System.out.printf("[%s:%d] req: %s, resp: %s\n",packet.getAddress().toString(), packet.getPort(), request, response);}}public String process(String request){return request;}public static void main(String[] args) throws IOException {UdpEchoServer udpEchoServer = new UdpEchoServer(9090);udpEchoServer.start();}
}
模擬服務器:難點用注釋標注了,看不明白就來私信我!?
接下來實現客戶端代碼:
import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket = null;private String SeverIp;private int SeverPort;public UdpEchoClient(String IP,int port) throws SocketException {this.SeverIp = IP;this.SeverPort = port;socket = new DatagramSocket();}public void start() throws IOException {Scanner scanner = new Scanner(System.in);while(true){System.out.println("請輸入要發送的內容");while(!scanner.hasNext()){break;}String request = scanner.nextLine();DatagramPacket datagramPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(SeverIp),SeverPort);socket.send(datagramPacket);DatagramPacket datagramPacket1 = new DatagramPacket(new byte[4090],4090);socket.receive(datagramPacket1);String response = new String(datagramPacket1.getData(),0,datagramPacket1.getLength());System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);udpEchoClient.start();}}
我們把兩個代碼的都運行下?
客戶端
服務器
4,TCP流套接字編程
接下來我們來實現TCP的網絡編程,還是先來學習一下方法:
SeverSocket
SeverSocket呢是創建TCP服務端Socket的ApI;
構造方法:
方法簽名 | 方法說明 |
SeverSocket(int port) | 創建一個服務端流套接字Socket,并綁定到指定端口 |
方法:
方法簽名 | 方法說明 |
Socket accept() | 開始監聽指定端口(創建時綁定的端口),有客戶端連接后,返回一個服務端Socket對象,并基于該Socket建立與客戶端的連接,否則阻塞等待; |
void close() | 關閉此套接字; |
Socket
socket是客戶端Socket,或服務端中接收到客戶端建立連接(accept)的請求后,返回的服務端Socket;
構造方法:
方法簽名 | 方法說明 |
Socket(String host,int port) | 創建一個客戶端流套接字Socket,并與對應IP的主機上,對應端口的進程建立連接; |
方法:
方法簽名 | 方法說明 |
inerAddress? getInetAddress() | 返回套接字所連接的地址 |
inputStream? getInputStream() | 返回此套接字的輸入流 |
OutputStream? getOutputStream() | 返回此套接字的輸出流 |
服務端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TcpEchoServer {private ServerSocket serverSocket = null;public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("啟動服務器");ExecutorService executorService = Executors.newCachedThreadPool();while(true){Socket socketClient = serverSocket.accept();executorService.submit(()->{try {processConnection(socketClient);} catch (IOException e) {throw new RuntimeException(e);}});}}public void processConnection(Socket socketClient) throws IOException {System.out.println("客戶端上線"+ socketClient.getInetAddress()+socketClient.getPort());try(InputStream inputStream = socketClient.getInputStream();OutputStream outputStream = socketClient.getOutputStream()){Scanner scanner = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while (true){if(!scanner.hasNext()){System.out.println("客戶端下線"+ socketClient.getInetAddress()+socketClient.getPort());break;}String request = scanner.nextLine();String response = process(request);printWriter.println(response);printWriter.flush();System.out.printf("[%s:%d] req: %s, resp: %s\n", socketClient.getInetAddress(), socketClient.getPort(),request, response);}}}public String process(String request){return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);tcpEchoServer.start();}
}
這里引入線程池來處理多個請求,避免其他請求發來處理不了的情況;
客戶端:
?
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String IP,int port) throws IOException {socket = new Socket(IP,port);}public void start() throws IOException {Scanner scanner = new Scanner(System.in);try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){Scanner scanner1 = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while(true){System.out.println("請輸入要發送的信息");String request = scanner.nextLine();printWriter.println(request);printWriter.flush();String response = scanner1.nextLine();System.out.println(response);}}}public static void main(String[] args) throws IOException {TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);tcpEchoClient.start();}
}