java基于TCP協(xié)議實(shí)現(xiàn)聊天程序
JAVA程序設(shè)計(jì)之基于TCP協(xié)議的socket聊天程序 ,供大家參考,具體內(nèi)容如下
一、程序?qū)崿F(xiàn)的功能
1、進(jìn)入客戶(hù)端界面
2、創(chuàng)建昵稱(chēng)
3、群發(fā)信息
4、@私聊
5、下線通知
6、在線人數(shù)統(tǒng)計(jì)
二、整體架構(gòu)圖

三、簡(jiǎn)單介紹
本程序?qū)崿F(xiàn)了基于TCP通信的聊天程序:
1 服務(wù)器端:
服務(wù)器端繼承JFrame框架,添加組件。創(chuàng)建服務(wù)器端的socket,起一個(gè)線程池,每接收到一個(gè)客戶(hù)端的連接,分配給其一個(gè)線程處理與客戶(hù)端的通信,將每個(gè)客戶(hù)端的昵稱(chēng)和服務(wù)器分配給其的輸出流存儲(chǔ)到哈希表中。通過(guò)檢索哈希表中昵稱(chēng)和輸出流實(shí)現(xiàn)群聊和私聊。
2 客戶(hù)端:
客戶(hù)端繼承JFrame框架,添加組件。創(chuàng)建客戶(hù)端的socket,輸入昵稱(chēng),創(chuàng)建客戶(hù)端socket對(duì)應(yīng)的輸入流輸出流,與服務(wù)器端建立通訊。
四、設(shè)計(jì)描述
本程序的實(shí)現(xiàn)是以服務(wù)器端為中繼,客戶(hù)端的所有信息都先發(fā)送給服務(wù)器端。服務(wù)器端辨別是否為私聊信息,私聊信息發(fā)送給相應(yīng)的私聊對(duì)象,否則,發(fā)送給所有的客戶(hù)端對(duì)象。
(一) 服務(wù)器端:
1.1 服務(wù)器端繼承JFrame框架,添加組件。
1.2 創(chuàng)建服務(wù)器端socket。建立一個(gè)哈希表,用于存儲(chǔ)客戶(hù)端的昵稱(chēng)以及服務(wù)器端對(duì)于每個(gè)連接的客戶(hù)端建立的輸出流。
1.3建立一個(gè)線程池,為每個(gè)連接的客戶(hù)端分配一個(gè)執(zhí)行線程。
1.3調(diào)用服務(wù)器端Socket的accept()函數(shù),等待客戶(hù)端的連接,每連接到一個(gè)客戶(hù)端,線程池給相應(yīng)的客戶(hù)端分配一個(gè)線程。
1.4每個(gè)線程內(nèi),調(diào)用服務(wù)器端Socket,封裝getOutputStream(),獲得服務(wù)器端Socket相對(duì)應(yīng)于連接的客戶(hù)端的輸出流和輸入流。輸入流首先讀取到客戶(hù)端發(fā)送來(lái)的昵稱(chēng)。將客戶(hù)端的昵稱(chēng)和服務(wù)器端的輸出流存儲(chǔ)在哈希表中,并遍歷整個(gè)哈希表,將某人上線的通知發(fā)送給所有的在線客戶(hù)端。
1.5客戶(hù)端將信息發(fā)送給服務(wù)器端,當(dāng)服務(wù)器端傳來(lái)的信息符合“@私聊對(duì)象:私聊信息”的格式時(shí),服務(wù)器端認(rèn)為這是私聊信息。服務(wù)器端將把私聊對(duì)象的昵稱(chēng)從信息中提取出來(lái),通過(guò)哈希表找到與昵稱(chēng)對(duì)應(yīng)的輸出流。將私聊信息通過(guò)輸出流發(fā)送給私聊對(duì)象。
當(dāng)不是私聊信息時(shí),服務(wù)器遍歷整個(gè)哈希表,通過(guò)每個(gè)客戶(hù)端對(duì)應(yīng)的輸出流發(fā)送給每個(gè)客戶(hù)端。
1.6當(dāng)客戶(hù)端下線時(shí),服務(wù)器端將移除哈希表中對(duì)應(yīng)存儲(chǔ)的客戶(hù)端昵稱(chēng)以及輸出流。并通知在線的每個(gè)客戶(hù)端,某人已下線。
(二) 客戶(hù)端:
2.1 客戶(hù)端繼承框架JFrame,添加各種組件。
2.2 創(chuàng)建客戶(hù)端Socket,設(shè)置請(qǐng)求連接服務(wù)器端IP,以及使用的端口號(hào)。
2.3 封裝客戶(hù)端Socket的getInputStream()函數(shù),獲得客戶(hù)端Socket的輸入流接受服務(wù)器端發(fā)來(lái)的信息,封裝Socket的getOutputStream()函數(shù),獲得Socket的輸出流向服務(wù)器端發(fā)送信息。
2.4 通過(guò)向文本框添加動(dòng)作事件監(jiān)聽(tīng)器,監(jiān)聽(tīng)文本框的輸入。
2.5 當(dāng)與服務(wù)器端連接成功時(shí),系統(tǒng)提醒輸入昵稱(chēng)。系統(tǒng)將對(duì)輸入的昵稱(chēng)進(jìn)行檢索。判斷是否有重復(fù)的昵稱(chēng)。如有重復(fù)則創(chuàng)建不成功,繼續(xù)輸入。
2.6 向服務(wù)器端發(fā)送信息時(shí),如想對(duì)某人發(fā)送私聊信息,則需輸入符合“@私聊對(duì)象的呢稱(chēng):私聊信息”的格式,此時(shí)只有私聊對(duì)象和本人可以接收到。否則,發(fā)送的信息為公聊信息,所有的客戶(hù)端都能夠收到。
2.7 客戶(hù)端不斷接受服務(wù)器端的信息顯示在文本域。
五、代碼說(shuō)話
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)建線程池來(lái)管理客戶(hù)端的連接線程
* 避免系統(tǒng)資源過(guò)度浪費(fèi)
*/
private ExecutorService exec;
// 存放客戶(hù)端之間私聊的信息
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();
}
}
// 將客戶(hù)端的信息以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ā)給所有客戶(hù)端
private synchronized void sendToAll(String message) {
for(PrintWriter out: storeInfo.values()) {
out.println(message);
// m_display.append("已經(jīng)發(fā)送了");
}
}
// 將給定的消息轉(zhuǎn)發(fā)給私聊的客戶(hù)端
private synchronized void sendToSomeone(String name,String message) {
PrintWriter pw = storeInfo.get(name); //將對(duì)應(yīng)客戶(hù)端的聊天信息取出作為私聊內(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("等待客戶(hù)端連接... ... \n");
Socket socket = serverSocket.accept();
// 獲取客戶(hù)端的ip地址
InetAddress address = socket.getInetAddress();
m_display.append("客戶(hù)端:“" + address.getHostAddress() + "”連接成功! ");
/*
* 啟動(dòng)一個(gè)線程,由線程來(lái)處理客戶(hù)端的請(qǐng)求,這樣可以再次監(jiān)聽(tīng)
* 下一個(gè)客戶(hù)端的連接
*/
exec.execute(new ListenrClient(socket)); //通過(guò)線程池來(lái)分配線程
}
} catch(Exception e) {
e.printStackTrace();
}
}
/**
* 該線程體用來(lái)處理給定的某一個(gè)客戶(hù)端的消息,循環(huán)接收客戶(hù)端發(fā)送
* 的每一個(gè)字符串,并輸出到控制臺(tái)
*/
class ListenrClient implements Runnable {
private Socket socket;
private String name;
public ListenrClient(Socket socket) {
this.socket = socket;
}
// 創(chuàng)建內(nèi)部類(lèi)來(lái)獲取昵稱(chēng)
private String getName() throws Exception {
try {
//服務(wù)端的輸入流讀取客戶(hù)端發(fā)送來(lái)的昵稱(chēng)輸出流
BufferedReader bReader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8"));
//服務(wù)端將昵稱(chēng)驗(yàn)證結(jié)果通過(guò)自身的輸出流發(fā)送給客戶(hù)端
PrintWriter ipw = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF-8"),true);
//讀取客戶(hù)端發(fā)來(lái)的昵稱(chēng)
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 {
/*
* 通過(guò)服務(wù)器端的socket分配給每一個(gè)
* 用來(lái)將消息發(fā)送給客戶(hù)端
*/
PrintWriter pw = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
/*
* 將客戶(hù)昵稱(chēng)和其所說(shuō)的內(nèi)容存入共享集合HashMap中
*/
name = getName();
putIn(name, pw);
Thread.sleep(100);
// 服務(wù)端通知所有客戶(hù)端,某用戶(hù)上線
sendToAll("*系統(tǒng)消息* “" + name + "”已上線");
/*
* 通過(guò)客戶(hù)端的Socket獲取輸入流
* 讀取客戶(hù)端發(fā)送來(lái)的信息
*/
BufferedReader bReader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8"));
String msgString = null;
while((msgString = bReader.readLine()) != null) {
// 檢驗(yàn)是否為私聊(格式:@昵稱(chēng):內(nèi)容)
if(msgString.startsWith("@")) {
int index = msgString.indexOf(":");
if(index >= 0) {
//獲取昵稱(chēng)
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;
}
}
// 遍歷所有輸出流,將該客戶(hù)端發(fā)送的信息轉(zhuǎn)發(fā)給所有客戶(hù)端
m_display.append(name+":"+ msgString+"\n");
sendToAll(name+":"+ msgString);
}
} catch (Exception e) {
// e.printStackTrace();
} finally {
remove(name);
// 通知所有客戶(hù)端,某某客戶(hù)已經(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、客戶(hù)端:
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("聊天程序客戶(hù)端");
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)移輸入焦點(diǎ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("請(qǐng)創(chuàng)建用戶(hù)名:");
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+"'"+"昵稱(chēng)設(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();
}
}
}
}
}
六、運(yùn)行結(jié)果
1、這里是服務(wù)器端,顯示當(dāng)前連接人數(shù),以及公聊信息:

2、此時(shí)為群內(nèi)成員公聊:
這里創(chuàng)建了三個(gè)角色:馬衍碩、李琦琦、小紅。


3、私聊:
私聊格式“@名稱(chēng):”+內(nèi)容。
私聊時(shí),只有私聊的兩個(gè)人可以接收到信息,其余人接收不到交流信息。
例:馬衍碩和李琦琦私聊,小紅接收不到私聊信息。
馬衍碩和李琦琦接收到了私聊信息:
小紅沒(méi)有接收到私聊信息:

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- java中UDP簡(jiǎn)單聊天程序?qū)嵗a
- 詳解基于java的Socket聊天程序——客戶(hù)端(附demo)
- java網(wǎng)絡(luò)編程學(xué)習(xí)java聊天程序代碼分享
- java基于C/S模式實(shí)現(xiàn)聊天程序(客戶(hù)端)
- 詳解基于java的Socket聊天程序——服務(wù)端(附demo)
- java實(shí)現(xiàn)基于Tcp的socket聊天程序
- 詳解基于java的Socket聊天程序——初始設(shè)計(jì)(附demo)
- java實(shí)現(xiàn)簡(jiǎn)單TCP聊天程序
- 基于Java的Socket多客戶(hù)端Client-Server聊天程序的實(shí)現(xiàn)
- 用Java實(shí)現(xiàn)聊天程序
相關(guān)文章
SpringBoot實(shí)現(xiàn)子類(lèi)的反序列化示例代碼
這篇文章主要給大家介紹了關(guān)于SpringBoot實(shí)現(xiàn)子類(lèi)的反序列化的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用SpringBoot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Java中mybatis關(guān)于example類(lèi)的使用詳解
這篇文章主要介紹了Java中mybatis中關(guān)于example類(lèi)的使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
java實(shí)現(xiàn)IP地址轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)IP地址轉(zhuǎn)換,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
最長(zhǎng)公共子序列問(wèn)題的深度分析與Java實(shí)現(xiàn)方式
本文詳細(xì)介紹了最長(zhǎng)公共子序列(LCS)問(wèn)題,包括其概念、暴力解法、動(dòng)態(tài)規(guī)劃解法,并提供了Java代碼實(shí)現(xiàn),暴力解法雖然簡(jiǎn)單,但在大數(shù)據(jù)處理中效率較低,動(dòng)態(tài)規(guī)劃解法通過(guò)構(gòu)建DP表,顯著提高了計(jì)算效率,適用于大規(guī)模數(shù)據(jù)處理2025-02-02
Java中隊(duì)列Queue和Deque的區(qū)別與代碼實(shí)例
學(xué)過(guò)數(shù)據(jù)結(jié)構(gòu)的,一定對(duì)隊(duì)列不陌生,java也實(shí)現(xiàn)了隊(duì)列,下面這篇文章主要給大家介紹了關(guān)于Java中隊(duì)列Queue和Deque區(qū)別的相關(guān)資料,需要的朋友可以參考下2021-08-08
Java中List與數(shù)組之間的相互轉(zhuǎn)換
在日常Java學(xué)習(xí)或項(xiàng)目開(kāi)發(fā)中,經(jīng)常會(huì)遇到需要int[]數(shù)組和List列表相互轉(zhuǎn)換的場(chǎng)景,然而往往一時(shí)難以想到有哪些方法,最后可能會(huì)使用暴力逐個(gè)轉(zhuǎn)換法,往往不是我們所滿(mǎn)意的,下面這篇文章主要給大家介紹了關(guān)于Java中List與數(shù)組之間的相互轉(zhuǎn)換,需要的朋友可以參考下2023-05-05
Java基礎(chǔ)之顏色工具類(lèi)(超詳細(xì)注釋)
這篇文章主要介紹了Java基礎(chǔ)之顏色工具類(lèi)(超詳細(xì)注釋?zhuān)?文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04

