一個多人聊天工具(C/S結構),實現了以下功能:前端
一個可視化窗口,支持鼠標點擊事件java
註冊功能,用戶能夠註冊本身的聊天帳號,mysql
註冊信息包括:web
帳號名(能夠用姓名來替代帳號,支持中文),sql
密碼(聊天框輸入時,會自動隱藏密碼),數據庫
個性簽名(設置座右銘)apache
成功登陸帳號後,進入好友列表頁面,登錄成功後其餘用戶會收到該用戶的登陸成功的消息,可是本人不可見(模仿實際qq登陸的場景)編程
能夠建立羣聊,拉指定的好友聊天,也能夠私聊json
用到的工具:IDEA後端
主要用到的技術:Json字符串,MySQL Jdbc、druid鏈接池,Socket編程,Java Swing,Java awt,Juc,反射,IO、單元測試
爲何用到json字符串?
爲何用到json?什麼是json(json是一種字符串)
先後端交互的標準
str{"1":"test"} key,value id爲1,用戶名爲test
至關於給服務器發送的特殊字符,而後把特殊字符解析作的一個判斷
羣聊字符串:G:msg
私聊字符串:P:username-msg
將對象序列化爲字符串再將字符串還原爲對象,由於若是不用json處理,那麼帶對話框界面時字符串將會很是複雜,
那麼對字符串處理會很麻煩,用到json會很簡單
如何添加json?
谷歌的gson庫
全部第三方建立,成熟的開源庫,建立者模式
private static final Gson GSON = new GsonBuilder().create();
json字符串:帶界面後更復雜,字符串拆分比較複雜,{"1":"value",}
json序列化 Object -> json字符串 反序列化 json -> Object
客戶端 (向服務器發送 O -> json;接收服務器,json ->O) <-> 服務器(向客戶端發送,O變爲json;接收客戶端 json變爲O)
Java序列化 把對象編程二進制流,一個個字節
判斷普通字符串和json字符串?
只要字符串開頭不是{}就是普通字符串
{"type":"1","content":".."}
深拷貝與淺拷貝:
深拷貝:好比說student類中包含了其餘類也建立了一個對象
淺拷貝:不創造新對象,與被複制的是一個對象
深淺拷貝和正反序列化的關係?
//序列化都是深拷貝 //{"id":1,"password":"999","brief":"帥","usernames":["test4","test2","test3"]} @Test public void object2Json(){ User user = new User(); user.setId(1); user.setPassword("999"); user.setBrief("帥"); Set<String> strings = new HashSet<>(); strings.add("test2"); strings.add("test3"); strings.add("test4"); user.setUsernames(strings); String str = CommUtils.object2Json(user); System.out.println(str); }
//反序列化是淺拷貝 //["test4","test2","test3"] @Test public void json2object(){ String jsonStr = "{\"id\":1,\"password\":\"999\",\"brief\":\"帥\"," + "\"usernames\":[\"test4\",\"test2\",\"test3\"]}"; User user = (User) CommUtils.json2object(jsonStr,User.class);//強轉 System.out.println(user.getUsernames()); }
第一步如何從java程序去操做數據庫?
數據源和傳統jdbc格式
傳統jdbc:第一步加載驅動 Class.forName("com.mysql.jdbc.Driver");
第二步獲取鏈接 Connection= DriverManager.getConnection(url)
第三步執行sql,String sql = "select id,name,created_time,modify_time from memo_group";
Statement(語句執行計劃):Statement statement = connection.createStatement();SQL注入漏洞 如何避免?
PreparedStatement
結果集,封裝查詢後的對象結果 resultSet = statement.executeQuery(sql);
CURD:查詢:ResultSet resultSet = statement.executeQuery(sql); String sql = "select id,name,created_time,modify_time from memo_group";
更新(插入)int effect = statement.executeUpdate(sql);String sql = "insert into map(keymap,valuemap) values ('4','2019-08-30 10:00:00')";
更新(刪除):int effect = statement.executeUpdate(sql); String sql = "delete from memo_group where id = 6661 ";
更新操做返回整形而不是結果集,告訴你受影響行數,若不跟where後綴,一行行刪,刪幾行返回幾 除了更新操做返回結果集,其他都是返回int
還有truncate操做?
誰快?
truncate,沒有返回值,怎麼刪,直接將表文件大小改成0,並且truncate操做不能回滾,delete from能夠回滾
將表打開一行行刪除
第四步、關閉資源(三個)
Connection
Statement
ResultSet
那麼除了傳統JDBC,還有數據源:datasource鏈接數據庫有何步驟區別?
除了第一步不同,其餘步驟全同樣
1.加載數據源(區別:類比線程池)(注意不是線程池)),二jdbc傳統格式能夠類比Thread類
要使用多線程通通用的線程池,有啥好處?1.線程池有空閒線程在等待任務,當須要用其執行任務的
時候少了建立銷燬過程,更快執行 2.當一堆線程被一個對象管理時,方便管理也方便監控
3.普通線程執行任務執行一次就完了,線程池線程能夠變廢爲寶,減小cpu資源開銷
類比結果:傳統jdbc調用connection.close()確實關了鏈接,每一個用戶都要建立一次,當用戶鏈接操做執行完後,關了
鏈接池放了一堆connection,當有用戶到達的時候,我已經建立好了,直接取就完了
當用戶調用數據源的connection.close,他並非將其鏈接關閉,而是將鏈接再放回池子裏,方便後續用戶複用
有哪些商用數據源?
c3p0(最先版本,已經不用了,效率很低)
druid(性能並非最高的,但功能徹底,方便監控)
Spring Hakari(Spring內置數據庫,效率很高)
用到的依賴:阿里巴巴數據源、Google-gson、junit(單元測試)庫、密碼加密包 commons-codec,apche(web 服務器軟件)組織的一個密碼加密包
加密工具用法:String password = DigestUtils.md5Hex("123");//加密密碼
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.13</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.6</version> </dependency> <!--密碼加密--> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.11</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.0</version> </dependency> </dependencies>
第一步:建立maven項目,建立maven框架 (用其搭好的框架):
org.jetbrains.idea.maven.model.MavenArchetype@88f75e0f
第二步:下載依賴
第三步:添加數據源:
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost/hhy_chatroom?charset=utf8&useSSL=false&allowPublicKeyRetrieval=true username=root password=hhyuan filters=stat initialSize=5 maxActive=30 maxWait=60000 timeBetweenEvictionRunsMillis=60000 minEvictableIdleTimeMillis=300000 validationQuery=SELECT 1 testWhileIdle=true testOnBorrow=false testOnReturn=false poolPreparedStatements=false
address=127.0.0.1
port=6666
配置文件在resources,與java同目錄,將其右鍵設置爲root resources目錄
建立database.properties和socket.properties(資源文件夾)
localhost 本機默認 3306 charset=utf8鏈接編碼集爲utf-8
鏈接底層爲TCP鏈接,http,https底層都爲TCP鏈接,SSL(安全通道),而若是是普通的tcp鏈接
一旦被監聽管道,用戶名和密碼將都會被看見,若是是SSL的話,是加密的,即使監聽管道,也看不到
通常狀況下,mysql要求咱們用安全鏈接(SSL),但因爲其比較麻煩,須要配置證書,通常而言,本地不須要
因此useSSL=false設置爲false
再來講一下其餘的參數
initialSize至關於線程中的核心池,默認創造五個鏈接給你連
maxWait=60000 數據源能夠空閒也能夠等待,但不能無限等待,防止無限等待
validationQuery=SELECT 1測試鏈接對仍是不對
第四步:
建一個類,寫一個封裝公共工具方法,加載配置文件、json序列化
第一步加載數據文件
咱們先把配置文件加載到程序中,不論是是數據庫操做仍是後面其餘操做,都須要加載配置文件
因此應將其做爲公共方法,CommUtils(封裝咱們公共工具的方法:加載配置文件、json序列化等)
哪些屬於公共方法?
SE部分,數組拷貝copy方法、排序(都是類名.方法名,由於全部程序都會用到)
package com.hhy.java.util; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * @Information:封裝公共工具方法,如加載配置文件、json序列化 * @Author: HeHaoYuan * @Date: Created at 13:48 on 2019/8/16 * @Package_Name: com.java.util */ public class CommUtils { //成熟的第三方開源庫,gson在谷歌庫上 private static final Gson GSON = new GsonBuilder().create(); /** * @Author: HeHaoYuan * @Date: 2019/8/16 * @Description: 加載配置文件名稱,fileName是其名稱 先獲取一個文件或者網絡輸入流 用最頂層的InputStream接受 */ //ctrl+shift+T 單元測試,由開發人員寫 public static Properties loadProperties(String fileName){ Properties properties = new Properties(); //獲取類加載器,加載指定文件變爲輸入流()只要資源文件和類在一個路徑下,就能夠加載它變爲輸入流 InputStream in = CommUtils.class.getClassLoader().getResourceAsStream(fileName);//核心代碼 try{ //加載資源 properties.load(in); } catch (IOException e) { return null; } return properties; } /** * @Author: HeHaoYuan * @Date: 2019/8/16 * @Description: 將任意字符串變爲json字符串 */ public static String object2Json(Object obj){ return GSON.toJson(obj); } /** * @Author: HeHaoYuan * @Date: \ * @Description:反序列化(反射原理) jsonStr json字符串 objClass 反序列化的類反射對象 */ public static Object json2object(String jsonStr,Class objClass){ return GSON.fromJson(jsonStr,objClass); } }
單元測試:對其是否可以成功的加載數據源,以及json的序列化和反序列化
package com.hhy; import com.hhy.java.client.entity.User; import com.hhy.java.util.CommUtils; import org.junit.Assert; import org.junit.Test; import java.util.HashSet; import java.util.Properties; import java.util.Set; /** * @Information: * @Author: HeHaoYuan * @Date: Created at 13:59 on 2019/8/16 * @Package_Name: com.java.util */ public class CommUtilsTest { @Test public void loadProperties() { String fileName = "datasource.properties"; Properties properties = CommUtils.loadProperties(fileName); // System.out.println(properties); Assert.assertNotNull(properties);//我認爲他不爲空 顯示結果若是爲綠色的√說明測試正確 //Assert } //序列化都是深拷貝 //{"id":1,"password":"999","brief":"帥","usernames":["test4","test2","test3"]} @Test public void object2Json(){ User user = new User(); user.setId(1); user.setPassword("999"); user.setBrief("帥"); Set<String> strings = new HashSet<>(); strings.add("test2"); strings.add("test3"); strings.add("test4"); user.setUsernames(strings); String str = CommUtils.object2Json(user); System.out.println(str); } //反序列化是淺拷貝 @Test public void json2object(){ String jsonStr = "{\"id\":1,\"password\":\"999\",\"brief\":\"帥\"," + "\"usernames\":[\"test4\",\"test2\",\"test3\"]}"; User user = (User) CommUtils.json2object(jsonStr,User.class); System.out.println(user.getUsernames()); } }
單元測試:咱們須要對是否能進行MySQL編程進行測試,測試功能爲插入數據(註冊功能),查詢數據(登陸驗證功能),針對查詢數據(登錄功能)又作了使用statement和preparestatement對比,
咱們選擇後者,防止sql注入,利用sql語句關鍵字、註釋,從而無需驗證就能登陸,這對用戶的隱私是不安全的
以後再關閉數據源
package com.hhy.java; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import com.hhy.java.util.CommUtils; import org.apache.commons.codec.digest.DigestUtils; import org.junit.Assert; import org.junit.Test; import java.sql.*; import java.util.Properties; /** * @Information: * @Author: HeHaoYuan * @Date: Created at 14:09 on 2019/8/16 * @Package_Name: com.java.java */ public class JDBCTest { private static DruidDataSource dataSource; //加載配置文件,隨着類加載運行,僅一次 static { Properties props = CommUtils.loadProperties("datasource.properties"); try { dataSource = (DruidDataSource)DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { e.printStackTrace(); } } //查詢 //八月 16, 2019 2:40:16 下午 com.alibaba.druid.pool.DruidDataSource info //信息: {dataSource-1} inited //阿里巴巴數據源打印,-1表示數據庫的一個鏈接 //id爲1,用戶名爲:java,密碼爲:123,簡介爲:帥 @Test public void testQuery(){ Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { connection = (Connection)dataSource.getPooledConnection(); String sql = "SELECT * FROM user WHERE username = ? AND password = ?"; statement = connection.prepareStatement(sql); String user = "hhl"; String pass = "123"; //爲何用preparedstatement?防止sql注入,因爲有佔位符存在,不會做爲關鍵字處理- ((PreparedStatement) statement).setString(1,user); ((PreparedStatement) statement).setString(2,pass); resultSet = ((PreparedStatement) statement).executeQuery(); if (resultSet.next()){ System.out.println("登錄成功"); } else { System.out.println("登陸失敗"); } // while (resultSet.next()){ // int id = resultSet.getInt("id"); // String userName = resultSet.getString("username"); // String password = resultSet.getString("password"); // String brief = resultSet.getString("brief"); // System.out.println("id爲"+id+",用戶名爲:"+userName+",密碼爲:"+password+ // ",簡介爲:"+brief); // // } } catch (SQLException e){ }finally { closeResource(connection,statement,resultSet); } } //插入數據 @Test public void testInsert(){ Connection connection = null; PreparedStatement statement = null; try { connection = (Connection)dataSource.getPooledConnection(); String password = DigestUtils.md5Hex("123"); String sql = "INSERT INTO user(username,password,brief)"+"VALUES(?,?,?)"; statement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS); statement.setString(1,"test3"); statement.setString(2,password); statement.setString(3,"仍是帥!"); int rows = statement.executeUpdate(); Assert.assertEquals(1,rows); } catch (SQLException e) { e.printStackTrace(); }finally { closeResource(connection,statement); } } @Test public void testLogin(){ String username = "hhy'"; //String username = "java' OR 1 = 1"; sql注入 //SELECT * FROM user WHERE username = 'java' OR 1 = 1 AND password = '1234' sql注入 //String username = "java'--"; --表示註釋 String password = "123"; Connection connection = null; Statement statement = null; ResultSet resultSet =null; try { connection = (Connection)dataSource.getPooledConnection(); String sql = "SELECT * FROM user WHERE username = '"+username+"" + " AND password = '"+password+"'"; //System.out.println(sql); statement = connection.createStatement(); resultSet = statement.executeQuery(sql); if (resultSet.next()){ System.out.println("登錄成功"); } else { System.out.println("登陸失敗"); } } catch (SQLException e) { e.printStackTrace(); }finally { closeResource(connection,statement,resultSet); } } public void closeResource(Connection connection, Statement statement){ if (connection != null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } if (statement != null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } } public void closeResource(Connection connection,Statement statement,ResultSet resultSet){ closeResource(connection,statement); if (resultSet != null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
加載數據源
獲取數據庫鏈接
準備SQL語句
關閉鏈接
註冊用戶:inset
用戶登陸:select
更新用戶信息:update
一、二、4是公有過程,咱們將其置爲父類,子類只須要繼承父類,那麼這三個功能就都有了,子類只須要擴展本身的第三步 父類名稱爲BasedDao
加載DruidDataSource數據源,首先獲取其配置文件
Properties properties = CommUtils.loadProperties("datasource.properties");
BasedDao類,獲取數據源、鏈接、關閉資源:
package com.hhy.java.client.dao; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import com.alibaba.druid.pool.DruidPooledConnection; import com.hhy.java.util.CommUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /** * @Information:封裝基礎dao操做,獲取數據源、鏈接、關閉資源等 * @Author: HeHaoYuan * @Date: Created at 19:11 on 2019/8/16 * @Package_Name: com.hhy.com.java.client.dao */ public class BasedDao { private static DruidDataSource dataSource;//阿里一個數據源 static{ Properties properties = CommUtils. loadProperties("datasource.properties"); try { dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } protected DruidPooledConnection getConnection(){ try { return (DruidPooledConnection) dataSource.getPooledConnection(); } catch (SQLException e) { System.out.println("數據庫鏈接失敗"); e.printStackTrace(); } return null; } protected void closeResources(Connection connection, Statement statement){ if (connection != null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } if (statement != null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } } //關閉數據源的重載函數 protected void closeResources(Connection connection, Statement statement, ResultSet resultSet){ closeResources(connection,statement); if (resultSet != null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
註冊帳號,客戶端將信息提交到服務器,進行tcp鏈接,註冊成功後,服務端將信息返回到客戶端(登陸界面),那麼tcp鏈接就斷掉了
比較複雜(牽扯到TCP鏈接的註銷模塊),如今咱們把註冊功能放到客戶端,可是應該放到服務器,註冊成功後信息就在MySQL服務器本地(也可用MySQL第三方服務器),但實際應該放到服務端,而咱們把註冊模塊放到了客戶端,爲了簡化開發
封裝一個類(用戶註冊和登陸模塊),寫 「用戶的註冊」 模塊,註冊本質上就是在數據庫中插入數據,再次類中另外寫一個方法,須要外界傳遞給本類用戶的註冊信息,經過statement的setString將其安置到數據庫中。。。
關鍵語句: connention.getpreparestatement (採用preparestatement防止SQL注入) preparestatement.setstring(1,"")(預編譯,幫助自動生成代碼)
登陸失敗返回null,不然爲空 返回成功後的結果集(用戶名,密碼,brief),應該有從結果集到對象的過程,private User getUser(ResultSet resultSet){}
package com.hhy.java.client.dao; import com.hhy.java.client.entity.User; import org.apache.commons.codec.digest.DigestUtils; import java.sql.*; /** * @Information: * @Author: HeHaoYuan * @Date: Created at 19:26 on 2019/8/16 * @Package_Name: com.hhy.com.java.client.dao */ public class AccountDao extends BasedDao{ //用戶註冊須要那些對象 insert //是否註冊成功 public boolean userReg(User user){ Connection connection = null; PreparedStatement statement = null; try { connection = getConnection(); //預編譯 String sql = "INSERT INTO user(username,password,brief)"+"VALUES(?,?,?)";//須要外界傳遞 statement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS); statement.setString(1,user.getUsername()); statement.setString(2,DigestUtils.md5Hex(user.getPassword())); statement.setString(3,user.getBrief()); int rows = statement.executeUpdate(); if (rows == 1){ return true; } } catch (SQLException e){ System.err.println("用戶註冊失敗"); e.printStackTrace(); }finally { closeResources(connection,statement); } return false; } public User userlogin(String username,String password){ Connection connection = null;//兩個業務不能用同一個connection,兩個事物 PreparedStatement statement = null; ResultSet resultSet = null; try { connection = getConnection(); String sql = "SELECT * FROM user WHERE username = ? AND password = ?"; statement = connection.prepareStatement(sql); statement.setString(1,username); statement.setString(2,DigestUtils.md5Hex(password)); resultSet = statement.executeQuery(); if (resultSet.next()){ User user = getuser(resultSet); return user; } }catch (SQLException e){ System.out.println("用戶登陸失敗"); e.printStackTrace(); }finally { closeResources(connection,statement,resultSet); } return null; } private User getuser(ResultSet resultSet) throws SQLException { User user = new User(); user.setId(resultSet.getInt("id")); user.setUsername(resultSet.getString("username")); user.setPassword(resultSet.getString("password")); user.setBrief(resultSet.getString("brief")); return user; } }
既然須要進行用戶的註冊,那麼就須要封裝一個用戶的User類,提供getter和setter方法:user 表 爲何用Integer不用int 爲何用數組不用動態數組?由於前者默認爲null,後者默認爲int,數據庫中默認用包裝類
package com.hhy.java.client.entity; import lombok.Data; import java.util.Set; /** * @Information: * @Author: HeHaoYuan * @Date: Created at 19:34 on 2019/8/16 * @Package_Name: com.hhy.java.client.entity */ @Data public class User { private Integer id;//int默認值爲0,Integer爲null,因此數據庫中默認基本類型用包裝類 private String username; private String password; private String brief; private Set<String> usernames; public Set<String> getUsernames() { return usernames; } public void setUsernames(Set<String> usernames) { this.usernames = usernames; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getBrief() { return brief; } public void setBrief(String brief) { this.brief = brief; } }
單元測試:用戶是否可以正確完成註冊,其實就是進行get和set方法:
package com.hhy.java.client.dao; import com.hhy.java.client.entity.User; import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.*; /** * @Information: * @Author: HeHaoYuan * @Date: Created at 20:10 on 2019/8/16 * @Package_Name: com.hhy.java.client.dao */ public class AccountDaoTest { private AccountDao accountDao = new AccountDao(); @Test public void userReg() { //註冊 User user = new User(); user.setUsername("aaa"); user.setPassword("456"); user.setBrief("帥仍是帥"); boolean flag = accountDao.userReg(user); Assert.assertTrue(flag); } @Test public void userlogin() { //查詢註冊 String username = "aaa"; String password = "456"; User user = accountDao.userlogin(username,password); System.out.println(user); Assert.assertNotNull(user); } }
寫完註冊功能後,咱們開始寫登陸的業務,此時就須要寫服務端和客戶端,ServerSocket 服務端的類,至關於創建一個基站,用accept()偵聽客戶端的鏈接,它會一直阻塞,
直到有新的客戶端和它鏈接
客戶端 Socket類,只須要傳入服務端的ip和端口號便可,服務端若不傳,默認本地端口號(127.0.0.1),再只需傳一個端口號便可
有了json字符串幫助,給服務器發送個對象過去,讓其解析爲json字符串(序列化)
能夠規定與服務器通訊的方式,http由來(格式同樣,只不過具體字段不同)
相似 咱們也創造一種通訊方式
字段:
type:1 表示用戶註冊到服務器 哪一種行爲
content:username 具體發送的內容
to: 給誰發,須要有值,不須要爲空
將上述包裝爲類,服務器與客戶端傳遞信息的載體 vo類
既然是客戶端與服務端進行通訊,咱們須要封裝一個類進行文本輸入輸出:
package com.hhy.java.vo; /** * @Information:客戶端和服務端都會用到的類,將其封裝,type(行爲的類型 1表示註冊,表示私聊)、 * content(發送到服務器的具體內容)、to(給誰發,私聊告知服務器要將信息發給哪一個客戶) * 服務器與客戶端傳遞信息的載體 * @Author: HeHaoYuan * @Date: Created at 0:01 on 2019/8/17 * @Package_Name: com.hhy.java.vo */ public class MessageVO { private String type; private String content; private String to; public String getType() { return type; } public void setType(String type) { this.type = type; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getTo() { return to; } public void setTo(String to) { this.to = to; } }
服務端類:
ServerSocket 服務端的類,至關於創建一個基站,如何偵聽客戶端鏈接?accept()方法,它會一直阻塞,
知道有新的客戶端和它鏈接 聊天室服務端:ServerSocket:服務端基站 IP不傳,默認本地創建基站,只須要傳遞端口號
服務端經過run方法接收,獲取i/o,不斷監聽客戶端的鏈接
從客戶端發來的是一個字符串,而後將其反序列化爲對象Object
經過以前規定的通訊協議字段進行解析
發回的相應內容爲在線的用戶名變爲json字符串再發回去 相似http的響應
服務端註冊羣:一、註冊羣 二、想客戶端發送羣set
package com.hhy.java.server; import com.hhy.java.util.CommUtils; import com.hhy.java.vo.MessageVO; import com.mysql.fabric.xmlrpc.Client; import javax.swing.*; import java.io.IOException; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @Information:處理每一個客戶端鏈接服務器端的類 * @Author: HeHaoYuan * @Date: Created at 0:05 on 2019/8/17 * @Package_Name: com.hhy.java.server */ public class MultiThreadServer { private static final String IP; private static final int port; //緩存當前服務器全部在線的客戶端信息 keyString是用戶名 private static Map<String,Socket> clients = new ConcurrentHashMap<>();//線程安全的HashMap //緩存當前服務器註冊的全部羣名稱以及羣好友 private static Map<String,Set<String>> groups = new ConcurrentHashMap<>(); //類加載時初始化 static { Properties pros = CommUtils.loadProperties("socket.properties"); IP = pros.getProperty("address"); port = Integer.parseInt(pros.getProperty("port")); } //處理每一個客戶端鏈接的內部類,新建新的鏈接 去處理,不然accept會一直阻塞 private static class ExecuteClient implements Runnable{ private Socket client; private Scanner in; private PrintStream out; public ExecuteClient(Socket client) { this.client = client; try { this.in = new Scanner(client.getInputStream()); this.out = new PrintStream(client.getOutputStream(), true,"UTF-8"); } catch (IOException e) { e.printStackTrace(); } } //服務端接受客戶端發來的信息 @Override public void run() { while (true){ if(in.hasNext()){ String jsonStrFromClient = in.nextLine(); //反序列化爲MessageVo MessageVO msgFromClient = (MessageVO) CommUtils.json2object(jsonStrFromClient,MessageVO.class); if (msgFromClient.getType().equals("1")){ //新用戶註冊到客戶端 String userName = msgFromClient.getContent(); //將當前在線全部用戶的用戶名發回客戶端 Http的響應 MessageVO meg2Client = new MessageVO(); meg2Client.setType("1"); meg2Client.setContent(CommUtils.object2Json(clients.keySet())); out.println(CommUtils.object2Json(meg2Client)); //將新上線的用戶信息發回給當前已在線的全部用戶,先發信息再存,不然會將本身消息發給本身 sendUserLogin("新上線通知:"+userName); //將當前新用戶註冊到服務端緩存 clients.put(userName,client); System.out.println(userName+"上線了!"); System.out.println("當前聊天室共有"+clients.size()+"人"); } else if (msgFromClient.getType().equals("2")){ //用戶私聊 String friendName = msgFromClient.getTo(); Socket clientSocket = clients.get(friendName); try { PrintStream out = new PrintStream(clientSocket.getOutputStream(), true,"UTF-8"); //發送內容 MessageVO msg2Client = new MessageVO(); msg2Client.setType("2"); msg2Client.setContent(msgFromClient.getContent()); System.out.println("收到私聊信息,內容爲"+msgFromClient.getContent()); out.println(CommUtils.object2Json(msg2Client)); } catch (IOException e) { e.printStackTrace(); } } else if(msgFromClient.getType().equals("3")){ //註冊羣 String groupName = msgFromClient.getContent(); //該羣的全部羣成員 Set<String> friends = (Set<String>) CommUtils. json2object(msgFromClient.getTo(),Set.class); groups.put(groupName,friends); System.out.println("有新的羣註冊成功,羣名稱爲"+ groupName+",一共有"+groups.size()+"個羣"); } else if (msgFromClient.getType().equals("4")){ //羣聊信息 System.out.println("服務器收到的羣聊信息爲:"+msgFromClient.getContent()); String groupName = msgFromClient.getTo(); Set<String> names = groups.get(groupName); Iterator<String> iterator = names.iterator(); while (iterator.hasNext()){ String socketName = iterator.next(); Socket client = clients.get(socketName); try { PrintStream out = new PrintStream(client.getOutputStream(),true, "UTF-8"); MessageVO messageVO = new MessageVO(); messageVO.setType("4"); messageVO.setContent(msgFromClient.getContent()); //羣名-[] 服務器第一次向拉進來的好友須要特別發送羣名稱和羣成員 //由於他都不知道本身被拉進羣,因此須要發送 messageVO.setTo(groupName+"-"+CommUtils.object2Json(names)); out.println(CommUtils.object2Json(messageVO)); System.out.println("服務端發送的羣聊信息爲"+messageVO.getContent()); } catch (IOException e) { e.printStackTrace(); } } } } } } //向全部在線用戶發送新用戶上線信息 private void sendUserLogin(String msg){ for (Map.Entry<String,Socket> entry : clients.entrySet()){ Socket socket = entry.getValue();//取出當前實體的每個socket,再調用其輸出流 try { PrintStream out = new PrintStream(socket.getOutputStream(), true,"UTF-8"); out.println(msg); } catch (IOException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(port); //建立線程池 ExecutorService executors = Executors.newFixedThreadPool(50); for (int i = 0;i < 50;i++){ System.out.println("等待客戶鏈接..."); Socket client = serverSocket.accept(); System.out.println("有新的鏈接,端口號爲"+client.getPort()); executors.submit(new ExecuteClient(client));//具體鏈接交給子線程去處理,服務器再繼續偵聽新的鏈接 } } }
客戶端類:客戶端類:Socket 傳入服務端ip、端口號,服務端若不傳,默認本地端口號,只需傳一個端口號
package com.hhy.java.client.entity.service; import com.hhy.java.util.CommUtils; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Properties; /** * @Information:當用戶點擊登陸時,客戶端進行頁面跳轉,鏈接還在,頁面不能直接的關閉 客戶端鏈接的輸入輸出流 * @Author: HeHaoYuan * @Date: Created at 12:39 on 2019/8/17 * @Package_Name: com.hhy.java.client.entity.service */ public class Connect2Service { private static final String IP; private static final int port; static { Properties pros = CommUtils.loadProperties("socket.properties"); IP = pros.getProperty("address"); port = Integer.parseInt(pros.getProperty("port")); } private Socket client; private InputStream in; //獲取客戶端的輸入輸出流 private OutputStream out; public Connect2Service() { try { client = new Socket(IP,port); //創建鏈接成功的時候,輸入輸出流能夠獲得 in = client.getInputStream(); out = client.getOutputStream(); } catch (IOException e) { System.out.println("與服務器創建鏈接失敗"); e.printStackTrace(); } } //獲取方法 拿輸入流獲取服務器發來的信息 public InputStream getIn(){ return in; } //拿輸出流給服務器發信息 public OutputStream getOut(){ return out; } }
Swing-JDK1.2界面編程包
JPanel-組件中存放其餘組件的基礎,至關於前端的div
JLabel-標籤
JTextField-輸入框
JButton-按鈕,右鍵選擇 Create Listener,能夠用來觸發點擊後的事件
JPasswordField:密碼文本框
JScrollPane:滑動盤子,在滑動好友、羣組列表時會用到這個結構
多行文本控制(用於多行聊天)JTextArea
鼠標點擊事件:MouseListener- >MousePressed
界面問題:每個界面至關於一個線程(有本身對應的主方法),最小化後並未關閉,只有x纔是關閉,全部彈起的界面都不能是主方法,必須經過其餘界面來調用它,咱們將其中一個主界面設置爲主方法,其餘方法的主方法變爲構造,放到主界面類裏面。就可生成其餘界面對象
點擊頁面(按鈕),就會觸發事件 createListener ActionListenListener FocusListener,鼠標放到觸發,MouseListener鼠標點擊事件
JOptionPane消息彈框 frame(基於什麼),message:註冊成功,title:提示信息,JOptionPane.INFORMATION_MESSAGE
註冊密碼是,因爲不能直接讀加密,因此String.valueof(passwordText.getPassword)
接下來圖畫好後,右鍵點擊Form ,Jump Source到對應圖框的類裏面,寫類信息
第一個:登陸頁面的編寫,以後在這裏的主函數中 啓動 用戶登陸界面 對話框
login 失敗 仍是 成功?
/失敗 保留登陸界面,提示用戶從新輸入信息
//成功 成功後 與服務器創建鏈接,將用戶名與用戶Socket註冊到服務端
//加載用戶列表頁面 提示已登陸用戶新用戶上線
有同時監聽服務端發來的用戶上線信息而且更新用戶列表
點擊登錄後,和服務器創建鏈接,登錄後此鏈接保存,在不一樣頁面跳轉的時候,此鏈接一直傳遞
package com.hhy.java.util; import com.hhy.java.client.dao.AccountDao; import com.hhy.java.client.entity.User; import com.hhy.java.client.entity.service.Connect2Service; import com.hhy.java.client.entity.service.FriendsList; import com.hhy.java.client.entity.service.userReg; import com.hhy.java.vo.MessageVO; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.util.Scanner; import java.util.Set; /** * @Information: * @Author: HeHaoYuan * @Date: Created at 17:34 on 2019/8/16 * @Package_Name: com.java.util */ public class TestGUI { private JPanel GUITestPanel; private JPanel qqPanel; private JPasswordField passwordField; private JTextField nameField; private JLabel username; private JLabel password; private JLabel My_chatroom; private JButton register; private JButton login; private JFrame frame; //單例模式 private AccountDao accountDao = new AccountDao(); //默認建立無參構造 public TestGUI() { frame = new JFrame("用戶登陸"); //最外層盤子 ,防止無限遞歸 frame.setContentPane(GUITestPanel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); frame.pack(); frame.setVisible(true); //註冊按鈕 register.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { //彈出註冊頁面 new userReg(); } }); //登陸按鈕 login.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { //登陸頁面 //校驗用戶信息 String username =nameField.getText(); String password = String.valueOf(passwordField.getPassword()); //查詢數據庫,根據user返回值是否爲null User user = accountDao.userlogin(username,password); if (user != null){ //成功 ,加載用戶列表 JOptionPane.showMessageDialog(frame,"登陸成功!","成功信息", JOptionPane.INFORMATION_MESSAGE); frame.setVisible(false); //與服務器創建鏈接,將當前用戶的用戶名與密碼發到服務端 Connect2Service connect2Service = new Connect2Service(); MessageVO msg2Server = new MessageVO(); msg2Server.setType("1");//註冊 msg2Server.setContent(username); //把要發的字符串序列化爲json對象 String json2Server = CommUtils.object2Json(msg2Server); //要發送信息就要獲取鏈接的輸出流 autoFlush自動刷新緩存區,編碼爲utf-8 try { PrintStream out = new PrintStream(connect2Service.getOut(), true,"UTF-8"); out.println(json2Server);//將當前用戶名信息發給服務端 //讀取服務端發回的全部在線用戶信息 Scanner in = new Scanner(connect2Service.getIn()); if (in.hasNext()){ String msgFromServerstr = in.nextLine(); MessageVO msgFromServer = (MessageVO) CommUtils.json2object( msgFromServerstr,MessageVO.class); Set<String> users = (Set<String>) CommUtils. json2object(msgFromServer.getContent(),Set.class); System.out.println("全部在線用戶爲:"+users); //加載用戶列表界面 //將當前用戶名、全部在線好友、與服務器創建鏈接傳遞到好友列表界面 new FriendsList(username,users,connect2Service); } } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); } } else { //失敗,停留當前登陸頁面,提示用戶信息錯誤 JOptionPane.showMessageDialog(frame,"登陸失敗!","錯誤信息", JOptionPane.ERROR_MESSAGE); } } }); } public static void main(String[] args) { new TestGUI(); } }
點擊一次JButton就是觸發一次事件,因此將除了主對話框外 其餘事件包裝爲類,寫好構造方法,去掉其餘類本身的主函數,再在剛纔寫的主頁面裏面啓動整個事件便可,有了其餘類事件的構造方法,
就可執行其相應的業務方法
用戶註冊包裝類,須要注意的是裏面的邏輯,若註冊成功將關閉註冊對話框,不然一直停留:
package com.hhy.java.client.entity.service; import com.hhy.java.client.dao.AccountDao; import com.hhy.java.client.entity.User; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** * @Information: * @Author: HeHaoYuan * @Date: Created at 21:28 on 2019/8/16 * @Package_Name: com.hhy.java.client.entity.service */ public class userReg { private JTextField account; private JPasswordField pass; private JTextField myself; private JLabel username; private JLabel password; private JLabel introduce; private JPanel first; private JPanel second; private JPanel third; private JPanel whole; private JButton 註冊按鈕; //關於數據庫增刪改查用戶的類 private AccountDao accountDao = new AccountDao(); public userReg() { JFrame frame = new JFrame("用戶註冊"); frame.setContentPane(first); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); frame.pack(); frame.setVisible(true); //點擊註冊按鈕,將信息持久化到db中,成功彈出提示框 註冊按鈕.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { //彈出提示框 //正確的提示框 //JOptionPane.showMessageDialog(frame,"註冊成功!","提示信息",JOptionPane.INFORMATION_MESSAGE); String username = account.getText(); String password = String.valueOf(pass.getPassword()); String brief = myself.getText(); //將輸入信息包裝爲user類,保存到數據庫中 User user = new User(); user.setUsername(username); user.setPassword(password); user.setBrief(brief); //調用dao對象 if (accountDao.userReg(user)){ //成功後返回登陸頁面 JOptionPane.showMessageDialog(frame,"註冊成功!","成功信息",JOptionPane.INFORMATION_MESSAGE); frame.setVisible(false);//將當前註冊頁面不可見的方法 } else { //彈出提示框 //仍保留當前註冊頁面 JOptionPane.showMessageDialog(frame,"註冊失敗!","錯誤信息",JOptionPane.ERROR_MESSAGE); } } }); } }
好友列表GUI生成後,再次用構造方法生成
用戶登陸成功後,彈出的好友列表包裝類,須要注意的是,登錄成功後,登陸頁面就會消失,進而彈出好友列表對話框:
在好友登錄成功彈窗提示時,要先彈窗再註冊(刷新好友列表),新用戶先存到map裏,不然會提醒本身上線的信息,因此要先發上線信息
請求響應
客戶端
客戶端將用戶名發送到服務端
同時接收服務端發回的全部在線用戶信息
服務端
在服務端加載全部全部在線用戶信息
服務端將全部在線用戶信息發一個上線通知給客戶端
將新用戶經過(Map.Entry<String,Socket>保存到緩存中
當客戶端收到服務端上線用戶消息的時候加載在線好友信息
當好友上線的時候,如何彈窗提醒?
後臺監聽,服務器將用戶的私聊界面喚醒
從登陸界面到好友列表界面需不須要傳參?
須要,傳遞username(本帳號名稱)、全部在線好友、與服務器創建鏈接傳遞到好友列表界面
package com.hhy.java.client.entity.service; import com.hhy.java.util.CommUtils; import com.hhy.java.vo.MessageVO; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Iterator; import java.util.Map; import java.util.Scanner; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * @Information: * @Author: HeHaoYuan * @Date: Created at 15:35 on 2019/8/17 * @Package_Name: com.hhy.java.client.entity.service */ public class FriendsList { private JPanel friendsPanel; private JScrollPane friendsList; private JButton creategroupButton; private JLabel groupLabel; private JScrollPane groupPanel; private JLabel friendLabel; private JFrame frame; private String username; //存儲當前全部好友列表 private Set<String> users; //存儲當前全部的羣名稱以及相應羣成員 private Map<String,Set<String>> groupList = new ConcurrentHashMap<>(); private Connect2Service connect2Service; //緩存全部私聊界面 private Map<String,PrivateChatGUI>privateChatGUIMap = new ConcurrentHashMap<>(); //緩存全部羣聊界面 private Map<String,GroupChatGUI> groupChatGUIMap = new ConcurrentHashMap<>(); //好友列表後臺任務,不斷監聽服務檯發送的信息,不斷刷新本身的好友列表 //好友上線信息、用戶私聊、羣聊 private class DaemonTask implements Runnable{ private Scanner in = new Scanner(connect2Service.getIn()); @Override public void run() { //收到服務器發來的信息 json字符串:type1 content。。。 to。。。 {「type」,content。。。} //newLogin:username 如何讓判斷json字符串,開頭是大括號 while (true){ //收到服務器發來的消息 if (in.hasNextLine()){ String strFromServer = in.nextLine(); //此時服務器發來json字符串 if (strFromServer.startsWith("{")){ //json -> Object MessageVO messageVO = (MessageVO) CommUtils.json2object(strFromServer,MessageVO.class); if (messageVO.getType().equals("2")){ //服務器發來的私聊信息 String friendName = messageVO.getContent().split("-")[0]; String msg = messageVO.getContent().split("-")[1]; //判斷此私聊是不是第一次建立 if (privateChatGUIMap.containsKey(friendName)){ PrivateChatGUI privateChatGUI = privateChatGUIMap.get(friendName); privateChatGUI.getFrame().setVisible(true); privateChatGUI.readFromServer(friendName+"說:"+msg); } else { PrivateChatGUI privateChatGUI = new PrivateChatGUI(friendName, username,connect2Service); privateChatGUIMap.put(friendName,privateChatGUI); privateChatGUI.readFromServer(friendName+"說:"+msg); } } else if (messageVO.getType().equals("4")){ //收到服務器發來的羣聊信息 //type:4 //content:sender-msg //to:groupName-[1,2,3...] String groupName = messageVO.getTo().split("-")[0]; String senderName = messageVO.getContent().split("-")[0]; String groupMsg = messageVO.getContent().split("-")[1]; //若此羣名稱在此羣列表 if (groupList.containsKey(groupName)){ if (groupChatGUIMap.containsKey(groupName)){ //羣聊界面彈出 GroupChatGUI groupChatGUI = groupChatGUIMap.get(groupName); groupChatGUI.getFrame().setVisible(true); groupChatGUI.readFromServer(senderName+"說:"+groupMsg); } else{ Set<String> names = groupList.get(groupName); GroupChatGUI groupChatGUI = new GroupChatGUI(groupName, names,username,connect2Service); groupChatGUIMap.put(groupName,groupChatGUI); groupChatGUI.readFromServer(senderName+"說:"+groupMsg); } } else{ //若羣成員第一次收到羣聊信息 //1.將羣名稱以及羣成員保存到當前客戶端羣聊列表 Set<String> friends = (Set<String>) CommUtils.json2object(messageVO.getTo(). split("-")[1],Set.class); groupList.put(groupName,friends);//保存到當前列表 loadGroupList();//刷新 //2.彈出羣聊界面 GroupChatGUI groupChatGUI = new GroupChatGUI(groupName, friends,username,connect2Service); groupChatGUIMap.put(groupName,groupChatGUI); groupChatGUI.readFromServer(senderName+"說:"+groupMsg); } } } else { //newLogin:username 新上線通知: if (strFromServer.startsWith("新上線通知:")){ String newFriendName = strFromServer.split(":")[1]; //在前面的用戶對話框加上新來的好友名稱 users.add(newFriendName); //彈框提醒用戶上線 JOptionPane.showMessageDialog(frame,newFriendName+"上線了!", "上線提醒",JOptionPane.INFORMATION_MESSAGE); //刷新好友列表 loadUsers(); } } } } } } //私聊標籤點擊事件 private class PrivateLabelAction implements MouseListener{ //點擊的哪一個標籤,friendname private String labelName; public PrivateLabelAction(String labelName){ this.labelName = labelName;//經過構造方法傳遞標籤事件 } //鼠標點擊執行事件 @Override public void mouseClicked(MouseEvent e) { //緩存已經建立好的私聊列表,即對象的複用,而不是從新new //判斷好友列表私聊界面緩存是否已經有指定的標籤 if (privateChatGUIMap.containsKey(labelName)) { PrivateChatGUI privateChatGUI = privateChatGUIMap.get(labelName); privateChatGUI.getFrame().setVisible(true); }else { //不然是第一次點擊,建立私聊界面,把須要的參數傳遞進去 PrivateChatGUI privateChatGUI = new PrivateChatGUI(labelName,username,connect2Service); privateChatGUIMap.put(labelName,privateChatGUI);//如果第一次經好友標籤緩存到界面 } } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } } //羣聊點擊事件 private class GroupLabelAction implements MouseListener{ private String groupName; public GroupLabelAction(String groupName) { this.groupName = groupName; } @Override public void mouseClicked(MouseEvent e) { //點擊的時候判斷緩存中有沒有,若是有無需再new,直接把窗口彈出 if (groupChatGUIMap.containsKey(groupName)){ GroupChatGUI groupChatGUI = groupChatGUIMap.get(groupName); groupChatGUI.getFrame().setVisible(true); } //不然建立者的 // 第一次點擊 else { //羣名集合 Set<String> names = groupList.get(groupName); GroupChatGUI groupChatGUI = new GroupChatGUI( groupName,names,username,connect2Service ); groupChatGUIMap.put(groupName,groupChatGUI); } } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } } //從登陸頁面跳到列表頁面須要傳參 public FriendsList(String username,Set<String>users, Connect2Service connect2Service){ this.username = username; this.users = users; this.connect2Service = connect2Service; frame = new JFrame(username); frame.setContentPane(friendsPanel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(400,300); frame.setLocationRelativeTo(null); frame.setVisible(true); loadUsers(); //啓動後臺線程不斷監聽服務器發來的消息 Thread daemonThread = new Thread(new DaemonTask()); daemonThread.setDaemon(true); daemonThread.start(); //建立羣組 creategroupButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new CreateGroupGUI(username,users,connect2Service,FriendsList.this); } }); } //加載全部在線用戶信息 public void loadUsers(){ JLabel[] userLabels = new JLabel[users.size()]; JPanel friends = new JPanel(); friends.setLayout(new BoxLayout(friends,BoxLayout.Y_AXIS)); //迭代users內容展現 Iterator<String> iterator = users.iterator(); int i = 0; while (iterator.hasNext()){ String userName = iterator.next(); userLabels[i] = new JLabel(userName); //添加鼠標點擊好友標籤事件 即添加標籤點擊事件 userLabels[i].addMouseListener(new PrivateLabelAction(userName)); friends.add(userLabels[i]); i++; } //從新加載佈局,從新實例化 friendsList.setViewportView(friends); //設置滾動條垂直滾動 friendsList.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); friends.revalidate(); friendsList.revalidate(); } //刷新羣組界面 public void loadGroupList(){ //存儲全部羣名稱標籤JPanel JPanel groupNamePanel = new JPanel(); groupNamePanel.setLayout(new BoxLayout(groupNamePanel,BoxLayout.Y_AXIS)); JLabel[] labels = new JLabel[groupList.size()]; //Map遍歷 Set<Map.Entry<String,Set<String>>> entries = groupList.entrySet(); Iterator<Map.Entry<String,Set<String>>> iterator = entries.iterator(); int i = 0; while (iterator.hasNext()){ Map.Entry<String,Set<String>> entry = iterator.next(); labels[i] = new JLabel(entry.getKey()); labels[i].addMouseListener(new GroupLabelAction(entry.getKey())); groupNamePanel.add(labels[i]); i++; } groupPanel.setViewportView(groupNamePanel); groupPanel.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); groupPanel.revalidate();//刷新 } public void addGroup(String groupName,Set<String> friends){ groupList.put(groupName,friends); } }
私聊:
package com.hhy.java.client.entity.service; import com.hhy.java.util.CommUtils; import com.hhy.java.vo.MessageVO; import javax.swing.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.PrintStream; import java.io.UnsupportedEncodingException; public class PrivateChatGUI { private JPanel PrivateChatPanel; private JTextField send2Server; private JTextArea readFromServer; private String friendName; private String myName; private Connect2Service connect2Service; private JFrame frame; private PrintStream out; public PrivateChatGUI(String friendName,String myName,Connect2Service connect2Service) { this.friendName = friendName; this.myName = myName; this.connect2Service = connect2Service; try { this.out = new PrintStream(connect2Service.getOut(), true, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } frame = new JFrame("與" + friendName + "私聊中..."); frame.setContentPane(PrivateChatPanel); //窗口關閉操做 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //此處應該設置爲隱藏窗口 frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); frame.setSize(400, 400); frame.setVisible(true); //捕捉輸入框的鍵盤輸入 send2Server.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { StringBuilder sb = new StringBuilder(); sb.append(send2Server.getText()); //一、當捕捉到按下Enter if (e.getKeyCode() == KeyEvent.VK_ENTER) { //二、將當前信息發給服務端 String msg = sb.toString(); MessageVO messageVO = new MessageVO(); messageVO.setType("2"); messageVO.setContent(myName+"-"+msg); messageVO.setTo(friendName); PrivateChatGUI.this.out.println(CommUtils.object2Json(messageVO)); //三、將本身發送的信息展現到當前的私聊頁面 readFromServer(myName+"說:"+msg); send2Server.setText("");//輸入框還原,爲了輸入 } } }); } //展現聊天界面 public void readFromServer(String msg) { readFromServer.append(msg+"\n"); } public JFrame getFrame() { return frame; } }
建立羣聊:建立羣聊給服務器傳參 :本身 好友列表 鏈接(每次向服務器提交必備)
一、判斷哪些好友被選擇
二、獲取輸入羣名稱
三、向服務器打包發送羣名稱、好友列表(後續加上了包括本身)
四、將當前創羣界面隱藏掉,刷新客戶端羣列表
package com.hhy.java.client.entity.service; import com.hhy.java.util.CommUtils; import com.hhy.java.vo.MessageVO; import com.sun.crypto.provider.JceKeyStore; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.util.HashSet; import java.util.Iterator; import java.util.Set; /** * @Information: * @Author: HeHaoYuan * @Date: Created at 22:07 on 2019/8/19 * @Package_Name: com.hhy.java.client.entity.service */ public class CreateGroupGUI { private JPanel CreateGroupGUI; private JPanel friendLabelPanel; private JLabel groupName; private JTextField groupNameText; private JButton submitButton; private String myName; private Set<String> friends; private Connect2Service connect2Service; private FriendsList friendsList; public CreateGroupGUI(String myName,Set<String> friends,Connect2Service connect2Service, FriendsList friendsList) { this.myName = myName; this.friends = friends; this.connect2Service = connect2Service; this.friendsList = friendsList; JFrame frame = new JFrame("建立羣組"); frame.setContentPane(CreateGroupGUI); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(400,300); frame.setLocationRelativeTo(null); frame.setVisible(true); //將在線好友以checkBox展現到界面中 動態遍歷 friendLabelPanel.setLayout(new BoxLayout(friendLabelPanel,BoxLayout.Y_AXIS)); JCheckBox[] checkBoxes = new JCheckBox[friends.size()]; Iterator<String> iterator = friends.iterator(); int i = 0; while (iterator.hasNext()){ String labelName = iterator.next(); checkBoxes[i] = new JCheckBox(labelName); friendLabelPanel.add(checkBoxes[i]); i++; } friendLabelPanel.revalidate();//刷新 //點擊提交按鈕提交信息到服務端 submitButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { //一、先判斷哪些checkBox被選中(那些好友被選中加入羣聊) //CheckBox的頂層父類 Set<String> selectedFriends = new HashSet<>(); Component[] comps = friendLabelPanel.getComponents(); //遍歷全部組件,看哪些Component被選中 for (Component comp : comps){//包括不少:box,field,button,label JCheckBox checkBox = (JCheckBox)comp;//用於強轉,因爲也不知道哪些工具 if (checkBox.isSelected()){ String labelName = checkBox.getText(); selectedFriends.add(labelName); } } selectedFriends.add(myName); //二、獲取羣名輸入框的羣名稱 String groupName = groupNameText.getText(); //三、將羣名+選中好友信息發送到服務端 //type:3 //content:羣名 groupName //to:[user1,user2,user3..] MessageVO messageVO = new MessageVO(); messageVO.setType("3"); messageVO.setContent(groupName); messageVO.setTo(CommUtils.object2Json(selectedFriends)); try { PrintStream out = new PrintStream(connect2Service.getOut(),true,"UTF-8"); out.println(CommUtils.object2Json(messageVO)); } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); } //4.將當前建立羣界面隱藏掉,刷新好友列表界面的羣列表 frame.setVisible(false); //addGroupInfo //loadGroup friendsList.addGroup(groupName,selectedFriends); friendsList.loadGroupList(); } }); } }
羣聊:
羣聊中:
客戶端
1.選擇在線好友建立羣聊,將建立羣聊信息發送給服務端
2.點擊羣聊列表的標籤,喚醒羣聊界面
3.喚醒羣聊界面後,進行羣聊信息的發送
第一次收到羣消息的客戶端首先將羣名保存到當前客戶端的羣列表中,
而後彈出羣聊界面,讀取別人發來的消息
服務端
1.接收用戶的羣註冊信息,將羣名與羣成員註冊到服務端緩存
2.接收用戶發來的羣消息,根據羣名稱轉發信息到相應的羣中
發送消息,輸入框不斷監聽鍵盤輸入事件,當回車後,發送給服務端
服務端先遍歷根據羣名獲取所有好友,而後愛各項每一個好友out信息
package com.hhy.java.client.entity.service; import com.hhy.java.util.CommUtils; import com.hhy.java.vo.MessageVO; import javax.swing.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.Set; import java.util.SplittableRandom; /** * @Information: * @Author: HeHaoYuan * @Date: Created at 13:01 on 2019/8/20 * @Package_Name: com.hhy.java.client.entity.service */ public class GroupChatGUI { private JPanel GroupChatPanel; private JTextArea readFromServer; private JTextField send2Server; private JPanel friendsPanel; private JFrame frame; private String groupName; private Set<String> friends; private String myName; private Connect2Service connect2Service; public GroupChatGUI(String groupName,Set<String> friends,String myName,Connect2Service connect2Service){ this.groupName = groupName; this.friends = friends; this.myName = myName; this.connect2Service = connect2Service; frame = new JFrame(groupName); frame.setContentPane(GroupChatPanel); frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); frame.setLocationRelativeTo(null); frame.setSize(400,400); frame.setVisible(true); //TODO 加載羣中的好友列表 //豎向展現 friendsPanel.setLayout(new BoxLayout(friendsPanel,BoxLayout.Y_AXIS)); Iterator<String> iterator = friends.iterator(); while (iterator.hasNext()){ String labelName = iterator.next(); JLabel jLabel = new JLabel(labelName); friendsPanel.add(jLabel); } send2Server.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { StringBuilder sb = new StringBuilder(); sb.append(send2Server.getText()); //捕捉回車事件 if (e.getKeyCode() == KeyEvent.VK_ENTER){ String str2Server = sb.toString(); //給服務器發哪些東西 //type:4 //content:myName-msg //to:groupName MessageVO messageVO = new MessageVO(); messageVO.setType("4"); messageVO.setContent(myName+"-"+str2Server); messageVO.setTo(groupName); try { PrintStream out = new PrintStream(connect2Service.getOut(),true, "UTF-8"); out.println(CommUtils.object2Json(messageVO)); System.out.println("客戶端發送的羣聊信息爲:"+messageVO.getContent()); } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); } } } }); } public void readFromServer (String msg){ readFromServer.append(msg+"\n"); } public JFrame getFrame() { return frame; } }
思考問題?
如何在外部類的匿名內部類裏獲取匿名內部類對象? 外部類.this