仿QQ多人聊天室
下載地址:鏈接:https://pan.baidu.com/s/1dLFjSxwTA4gL5lI0B4UGuQ
提取碼:2qs0
有兩個項目,分別是服務器chatterServer和客戶端chatter,先開啓服務器,再開啓客戶端,默認8888端口。
先上圖(迴環測試,即自己和自己聊天):
實現的主要功能:
1、一對一聊天:連接到服務器的所有客戶端和另一個客戶端“一對一”聊天
2、多對多聊天:連接到服務器的所有客戶端在同一聊天室內聊天
使用到的Java技術:
1、Java圖形界面編程(JFrame和各種組件的使用)。還包括內部類,事件監聽。
2、Java IO。主要是字節流,聊天信息通過字節流編碼併發送、接收。
3、Java TCP通信。
4、Java多線程。
5、Java集合。其中一個重點是重寫equals方法和hashCode方法
6、Java異常處理
代碼結構:
客戶端
代碼分爲4個包:
com.chatter.GUI | 和圖形界面相關的類,主要是GUI類 |
com.chatter.client | 和客戶端後臺有關的類,主要是Client類 |
com.chatter.user | 和用戶信息存儲有關的類,包含Message類(數據的打包和解包),User類(用戶信息,最後沒用),PersonRecord類(用於記錄和某個特定客戶端的聊天記錄),Record類(用於記錄用戶和每個其他用戶的聊天記錄) |
com.chatter.main | main方法 |
項目類圖(客戶端):
服務器只有一個類,和從客戶端複製過去的一些類
主要代碼如下:
UI模塊:
package com.chatter.gui;
import java.awt.*;
import java.awt.event.*;
import java.util.Collection;
import java.util.HashSet;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import com.chatter.client.Client;
import com.chatter.user.Message;
import com.chatter.user.PersonRecord;
import com.chatter.user.User;
public class GUI {
Client client;
JFrame frame;
Container pane;
JPanel window, up, left, center, down;
//上方的軟件名
private JLabel label;
//左側選擇聊天對象的列表
JList<String> list;
DefaultListModel<String> dlm;
//中間的聊天消息顯示
JTextArea text;
//下方輸入文本的區域
public JTextField words;
//發送按鈕
JButton button;
final int WINDOW_WIDTH = 480;
final int WINDOW_HEIGHT = 640;
final double CUT_RATE_LEFT = 0.30;
public GUI() {
frame = new JFrame();
pane = frame.getContentPane();
window = new JPanel(new BorderLayout());
up = new UpperPanel();
left = new JPanel();
center = new JPanel();
down = new JPanel(new BorderLayout());
//上方的軟件圖標和名稱
label = new JLabel();
label.setPreferredSize(new Dimension(150,50));
label.setFont(new Font("楷體", Font.PLAIN, 30));
label.setText("GG聊天室");
up.add(label);
pane.add(BorderLayout.NORTH, up);
//左側的聊天對象選擇欄
dlm = new DefaultListModel<String>();
list = new JList<String>(dlm);
list.setPreferredSize(new Dimension((int)(CUT_RATE_LEFT*WINDOW_WIDTH), 400));
list.setModel(dlm);
dlm.clear();
dlm.addElement("0000000000008888");
list.setSelectedIndex(0);
list.addListSelectionListener((ListSelectionListener) new ListListener());;
left.add(list);
pane.add(BorderLayout.WEST, left);
//中心的聊天消息顯示
text = new JTextArea();
//text.setPreferredSize(new Dimension(280,2000));
text.setEditable(true);
text.setLineWrap(true);
JScrollPane scrollpane1 = new JScrollPane(text);
scrollpane1.setPreferredSize(new Dimension(300, 400));
scrollpane1.createVerticalScrollBar().setVisible(false);
scrollpane1.createHorizontalScrollBar().setVisible(true);
center.add(scrollpane1);
pane.add(BorderLayout.CENTER, center);
//下方的聊天內容輸入
words = new JTextField();
//words.setText("你好,我已經加你爲好友\n,我們可以開始聊天了");
words.setEditable(true);
//words.setLineWrap(true);
//words.setWrapStyleWord(true);
JScrollPane scrollpane2= new JScrollPane(words);
scrollpane2.setPreferredSize(new Dimension(350, 100));
scrollpane2.createVerticalScrollBar().setVisible(false);
scrollpane2.createHorizontalScrollBar().setVisible(true);
//scrollpane2.setAutoscrolls(true);
down.add(BorderLayout.WEST, scrollpane2);
//發送按鈕
button = new JButton("發送");
BottonListener bl = new BottonListener();
//bl.setTextField(words);
button.addActionListener(bl);
button.setPreferredSize(new Dimension(100, 30));
down.add(BorderLayout.EAST, button);
pane.add(BorderLayout.SOUTH, down);
frame.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public void receive(Message msg) {
System.out.println("收到消息,準備更新. msg Type: " + msg.getHead());
if (msg.getHead() == 1) {
String[] s = msg.getContent().split("[|]");
Collection<PersonRecord> tmp_c = new HashSet<PersonRecord>();
for(String str:s) {
tmp_c.add(new PersonRecord(Long.parseLong(str)));
System.out.println(str);
}
tmp_c.add(new PersonRecord(8888));
System.out.println("新建列表內容");
for (Object o:tmp_c.toArray()) {
System.out.println(((PersonRecord)o).personId);
}
System.out.println("_______ENC");
client.record.c.addAll(tmp_c);
client.record.c.retainAll(tmp_c);
System.out.println("加入後原列表內容");
for (Object o:client.record.c.toArray()) {
System.out.println(((PersonRecord)o).personId);
}
System.out.println("_______ENC2");
//System.out.println("系統原有集合:"+(PersonRecord[])(client.record.c.toArray()));
//System.out.println("新創建集合:"+ (PersonRecord[])(tmp_c.toArray()));
for(PersonRecord pr: client.record.c) {
if (dlm.contains(String.format("%016d" ,pr.personId)) == false)
dlm.addElement(String.format("%016d" ,pr.personId));
}
}
else if (msg.getHead() == 0)
for(PersonRecord pr:client.record.c) {
if(pr.personId == msg.getTransmitter())
pr.chatRecord.append("對方:");
pr.chatRecord.append(msg.getContent());
pr.chatRecord.append("\n");
text.setText(pr.chatRecord.toString());
}
else if (msg.getHead() == 99) {
for(PersonRecord pr:client.record.c) {
pr.chatRecord.append("對方:");
pr.chatRecord.append(msg.getContent());
pr.chatRecord.append("\n");
text.setText(pr.chatRecord.toString());
}
}
}
public void send(String content) {
System.out.println("正在發送給"+list.getSelectedValue()+" 內容:" + content);
Message m = new Message();
content.replaceAll("\\n", "");
m.setContent(content);
//0:普通消息
if (Long.parseLong(list.getSelectedValue()) == 8888) {
m.setHead(99);
m.setReceiver(Long.parseLong(list.getSelectedValue()));
}
else {
m.setHead(0);
m.setReceiver(Long.parseLong(list.getSelectedValue()));
}
System.out.println("發送與接收者:" +m.getTransmitter() +" "+m.getReceiver());
for(PersonRecord pr:client.record.c) {
System.out.println("現有用戶id:"+pr.personId);
if(pr.personId == m.getReceiver()) {
System.out.println("正在添加聊天記錄");
pr.chatRecord.append("你:");
pr.chatRecord.append(m.getContent());
pr.chatRecord.append("\n");
text.setText(pr.chatRecord.toString());
}
}
System.out.println("-------ready to send msg--------");
client.send(m);
}
class BottonListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
String str = words.getText();
if(str.trim().length() == 0) return;
words.setText("");
send(str);
}
}
class ListListener implements ListSelectionListener{
public void actionPerformed(ActionEvent e) {
}
public void valueChanged(ListSelectionEvent e) {
for(PersonRecord pr:client.record.c) {
if(pr.personId == Long.parseLong(list.getSelectedValue()))
text.setText(pr.chatRecord.toString());
}
}
}
public void setClient(Client c) {
this.client = c;
}
}
客戶端後臺Client:
package com.chatter.client;
import java.io.*;
import java.io.IOException;
import java.net.*;
import com.chatter.gui.GUI;
import com.chatter.user.Message;
import com.chatter.user.PersonRecord;
import com.chatter.user.Record;
public class Client {
private String ip;
private int port;
private long localId;
private Socket socket;
private PrintWriter pw;
private BufferedReader br;
private GUI gui;
public Record record;
public Client(String ip, int port) {
this.ip = ip;
this.port = port;
try {
socket = new Socket(ip, port);
OutputStream os = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, "utf-8");
BufferedWriter bw = new BufferedWriter(osw);
pw = new PrintWriter(bw, true);
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
br = new BufferedReader(isr);
localId = getLocalId(ip, port);
record = new Record();
//record.c.add(new PersonRecord(8888));
}catch (Exception e) {
System.out.println("連接服務器失敗");
System.exit(0);
}
}
public boolean setGUI(GUI g) {
if (g == null)
return false;
this.gui = g;
return true;
}
public void start() {
Thread t_receive = new Thread() {
String line;
public void run() {
try {
while((line = br.readLine()) != null) {
try {
System.out.print("收到新消息");
System.out.println("\t" + line);
Message msg = new Message(line);
System.out.println("Before calling gui.receive: ");
System.out.println(msg.getContent());
gui.receive(msg);
}catch(Exception e) {
e.printStackTrace();
System.out.println("Message encoding error");
}
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("服務器主動斷開連接。");
System.exit(0);
}
}
};
t_receive.start();
}
static public long getLocalId(String IP, int port) {
String[] ipSplit = IP.split("\\.");
long[] arr = new long[5];
for (int i=0; i<ipSplit.length; i++) {
arr[i] = Integer.parseInt(ipSplit[i]);
}
arr[4] = port;
return arr[4]+arr[3]*10000+arr[2]*10000000+arr[1]*1000000000L+arr[0]*10000000000000L;
}
public void send(Message msg) {
if (msg.getHead() == 0) {
System.out.println("Sending message to server...");
msg.setTransmitter(localId);
String str = msg.toString();
System.out.println("\tmessage content: "+msg.getContent());
pw.println(str);
}
else if (msg.getHead() == 99) {
msg.setTransmitter(localId);
}
}
public void setRecord(Record r) {
this.record = r;
record.c.add(new PersonRecord(8888));
}
}
服務器Server:
package com.chatter.server;
import java.io.*;
import java.net.*;
import java.util.Collection;
import java.util.HashSet;
import com.chatter.user.Message;
import com.chatter.user.User;
public class Server {
ServerSocket serverSocket;
Collection<User> c = new HashSet<User>();
public Server() {
try {
serverSocket = new ServerSocket(8888);
while(true) {
Socket socket = serverSocket.accept();
long remoteID = getIDFromIP(socket.getInetAddress().getHostAddress());
System.out.println(socket.getInetAddress() + "已連接");
Thread thread = new Thread() {
public void run() {
try {
OutputStream os = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, "utf-8");
BufferedWriter bw = new BufferedWriter(osw);
PrintWriter pw = new PrintWriter(osw, true);
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
User u = new User(socket, getIDFromIP(socket.getInetAddress().getHostAddress()), br, pw);
c.add(u);
System.out.println(u.id + "已連接");
sendUpdateMsg();
//System.exit(0);
String line;
while((line = br.readLine()) != null) {
Message msg = new Message(line);
System.out.println("服務器收到消息: " + msg.toString());
if (msg.getHead() == 0) {
long id = msg.getReceiver();
System.out.println("Receiver from raw msg: "+msg.getReceiver());
//id = id*10000+8888;
System.out.println("Sending to Client: "+id);
for (User tmp: c) {
System.out.println("服務器現有用戶: "+tmp.id);
if (tmp.id == id) {
System.out.println("Sending to Client: "+tmp.socket.getInetAddress());
tmp.pw.println(msg.toString());
pw.flush();
}
}
}
else if (msg.getHead() == 99) {
for (User tmp: c) {
tmp.pw.println(msg.toString());
}
}
}
}catch(Exception e) {
for (User u: c) {
if (u.id == remoteID) {
c.remove(u);
sendUpdateMsg();
}
}
e.printStackTrace();;
}
}
};
thread.start();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("服務器開啓失敗");
System.exit(0);
}
}
private void sendUpdateMsg() {
System.out.println("發送在線列表更新信息中...");
Message msg = new Message();
msg.setHead(1);
msg.setTransmitter(0);
StringBuffer s = new StringBuffer();
for (User tmp:c) {
s.append(String.format("%016d", tmp.id));
s.append('|');
}
msg.setContent(s.toString());
for (User tmp:c) {
System.out.println("Sending to "+tmp.id);
msg.setReceiver(tmp.id);
tmp.pw.println(msg.toString());
}
}
private long getIDFromIP(String IP) {
try {
String[] ipSplit = IP.split("\\.");
long[] arr = new long[5];
for (int i=0; i<ipSplit.length; i++) {
arr[i] = Integer.parseInt(ipSplit[i]);
}
arr[4] = 8888;
return arr[4]+arr[3]*10000+arr[2]*10000000+arr[1]*1000000000L+arr[0]*10000000000000L;
}catch(Exception e) {
e.printStackTrace();
}
return 0;
}
}
個人作品,請勿轉載。