Java實現(xiàn)多線程聊天室
本文實例為大家分享了Java實現(xiàn)多線程聊天室的具體代碼,供大家參考,具體內(nèi)容如下
之前呢已經(jīng)用單線程的方式來實現(xiàn)了聊天室,但其實它的功能并不齊全,下面用多線程來實現(xiàn),功能會比單線程聊天室更加齊全,也更人性化一點
多線程版本的聊天室
1. 功能分析:
- 實現(xiàn)用戶注冊,上線,下線
- 實現(xiàn)群聊和私聊
- 統(tǒng)計當(dāng)前在線人數(shù)
2. 服務(wù)端實現(xiàn)
1.維護所有的在線用戶
2.注冊功能:客戶端名稱,添加到服務(wù)器的客戶端集合里
3.群聊功能:客戶端發(fā)送消息,所有的客戶端都能接收到
4.私聊功能:客戶端與指定客戶端進發(fā)送和接收消息
5.退出功能: 從服務(wù)器客戶端集合中移除客戶端
3. 客戶端實現(xiàn)
1.注冊功能:創(chuàng)建Socket對象,給服務(wù)器發(fā)送注冊執(zhí)行(消息)
2.群聊功能:客戶端發(fā)送和接收數(shù)據(jù)
3.私聊功能:客戶端指定客戶端(用戶),發(fā)送和接收數(shù)據(jù)
4.退出功能:給服務(wù)器發(fā)送退出指令(消息)
5.命令行的交互式輸入輸出
4.實現(xiàn)思路:
首先,要實現(xiàn)服務(wù)端與客戶端之間的連接
這里是使用套接字建立TCP連接:
(1)服務(wù)器端先實例化一個描述服務(wù)器端口號的ServerSocket對象
(2)客戶端要創(chuàng)建Socket對象來連接指定的服務(wù)器端
(3)服務(wù)器端調(diào)用ServerSocket類的accept()方法來監(jiān)聽連接到服務(wù)器端的客戶端信息
(4)若服務(wù)器端與客戶端連接成功,雙方將返回一個Socket對象,此時雙方可以進行通信
(5)服務(wù)器端與客戶端使用I/O流進行連接,服務(wù)端的輸出流連接客戶端的輸入流,客戶端的輸出流連接服務(wù)端的輸入流
(6)使用close()方法關(guān)閉套接字(一定要記得關(guān)閉)
2.因為是擁有一個服務(wù)端來實現(xiàn)多個客戶端的連接,此處還要解決的是多線程的問題。
每個客戶端需要兩個線程,來分別處理向服務(wù)端發(fā)送消息和向服務(wù)端接收消息
而服務(wù)端,當(dāng)每增加一個客戶端與服務(wù)端連接,服務(wù)端都要多創(chuàng)建一個線程來處理與客戶端的連接
5. 圖解析

6. 服務(wù)端代碼實現(xiàn)
Server類
package test.Server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* package:test.Server
* Description:服務(wù)器端
* @date:2019/8/14
* @Author:weiwei
**/
public class server {
public static void main(String[] args) {
try {
int port = 6666;
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("服務(wù)器啟動..." + serverSocket.getLocalSocketAddress()); //服務(wù)器啟動,打印本地地址
//線程池
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
while (true) { //死循環(huán)
Socket client = serverSocket.accept();
System.out.println("有客戶端連接到服務(wù)器:" + client.getRemoteSocketAddress());
executorService.execute(new HandlerClient(client));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}HandlerClient類
package test.Server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
/**
* Author:weiwei
* description:HandlerClient
* Creat:2019/3/12
**/
public class HandlerClient implements Runnable {
/**
* 維護所有的連接到服務(wù)端的客戶端對象
*/
private static final Map<String,Socket> ONLINE_CLIENT_MAP =
new ConcurrentHashMap<String, Socket>(); //靜態(tài)是為了不讓對象變化,final不讓對象被修改,ConcurrentHashMap是線程安全的類
//static final修飾后變量名應(yīng)該用常量--大寫字母加下劃線分隔
private final Socket client;
public HandlerClient(Socket client) { //HandlerClient在多線程環(huán)境下調(diào)用,所以會產(chǎn)生資源競爭,用一個并發(fā)的HashMap
this.client = client; //為了防止變量被修改,用final修飾
}
//@Override
public void run() {
try {
InputStream clientInput=client.getInputStream(); //獲取客戶端的數(shù)據(jù)流
Scanner scanner = new Scanner(clientInput); //字節(jié)流轉(zhuǎn)字符流
/**
*消息是按行讀取
* 1.register:<username> 例如: register:張三
* 2.群聊: groupChat:<message> 例如:groupChat:大家好
* 3.私聊: privateChat:張三:你好,還錢
* 4.退出:bye
*/
while(true){
String data = scanner.nextLine(); //讀數(shù)據(jù),按行讀
if(data.startsWith("register:")){
//注冊
String userName = data.split(":")[1];//冒號分隔,取第一個
register(userName);
continue;
}
if(data.startsWith("groupChat:")){
String message = data.split(":")[1];
groupChat(message);
continue;
}
if(data.startsWith("privateChat:")){
String [] segments = data.split(":");
String targetUserName = segments[1].split("\\-")[0]; //取目標(biāo)用戶名
String message = segments[1].split("\\-")[1]; //因為要取兩次,所以用數(shù)組 //取發(fā)送的消息內(nèi)容
privateChat(targetUserName,message);
continue;
}
if(data.equals("bye")){
//表示退出
bye();
continue;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 當(dāng)前客戶端退出
*/
private void bye() {
for(Map.Entry<String,Socket> entry : ONLINE_CLIENT_MAP.entrySet()){
Socket target = entry.getValue();
if(target.equals(this.client)){ //在在線用戶中找到自己并且移除
ONLINE_CLIENT_MAP.remove(entry.getKey());
break;
}
System.out.println(getCurrentUserName()+"退出聊天室");
}
printOnlineClient();//打印當(dāng)前用戶
}
private String getCurrentUserName(){
for (Map.Entry<String, Socket> entry : ONLINE_CLIENT_MAP.entrySet()) {
Socket target = entry.getValue(); //getvalue得到Socket對象
if(target.equals(this.client)){ //排除群聊的時候自己給自己發(fā)消息的情況
return entry.getKey();
}
}
return "";
}
/**
* 私聊,給targetUserName發(fā)送message消息
* @param targetUserName
* @param message
*/
private void privateChat(String targetUserName, String message) {
Socket target = ONLINE_CLIENT_MAP.get(targetUserName);//獲取目標(biāo)用戶名
if(target == null){
this.sendMessage(this.client,"沒有這個人"+targetUserName,false);
}else{
this.sendMessage(target,message,true);
}
}
/**
* 群聊,發(fā)送message
* @param message
*/
private void groupChat(String message) {
for (Map.Entry<String, Socket> entery : ONLINE_CLIENT_MAP.entrySet()) {
Socket target = entery.getValue(); //getvalue得到Socket對象
if(target.equals(this.client)){
continue; //排除群聊的時候自己給自己發(fā)消息的情況
}
this.sendMessage(target,message,true);
}
}
/**
* 以userName為key注冊當(dāng)前用戶(Socket client)
* @param userName
*/
private void register(String userName) {
if(ONLINE_CLIENT_MAP.containsKey(userName)){
this.sendMessage(this.client,"您已經(jīng)注冊過了,無需重復(fù)注冊",false);
}else{
ONLINE_CLIENT_MAP.put(userName,this.client);
printOnlineClient();
this.sendMessage(this.client,"恭喜"+userName+"注冊成功\n",false);
}
}
private void sendMessage(Socket target,String message,boolean prefix){
OutputStream clientOutput = null; //value是每一個客戶端
try {
clientOutput = target.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(clientOutput);
if(prefix) {
String currentUserName = this.getCurrentUserName();
writer.write("<" + currentUserName + "說:>" + message + "\n");
}else{
writer.write( message + "\n");
}
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 打印在線客戶端
*/
private void printOnlineClient(){
System.out.println("當(dāng)前在線人數(shù):"+ONLINE_CLIENT_MAP.size()+","+"用戶名如下列表:");
for(String userName : ONLINE_CLIENT_MAP.keySet()){ //Map的key為用戶名
System.out.println(userName);
}
}
}7. 客戶端代碼實現(xiàn)
Client類
package Cilent;
import java.io.IOException;
import java.net.Socket;
/**
* package:Cilent
* Description:客戶端
* @date:2019/8/14
* @Author:weiwei
**/
public class cilent {
public static void main(String[] args) {
try {
//讀取地址
String host = "127.0.0.1";
//讀取端口號
int port = 6666;
Socket client = new Socket(host,port); //先寫數(shù)據(jù)再讀數(shù)據(jù),讀寫線程分離
new ReadDataFromServerThread(client).start();//啟動讀線程
new WriteDataToServerThread(client).start();//啟動寫線程
} catch (IOException e) {
e.printStackTrace();
}
}
}WriteDateToServer類
package Cilent;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* Author:weiwei
* description:客戶端給服務(wù)端發(fā)送數(shù)據(jù)的線程
* 發(fā)送的數(shù)據(jù)來自命令行的交互式輸入
* Creat:2019/3/12
**/
public class WriteDataToServerThread extends Thread{
private final Socket client;
public WriteDataToServerThread(Socket client){
this.client = client;
}
@Override
public void run(){
try {
OutputStream clientOutput = this.client.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(clientOutput);
Scanner scanner = new Scanner(System.in); //有客戶端輸入數(shù)據(jù)
while(true){
System.out.print("請輸入>>");
String data = scanner.nextLine(); //讀數(shù)據(jù)
writer.write(data+"\n");
writer.flush();
if(data.equals("bye")){
System.out.println("您已下線...");
break;
}
}
this.client.close();
} catch (IOException e) {
// e.printStackTrace();
}
}
}ReadDateFromServer類
package Cilent;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* Author:weiwei
* description:客戶端從服務(wù)端讀取數(shù)據(jù)的線程
* Creat:2019/3/12
**/
public class ReadDataFromServerThread extends Thread {
private final Socket client;
public ReadDataFromServerThread(Socket client){
this.client=client;
}
@Override
public void run(){
try {
InputStream clientInput = this.client.getInputStream();
Scanner scanner = new Scanner(clientInput);
while(true){
String data = scanner.nextLine();//按行讀數(shù)據(jù)
System.out.println("來自服務(wù)端消息:"+data);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解設(shè)計模式中的proxy代理模式及在Java程序中的實現(xiàn)
代理模式主要分為靜態(tài)代理和動態(tài)代理,使客戶端方面的使用者通過設(shè)置的代理來操作對象,下面來詳解設(shè)計模式中的proxy代理模式及在Java程序中的實現(xiàn)2016-05-05
java設(shè)置session過期時間的實現(xiàn)方法
這篇文章主要介紹了java設(shè)置session過期時間的實現(xiàn)方法,以實例形式詳細(xì)講述了具體實現(xiàn)過程,非常具有參考借鑒價值,需要的朋友可以參考下2014-10-10
詳解MyBatis多數(shù)據(jù)源配置(讀寫分離)
這篇文章主要介紹了詳解MyBatis多數(shù)據(jù)源配置(讀寫分離),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-01-01

