Java Socket編程實(shí)現(xiàn)群聊實(shí)踐案例
上一篇文章已經(jīng)可以實(shí)現(xiàn)服務(wù)端與客戶端之間消息的交換并且開啟多個客戶端,那么如何實(shí)現(xiàn)特定客戶端與特定客戶端之間私聊以及特定客戶端發(fā)給所有在線客戶端的群聊呢,這一篇文章一起學(xué)習(xí)一下吧
代碼詳細(xì)解析
1.Mysever類-服務(wù)器主程序
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
/**1.創(chuàng)建服務(wù)器 綁定端口號
* 2.在循環(huán)中 調(diào)用accept方法 重復(fù)監(jiān)聽連接過來的客戶端
* 3.啟動線程保持和多個客戶端連接
* 4.在線程中 傳進(jìn)去對應(yīng)的socket 獲取輸入輸出流 實(shí)現(xiàn)和客戶端的雙向通訊
* 5.創(chuàng)建客戶端對象 綁定服務(wù)器的IP地址和端口 讓客戶端去連接服務(wù)器
* 6.利用客戶端輸入輸出流 跟服務(wù)端通訊
* 7.在客戶端啟動線程 一直讀取服務(wù)器發(fā)來的消息
*
*/
public class MyServer {
public static void main(String[] args){
try {
new MyServer().startServer();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//啟動服務(wù)器
public void startServer() throws IOException {
//創(chuàng)建服務(wù)器對象,并綁定監(jiān)聽器端口
//端口取值范圍(2字節(jié)):0~65535
//知名端口:0~1024
ServerSocket server=new ServerSocket(8888);
System.out.println("服務(wù)端在8888端口監(jiān)聽器");
//哈希表:保存一組映射關(guān)系:每個編號對應(yīng)唯一的socket
//基本數(shù)據(jù)類型對應(yīng)的包裝類
HashMap<Integer,Socket> mp=new HashMap<>();
//每個鏈接過來的客戶端ID
int ID=1;
//重復(fù)監(jiān)聽連接這個服務(wù)器的客戶端
while(true){
//監(jiān)聽連接這個服務(wù)器的客戶端
//該方法的返回的socket用來客戶端通信
Socket socket=server.accept();
//保存當(dāng)前對應(yīng)的數(shù)據(jù)
mp.put(ID,socket);
//利用線程保持和多個客戶端連接
ServerThread st=new ServerThread(socket,mp,ID);
new Thread(st).start();
ID++;
}
}
}知識點(diǎn)解析說明:
1.端口號
ServerSocket(int port):創(chuàng)建綁定到特定端口的服務(wù)器套接字
端口范圍:0~65535
知名端口:0~1024,被系統(tǒng)服務(wù)占用
2.hashMap的使用
HashMap<Integer,Socket> mp=new HashMap<>();
key:Integer類型的客戶端ID
values:Socket對象,代表與客戶端的鏈接
作用:集中管理所有客戶端鏈接,便于消息路由
3.多線程處理
new Thread(st).start();
為每個客戶端連接創(chuàng)造獨(dú)立線程
避免單個客戶端阻塞整個服務(wù)器
實(shí)現(xiàn)真正的并發(fā)處理
2.ServerThread類-服務(wù)器線程處理
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
public class ServerThread implements Runnable{
//跟客戶端連接的套接字
public Socket socket;
public InputStream is;
public OutputStream os;
public HashMap<Integer,Socket> mp;
public int ID;
public ServerThread(Socket socket,HashMap<Integer,Socket> mp,int ID){
this.socket=socket;
this.mp=mp;
try {
os = socket.getOutputStream();
is = socket.getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
//發(fā)送客戶端上線消息
sendMsg(ID+":客戶端上線..",os);
}
@Override
public void run() {
while(true) {
try {
//msg消息內(nèi)容: 目標(biāo)用戶:消息內(nèi)容
String msg = readMsg();
//對msg進(jìn)行解析:拆分
// 字符串分割
String[] msgArr = msg.split(":");
System.out.println(msgArr[0] + ":" + msgArr[1]);
if(msgArr[0].equals("g")){
Collection<Socket> values=mp.values();
for (Socket value : values) {
//不將消息發(fā)給自己
if(value != this.socket) {
System.out.println("值:" + value);
OutputStream output = value.getOutputStream();
sendMsg(msgArr[1], output);
}
}
}else{
//根據(jù)目標(biāo)用戶(ID)從mp中找到對應(yīng)的socket(聊天對象)
int id = Integer.parseInt(msgArr[0]); //字符串?dāng)?shù)字轉(zhuǎn)成int
Socket socket = mp.get(id);
//獲取該對象的輸出流,寫入數(shù)據(jù)
OutputStream output = socket.getOutputStream();
//把聊天內(nèi)容轉(zhuǎn)發(fā)給目標(biāo)用戶(聊天對象)
sendMsg(msgArr[1], output);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//發(fā)送消息的方法
public void sendMsg(String msg,OutputStream os){
try {
byte[] b = msg.getBytes();
os.write(b);
os.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//讀取消息的方法
public String readMsg(){
byte[] b = new byte[1024]; //最多讀取的消息長度
try {
is.read(b);
} catch (IOException e) {
throw new RuntimeException(e);
}
// trim() 去掉字符尾部空格
String s=new String(b);
return s.trim();
}
}知識點(diǎn)解釋說明:
InputStream:從套接字讀取消息(接收消息)
OutputStream:向套接字寫入數(shù)據(jù)(發(fā)送消息)
私聊:
客戶端1給客戶端3發(fā)消息,讀取客戶端1消息,獲得客戶端3輸出流 ,將消息發(fā)給客戶端3
群聊:
客戶端1將消息發(fā)送給除自己以外所有客戶端,接收客戶端1消息,獲得其他所有客戶端輸出流,將消息發(fā)送給其他所有客戶端
2.消息格式
String[] msgArr = msg.split(":");
System.out.println(msgArr[0] + ":" + msgArr[1]);split()分隔符
返回值:分割后的字符串?dāng)?shù)組
+操作符用于字符串連接,將數(shù)組第一個元素、冒號分隔符、第二個元素連接成一個新字符串
3.消息協(xié)議
群聊:g:消息內(nèi)容
私聊:目標(biāo)ID:消息內(nèi)容
4.遍歷HashMap中所有數(shù)據(jù)
因?yàn)楸揪幊绦枰闅v套接字,查看原代碼socket是values,所以用values方法遍歷
代碼示例:
import java.util.Collection;
import java.util.HashMap;
public class HashMapTraversal {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
Collection<Integer> values = map.values();
for (Integer value : values) {
System.out.println("Value: " + value);
}
}
}通過values()獲取所有值的集合,適合只關(guān)心值的場景。
3.Client類-客戶端程序
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args){
try {
new Client().startClient();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public InputStream is;
public OutputStream os;
//啟動客戶端
public void startClient() throws IOException{
Socket client =new Socket("127.0.0.1",8888);
os=client.getOutputStream();
is=client.getInputStream();
new Thread(()->{
while(true){
//讀取服務(wù)器消息
String msg=readMsg();
System.out.println(msg);
}
}).start();
//發(fā)消息
Scanner scanner=new Scanner(System.in);
while(true){
System.out.println("client:");
String clientMsg=scanner.nextLine();
sendMsg(clientMsg);
}
}
//發(fā)送消息的方法
public void sendMsg(String msg){
try {
os.write(msg.getBytes());
os.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String readMsg(){
byte[] b=new byte[1024];
try {
//阻塞方法:一直等待讀取消息,如果沒有會一直等著
is.read(b);
} catch (IOException e) {
throw new RuntimeException(e);
}
//trim
return new String(b).trim();
}
}知識點(diǎn)解釋說明:
1.Socket客戶端連接
127.0.0.1:本地回環(huán)地址,用于本地測試
8888:服務(wù)端監(jiān)聽端口
2.多線程消息處理
接收線程:
new Thread(()->{
while(true){
//讀取服務(wù)器消息
String msg=readMsg();
System.out.println(msg);
}
}).start();· 持續(xù)監(jiān)聽服務(wù)器發(fā)來的消息
發(fā)送消息:
Scanner scanner=new Scanner(System.in);
while(true){
System.out.println("client:");
String clientMsg=scanner.nextLine();
sendMsg(clientMsg);
}
}Scanner用于讀取控制臺輸入
nextLine()是阻塞方法,等待用戶輸入
補(bǔ)充:
服務(wù)端為什么使用線程:服務(wù)端要保持與多個客戶端通信,每個客戶端都需要一個單獨(dú)的線程來控制通信,不然沒有辦法進(jìn)行消息的收發(fā)
客戶端為什么使用線程:讀消息是一個阻塞方法,如果讀消息發(fā)消息沒有單獨(dú)的線程去控制,他就只能按順序收發(fā),無法連續(xù)發(fā)消息 ,想要連續(xù)發(fā)消息,就沒有辦法控制,必須要有單獨(dú)的線程去控制讀和寫
4.使用指南
1.啟動服務(wù)器
2.啟動客戶端(可啟動多個)
3.消息發(fā)送格式
群發(fā)消息:g:hello
私聊消息:3:nihao
測試:
群聊
客戶端1輸入:g;hello
客戶端2顯示:hello
客戶端3顯示:hello
私聊
客戶端1輸入:3:nihao
客戶端2顯示:無顯示
客戶端3顯示:nihao
總結(jié)
這個Java聊天室項(xiàng)目展示了Socket編程的核心概念,包括服務(wù)器監(jiān)聽、客戶端連接、多線程處理和消息路由。雖然功能相對基礎(chǔ),但架構(gòu)清晰,為擴(kuò)展更復(fù)雜的功能提供了良好的基礎(chǔ)。
通過這個項(xiàng)目,可以深入理解網(wǎng)絡(luò)編程、多線程同步和客戶端-服務(wù)器架構(gòu)的設(shè)計模式,是學(xué)習(xí)Java網(wǎng)絡(luò)編程的優(yōu)秀實(shí)踐案例。
后續(xù)可以再進(jìn)行擴(kuò)展升級,例如添加注冊登錄及聊天界面。
到此這篇關(guān)于Java Socket編程實(shí)現(xiàn)群聊的文章就介紹到這了,更多相關(guān)Java Socket群聊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- WebSocket(java版)服務(wù)核心實(shí)例代碼
- Java通過ServerSocket與Socket實(shí)現(xiàn)通信過程
- java網(wǎng)絡(luò)編程之socket網(wǎng)絡(luò)編程示例(服務(wù)器端/客戶端)
- 使用Java和WebSocket實(shí)現(xiàn)網(wǎng)頁聊天室實(shí)例代碼
- java使用Socket類接收和發(fā)送數(shù)據(jù)
- java socket編程實(shí)例代碼講解
- 簡單的java socket客戶端和服務(wù)端示例
- Java后端Tomcat實(shí)現(xiàn)WebSocket實(shí)例教程
- 教你怎么使用Java實(shí)現(xiàn)WebSocket
- Java Socket通信(一)之客戶端程序 發(fā)送和接收數(shù)據(jù)
- 基于Java Socket實(shí)現(xiàn)一個簡易在線聊天功能(一)
- Java網(wǎng)絡(luò)編程:Socket套接字、TCP和UDP協(xié)議以及Java高性能實(shí)戰(zhàn)
相關(guān)文章
SpringBoot整合mybatis-plus實(shí)現(xiàn)分頁查詢功能
這篇文章主要介紹了SpringBoot整合mybatis-plus實(shí)現(xiàn)分頁查詢功能,pringBoot分頁查詢的兩種寫法,一種是手動實(shí)現(xiàn),另一種是使用框架實(shí)現(xiàn),現(xiàn)在我將具體的實(shí)現(xiàn)流程分享一下,需要的朋友可以參考下2023-11-11
Spring Data JPA踩坑記錄(@id @GeneratedValue)
這篇文章主要介紹了Spring Data JPA踩坑記錄(@id @GeneratedValue),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
如何使用Spring Validation優(yōu)雅地校驗(yàn)參數(shù)
這篇文章主要介紹了如何使用Spring Validation優(yōu)雅地校驗(yàn)參數(shù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
Java中創(chuàng)建線程池的幾種方式以及區(qū)別
創(chuàng)建線程池有多種方式,主要通過 Java 的 java.util.concurrent 包提供的 Executors 工具類來實(shí)現(xiàn),本文給大家介紹了幾種常見的線程池類型及其區(qū)別,并通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-11-11

