Java最後一道作業題竟然是寫個多線程聊天工具。以前在一次實訓中做過linux版的。當時就有人說Java版的實作比較簡單,如今看來确實有理。尤其是在做UI上,不用像gtk那麼麻煩。先将我搜到的資源與大家共享(親測可用):
該網絡聊天程式大緻分為三個主要部分:用戶端、伺服器端和使用者圖形界面。各個部分的初步設計思想、流程及存儲結構如下:
1. 程式整體架構:主程式監聽一端口,等待客戶接入;同時構造一個線程類,準備接管會話。當一個Socket會話産生後,将這個會話交給線程處理,然後主程式繼續監聽。
打開Socket |
命 名 |
監聽端口 |
建立連接配接 |
收發消息 |
關閉連接配接 |
打開Socket |
連接配接伺服器 |
收發消息 |
關閉連接配接 |
伺服器端程式 |
用戶端程式 |
2. 用戶端(Client)
用戶端,使用Socket對網絡上某一個伺服器的某一個端口發出連接配接請求,一旦連接配接成功,打開會話;會話完成後,關閉Socket。用戶端不需要指定打開的端口,通常臨時的、動态的配置設定一個端口。
3. 伺服器端(Server)
伺服器端,使用ServerSocket監聽指定的端口,端口可以随意指定(由于1024以下的端口通常屬于保留端口,在一些作業系統中不可以随意使用,是以建議使用大于1024的端口),等待客戶連接配接請求,客戶連接配接後,會話産生;在完成會話後,關閉連接配接。
4. 使用者圖形界面
使用者圖形界面友善程式與使用者的互動,多個使用者參加,完成會話功能,具體的設計要友善使用者的使用,直覺清晰,簡潔明了,友好美觀。
5. 存儲結構
下面列出主要存儲結構或變量:
存儲結構、變量、對象 | 類型 | 說明 |
post | InetAddress | 辨別IP位址 |
Port | int | 辨別端口 |
Server [ ] | ServerThread | 伺服器端連接配接數 |
Client [ ] | Socket | 用戶端連接配接數 |
Client(String ip,int p,Face chat) | public | Client類成員函數 |
Public void run() | Void | Client、Server類成員函數 |
Server(int port,Face chat) | public | Server類成員函數 |
Face() | Public | Face類成員函數 |
1.伺服器端
伺服器端主要是使用ServerSocket類,相當于伺服器Socket,用來監聽試圖進入的連接配接,當新的連接配接建立後,該類為他們執行個體化一個Socket對象,同時得到輸入輸出流,調用相應方法完成會話。
具體代碼如下:
package nupt.java.socket;
import java.awt.*;
import java.net.*;
import java.io.*;
public class Server extends Thread {
ServerSocket skt; // ServerSocket類監聽進入的連接配接,為每個新的連接配接産生一個Socket對象
Socket Client[ ]=new Socket[10];
Socket Client1=null;
int i = 0;
TextArea in;
int port,k=0,l=0;
PrintStream theOutputStream;
Face chat;
public Server(int port, Face chat) {
try {
this.port = port;
skt = new ServerSocket(port);
this.chat = chat;
} catch (IOException e) {
chat.ta.append(e.toString());
}
}
public void run() {
chat.ta.append("等待連線......");
while (true) {
try {
Client[k] = skt.accept();
//當有用戶端連接配接時就建立一個子線程
if (i < 2) {
ServerThread server[] = new ServerThread[10];
server[k]= new ServerThread(Client[k], this.chat, i);
l=server.length;
server[k].start();
chat.ta.append(“用戶端“+ Client[k].getInetAddress() + "已連線\n");
//for(int j=0;j<server.length;j++)
theOutputStream = new PrintStream(server[k].getClient().getOutputStream());
i = server[k].getI();
k++;
} else {
//theOutputStream = new PrintStream(null);
}
} catch (SocketException e) {
} catch (IOException e) {
chat.ta.append(e.toString());
}
}
}
public void dataout(String data) {
//for(int j=0;j<l;j++)
theOutputStream.println(data);
}
}
class ServerThread extends Thread {
ServerSocket skt;
Socket Client;
TextArea in;
int port,i;
BufferedReader theInputStream;
PrintStream theOutputStream;
String readin;
Face chat;
//服務端子線程
public ServerThread(Socket s, Face chat, int i) {
this.i = ++i;
Client = s;
this.chat = chat;
}
public int getI() {
return this.i;
}
public Socket getClient() {
return this.Client;
}
public void run() {
try {
theInputStream = new BufferedReader(new InputStreamReader(Client
.getInputStream()));
theOutputStream = new PrintStream(Client.getOutputStream());
while (true) {
readin = theInputStream.readLine();
chat.ta.append(readin + "\n");
}
} catch (SocketException e) {
chat.ta.append("連線中斷!\n");
// 設定元件可用性
chat.clientBtn.setEnabled(true);
chat.serverBtn.setEnabled(true);
chat.tfaddress.setEnabled(true);
chat.tfport.setEnabled(true);
try {
i - -;
skt.close();
Client.close();
} catch (IOException err) {
chat.ta.append(err.toString());
}
} catch (IOException e) {
chat.ta.append(e.toString());
}
}
public void dataout(String data) {
theOutputStream.println(data);
}
}
2.用戶端
用戶端主要是使用Socket類,該類是JAVA實作網絡程式設計重要的基礎類,實作程式間雙向的面向連接配接的通信。調用public Socket(String host,int port)方法設定IP和端口。建好連接配接後,使用者通過得到Socket的輸入輸出流對象後,利用流的方法實作資料的傳輸。調用public InputStream getInputStream()和public OutputStream getOutputStream()方法,分别得到Socket對象的輸入輸出流;
具體實作代碼如下:
package nupt.java.socket;
import java.net.*;
import java.io.*;
import javax.swing.Timer;
public class Client extends Thread {
Socket skt; // 用于用戶端的連接配接
InetAddress host; // 主機位址
int port; // 端口号
BufferedReader theInputStream;
PrintStream theOutputStream;
String readin;
Face chat;
public Client(String ip, int p, Face chat) {
try {
host = InetAddress.getByName(ip); // 擷取IP位址
port = p; // 擷取端口号
this.chat = chat;
} catch (IOException e) {
chat.ta.append(e.toString());
}
}
public void run() {
try {
chat.ta.append("準備連線,稍後!");
skt = new Socket(host, port); // 建立Socket對象
chat.ta.append("成功\n"); // 緩沖區末尾添加字元串
theInputStream = new BufferedReader(new InputStreamReader(skt.getInputStream()));
theOutputStream = new PrintStream(skt.getOutputStream());
while (true) {
readin = theInputStream.readLine();
chat.ta.append(readin + "\n");
}
} catch (SocketException e) {
chat.ta.append("未連上!\n");
chat.clientBtn.setEnabled(true);
chat.serverBtn.setEnabled(true);
chat.tfaddress.setEnabled(true);
chat.tfport.setEnabled(true);
try {
skt.close();
} catch (IOException err) {
chat.ta.append(err.toString());
}
} catch (IOException e) {
chat.ta.append(e.toString());
}
}
public void dataout(String data) {
theOutputStream.println(data);
}
}
3.使用者圖形界面
該部分主要是完成界面的初始化,合理布局元件,友善使用者互動。主要是JAVA按鈕,文本域,标簽,布局管理器的使用。主要處理了鍵盤Enter消息接受,下面是實作代碼:
package nupt.java.socket;
import java.awt.*;
import java.awt.event.*;
public class Face extends Frame {
private static final long serialVersionUID = 1L;
Button clientBtn, serverBtn;
TextArea ta;
TextField tfaddress, tfport, tftype;
Label lbl1,lbl2,lbl3;
int port;
Client client;
Server server;
boolean iamserver;
static Face frm;
public Face() {
// 執行個體化元件
clientBtn = new Button("用戶端");
serverBtn = new Button("伺服器");
ta = new TextArea("", 10, 50, TextArea.SCROLLBARS_BOTH);
lbl1 = new Label("IP位址:");
tfaddress = new TextField("192.168.1.104", 10);
lbl2 = new Label("端口:");
tfport = new TextField("8080");
lbl3 = new Label("發送内容:");
tftype = new TextField(40);
tftype.addKeyListener(new TFListener());
ta.setEditable(false);
//向容器中加入以上元件
setLayout(new FlowLayout());
add(lbl1);
add(tfaddress);
add(lbl2);
add(tfport);
add(clientBtn);
add(serverBtn);
add(ta);
add(lbl3);
add(tftype);
//設定格式
setLocation(400, 250); //視窗顯示再螢幕的位置坐标
setSize(400, 300); //設定窗體大小
setTitle("基于Socket和多線程程式設計的聊天程式");
this.setVisible(true); //設定窗體可見
//事件響應
clientBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
port = Integer.parseInt(tfport.getText());
client = new Client(tfaddress.getText(), port, frm);
client.start();
tfaddress.setEnabled(false);
tfport.setEnabled(false);
serverBtn.setEnabled(false);
clientBtn.setEnabled(false);
}
});
serverBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
port = Integer.parseInt(tfport.getText());
server = new Server(port, frm);
server.start();
iamserver = true;
tfaddress.setText("成為伺服器");
tfaddress.setEnabled(false);
tfport.setEnabled(false);
serverBtn.setEnabled(false);
clientBtn.setEnabled(false);
}
});
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String[] args) { //主方法
// TODO Auto-generated method stub
frm = new Face();
}
private class TFListener implements KeyListener {
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) { //按Enter輸出顯示聊天内容
ta.append(">" + tftype.getText() + "\n");
if (iamserver)
server.dataout(tftype.getText());
else
client.dataout(tftype.getText());
tftype.setText("");
}
}
public void keyTyped(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
}
}
}
如有不對的地方或者更好的方法,歡迎大家批評指正,交流學習。