線上聊天(JavaSE實戰專案)學習筆記
Chat0.1
搭建客戶端ChatClient視窗,有兩種方式:
一、繼承Frame類(比較靈活)
呼叫Frame類中的setLocation(int x, int y)方法設定視窗位置,setSize(int width, int height)方法設定視窗大小,setVisible(true)方法將視窗顯示出來
setSize(int width, int height):其實就是定義控制元件的大小,有兩個引數,分別對應寬度和高度;
setLocation(int x, int y):將元件移到新位置,用x 和 y 引數來指定新位置的左上角
setBounds(int x, int y, int width, int height):四個引數,既定義元件的位置,也定義控制元件的大小; 其實它就是上面兩個函式的功能的組合
import java.awt.Frame;
public class ChatClient extends Frame{
public static void main(String[] args) {
new ChatClient().launchFrame();
}
public void launchFrame()
{
setLocation(400, 300);
setSize(300, 300);
setVisible(true);
}
}
二、直接使用Frame類
Chat0.2
向客戶端視窗中新增文字輸入框TextField和文字輸入區TextArea(可以輸入多行文字
import java.awt.*; public class ChatClient extends Frame{ TextField tfTxt = new TextField(); TextArea taContent = new TextArea(); public static void main(String[] args) { new ChatClient().launchFrame(); } public void launchFrame(){ setLocation(400, 300); setSize(300, 300); add(tfTxt, BorderLayout.SOUTH); add(taContent, BorderLayout.NORTH); pack();//void pack() 使此視窗的大小適合其子元件的首選大小和佈局。 setVisible(true); } }
Chat0.3
新增客戶端視窗關閉功能,使用視窗的 addWindowListener 方法
WindowListener是java中的介面。主要作用:用於接收視窗事件的偵聽器介面。旨在處理視窗事件的類要麼實現此介面(及其包含的所有方法),要麼擴充套件抽象類WindowAdapter(僅重寫所需的方法)。然後使用視窗的 addWindowListener 方法將從該類所建立的偵聽器物件向該 Window 註冊。當通過開啟、關閉、啟用或停用、圖示化或取消圖示化而改變了視窗狀態時,將呼叫該偵聽器物件中的相關方法,並將 WindowEvent 傳遞給該方法。import java.awt.*;
import java.awt.event.*;
public class ChatClient extends Frame{
TextField tfTxt = new TextField();
TextArea taContent = new TextArea();
public static void main(String[] args){
new ChatClient().launchFrame();
}
public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
setVisible(true);
}
}
Chat0.4
向客戶端中新增功能:在文字輸入框TextFiled中輸入內容按回車鍵後,內容會顯示到文字輸入區TextArea中
取文字內容getText()方法,設定文字內容setText()方法
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;
public class ChatClient extends Frame{
TextField tfTxt = new TextField();
TextArea taContent = new TextArea();
public static void main(String[] args){
new ChatClient().launchFrame();
}
public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
tfTxt.addActionListener(new TFListener());
setVisible(true);
}
private class TFListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
String s = tfTxt.getText().trim();
taContent.setText(s);
tfTxt.setText("");
}
}
}
Chat0.5
建立ChatSever服務端
1、使用ServerSocket建立服務端(ServerSocket物件用於監聽來自客戶端的Socket連線)
ServerSocket(int port):用指定的埠port來建立一個ServerSocket。該埠應該有一個有效的埠整數值,即0~65535。
2、接收來自客戶端Socket的連線請求,使用accept()方法
ServerSocket包含一個監聽來自客戶端連線請求的方法,即accept()方法
Socket accept():如果接收到一個客戶端Socket的連線請求,該方法將返回一個與客戶端Socket對應的Socket;否則該方法將一直處於等待狀態,執行緒也被阻塞。
注意:服務端只有一個,客戶端有多個,需使用while迴圈來接收多個客戶端
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ChatServer {
public static void main(String[] args) {
try{
ServerSocket ss = new ServerSocket(8888);
while(true) {//用於接收多個客戶端
Socket s = ss.accept();
System.out.println("a client connected");//用於驗證客戶端是否已連線成功
}
}
catch(IOException e) {
e.printStackTrace();
}
}
}
Chat0.6
ChatClient
1、在顯示客戶端視窗的同時,將客戶端與服務端進行連線,將連線步驟封裝成connect()方法
2、connect()方法
(1)建立客戶端物件:客戶端通常使用Socket的構造器來連線到指定的伺服器
Socket(InetAddress, int port):建立連線到指定遠端主機、遠端埠的Socket,該構造器沒有指定本地地址、本地埠,預設使用本地主機的預設IP地址,預設使用系統動態分配的埠。
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.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
public class ChatClient extends Frame{
Socket s = null;
TextField tfTxt = new TextField();
TextArea taContent = new TextArea();
public static void main(String[] args) {
new ChatClient().launchFrame();
}
public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
tfTxt.addActionListener(new TFListener());
setVisible(true);
connect();
}
public void connect() {
try {
s = new Socket("127.0.0.1", 8888);
System.out.println("connected");
}catch(UnknownHostException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
}
public class TFListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
String str = tfTxt.getText().trim();
taContent.setText(str);
}
}
}
ChatServer
接收客戶端傳送過來的資料
當客戶端、服務端產生了對應的Socket之後,,程式無須再區分伺服器端、客戶端,而是通過各自的Socket通訊。Socket提供瞭如下兩個方法來獲取輸入流和輸出流。
(1)InputStream getInputStream():返回該Socket物件對應的輸入流,讓程式通過該輸入流從Socket中取出資料。
(2)OutputStream getOutputStream():返回該Socket物件對應的輸出流,讓程式通過該輸出流向Socket中輸出資料。
import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ChatServer {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(8888);
while(true) {
Socket s = ss.accept();
System.out.println("a client connected");
DataInputStream dis = new DataInputStream(s.getInputStream());
String str = dis.readUTF();
System.out.println(str);
dis.close();
}
}catch(IOException e) {
e.printStackTrace();
}
}
}
Chat0.7
ChatClient
將從文字輸入框TextField中獲取到資料傳送到服務端(客戶端---->服務端)
OutputStream getOutputStream():返回該Socket物件對應的輸出流,讓程式通過該輸出流向Socket中輸出資料。
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.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
public class ChatClient extends Frame{
Socket s = null;
TextField tfTxt = new TextField();
TextArea taContent = new TextArea();
public static void main(String[] args) {
new ChatClient().launchFrame();
}
public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
tfTxt.addActionListener(new TFListener());
setVisible(true);
connect();
}
public void connect() {
try {
s = new Socket("127.0.0.1", 8888);
System.out.println("connected");
}catch(UnknownHostException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
}
public class TFListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
String str = tfTxt.getText().trim();
taContent.setText(str);
tfTxt.setText("");
try {
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
dos.writeUTF(str);
dos.flush();
dos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
Chat0.8
ChatClient
在關閉視窗的同時關閉輸出流,關閉Socket
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.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
public class ChatClient extends Frame{
Socket s = null;
DataOutputStream dos = null;
TextField tfTxt = new TextField();
TextArea taContent = new TextArea();
public static void main(String[] args) {
new ChatClient().launchFrame();
}
public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
disconnect();
System.exit(0);
}
});
tfTxt.addActionListener(new TFListener());
setVisible(true);
connect();
}
public void connect() {
try {
s = new Socket("127.0.0.1", 8888);
dos = new DataOutputStream(s.getOutputStream());
System.out.println("connected");
}catch(UnknownHostException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
}
public void disconnect() {
try{
dos.close();
s.close();
}catch(IOException e) {
e.printStackTrace();
}
}
public class TFListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
String str = tfTxt.getText().trim();
taContent.setText(str);
tfTxt.setText("");
try {
dos.writeUTF(str);
dos.flush();
//dos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
ChatServer
在之前的版本中服務端不能連續讀取客戶端傳送過來的資料。
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.DataInputStream;
public class ChatServer {
public static void main(String[] args) {
boolean started = false;
try{
ServerSocket ss = new ServerSocket(8888);
started = true;
while(started) {//該while迴圈用於接收多個客戶端
boolean bConnected = false;
Socket s = ss.accept();
System.out.println("a client connected");
bConnected = true;
DataInputStream dis = new DataInputStream(s.getInputStream());
while(bConnected) {//該while迴圈可以連續讀取客戶端傳送過來的資訊
String str = dis.readUTF();
System.out.println(str);
}
dis.close();
}
}catch(IOException e) {
e.printStackTrace();
}
}
}
Chat0.9
ChatClient無改動
ChatServer
異常處理:
(1)伺服器端啟動後,若再次啟動服務端,則會產生BindException異常。原因是伺服器端啟動後,埠已經被佔用,無法二次啟動。
(2)啟動伺服器端和客戶端後,若關閉客戶端則會發生EOFException。
產生原因:服務端讀取客戶端發過來的資料使用的是readUTF()方法,該方法是阻塞式方法。在客戶端關閉後,服務端並不知曉,仍在等待接收資料。
通過這個API,我們可以得出以下資訊:
- 這是一個IO異常的子類,名字也是END OF FILE的縮寫,當然也表示流的末尾
- 它在表明一個資訊,流已經到末尾了,而大部分做法是以特殊值的形式返回給我們,而不是拋異常
也就是說這個異常是被主動丟擲來的,而不是底層或者編譯器返回給我的,就像NullPointerException或IndexOutOfBoundsException一樣。
詳細解釋見:https://www.cnblogs.com/yiwangzhibujian/p/7107084.html
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.DataInputStream;
public class ChatServer {
public static void main(String[] args) {
boolean started = false;
try{
ServerSocket ss = new ServerSocket(8888);
started = true;
while(started) {//該while迴圈用於接收多個客戶端
boolean bConnected = false;
Socket s = ss.accept();
System.out.println("a client connected");
bConnected = true;
DataInputStream dis = new DataInputStream(s.getInputStream());
while(bConnected) {//該while迴圈可以連續讀取客戶端傳送過來的資訊
String str = dis.readUTF();
System.out.println(str);
}
dis.close();
}
}catch(IOException e) {
e.printStackTrace();
}
}
}
Chat1.0
啟動伺服器端後,若開啟多個客戶端視窗,則伺服器端顯示只能連線一個客戶端,其他客戶端視窗連線不上伺服器,向伺服器傳送資料也無法顯示。
原因:伺服器端讀取客戶端傳送過來的資料使用的是readUTF()方法,該方法為阻塞式方法,主方法執行後,會卡在該方法處,一直等待接收資料。這也就導致伺服器端只能處理一個客戶端。
處理方法:開啟多執行緒
import java.io.*;
import java.net.*;
public class ChatServer {
boolean started = false;
ServerSocket ss = null;
public static void main(String[] args) {
new ChatServer().start();
}
public void start() {
try {
ss = new ServerSocket(8888);
} catch(BindException e) {
System.out.println("埠使用中......");
System.out.println("請關掉相關程式,並重新執行!");
System.exit(0);
} catch(IOException e) {
e.printStackTrace();
}
try {
started = true;
while (started) {
Socket s = ss.accept();
Client c = new Client(s);
new Thread(c).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ss.close();
} catch(IOException e1) {
e1.printStackTrace();
}
}
}
class Client implements Runnable {
private Socket s;
private DataInputStream dis;
private boolean bConnected = false;
public Client(Socket s) {
this.s = s;
try {
dis = new DataInputStream(s.getInputStream());
bConnected = true;
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
System.out.println("a client connected");
try {
while (bConnected) {
String str = dis.readUTF();
System.out.println(str);
}
} catch(EOFException e) {
System.out.println("Client closed");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
dis.close();
s.close();
} catch(IOException e1) {
e1.printStackTrace();
}
}
}
}
}
Chat1.1
將從某一客戶端接收到的資料轉發給其他客戶端。
List<Client> clients = new ArrayList<>();//將執行緒物件儲存起來,一個執行緒物件即代表一個客戶端
while (bConnected) {
String str = dis.readUTF();
System.out.println(str);
for(int i = 0; i < clients.size(); i++) {
Client c = clients.get(i);
c.send(str);
}
}
將從客戶端讀取到的資料,通過對List集合的逐一遍歷,傳送給每個客戶端物件。import java.io.*;
import java.net.*;
import java.util.*;
public class ChatServer {
ServerSocket ss = null;
boolean started = false;
List<Client> clients = new ArrayList<>();
public static void main(String[] args) {
new ChatServer().start();
}
public void start() {
try {
ss = new ServerSocket(8888);
started = true;
} catch(BindException e) {
System.out.println("埠使用中");
System.out.println("請關閉相關程式重新執行伺服器");
System.exit(0);
} catch(IOException e) {
e.printStackTrace();
}
try {
while(started) {
Socket s = ss.accept();
System.out.println("a client connected");
Client c = new Client(s);
new Thread(c).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Client implements Runnable {
private Socket s;
private DataInputStream dis;
private DataOutputStream dos;
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) {
e.printStackTrace();
}
}
public void send(String str) {
try {
dos.writeUTF(str);
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
try {
while (bConnected) {
String str = dis.readUTF();
System.out.println(str);
for(int i = 0; i < clients.size(); i++) {
Client 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(s != null) {
s.close();
}
if(dos != null) {
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Chat1.2
客戶端接收從服務端傳送過來的每個客戶端的資料,並將資料顯示到文字輸入區TextArea中
當我們在向伺服器端傳送本地客戶端的資料時,需要同時接收其他客戶端傳送過來的資料。因此需要使用多執行緒。
1.2異常處理:
開啟多個客戶端,同時進行聊天,當某一個客戶端關閉後,會產生SocketException
原因:開啟多個客戶端後,在關閉其中某一個客戶端時(主執行緒結束),會呼叫disconnect()方法,將客戶端Socket關閉,而此時接收資料的執行緒中的輸入流可能並未關閉,仍會從客戶端Socket中讀取資料,此時就會產生SocketException異常。
簡單處理方式:直接進行捕捉,給出相應提示。
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{
TextField tfTxt = new TextField();
TextArea taContent = new TextArea();
Socket s = null;
DataOutputStream dos = null;
DataInputStream dis = null;
private boolean bConnected = false;
public static void main(String[] args) {
new ChatClient().launchFrame();
}
public void launchFrame() {
setLocation(400, 300);
setSize(300, 300);
add(tfTxt, BorderLayout.SOUTH);
add(taContent, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
disconnect();
System.exit(0);
}
});
tfTxt.addActionListener(new TFListener());
setVisible(true);
connect();
new Thread(new RecvThread()).start();
}
public void connect() {
try {
s = new Socket("127.0.0.1", 8888);
dos = new DataOutputStream(s.getOutputStream());
dis = new DataInputStream(s.getInputStream());
System.out.println("connected");
bConnected = true;
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void disconnect() {
try {
dos.close();
dis.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public 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();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
private class RecvThread implements Runnable {
public void run() {
try {
while(bConnected) {
String str = dis.readUTF();
taContent.setText(taContent.getText() + str + '\n');
}
} catch(SocketException e) {
System.out.println("退出了,bye!");
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
Chat1.3異常處理:
(1)同時開啟多個客戶端進行聊天,當其中某個客戶端關閉,繼續進行聊天時會產生SocketException。
產生原因:伺服器端會通過Socket向每個客戶端傳送資料,我們將每個客戶端的Socket用List集合進行儲存,伺服器端向每個客戶端傳送資料時,會通過遍歷List集合逐一發送,而此時關閉的客戶端的Socket仍在其中,並未從List集合中移除,因此伺服器端仍然會像關閉的客戶端傳送資料,此時便會產生SocketException異常。
處理方式:在傳送資料時直接從List集合中移除,並給出提示資訊。
import java.io.*;
import java.net.*;
import java.util.*;
public class ChatServer {
ServerSocket ss = null;
boolean started = false;
List<Client> clients = new ArrayList<>();
public static void main(String[] args) {
new ChatServer().start();
}
public void start() {
try {
ss = new ServerSocket(8888);
started = true;
} catch(BindException e) {
System.out.println("埠使用中");
System.out.println("請關閉相關程式重新執行伺服器");
System.exit(0);
} catch(IOException e) {
e.printStackTrace();
}
try {
while(started) {
Socket s = ss.accept();
System.out.println("a client connected");
Client c = new Client(s);
new Thread(c).start();
clients.add(c);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Client implements Runnable {
private Socket s;
private DataInputStream dis;
private DataOutputStream dos;
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) {
e.printStackTrace();
}
}
public void send(String str) {
try {
dos.writeUTF(str);
} catch (SocketException e) {
clients.remove(this);
System.out.println("對方退出了!我從List裡面去掉了!");
} catch (IOException e) {
clients.remove(this);
System.out.println("對方退出了");
}
}
public void run() {
try {
while (bConnected) {
String str = dis.readUTF();
System.out.println(str);
for(int i = 0; i < clients.size(); i++) {
Client 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(s != null) {
s.close();
}
if(dos != null) {
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:EOFException、SocketException均是IOException的子類