此聊天程序,可以作爲java網絡學習的入門例子,程序雖小,五臟俱全,不過很多細節問題需要花時間完善。
1、聊天系統主要有兩個模塊:Client和Server
2、Client端功能:
①產生一個聊天窗口
②根據服務端的ip+端口後,通過Socket連接到服務端
③向服務端發送消息,並接受服務端的消息
④同時將消息顯示在聊天窗口上面
3、Server端功能:
①監聽端口
②接受服務端的請求,並與服務端建立連接
③接受消息,轉發消息至所有服務端
4、代碼ChatClient.java
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
public class ChatClient extends Frame {
private Socket s = null;
DataOutputStream dos;
DataInputStream dis;
private boolean bConnected = false;
TextField tfTxt;
TextArea taContent;
// 定義接收消息的線程
Thread tRecv = new Thread(new RecvThread());
public static void main(String[] args) {
new ChatClient().lanuchFrame();
}
public void lanuchFrame() {
this.setLocation(400, 300);
this.setSize(300, 300);
tfTxt = new TextField();
taContent = new TextArea();
taContent.setEditable(false);
this.add(tfTxt, BorderLayout.SOUTH);
this.add(taContent, BorderLayout.NORTH);
this.pack();
// 添加匿名監聽器
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
disConnect();
System.exit(0);
}
});
tfTxt.addActionListener(new TFListener());
this.setVisible(true);
connect();
tRecv.start();
}
/**
* 連接服務器
*/
public void connect() {
try {
s = new Socket("127.0.0.1", 8888); //這個地方用來添加服務器ip+端口,根據實際改
dos = new DataOutputStream(s.getOutputStream());
dis = new DataInputStream(s.getInputStream());
System.out.println("connected!");
bConnected = true;
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void disConnect() {
/*
try {
// ①讓接收消息的線程停下來
bConnected = false;
// ②結束線程
tRecv.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
dos.close();
dis.close();
s.close();
} catch (IOException e) {
// TODO Auto-generated catch
e.printStackTrace();
}
}
*/
try {
// ①讓接收消息的線程停下來
bConnected = false;
// ②結束線程
tRecv.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 添加監聽器的三種方式:①外部類 ②內部類 ③匿名類
private class TFListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String str = tfTxt.getText().trim();
// taContent.setText(str);
tfTxt.setText("");
try {
dos.writeUTF(str);
dos.flush();
/*
* //當dis關閉時,裏面包裝的流都會被關閉, 導致發一次消息就不能再放第二次了,所有要註釋掉dos.close();
*/
// dos.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
private class RecvThread implements Runnable {
@Override
public void run() {
try {
while (bConnected) {
String str = dis.readUTF();
// System.out.println(str);
taContent.setText(taContent.getText() + str + "\n");
}
} catch (SocketException e) {
System.out.println("退出了!和你們說88了");
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("退出了!和你們說88了");
}
}
}
}
5、代碼ChatServer.java
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
/**
* 設計思路:1 在主線程裏面啓動一個ServerSocket,不停的監聽客戶端的請求
* 2:每當接收到了一個客服端,將其Socket包裝到一個線程裏面,讓線程來 處理服務器與客戶端的通信
* 3:這樣主線程用來接收客服端,子線程用來和每個客戶端通信
*
* 注意:內部類的權限和非靜態實例的一樣,不能在靜態方法裏面new內部類, 只能用外部實例來new內部類
*
* @author Administrator
*
*/
public class ChatServer {
ServerSocket ss = null;
boolean started = false;
List<Client> clients = new ArrayList<Client>();
public static void main(String[] args) {
new ChatServer().start();
}
public void start() {
// ①建立一個ServerSocket
try {
ss = new ServerSocket(8888);
started = true;
} catch (BindException e1) {
// TODO Auto-generated catch block
System.out.println("端口使用中...");
System.out.println("請關閉相關的程序並重新運行服務器!");
System.exit(0);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// ②連接客戶端
try {
while (started) {
Socket s = ss.accept();
// 服務器接收一個Socket後,啓動一個線程來處理客服端的
// 通信
Client c = new Client(s);
System.out.println("a client connected!");
// 使用線程來接收客服端的信息
new Thread(c).start();
clients.add(c);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
ss.close();// 關閉ServerSocket
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/*
* 客戶端在服務端的一個包裝
*/
class Client implements Runnable {
private Socket s;
private DataInputStream dis = null;
private DataOutputStream dos = null;
private boolean bConnected = false;
public Client(Socket s) {
this.s = s;
try {
dis = new DataInputStream(s.getInputStream());
dos = new DataOutputStream(s.getOutputStream());
bConnected = true;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void send(String str) {
try {
dos.writeUTF(str);
} catch (IOException e) {
clients.remove(this);
System.out.println("對方退出了!我從List裏面去掉了");
// e.printStackTrace();
}
}
@Override
public void run() {
Client c=null;
try {
while (bConnected) {
String str = dis.readUTF();
// 每接收一個字符串,就全部轉發給其他的客戶端
System.out.println(str);
// for (Client c : clients) {
// c.send(str);
// }
for(int i=0;i<clients.size();i++){
c=clients.get(i);
c.send(str);
}
}
} catch (EOFException e) {
System.out.println("Client closed!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dis != null)
dis.close();
if (dos != null)
dos.close();
if (s != null) {
s.close();
// s=null;
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
}
6、運行:1)運行ChatServer.java:①爲其指定監聽端口,我這裏設置爲8888,
2)運行ChatClient.java:①爲其指定連接的ip+端口:這裏我用的是本地127.0.0.1:8888,這根據ChatServer運行的主機爲主
3)效果圖: