文章目錄
- 1 基本流程
- 2 通訊核心類
- 2.1 服務端 :
- 2.2 客戶端 :
- 2.3運行結果:
- 3 代碼實作
- 3.1 定義訊息類
- 3.2 定義訊息處理類
- 3.3 定義服務端
- 3.4 添加執行緒處理
- 3.5 定義客戶端
- 3.6 運行結果:
1 基本流程
客戶端發送資訊(指定目標客戶端)至固定的一個服務端,服務端接收資訊進行處理后發送至相應的客戶端

2 通訊核心類
Socket類與流相輔相成,完成通訊,在accept方法回傳了一個Socket物件后,獲取socket的輸入輸出流,就可以接收資訊或發送資訊了,以一對一為例:
2.1 服務端 :
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @ClassName Server
* @Description 服務端
* @Author issac
* @Date 2021/4/13 17:26
*/
public class Server {
public static void main(String[] args) throws IOException {
// 創建服務端套接字并指定埠
ServerSocket server = new ServerSocket(88);
// 接收創建建立,回傳連接創建好后服務器的socket物件
Socket socket = server.accept();
InputStreamReader reader = new InputStreamReader(socket.getInputStream());
BufferedReader bufferedReader = new BufferedReader(reader);
// 獲取請求
String request = bufferedReader.readLine();
System.out.println("client say:" + request);
// 寫到輸出流傳遞給客戶端
PrintWriter writer = new PrintWriter(socket.getOutputStream());
String line = "hello too";
writer.println(line);
writer.flush();
// 關閉處理流的工具、socket套接字、服務套接字
writer.close();
bufferedReader.close();
socket.close();
server.close();
}
}
2.2 客戶端 :
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* @ClassName Client
* @Description 客戶端
* @Author issac
* @Date 2021/4/13 17:26
*/
public class Client {
public static void main(String[] args) throws IOException {
// 創建socket連接,指明其地址和埠
Socket socket = new Socket("127.0.0.1", 88);
// 獲取套接字的輸出流,輸出hello
PrintWriter writer = new PrintWriter(socket.getOutputStream());
String readLine = "Hello";
writer.println(readLine);
writer.flush();
// 從套接字的輸入流中獲取資訊
InputStreamReader reader = new InputStreamReader(socket.getInputStream());
BufferedReader bufferedReader = new BufferedReader(reader);
String respond = bufferedReader.readLine();
System.out.println("server say:" + respond);
bufferedReader.close();
writer.close();
socket.close();
}
}
2.3運行結果:

需要注意的是accept方法在沒有連接的時候會阻塞,而導致后面的代碼無法執行,在接下來的多對多通訊中需要依靠多執行緒來解決這個問題,
3 代碼實作
3.1 定義訊息類
為了方便服務端和客戶端對資訊的處理,決議,首先定義一個訊息類,定義屬性分別為埠的本地地址,發送的訊息內容,發送的目標地址,定義靜態方法:將字串決議為該類實體,處理訊息的收發:
import com.alibaba.fastjson.JSON;
import java.io.Serializable;
import com.alibaba.fastjson.JSON;
import java.io.*;
import java.net.Socket;
/**
* 在網路中,所有被進行通訊的物件,都需要實作 Serializable 這個介面
* <p>
* 該類,主要用于本專案例子中,socket傳輸的物件,請勿使用其他或字串,
* 為了后期更方便修改或者是其他操作
*
* @ClassName SocketMessage
* @Description TODO
* @Author issac
* @Date 2021/4/18 22:02
*/
public class SocketMessage implements Serializable {
/**
* 我自己的名稱 ip:port
**/
private String key;
/**
* 我的目標 ip:port
**/
private String to;
/**
* 發送的內容
**/
private String content;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
/**
* 向目標客戶端寫出從發送者獲取到的訊息
*/
public static void writeTargetMessage(SocketMessage message, Socket socket) throws IOException {
PrintWriter writer = new PrintWriter(socket.getOutputStream());
// 統一字串標準,以便于服務端決議
writer.println(JSON.toJSONString(message));
writer.flush();
}
/**
* 將輸入流中接收的字串決議為SocketMessage物件
*
* @param is
* @return SocketMessage
* @throws Exception
*/
public static SocketMessage parseSocket(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String info = reader.readLine();
return parseSocketByStr(info);
}
/**
* 將傳入字串決議為SocketMessage物件并回傳
*
* @param str
* @return SocketMessage
*/
public static SocketMessage parseSocketByStr(String str) {
SocketMessage socketMessage = null;
try {
socketMessage = JSON.parseObject(str, SocketMessage.class);
} catch (Exception ex) {
throw new RuntimeException("socket之間通訊不能不使用SocketMessage");
}
return socketMessage;
}
@Override
public String toString() {
// 通過 阿里巴巴 的FastJson 庫,將一個物件轉換為 字串 ,統一標準,以便于將字串決議為該類
return JSON.toJSONString(this);
}
}
3.2 定義訊息處理類
再單獨定義一個服務端的訊息處理類,該類用于發送訊息至特定的客戶端,所以定義兩個屬性,1.發送的訊息,2.目標客戶端的套接字:
import java.net.Socket;
/**
* @ClassName SocketMessageHandler
* @Description 服務端針對客戶端的訊息處理器
* @Author issac
* @Date 2021/4/18 22:34
*/
public class SocketMessageHandler {
SocketMessage sm;
Socket targetSocket;
public SocketMessageHandler(SocketMessage sm,Socket targetSocket) {
this.sm = sm;
this.targetSocket = targetSocket;
}
public void setSm(SocketMessage sm) {
this.sm = sm;
}
/**
* 發送訊息
*/
public void send() {
if (this.sm == null) {
return;
}
try {
System.out.println(sm.getContent());
// 發送
SocketMessage.writeTargetMessage(sm, this.targetSocket);
} catch ( Exception ex) {
ex.printStackTrace();
}
}
}
3.3 定義服務端
接下來進行服務端的定義,我們的服務端需要處理多個客戶端的訊息,所以要定義一個容器存放客戶端地址,在此之前我們已經定義了處理服務端訊息的SocketMessageHandler類,因為我們的最終目的是為了處理資訊,所以可以直接將SocketMessageHandler類存放至容器,我們用map來存盤,而key就是客戶端的地址:
import com.issac.task_05.task.msg.SocketMessage;
import com.issac.task_05.task.msg.SocketMessageHandler;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
/**
* n - m: 一個服務端,同時服務多個客戶端
*
* @ClassName SocketServer
* @Description 服務端
* @Author issac
* @Date 2021/4/18 21:29
*/
public class SocketServer {
// 存放訊息處理器
private static final Map<String, SocketMessageHandler> clientContainer = new HashMap<>();
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(8888);
Socket accept;
while (true) {
/* 只有建立新連接時accept才會有回應而執行以下代碼,否則會阻塞:客戶端與服務器連接,并將已連接的客戶端放入容器 */
accept = ss.accept();
SocketMessage msg = SocketMessage.parseSocket(accept.getInputStream()); // 獲取資訊
System.out.println("客戶端建立連接:" + msg.getKey());
// 建立連接后將客戶端地址存入容器
clientContainer.put(msg.getKey(), new SocketMessageHandler(msg, accept));
/* 在已經建立連接后,沒有新連接,accept會處于阻塞狀態,因此我們需要另外開辟一個執行緒來處理訊息 */
new ServerThread(accept, clientContainer).start();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
3.4 添加執行緒處理
在這里需要注意ServerSocket類的accept方法,在沒有新連接的時候,該方法會阻塞,而之后的代碼就無法執行了,我們在客戶端與服務端連接成功之后進行訊息收發的時候是沒有新連接產生的,此時的阻塞導致無法進行通訊,于是乎我們需要再開辟一個執行緒,進行訊息處理,那么我們定義一個繼承Thread的訊息處理類,將每次連接成功回傳的套接字接收,進行資訊處理,如此一來,只要有訊息的傳遞該執行緒就可以進行獲取:
import com.issac.task_05.task.msg.SocketMessage;
import com.issac.task_05.task.msg.SocketMessageHandler;
import java.io.InputStream;
import java.net.Socket;
import java.util.Map;
/**
* @ClassName ServerThread
* @Description 處理資訊
* @Author issac
* @Date 2021/4/21 21:25
*/
public class ServerThread extends Thread{
private Socket socket;
InputStream inputStream;
Map<String, SocketMessageHandler> clientContainer;
public ServerThread(Socket socket,Map<String, SocketMessageHandler> clientContainer){
this.socket = socket;
this.clientContainer = clientContainer;
}
public void run(){
try{
while (true){
// 將輸入流中的資料決議為SocketMessage物件
inputStream = socket.getInputStream();
SocketMessage msg = SocketMessage.parseSocket(inputStream);
System.out.println(msg);
// 在容器中獲取目標地址
SocketMessageHandler socketMessageHandler = clientContainer.get(msg.getTo());
// 設定需要傳輸的資訊
socketMessageHandler.setSm(msg);
// 傳輸資訊
socketMessageHandler.send();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
3.5 定義客戶端
最后就是客戶端了,每個客戶端所對應的服務端都相同,在客戶端寫一個簡易的選單,選擇接識訓發送訊息即可:
import com.issac.task_05.task.msg.SocketMessage;
import java.net.Socket;
import java.util.Scanner;
/**
* @ClassName Client
* @Description 客戶端
* @Author issac
* @Date 2021/4/19 21:08
*/
public class Client {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Socket s = null;
try {
s = new Socket("localhost", 8888);
// 第一次啟動,創建socket,向服務器發送我是誰
SocketMessage initMsg = getSocketMsg(s.getLocalSocketAddress().toString(), null, null);
System.out.println("開始與服務器建立連接: " + initMsg.toString());
SocketMessage.writeTargetMessage(initMsg, s);
// 開始 回圈等待
while (true) {
System.out.println("===================menu=====================");
System.out.println("1:發送訊息");
System.out.println("2:接收訊息");
int choice = scanner.nextInt();
switch (choice){
case 1: // 發送訊息
String target = input("請輸入您要發給誰:");
String content = input("請輸入您要發送的內容:");
System.out.println();
SocketMessage afterMsg = getSocketMsg(s.getLocalSocketAddress().toString(), target, content);
SocketMessage.writeTargetMessage(afterMsg, s);
break;
case 2: // 接收并列印訊息
showRequiredMsg(s);
break;
default:
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 根據提示輸入內容
**/
public static String input(String tip) {
Scanner input = new Scanner(System.in);
System.out.println(tip);
return input.next();
}
/**
* 將用戶輸入傳遞的本地地址,目標地址與傳遞內容轉化為SocketMessage物件
* @param localSocketAddress
* @param to
* @param content
* @return
*/
public static SocketMessage getSocketMsg(String localSocketAddress, String to, String content) {
SocketMessage socketMessage = new SocketMessage();
// to 為null的時候,說明只是對服務器的初始
socketMessage.setKey(localSocketAddress.replaceAll("\\/", ""));
socketMessage.setTo(to);
socketMessage.setContent(content);
return socketMessage;
}
/**
* 接收訊息并列印
* @param socket
* @throws Exception
*/
public static void showRequiredMsg(Socket socket) throws Exception {
SocketMessage socketMessage = SocketMessage.parseSocket(socket.getInputStream());
String source = socketMessage.getKey();
String content = socketMessage.getContent();
System.out.println("接收到來自《"+source+"》的資訊:"+content+"\n");
}
}
3.6 運行結果:


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/280380.html
標籤:java
