IP地址(源IP,目的IP)
用來識別互聯網上一臺主機的位置
端口號(源端口,目的端口)
用來區分一臺主機上的哪個應用程序
(佔兩個字節的整數)
五元組:
源IP,
目的IP
源端口,
目的端口
協議類型
通過一個五元組表示一個唯一的通信
Socket API(本質是一個文件,網卡的抽象)
Java標準庫中提供了兩種風格:
1.UDP DatagramSocket:面向數據報(發送接收數據,必須以一定的數據包爲單位進行傳輸)
2.TCP ServerSocket:面向字節流
這兩個是傳輸層中最重要的兩個協議。
UDP服務器
現在以回顯服務器爲例(echo server)
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
//對於一個服務器,核心流程爲兩步
//1.初始化操作(實例化Socket對象)
//2.進入主循環,接受並處理請求
//讀取數據並解析
//根據請求計算響應
//吧響應結果返回到客戶端
private DatagramSocket socket = null;
//綁定端口號
//構造Socket時如果沒寫IP,默認是0.0.0.0(特殊IP)會關聯到這個主機的所有網卡IP
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服務器啓動");
while (true) {
//UDP socket發送接受數據的基本單位
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
//當客戶端沒發任何數據,receive就會阻塞
socket.receive(requestPacket);
//當有客戶端數據過來了,receive就會把數據放到DatagramPacket對象的緩衝區
//吧數據轉成String
//用戶實際發送的數據可能遠小於4096,而此處得到的長度就是4096,
//可以通過trim就可以去掉不必要的空白字符
String request = new String(requestPacket.getData(),
0,requestPacket.getLength()).trim();
String response = process(request);
//requestPacket.getSocketAddress()就是客戶端的地址和端口
//response.getBytes().length得到的是字節數
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
response.getBytes().length,requestPacket.getSocketAddress());
socket.send(requestPacket);
System.out.printf("[%s:%d] req: %s;resp:%s\n",requestPacket.getAddress().toString(),
requestPacket.getPort(),request,response);
}
}
public String process(String request) {
//此處是回顯服務器,請求啥響應內容就是啥
//如果是更復雜的服務器,此處就包含很多業務邏輯
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
//客戶端分四步
//1.從用戶這裏讀取數據
//2.構造請求發給服務器
//3.從服務器讀取響應
//4.吧響應寫回給客戶端
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
//需要在啓動客戶端是來指定需要連接那個服務器
public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
//不需要綁定端口號,由操作系統自動分配一個空閒端口
//一個端口號通常只能被一個進程綁定
//服務器綁定了端口之後,客戶端才能訪問
//客戶端如果綁定端口,一個主機只能啓動一個客戶端(通常不允許)
socket = new DatagramSocket();
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("->");
String request = scanner.nextLine();
if (request.equals("exit")) {
break;
}
//構造請求
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String response = new String(requestPacket.getData(),0,requestPacket.getLength()).trim();
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
//這裏的IP是特殊IP,環回IP,由於這裏服務器和客戶端在同一主機
//如果不在同一主機,要寫成服務器IP
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
客戶端服務器是爲了跨主機通信
要把服務器部署到雲服務器上
部署主要是兩個步驟:
1.把代碼打成jar包
2.把jar包拷貝到服務器上運行
用idea打jar包:
現在寫一個翻譯服務器來體會服務器裏面process的業務邏輯,一般比較複雜的服務器,process裏面的代碼往往很複雜。
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
public class UdpDictServer extends UdpEchoServer {
private Map<String,String> dict = new HashMap<>();
public UdpDictServer(int port) throws SocketException {
super(port);
dict.put("cat","小貓");
dict.put("dog","小狗");
dict.put("bird","小鳥");
}
@Override
public String process(String request) {
return dict.getOrDefault(request,"這超出了我的知識範圍");
}
public static void main(String[] args) throws IOException {
UdpDictServer server = new UdpDictServer(9090);
server.start();
}
}