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 服務器端:
服務器端繼承JFrame框架,添加組件。創(chuàng)建服務器端的socket,起一個線程池,每接收到一個客戶端的連接,分配給其一個線程處理與客戶端的通信,將每個客戶端的昵稱和服務器分配給其的輸出流存儲到哈希表中。通過檢索哈希表中昵稱和輸出流實現(xiàn)群聊和私聊。
2 客戶端:
客戶端繼承JFrame框架,添加組件。創(chuàng)建客戶端的socket,輸入昵稱,創(chuàng)建客戶端socket對應的輸入流輸出流,與服務器端建立通訊。
四、設(shè)計描述
本程序的實現(xiàn)是以服務器端為中繼,客戶端的所有信息都先發(fā)送給服務器端。服務器端辨別是否為私聊信息,私聊信息發(fā)送給相應的私聊對象,否則,發(fā)送給所有的客戶端對象。
(一) 服務器端:
1.1 服務器端繼承JFrame框架,添加組件。
1.2 創(chuàng)建服務器端socket。建立一個哈希表,用于存儲客戶端的昵稱以及服務器端對于每個連接的客戶端建立的輸出流。
1.3建立一個線程池,為每個連接的客戶端分配一個執(zhí)行線程。
1.3調(diào)用服務器端Socket的accept()函數(shù),等待客戶端的連接,每連接到一個客戶端,線程池給相應的客戶端分配一個線程。
1.4每個線程內(nèi),調(diào)用服務器端Socket,封裝getOutputStream(),獲得服務器端Socket相對應于連接的客戶端的輸出流和輸入流。輸入流首先讀取到客戶端發(fā)送來的昵稱。將客戶端的昵稱和服務器端的輸出流存儲在哈希表中,并遍歷整個哈希表,將某人上線的通知發(fā)送給所有的在線客戶端。
1.5客戶端將信息發(fā)送給服務器端,當服務器端傳來的信息符合“@私聊對象:私聊信息”的格式時,服務器端認為這是私聊信息。服務器端將把私聊對象的昵稱從信息中提取出來,通過哈希表找到與昵稱對應的輸出流。將私聊信息通過輸出流發(fā)送給私聊對象。
當不是私聊信息時,服務器遍歷整個哈希表,通過每個客戶端對應的輸出流發(fā)送給每個客戶端。
1.6當客戶端下線時,服務器端將移除哈希表中對應存儲的客戶端昵稱以及輸出流。并通知在線的每個客戶端,某人已下線。
(二) 客戶端:
2.1 客戶端繼承框架JFrame,添加各種組件。
2.2 創(chuàng)建客戶端Socket,設(shè)置請求連接服務器端IP,以及使用的端口號。
2.3 封裝客戶端Socket的getInputStream()函數(shù),獲得客戶端Socket的輸入流接受服務器端發(fā)來的信息,封裝Socket的getOutputStream()函數(shù),獲得Socket的輸出流向服務器端發(fā)送信息。
2.4 通過向文本框添加動作事件監(jiān)聽器,監(jiān)聽文本框的輸入。
2.5 當與服務器端連接成功時,系統(tǒng)提醒輸入昵稱。系統(tǒng)將對輸入的昵稱進行檢索。判斷是否有重復的昵稱。如有重復則創(chuàng)建不成功,繼續(xù)輸入。
2.6 向服務器端發(fā)送信息時,如想對某人發(fā)送私聊信息,則需輸入符合“@私聊對象的呢稱:私聊信息”的格式,此時只有私聊對象和本人可以接收到。否則,發(fā)送的信息為公聊信息,所有的客戶端都能夠收到。
2.7 客戶端不斷接受服務器端的信息顯示在文本域。
五、代碼說話
1、服務器端:
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("聊天程序服務器端");
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("當前在線人數(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); //將對應客戶端的聊天信息取出作為私聊內(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 {
//服務端的輸入流讀取客戶端發(fā)送來的昵稱輸出流
BufferedReader bReader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8"));
//服務端將昵稱驗證結(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 {
/*
* 通過服務器端的socket分配給每一個
* 用來將消息發(fā)送給客戶端
*/
PrintWriter pw = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
/*
* 將客戶昵稱和其所說的內(nèi)容存入共享集合HashMap中
*/
name = getName();
putIn(name, pw);
Thread.sleep(100);
// 服務端通知所有客戶端,某用戶上線
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ū)域
//將光標放置在文本區(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、這里是服務器端,顯示當前連接人數(shù),以及公聊信息:

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


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

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java8 Stream API 詳細使用方法與操作技巧指南
這篇文章主要介紹了Java8 Stream API 詳細使用方法與操作技巧,總結(jié)分析了Java8 Stream API 基本功能、使用方法與操作注意事項,需要的朋友可以參考下2020-05-05
Shiro + JWT + SpringBoot應用示例代碼詳解
這篇文章主要介紹了Shiro (Shiro + JWT + SpringBoot應用),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
Java多態(tài)實現(xiàn)原理詳細梳理總結(jié)
這篇文章主要介紹了Java多態(tài)實現(xiàn)原理詳細梳理總結(jié),多態(tài)是繼封裝、繼承之后,面向?qū)ο蟮牡谌筇匦?,本文只總結(jié)了多態(tài)的實現(xiàn)原理,需要的朋友可以參考一下2022-06-06
基于Java數(shù)組實現(xiàn)循環(huán)隊列的兩種方法小結(jié)
下面小編就為大家分享一篇基于Java數(shù)組實現(xiàn)循環(huán)隊列的兩種方法小結(jié),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12
spring boot中多線程開發(fā)的注意事項總結(jié)
spring boot 通過任務執(zhí)行器 taskexecutor 來實現(xiàn)多線程和并發(fā)編程。下面這篇文章主要給大家介紹了關(guān)于spring boot中多線程開發(fā)的注意事項,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2018-09-09
spring中使用@Autowired注解無法注入的情況及解決
這篇文章主要介紹了spring中使用@Autowired注解無法注入的情況及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09

