TCP多線程聊天室

 TCP協議,一個服務器(ServerSocket)只服務於一個客戶端(Socket),那麼能夠經過ServerSocket+Thread的方式,實現一個服務器服務於多個客戶端。java

多線程服務器實現原理——多線程併發機制
一、建立服務器ServerSocket,開啓監聽.accept(),當有客戶端鏈接,返回一個Socket對象。
二、把Socket對象須要處理的事務,交給Thread,此線程會到一邊默默地執行,那麼服務器監聽就會空閒出來,等待另一個客戶端鏈接。
三、另外一個客戶端鏈接成功,新的Socket對象又交給另外一個Thread,以此類推。服務器

【多線程聊天室---羣聊】先運行服務器,在運行客戶端(2個客戶端則運行兩次)多線程

                                

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;

//獲取客戶端發來的消息,並把消息發送到全部客戶端
public class Server {
    private HashSet<Socket> allSocket;//保存全部服務器的套接字集合
    private ServerSocket server;//服務器套接字

    public Server() {//構造方法
        try {
            server = new ServerSocket(4567);//實例化服務器套接字,設定端口4567
        } catch (IOException e) {
            e.printStackTrace();
        }
        allSocket = new HashSet<>();//實例化服務器套接字集合
    }

    public void startServer() {//啓動服務器,循環獲取服務器監聽
        while (true) {
            Socket socket = null;
            try {
                socket = server.accept();//服務器監聽是否有客戶端接入
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("用戶進入聊天室");//監聽成功(客戶端鏈接成功),輸出此句代碼
            allSocket.add(socket);//鏈接成功的客戶端放入集合中
            ServerThread t = new ServerThread(socket);//將成功鏈接的客戶端交給線程
            t.start();
        }
    }

    private class ServerThread extends Thread {//發送一個客戶端信息給全部客戶端
        Socket socket;//客戶端套接字

        public ServerThread(Socket socket) {//帶參數(客戶端套接字)的構造方法
            this.socket = socket;//參數賦值給客戶端套接字
        }

        public void run() {//發送一個客戶端信息給全部客戶端
            BufferedReader br = null;//讀取客戶端發來的消息
            try {
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//得到套接字的字節流
                while (true) {//把客戶端發來的消息,發給全部客戶端
                    String str = br.readLine();//讀取字符串
                    //若字符串中有EXIT內容(有用戶退出聊天室),提醒其餘人
                    if (str.contains("EXIT")) {//用戶退出聊天室,給服務器發送"%EXIT%:用戶名"(自定義規則)
                        String tmp = str.split(":")[1] + "用戶退出聊天室";//依據:分隔成兩個元素,取第2個元素(用戶名)
                        sendMessageToAllClient(tmp);//告訴全部人,他退出了
                        allSocket.remove(socket);//移除離開的客戶端
                        socket.close();//關閉客戶端
                        return;//中止線程
                    }
                    sendMessageToAllClient(str);//若是沒有EXIT內容,直接發送給全部客戶端。調用自定義方法
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void sendMessageToAllClient(String str) {//發給全部人,參數str是發送的內容
        for (Socket s : allSocket) {//遍歷全部接入的客戶端
            try {
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//客戶端輸出流
                bw.write(str);//輸出str
                bw.newLine();//換行
                bw.flush();//強制將輸出流緩衝區的數據送出,清空緩衝區(通常用在IO中)
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Server s = new Server();
        s.startServer();
    }
}
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
//登錄界面、獲取用戶名與IP
public class LinkServerFrame extends JFrame {
    private JPanel contentPane;
    private JLabel lblIP;
    private JLabel lblUserName;
    private JTextField tfIP;
    private JTextField tfUserName;
    private JButton btnLink;

    public LinkServerFrame(){//構造方法
        setTitle("鏈接服務器");
        setResizable(false);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setBounds(100,100,390,150);

        contentPane=new JPanel();
        contentPane.setBorder(new EmptyBorder(5,5,5,5));
        contentPane.setLayout(null);
        setContentPane(contentPane);

        lblIP=new JLabel("服務器IP地址:");
        lblIP.setFont(new Font("微軟雅黑",Font.PLAIN,14));
        lblIP.setBounds(20,15,100,15);
        contentPane.add(lblIP);

        tfIP=new JTextField("127.0.0.1");//指定IP內容,本地服務器
        tfIP.setBounds(121,13,242,21);
        contentPane.add(tfIP);
        tfIP.setColumns(10);

        lblUserName=new JLabel("用戶名");
        lblUserName.setFont(new Font("微軟雅黑",Font.PLAIN,14));
        lblUserName.setBounds(60,40,60,15);
        contentPane.add(lblUserName);

        tfUserName=new JTextField();
        tfUserName.setBounds(121,42,242,21);
        contentPane.add(tfUserName);
        tfUserName.setColumns(10);

        btnLink=new JButton("鏈接服務器");//登陸按鈕
        btnLink.addActionListener(new ActionListener() {//實現登陸,顯示客戶端窗體,關閉登陸窗體
            public void actionPerformed(ActionEvent e) {
                do_btnLink_actionPerformed(e);
            }
        });
        btnLink.setFont(new Font("微軟雅黑",Font.PLAIN,14));
        btnLink.setBounds(140,80,120,23);
        contentPane.add(btnLink);
    }

    public static void main(String[] args) {
        LinkServerFrame linkServerFrame=new LinkServerFrame();//建立窗體對象
        linkServerFrame.setVisible(true);//顯示窗體
    }

    protected void do_btnLink_actionPerformed(ActionEvent e){
        if(!tfIP.getText().equals("")&&!tfUserName.getText().equals("")){//文本框內容不爲空
            dispose();//關閉窗體,釋放資源
            //獲取文本併除去空格,.trim()的做用是:去掉字符串左右的空格
            ClientFrame clientFrame=new ClientFrame(tfIP.getText().trim(),tfUserName.getText().trim());//實例化登陸窗口,並賦值IP、用戶名
            clientFrame.setVisible(true);//顯示客戶端窗體
        }else {
            JOptionPane.showMessageDialog(null,"內容不能爲空!","警告",JOptionPane.WARNING_MESSAGE);
        }
    }
}
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.event.*;
import java.text.SimpleDateFormat;
import java.util.Date;
//客戶端窗體、共享消息在文本域中、用戶退出提醒
public class ClientFrame extends JFrame {
    private JPanel contentPane;
    private JLabel lblUserName;//顯示用戶名
    private JTextField tfMessage;//信息輸入文本框
    private JButton btnSend;//發送按鈕
    private JTextArea textArea;//信息接收文本域
    private String userName;//用戶名稱
    Client client;//調用自定義類(在Client.java中)

    public ClientFrame(String ip,String userName){//ip與userName由登陸窗體得到(LinkServerFrame.java中)
        this.userName=userName;
        init();//窗體初始化,自定義方法
        addListener();//調用發送按鈕監聽,自定義方法

        client=new Client(ip,4567);//賦值形參。實例化自定義類(在Client.java中)
        ReadMessageThread t=new ReadMessageThread();//顯示消息在文本域中
        t.start();
    }

    private void init(){//窗體屬性(顯示與否,由LinkServerFrame.java控制)
        setTitle("客戶端");
        setResizable(false);
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        setBounds(100,100,450,300);

        contentPane=new JPanel();
        contentPane.setBorder(new EmptyBorder(5,5,5,5));
        contentPane.setLayout(null);
        setContentPane(contentPane);

        JScrollPane scrollPane=new JScrollPane();
        scrollPane.setBounds(5,5,434,229);
        contentPane.add(scrollPane);

        textArea=new JTextArea();
        //添加滾動條,或JScrollPane scrollPane=new JScrollPane(textArea);
        scrollPane.setViewportView(textArea);
        textArea.setEditable(false);//文本域不可編輯

        JPanel panel=new JPanel();
        panel.setBounds(5,235,434,32);
        contentPane.add(panel);
        panel.setLayout(null);

        lblUserName=new JLabel(userName);
        lblUserName.setHorizontalAlignment(SwingConstants.TRAILING);
        lblUserName.setBounds(2,4,55,22);
        panel.add(lblUserName);

        tfMessage=new JTextField();
        tfMessage.setBounds(62,5,274,22);
        tfMessage.setColumns(10);
        panel.add(tfMessage);

        btnSend=new JButton("發送");
        btnSend.setBounds(336,4,93,23);
        panel.add(btnSend);

        tfMessage.validate();//驗證容器中的組件,即刷新。
    }
    //顯示消息在文本域中
    private class ReadMessageThread extends Thread{//內部類,收消息線程類
        public void run() {
            while (true) {
                String str=client.receiveMessage();//調用自定義方法(在Client.java中)
                textArea.append(str+"\n");//將消息顯示在文本域中
            }
        }
    }

    private void addListener(){
        btnSend.addActionListener(new ActionListener() {//發送按鈕的動做監聽
            public void actionPerformed(ActionEvent e) {
                Date date=new Date();
                SimpleDateFormat sf=new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
                client.sendMessage(userName+" "+sf.format(date)+":\n"+tfMessage.getText());
                tfMessage.setText("");//消息發送後,文本框清空
            }
        });
        //開啓窗口監聽,單擊窗口「X」,彈出確認對話框。「是」則關閉客戶端窗體
        this.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent atg0){//窗口關閉時
              int op=JOptionPane.showConfirmDialog(ClientFrame.this,"肯定要退出聊天室嗎?","肯定",JOptionPane.YES_NO_OPTION);
              if(op==JOptionPane.YES_OPTION){//若是選擇「是」
                  client.sendMessage("%EXIT%:"+userName);//若是退出,發送"%EXIT%:用戶名"給服務器
                  try {
                      Thread.sleep(200);//200ms後,關閉客戶端
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  client.close();//關閉客戶端套接字,調用Client類中的close方法(Client.java中)
                  System.exit(0);//關閉程序
              }
            }
        });
    }
}
import java.io.*;
import java.net.Socket;
//供ClientFrame.java調用的各類方法,消息輸入、輸出,關閉套接字
public class Client {
    Socket socket;//客戶端套接字
    BufferedWriter bw;//發數據
    BufferedReader br;//收數據

    public Client(String ip,int port){//帶參數構造方法,參數值由ClientFrame.java賦予
        try {
            socket=new Socket(ip,port);//實例化客戶端套接字,鏈接服務器
            bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));//客戶端輸出流,輸出到服務器
            br=new BufferedReader(new InputStreamReader(socket.getInputStream()));//客戶端輸入流,服務器反饋給客戶端
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void sendMessage(String message){//發消息
        try {
            bw.write(message);//發消息
            bw.newLine();//換行
            bw.flush();//強制將輸出流緩衝區的數據送出,清空緩衝區(通常用在IO中)
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String receiveMessage(){//收消息
        String message=null;
        try {
            message=br.readLine();//收消息
        } catch (IOException e) {
            e.printStackTrace();
        }
        return message;//返回消息結果
    }

    public void close(){//關閉客戶端套接字
        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
相關文章
相關標籤/搜索