天天看點

基于Socket和多線程程式設計的聊天程式實作

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) {
        }
    }
}
           

如有不對的地方或者更好的方法,歡迎大家批評指正,交流學習。