java基于TCP協(xié)議實現(xiàn)聊天程序
JAVA程序設(shè)計之基于TCP協(xié)議的socket聊天程序 ,供大家參考,具體內(nèi)容如下
一、程序?qū)崿F(xiàn)的功能
1、進入客戶端界面
2、創(chuàng)建昵稱
3、群發(fā)信息
4、@私聊
5、下線通知
6、在線人數(shù)統(tǒng)計
二、整體架構(gòu)圖
三、簡單介紹
本程序?qū)崿F(xiàn)了基于TCP通信的聊天程序:
1 服務(wù)器端:
服務(wù)器端繼承JFrame框架,添加組件。創(chuàng)建服務(wù)器端的socket,起一個線程池,每接收到一個客戶端的連接,分配給其一個線程處理與客戶端的通信,將每個客戶端的昵稱和服務(wù)器分配給其的輸出流存儲到哈希表中。通過檢索哈希表中昵稱和輸出流實現(xiàn)群聊和私聊。
2 客戶端:
客戶端繼承JFrame框架,添加組件。創(chuàng)建客戶端的socket,輸入昵稱,創(chuàng)建客戶端socket對應(yīng)的輸入流輸出流,與服務(wù)器端建立通訊。
四、設(shè)計描述
本程序的實現(xiàn)是以服務(wù)器端為中繼,客戶端的所有信息都先發(fā)送給服務(wù)器端。服務(wù)器端辨別是否為私聊信息,私聊信息發(fā)送給相應(yīng)的私聊對象,否則,發(fā)送給所有的客戶端對象。
(一) 服務(wù)器端:
1.1 服務(wù)器端繼承JFrame框架,添加組件。
1.2 創(chuàng)建服務(wù)器端socket。建立一個哈希表,用于存儲客戶端的昵稱以及服務(wù)器端對于每個連接的客戶端建立的輸出流。
1.3建立一個線程池,為每個連接的客戶端分配一個執(zhí)行線程。
1.3調(diào)用服務(wù)器端Socket的accept()函數(shù),等待客戶端的連接,每連接到一個客戶端,線程池給相應(yīng)的客戶端分配一個線程。
1.4每個線程內(nèi),調(diào)用服務(wù)器端Socket,封裝getOutputStream(),獲得服務(wù)器端Socket相對應(yīng)于連接的客戶端的輸出流和輸入流。輸入流首先讀取到客戶端發(fā)送來的昵稱。將客戶端的昵稱和服務(wù)器端的輸出流存儲在哈希表中,并遍歷整個哈希表,將某人上線的通知發(fā)送給所有的在線客戶端。
1.5客戶端將信息發(fā)送給服務(wù)器端,當(dāng)服務(wù)器端傳來的信息符合“@私聊對象:私聊信息”的格式時,服務(wù)器端認(rèn)為這是私聊信息。服務(wù)器端將把私聊對象的昵稱從信息中提取出來,通過哈希表找到與昵稱對應(yīng)的輸出流。將私聊信息通過輸出流發(fā)送給私聊對象。
當(dāng)不是私聊信息時,服務(wù)器遍歷整個哈希表,通過每個客戶端對應(yīng)的輸出流發(fā)送給每個客戶端。
1.6當(dāng)客戶端下線時,服務(wù)器端將移除哈希表中對應(yīng)存儲的客戶端昵稱以及輸出流。并通知在線的每個客戶端,某人已下線。
(二) 客戶端:
2.1 客戶端繼承框架JFrame,添加各種組件。
2.2 創(chuàng)建客戶端Socket,設(shè)置請求連接服務(wù)器端IP,以及使用的端口號。
2.3 封裝客戶端Socket的getInputStream()函數(shù),獲得客戶端Socket的輸入流接受服務(wù)器端發(fā)來的信息,封裝Socket的getOutputStream()函數(shù),獲得Socket的輸出流向服務(wù)器端發(fā)送信息。
2.4 通過向文本框添加動作事件監(jiān)聽器,監(jiān)聽文本框的輸入。
2.5 當(dāng)與服務(wù)器端連接成功時,系統(tǒng)提醒輸入昵稱。系統(tǒng)將對輸入的昵稱進行檢索。判斷是否有重復(fù)的昵稱。如有重復(fù)則創(chuàng)建不成功,繼續(xù)輸入。
2.6 向服務(wù)器端發(fā)送信息時,如想對某人發(fā)送私聊信息,則需輸入符合“@私聊對象的呢稱:私聊信息”的格式,此時只有私聊對象和本人可以接收到。否則,發(fā)送的信息為公聊信息,所有的客戶端都能夠收到。
2.7 客戶端不斷接受服務(wù)器端的信息顯示在文本域。
五、代碼說話
1、服務(wù)器端:
package server; import java.io.*; import java.net.*; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import javax.swing.*; import java.awt.*; public class TCPServer extends JFrame{ private JTextArea m_display=new JTextArea(); private ServerSocket serverSocket; /** * 創(chuàng)建線程池來管理客戶端的連接線程 * 避免系統(tǒng)資源過度浪費 */ private ExecutorService exec; // 存放客戶端之間私聊的信息 private Map<String,PrintWriter> storeInfo; public TCPServer() { super("聊天程序服務(wù)器端"); Container c=getContentPane(); c.add(new JScrollPane(m_display),BorderLayout.CENTER); try { serverSocket = new ServerSocket(6666); storeInfo = new HashMap<String, PrintWriter>(); exec = Executors.newCachedThreadPool(); } catch (Exception e) { e.printStackTrace(); } } // 將客戶端的信息以Map形式存入集合中 private void putIn(String key,PrintWriter value) { synchronized(this) { storeInfo.put(key, value); } } // 將給定的輸出流從共享集合中刪除 private synchronized void remove(String key) { storeInfo.remove(key); m_display.append("當(dāng)前在線人數(shù)為:"+ storeInfo.size()); //for(String name: storeInfo.key) } // 將給定的消息轉(zhuǎn)發(fā)給所有客戶端 private synchronized void sendToAll(String message) { for(PrintWriter out: storeInfo.values()) { out.println(message); // m_display.append("已經(jīng)發(fā)送了"); } } // 將給定的消息轉(zhuǎn)發(fā)給私聊的客戶端 private synchronized void sendToSomeone(String name,String message) { PrintWriter pw = storeInfo.get(name); //將對應(yīng)客戶端的聊天信息取出作為私聊內(nèi)容發(fā)送出去 if(pw != null) pw.println("私聊: "+message); } public void start() { try { m_display.setVisible(true); //m_display.append("mayanshuo"); while(true) { m_display.append("等待客戶端連接... ... \n"); Socket socket = serverSocket.accept(); // 獲取客戶端的ip地址 InetAddress address = socket.getInetAddress(); m_display.append("客戶端:“" + address.getHostAddress() + "”連接成功! "); /* * 啟動一個線程,由線程來處理客戶端的請求,這樣可以再次監(jiān)聽 * 下一個客戶端的連接 */ exec.execute(new ListenrClient(socket)); //通過線程池來分配線程 } } catch(Exception e) { e.printStackTrace(); } } /** * 該線程體用來處理給定的某一個客戶端的消息,循環(huán)接收客戶端發(fā)送 * 的每一個字符串,并輸出到控制臺 */ class ListenrClient implements Runnable { private Socket socket; private String name; public ListenrClient(Socket socket) { this.socket = socket; } // 創(chuàng)建內(nèi)部類來獲取昵稱 private String getName() throws Exception { try { //服務(wù)端的輸入流讀取客戶端發(fā)送來的昵稱輸出流 BufferedReader bReader = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8")); //服務(wù)端將昵稱驗證結(jié)果通過自身的輸出流發(fā)送給客戶端 PrintWriter ipw = new PrintWriter( new OutputStreamWriter(socket.getOutputStream(), "UTF-8"),true); //讀取客戶端發(fā)來的昵稱 while(true) { String nameString = bReader.readLine(); if ((nameString.trim().length() == 0) || storeInfo.containsKey(nameString)) { ipw.println("FAIL"); } else { ipw.println("OK"); return nameString; } } } catch(Exception e) { throw e; } } @Override public void run() { try { /* * 通過服務(wù)器端的socket分配給每一個 * 用來將消息發(fā)送給客戶端 */ PrintWriter pw = new PrintWriter( new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true); /* * 將客戶昵稱和其所說的內(nèi)容存入共享集合HashMap中 */ name = getName(); putIn(name, pw); Thread.sleep(100); // 服務(wù)端通知所有客戶端,某用戶上線 sendToAll("*系統(tǒng)消息* “" + name + "”已上線"); /* * 通過客戶端的Socket獲取輸入流 * 讀取客戶端發(fā)送來的信息 */ BufferedReader bReader = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8")); String msgString = null; while((msgString = bReader.readLine()) != null) { // 檢驗是否為私聊(格式:@昵稱:內(nèi)容) if(msgString.startsWith("@")) { int index = msgString.indexOf(":"); if(index >= 0) { //獲取昵稱 String theName = msgString.substring(1, index); String info = msgString.substring(index+1, msgString.length()); info = name + ":"+ info; //將私聊信息發(fā)送出去 sendToSomeone(theName, info); sendToSomeone(name,info); continue; } } // 遍歷所有輸出流,將該客戶端發(fā)送的信息轉(zhuǎn)發(fā)給所有客戶端 m_display.append(name+":"+ msgString+"\n"); sendToAll(name+":"+ msgString); } } catch (Exception e) { // e.printStackTrace(); } finally { remove(name); // 通知所有客戶端,某某客戶已經(jīng)下線 sendToAll("*系統(tǒng)消息* "+name + "已經(jīng)下線了。\n"); if(socket!=null) { try { socket.close(); } catch(IOException e) { e.printStackTrace(); } } } } } public static void main(String[] args) { TCPServer server = new TCPServer(); server.setSize(400,400); server.setVisible(true); server.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); server.start(); } }
2、客戶端:
package server; import java.io.*; import java.net.*; import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import javax.swing.*; import java.awt.*; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; public class TCPClient extends JFrame { private JTextField m_enter=new JTextField(); private JTextArea m_display=new JTextArea(); private int m_count=0; private static Socket clientSocket; //private ExecutorService exec = Executors.newCachedThreadPool(); private BufferedReader br; private PrintWriter pw; public TCPClient() { super("聊天程序客戶端"); Container c=getContentPane(); //m_enter.setSize(100,99); //m_display.setSize(200,100); m_enter.setVisible(true); m_display.setVisible(true); m_enter.requestFocusInWindow(); //轉(zhuǎn)移輸入焦點到輸入?yún)^(qū)域 //將光標(biāo)放置在文本區(qū)域的尾部 m_display.setCaretPosition(m_display.getText().length()); c.add(m_enter,BorderLayout.SOUTH); c.add(new JScrollPane(m_display),BorderLayout.CENTER); // this.add(panel); // this.setContentPane(jp); } public static void main(String[] args) throws Exception { TCPClient client = new TCPClient(); client.setVisible(true); client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); client.setSize(470,708); client.start(); } public void start() { try { m_display.append("請創(chuàng)建用戶名:"); clientSocket=new Socket("localhost",6666); BufferedReader br = new BufferedReader( new InputStreamReader(clientSocket.getInputStream(), "UTF-8")); PrintWriter pw = new PrintWriter( new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"), true); //ListenrServser l=new ListenrServser(); m_enter.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent event) { try{ String s=event.getActionCommand(); m_enter.setText(""); if(m_count==0) { pw.println(s); m_display.append("\n'"+s+"'"+"昵稱設(shè)置成功。\n"); } else { pw.println(s); } m_count++; }catch(Exception e) { e.printStackTrace(); } } }); String msgString; while((msgString = br.readLine())!= null) { m_display.append(msgString+"\n"); } } catch(Exception e) { e.printStackTrace(); } finally { if (clientSocket !=null) { try { clientSocket.close(); } catch(IOException e) { e.printStackTrace(); } } } } }
六、運行結(jié)果
1、這里是服務(wù)器端,顯示當(dāng)前連接人數(shù),以及公聊信息:
2、此時為群內(nèi)成員公聊:
這里創(chuàng)建了三個角色:馬衍碩、李琦琦、小紅。
3、私聊:
私聊格式“@名稱:”+內(nèi)容。
私聊時,只有私聊的兩個人可以接收到信息,其余人接收不到交流信息。
例:馬衍碩和李琦琦私聊,小紅接收不到私聊信息。
馬衍碩和李琦琦接收到了私聊信息:
小紅沒有接收到私聊信息:
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- java中UDP簡單聊天程序?qū)嵗a
- 詳解基于java的Socket聊天程序——客戶端(附demo)
- java網(wǎng)絡(luò)編程學(xué)習(xí)java聊天程序代碼分享
- java基于C/S模式實現(xiàn)聊天程序(客戶端)
- 詳解基于java的Socket聊天程序——服務(wù)端(附demo)
- java實現(xiàn)基于Tcp的socket聊天程序
- 詳解基于java的Socket聊天程序——初始設(shè)計(附demo)
- java實現(xiàn)簡單TCP聊天程序
- 基于Java的Socket多客戶端Client-Server聊天程序的實現(xiàn)
- 用Java實現(xiàn)聊天程序
相關(guān)文章
Java8 Stream API 詳細(xì)使用方法與操作技巧指南
這篇文章主要介紹了Java8 Stream API 詳細(xì)使用方法與操作技巧,總結(jié)分析了Java8 Stream API 基本功能、使用方法與操作注意事項,需要的朋友可以參考下2020-05-05Shiro + JWT + SpringBoot應(yīng)用示例代碼詳解
這篇文章主要介紹了Shiro (Shiro + JWT + SpringBoot應(yīng)用),本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06Java多態(tài)實現(xiàn)原理詳細(xì)梳理總結(jié)
這篇文章主要介紹了Java多態(tài)實現(xiàn)原理詳細(xì)梳理總結(jié),多態(tài)是繼封裝、繼承之后,面向?qū)ο蟮牡谌筇匦裕疚闹豢偨Y(jié)了多態(tài)的實現(xiàn)原理,需要的朋友可以參考一下2022-06-06基于Java數(shù)組實現(xiàn)循環(huán)隊列的兩種方法小結(jié)
下面小編就為大家分享一篇基于Java數(shù)組實現(xiàn)循環(huán)隊列的兩種方法小結(jié),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12spring boot中多線程開發(fā)的注意事項總結(jié)
spring boot 通過任務(wù)執(zhí)行器 taskexecutor 來實現(xiàn)多線程和并發(fā)編程。下面這篇文章主要給大家介紹了關(guān)于spring boot中多線程開發(fā)的注意事項,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-09-09spring中使用@Autowired注解無法注入的情況及解決
這篇文章主要介紹了spring中使用@Autowired注解無法注入的情況及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09