Java如何实现不同局域网TCP通信+群聊+私聊(云服务器实现)
繼上一篇實現群聊,現在記錄私聊的實現,下一篇記錄一下實現文件的傳輸。
socket keepalive理解 - 小小小小濤 - 博客園 (cnblogs.com)
目錄
一、實現思路
1.實現思路一
2.實現思路二
二、實現代碼
1.思路一(服務端代碼)
2.思路二(服務端代碼)
三、運行效果
一、實現思路
用戶輸入@name的方式發送消息(類似于QQ你@別人),但是想通過服務器端從信息中解析出這個@的人的名字不太可能,因為服務器端不知道截多長才是用戶的名字。所以只能判斷信息中是否包含了@name。String類的contains()方法可以做到。
服務端有以下兩種思路去實現私聊
1.實現思路一
先判斷用戶是否@人了。
如果@人了,則發送消息的用戶的服務套接字讓其他的服務套接字判斷這個@的名字是不是自己服務的用戶的名字,如果是就轉發,不是就不轉發。
如果沒@,則全部轉發。
2.實現思路二
先判斷用戶是否@人了。
如果@人了,發送消息的用戶的服務套接字根據@的名字讓該名字的服務套接字轉發。(哈希表)
如果沒@,則全部轉發。
二、實現代碼
我們的用戶端只負責輸入,因此只需要改變服務端的代碼。(這里也只記錄服務器端的代碼,用戶端的代碼和工具類代碼在上一篇)。
1.思路一(服務端代碼)
import java.io.Closeable; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List;import com.csi.qunliaoTest.OpenClient.ReceiveMsgThread;public class OpenServer {//一個列表存儲服務SocketList<ClientSocket> clientSockets =new ArrayList<>(); ServerSocket serverSocket = null; //構造方法中創建服務器ServerSocketpublic OpenServer() {try {//因為本代碼是在云服務器運行,直接用InetAddress.getLocalHost()方法綁定云服務器的IP地址及端口號serverSocket = new ServerSocket(7777, 50, InetAddress.getLocalHost()); System.out.println("----------服務器----------");} catch (IOException e) {//發生異常。調用自己寫的Utils類關閉服務器Utils.close(serverSocket);}//ServerSocket綁定成功,開始等待用戶接入if(serverSocket.isBound())acceptClient();}//等待用戶,并為他生成服務Socketprivate void acceptClient(){//while循環讓服務器一直可以接入用戶,一個用戶接入服務器,服務器就生成一個為該用戶服務的Socketwhile(true) {Socket socket=null;try {//ServerScoket的accept()方法是一個阻塞方法,他會在這里等用戶接入,直到有用戶接入,才會運行下面的代碼socket=serverSocket.accept();System.out.println("一個用戶接入.....");//用ClientSocket類包裝Scoket類,ClientSocket類是自己寫的內部線程類,該類實現了接收和轉發用戶消息ClientSocket clientSocket=new ClientSocket(socket);//開啟服務線程clientSocket.start();//將這個包裝了服務Scoket的對象添加進列表clientSockets.add(clientSocket);} catch (IOException e) {Utils.close(socket);} }}public static void main(String[] args) {//執行服務器代碼new OpenServer();}class ClientSocket extends Thread{Socket socket=null;DataInputStream dataInputStream=null;DataOutputStream dataOutputStream=null;String name = null;public ClientSocket(Socket socket) {this.socket = socket;//包裝服務Socket的輸入輸出流,異常就調用closeScoket()方法try {dataInputStream=new DataInputStream(socket.getInputStream());} catch (IOException e) {closeSocket(dataInputStream,socket);}try {dataOutputStream=new DataOutputStream(socket.getOutputStream());} catch (IOException e) {closeSocket(dataOutputStream,socket);}}//上面的代碼我們關閉的都是裝飾流,因為關閉裝飾流會將內部流也關閉,Socket也會因此關閉,同時我們也要將列表里對應Socket的刪除public void closeSocket(Closeable...closeables) {Utils.close(closeables);System.out.println("一位用戶退出");clientSockets.remove(this);}@Overridepublic void run() {//讀取用戶姓名,并讓列表所有用戶轉發歡迎信息//數據流DataIn/OutputStream的readUTF()和writeUTF(String data)要一起用,是將數據以UTF-8的編碼方式發出或者接收try {//readUTF()方法也是阻塞方法,讀取用戶發來的名字name=dataInputStream.readUTF();} catch (IOException e1) {closeSocket(dataInputStream);}//循環列表,除了自己,其他服務Scoket全部轉發消息for(ClientSocket clientSocket:clientSockets) {if(clientSocket!=this) {try {clientSocket.dataOutputStream.writeUTF("歡迎"+name+"進入聊天室");clientSocket.dataOutputStream.flush();//清空緩存區,讓緩存區的數據全部出來} catch (IOException e) {clientSocket.closeSocket(clientSocket.dataOutputStream);}}}//知道名字后就一直等待接收用戶端發的消息,異常就關閉Scoket并跳出循環while(true) { String msg = null;//在讀取到的消息前加上姓名try {msg = name+":"+dataInputStream.readUTF();} catch (IOException e) {closeSocket(dataInputStream);break;}//判斷信息中是否@人了,如果@人了,其他每個服務Socket判斷@的名字里是不是自己服務的用戶名字;如果沒有@,則全部轉發if(msg.contains("@")) {for(ClientSocket clientSocket:clientSockets) {if(clientSocket!=this) {try {if(msg.contains("@"+clientSocket.name)) { //找到了是自己的用戶端名字//將@名字替換為空,replace返回一個心得字符串clientSocket.dataOutputStream.writeUTF(msg.replace("@"+clientSocket.name, ""));clientSocket.dataOutputStream.flush();}} catch (IOException e) {clientSocket.closeSocket(clientSocket.dataOutputStream);}}}}else {for(ClientSocket clientSocket:clientSockets) {if(clientSocket!=this) {try {clientSocket.dataOutputStream.writeUTF(msg);clientSocket.dataOutputStream.flush();} catch (IOException e) {clientSocket.closeSocket(clientSocket.dataOutputStream);}}}}}}}}2.思路二(服務端代碼)
用戶名不要寫一樣的名字,他是根據名字找套接字,當查找到這個名字就去哈希表找對應的套接字,如果名字相同,他只會找第一個與這個名字相同的套接字。
package com.csi.siliaoTest;import java.io.Closeable; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.List;import com.csi.siliaoTest.OpenServer.ClientSocket;public class OpenServer2 {//一個哈希表存儲用戶名字和服務套接字HashMap<String, ClientSocket> map= new HashMap<>();//一個列表存儲服務SocketList<ClientSocket> clientSockets =new ArrayList<>();//一個列表存儲用戶名字List<String> names = new ArrayList<>();ServerSocket serverSocket = null; //構造方法中創建服務器ServerSocketpublic OpenServer2() {try {//因為本代碼是在云服務器運行,直接用InetAddress.getLocalHost()方法綁定云服務器的IP地址及端口號serverSocket = new ServerSocket(7777, 50, InetAddress.getLocalHost()); System.out.println("----------服務器----------");} catch (IOException e) {//發生異常。調用自己寫的Utils類關閉服務器Utils.close(serverSocket);}//ServerSocket綁定成功,開始等待用戶接入if(serverSocket.isBound())acceptClient();}//等待用戶,并為他生成服務Socketprivate void acceptClient(){//while循環讓服務器一直可以接入用戶,一個用戶接入服務器,服務器就生成一個為該用戶服務的Socketwhile(true) {Socket socket=null;try {//ServerScoket的accept()方法是一個阻塞方法,他會在這里等用戶接入,直到有用戶接入,才會運行下面的代碼socket=serverSocket.accept();System.out.println("一個用戶接入.....");//用ClientSocket類包裝Socket類,ClientSocket類是自己寫的內部線程類,該類實現了接收和轉發用戶消息ClientSocket clientSocket=new ClientSocket(socket);//開啟服務線程clientSocket.start();//將這個包裝了服務Socket的對象添加進列表clientSockets.add(clientSocket);} catch (IOException e) {Utils.close(socket);} }}public static void main(String[] args) {//執行服務器代碼new OpenServer2();}class ClientSocket extends Thread{Socket socket=null;DataInputStream dataInputStream=null;DataOutputStream dataOutputStream=null;String name = null;public ClientSocket(Socket socket) {this.socket = socket;//包裝服務Socket的輸入輸出流,異常就調用closeScoket()方法try {dataInputStream=new DataInputStream(socket.getInputStream());} catch (IOException e) {closeSocket(dataInputStream,socket);}try {dataOutputStream=new DataOutputStream(socket.getOutputStream());} catch (IOException e) {closeSocket(dataOutputStream,socket);}}//上面的代碼我們關閉的都是裝飾流,因為關閉裝飾流會將內部流也關閉,Socket也會因此關閉,同時我們也要將列表里對應Socket的刪除public void closeSocket(Closeable...closeables) {Utils.close(closeables);System.out.println("一位用戶退出");clientSockets.remove(this);names.remove(name);map.remove(name);}@Overridepublic void run() {//讀取用戶姓名,并讓列表所有用戶轉發歡迎信息//數據流DataIn/OutputStream的readUTF()和writeUTF(String data)要一起用,是將數據以UTF-8的編碼方式發出或者接收try {//readUTF()方法也是阻塞方法,讀取用戶發來的名字并添加哈希表和列表name=dataInputStream.readUTF();names.add(name);map.put(name, this);} catch (IOException e1) {closeSocket(dataInputStream);}//循環列表,除了自己,其他服務Socket全部轉發消息for(ClientSocket clientSocket:clientSockets) {if(clientSocket!=this) {try {clientSocket.dataOutputStream.writeUTF("歡迎"+name+"進入聊天室");clientSocket.dataOutputStream.flush();//清空緩存區,讓緩存區的數據全部出來} catch (IOException e) {clientSocket.closeSocket(clientSocket.dataOutputStream);}}}//知道名字后就一直等待接收用戶端發的消息,異常就關閉Scoket并跳出循環while(true) { String msg = null;//在讀取到的消息前加上姓名try {msg = name+":"+dataInputStream.readUTF();} catch (IOException e) {closeSocket(dataInputStream);break;}//判斷信息中是否@人了,如果@人了,查找是否有@的這個名字的用戶if(msg.contains("@")) {ClientSocket clientSocket = null;for(String aname: names) {if (msg.contains("@"+aname)) {try {clientSocket =map.get(aname);clientSocket.dataOutputStream.writeUTF(msg.replace("@"+aname, ""));clientSocket.dataOutputStream.flush();} catch (IOException e) {clientSocket.closeSocket(clientSocket.dataOutputStream);}}}}else {for(ClientSocket clientSocket:clientSockets) {if(clientSocket!=this) {try {clientSocket.dataOutputStream.writeUTF(msg);clientSocket.dataOutputStream.flush();} catch (IOException e) {clientSocket.closeSocket(clientSocket.dataOutputStream);}}}}}}}}注意:不要寫一樣的用戶名字,他是在哈希表里根據名字找套接字,如果名字相同,他只會找第一個與這個名字相同的套接字。如果寫一樣得名字就只有一個用戶會收到@的信息,并且會收到好幾條(有幾個重名就有幾條)。如下圖,我寫了兩個張三。
三、運行效果
總結
以上是生活随笔為你收集整理的Java如何实现不同局域网TCP通信+群聊+私聊(云服务器实现)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网站死链查询检测方法(seo的优化工作全
- 下一篇: JavaGUI--模拟QQ聊天界面私聊群