下面寫一個簡單的UDP客戶端服務(wù)器流程
思路:
對于服務(wù)器端:讀取請求,并解析–> 根據(jù)解析出的請求,做出響應(yīng)(這里是一個回顯,)–>把響應(yīng)寫回客戶端
對于客戶端:從控制臺讀取用戶輸入的內(nèi)容–>從控制臺讀取用戶輸入的內(nèi)容–>從控制臺讀取用戶輸入的內(nèi)容–>將其顯示在屏幕上
全部代碼如下:
服務(wù)器端:
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
//UDP的回顯服務(wù)器 客戶端發(fā)出的請求是啥,服務(wù)器返回的響應(yīng)就是啥
public class UdpEchoServer {
private DatagramSocket socket=null;
// 指定服務(wù)器的port
public UdpEchoServer(int port) throws SocketException {
socket=new DatagramSocket(port);
}
//指定一個方法啟動服務(wù)器
public void start() throws IOException {
System.out.println("服務(wù)器開始啟動");
while(true){
// 反復(fù)的, 長期的執(zhí)行針對客戶端請求處理的邏輯.
// 一個服務(wù)器, 運(yùn)行過程中, 要做的事情, 主要是三個核心環(huán)節(jié).
//服務(wù)器這里需要接收請求
//1.讀取請求,并解析
DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
//解析
String request=new String(requestPacket.getData(),0, requestPacket.getLength());
//2.根據(jù)解析出的請求,做出響應(yīng)(這里是一個回顯,)
String response=process(request);
//3. 把響應(yīng)寫回客戶端 此時需要告訴網(wǎng)卡,要發(fā)的內(nèi)容是啥,發(fā)給誰
//構(gòu)造一個發(fā)送數(shù)據(jù)包
DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(responsePacket);
//記錄日志
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 udpEchoServer=new UdpEchoServer(9090);
udpEchoServer.start();
}
}
客戶端:
package network;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
//由于客戶端的port是自動分配的,所以這里不會像服務(wù)器那樣配置port
//但是,客戶端需要向服務(wù)器發(fā)送請求,所以,這里我們需要知道服務(wù)器的ip和port
private DatagramSocket socket=null;
private String serverIp;
private int serverPort;
//外部指定服務(wù)器的ip和port
public UdpEchoClient(String ip,int port) throws SocketException {
this.serverIp=ip;
this.serverPort=port;
//客戶端的port是自動分配的
socket=new DatagramSocket();
}
// 讓這個客戶端反復(fù)的從控制臺讀取用戶輸入的內(nèi)容. 把這個內(nèi)容構(gòu)造成 UDP 請求, 發(fā)給服務(wù)器. 再讀取服務(wù)器返回的 UDP 響應(yīng)
// 最終再顯示在客戶端的屏幕上.
public void start() throws IOException {
Scanner scanner=new Scanner(System.in);
System.out.println("客戶端開始啟動");
while(true){
//1. 從控制臺讀取用戶輸入的內(nèi)容
System.out.println("->");
String requset=scanner.next();
//2.構(gòu)造請求對象,發(fā)送給服務(wù)器
DatagramPacket requsetPacket=new DatagramPacket(requset.getBytes(),requset.getBytes().length,
InetAddress.getByName(serverIp),serverPort);
socket.send(requsetPacket);
//3.讀取服務(wù)器的響應(yīng),并解析出其內(nèi)容
DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
String response=new String(responsePacket.getData(),0,responsePacket.getLength());
//4 。將其顯示在屏幕上
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient=new UdpEchoClient("127.0.0.1",9090);//127.0.0.1 本機(jī)ip
udpEchoClient.start();
}
}
運(yùn)行結(jié)果如下
對上述過程中的一些談?wù)摵头治觯?/p>
多個客戶端向一個服務(wù)器發(fā)送請求
下面寫一個簡單的翻譯服務(wù)器
重寫的服務(wù)器端的代碼如下:
package network;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
public class UdpDictServer extends UdpEchoServer{
//使用HashMap保存中英文翻譯的鍵值對
private Map<String,String> dict =new HashMap<>();
//實(shí)現(xiàn)父類的構(gòu)造方法
public UdpDictServer(int port) throws SocketException {
super(port);
//一些原始的鍵值對
dict.put("cat","貓");
dict.put("dog","狗");
dict.put("people","人");
}
//與原始的UdpEachServer相比,這里對于請求的處理過程是不一樣的
//重寫process方法
@Override
public String process(String request) {
//找到對應(yīng)的翻譯,并返回
//getOrDefault方法,找到key所對應(yīng)的value值,如果沒有找到,則返回defaultValue(即第二個參數(shù))
return dict.getOrDefault(request,"該詞沒有查詢到");
}
public static void main(String[] args) throws IOException {
UdpDictServer server=new UdpDictServer(9090);
// start 不需要重新再寫一遍了. 直接就復(fù)用了之前的 start
server.start();
}
}
執(zhí)行結(jié)果如下:
下面寫一個基于TCP 的回顯流程
思路:
服務(wù)器端:先從隊(duì)列中拿到一個“連接”–> 讀取請求并解析–>根據(jù)請求計(jì)算響應(yīng)–>把響應(yīng)寫回給客戶端
客戶端:從控制臺輸入字符串–>把請求發(fā)送給服務(wù)器–>從服務(wù)器讀取響應(yīng).–>把響應(yīng)打印出來
全部代碼如下:
服務(wù)器端代碼:
package network;
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;
//基于TCP的回顯服務(wù)器
public class TcpEachServer {
private ServerSocket serverSocket=null;
//綁定端口號
public TcpEachServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
//啟動服務(wù)器
public void start() throws IOException {
System.out.println("服務(wù)器開始啟動");
while(true){
//從管理連接的隊(duì)列中拿出一個“連接”出來
Socket clientSocket=serverSocket.accept();
//處理這個連接內(nèi)的請求
processConnection(clientSocket);
}
}
//這個方法用來處理連接中的邏輯
private void processConnection(Socket clientSocket) throws IOException {
//日志
System.out.printf("[%s:%d] 客戶端上線\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
//下面開始讀取請求,計(jì)算響應(yīng),返回響應(yīng) 三步曲
//Socket對象內(nèi)部包含兩種字節(jié)流對象InputStream和OutputStream,可以先把這兩個對象流獲
// 取到,方便后續(xù)處理過程種的讀寫工作
try(InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream()){
//不同于UDP協(xié)議中的無連接,在客戶端的一次連接過程中,可能涉及多次請求/響應(yīng)過程
//因此。這里使用一個while循環(huán),直到該連接中的所有請求處理完畢
while(true){
//1,讀取請求并解析
Scanner scanner=new Scanner(inputStream);
//hasNext的作用是,檢測輸入流中是否有結(jié)束輸入的控制符,比如0x1A(EOF,Ctrl-Z)
//用于檢測一個連接是否結(jié)束
if(!scanner.hasNext()){
//一個連接處理完畢
System.out.printf("[%s:%d] 客戶端本次連接處理完畢,下線!\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
// 這個代碼暗含一個約定, 客戶端發(fā)過來的請求, 得是文本數(shù)據(jù), 同時, 還得帶有空白符作為分割. (比如換行這種)
//next():當(dāng)輸入到空白符結(jié)束
String request=scanner.next();
//2.根據(jù)請求計(jì)算響應(yīng)
String response=process(request);
//3. 把響應(yīng)寫回客戶端,把OutputStream用PrintWriter(此處的PrintWriter相當(dāng)于Scanner)包裹一下,便于發(fā)送數(shù)據(jù)
//將outputStream和PrintWriter關(guān)聯(lián)起來
PrintWriter writer=new PrintWriter(outputStream);
//使用 PrintWriter 的 println 方法,打印到輸出流中 把響應(yīng)返回給客戶端.
//此處用 println, 而不是 print 就是為了在結(jié)尾加上 \n . 方便客戶端讀取響應(yīng), 使用 scanner.next 讀取.
writer.println(response);
//這里還需要加一個 "刷新緩沖區(qū)" 操作.將緩沖區(qū)的數(shù)據(jù)強(qiáng)制輸出,用于清空緩沖區(qū)
writer.flush();
//日志 記錄當(dāng)前的請求和響應(yīng)
System.out.printf("[%s:%d] req: %s,resp: %s\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
}
}
}
//回顯,只需要再返回這個字符串
public String process(String requset){
return requset;
}
public static void main(String[] args) throws IOException {
TcpEachServer tcpEachServer=new TcpEachServer(9090);
tcpEachServer.start();
}
}
客戶端代碼:
package network;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
private Socket socket=null;
//服務(wù)器端的ip和port
public TcpEchoClient(String serverIp,int serverPort) throws IOException {
//這個new的動作完成后,完成了tcp的建立
socket=new Socket(serverIp,serverPort);
}
public void start() throws IOException {
System.out.println("客戶端啟動");
Scanner scannerConsole=new Scanner(System.in);
//Socket對象內(nèi)部包含兩種字節(jié)流對象InputStream和OutputStream,可以先把這兩個對象流獲
// 取到,方便后續(xù)處理過程種的讀寫工作
try(InputStream inputStream=socket.getInputStream();
OutputStream outputStream=socket.getOutputStream()){
while(true){
//1.從控制臺輸入字符串
System.out.println("-->");
String request=scannerConsole.next();
//2.把請求發(fā)送給服務(wù)器 需要對request進(jìn)行包裝,使用PrintWriter
PrintWriter printWriter=new PrintWriter(outputStream);
//使用 println 帶上換行. 后續(xù)服務(wù)器讀取請求, 就可以使用 scanner.next 來獲取了
printWriter.println(request);//發(fā)送請求
printWriter.flush();
//3.從服務(wù)器中接收響應(yīng)
Scanner scannerNetwork=new Scanner(inputStream);
String response=scannerNetwork.next();
//4.把響應(yīng)打印出來
System.out.println(response);
}
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient=new TcpEchoClient("127.0.0.1",9090);
tcpEchoClient.start();
}
}
當(dāng)開多個線程時,發(fā)現(xiàn)只有一個線程在被處理,其它線程都在等待,
當(dāng)被處理的線程下線后,其他線程的邏輯才開始被處理
原因在于 Socket clientSocket = serverSocket.accept();和processConnection(clientSocket);都是主線程進(jìn)行處理的且在同一次循環(huán)體中,只有一個clinetSocket連接被處理完后,才會去隊(duì)列中accept下一個連接,為此,這里我們可以采用多線程進(jìn)行處理。
修改為多線程后,可以看到 有多個客戶端可以訪問服務(wù)器
考慮到一個現(xiàn)實(shí)的情況,許多客戶端需要頻繁的訪問服務(wù)器,那就是需要頻繁的斷開/連接,我們這里可以使用線程池
同樣也可以實(shí)現(xiàn)多個客戶端同時訪問服務(wù)器。
最終的服務(wù)器的代碼如下:
package network;
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;
//基于TCP的回顯服務(wù)器
public class TcpEachServer {
private ServerSocket serverSocket=null;
//創(chuàng)建一個非固定數(shù)目的線程池
private ExecutorService service= Executors.newCachedThreadPool();
//綁定端口號
public TcpEachServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
//啟動服務(wù)器
public void start() throws IOException {
System.out.println("服務(wù)器開始啟動");
while(true){
//從管理連接的隊(duì)列中拿出一個“連接”出來
Socket clientSocket=serverSocket.accept();
//處理這個連接內(nèi)的請求
service.submit(new Runnable() {
@Override
public void run() {
try {
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
});
/* Thread t=new Thread(() ->{
try {
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();*/
}
}
//這個方法用來處理連接中的邏輯
private void processConnection(Socket clientSocket) throws IOException {
//日志
System.out.printf("[%s:%d] 客戶端上線\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
//下面開始讀取請求,計(jì)算響應(yīng),返回響應(yīng) 三步曲
//Socket對象內(nèi)部包含兩種字節(jié)流對象InputStream和OutputStream,可以先把這兩個對象流獲
// 取到,方便后續(xù)處理過程種的讀寫工作
try(InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream()){
//不同于UDP協(xié)議中的無連接,在客戶端的一次連接過程中,可能涉及多次請求/響應(yīng)過程
//因此。這里使用一個while循環(huán),直到該連接中的所有請求處理完畢
while(true){
//1,讀取請求并解析
Scanner scanner=new Scanner(inputStream);
//hasNext的作用是,檢測輸入流中是否有結(jié)束輸入的控制符,比如0x1A(EOF,Ctrl-Z)
//用于檢測一個連接是否結(jié)束
if(!scanner.hasNext()){
//一個連接處理完畢
System.out.printf("[%s:%d] 客戶端本次連接處理完畢,下線!\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
// 這個代碼暗含一個約定, 客戶端發(fā)過來的請求, 得是文本數(shù)據(jù), 同時, 還得帶有空白符作為分割. (比如換行這種)
//next():當(dāng)輸入到空白符結(jié)束
String request=scanner.next();
//2.根據(jù)請求計(jì)算響應(yīng)
String response=process(request);
//3. 把響應(yīng)寫回客戶端,把OutputStream用PrintWriter(此處的PrintWriter相當(dāng)于Scanner)包裹一下,便于發(fā)送數(shù)據(jù)
//將outputStream和PrintWriter關(guān)聯(lián)起來
PrintWriter writer=new PrintWriter(outputStream);
//使用 PrintWriter 的 println 方法,打印到輸出流中 把響應(yīng)返回給客戶端.
//此處用 println, 而不是 print 就是為了在結(jié)尾加上 \n . 方便客戶端讀取響應(yīng), 使用 scanner.next 讀取.
writer.println(response);
//這里還需要加一個 "刷新緩沖區(qū)" 操作.將緩沖區(qū)的數(shù)據(jù)強(qiáng)制輸出,用于清空緩沖區(qū)
writer.flush();
//日志 記錄當(dāng)前的請求和響應(yīng)
System.out.printf("[%s:%d] req: %s,resp: %s\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
}
} finally {
clientSocket.close();
}
}
//回顯,只需要再返回這個字符串
public String process(String requset){
return requset;
}
public static void main(String[] args) throws IOException {
TcpEachServer tcpEachServer=new TcpEachServer(9090);
tcpEachServer.start();
}
}
上述過程中的一些思路
文章來源:http://www.zghlxwxcb.cn/news/detail-728019.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-728019.html
到了這里,關(guān)于JavaEE-網(wǎng)絡(luò)編程套接字(UDP/TCP)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!