JWorld@TW the best professional Java site in Taiwan
      註冊 | 登入 | 全文檢索 | 排行榜  

» JWorld@TW » Java 程式分享區 » Networking  

按列印兼容模式列印這個話題 列印話題    把這個話題寄給朋友 寄給朋友    訂閱主題
reply to topicthreaded modego to previous topicgo to next topic
本主題所含的標籤
無標籤
作者 [網路]多人聊天伺服器 v0.3 [精華]
caterpillar

良葛格

版主

發文: 2613
積分: 70
於 2004-03-02 19:04 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
這個程式有新版。。。 。:D

=================

為什麼是v0.3?因為之前有舊的版本(廢話),v0.1的架構很差,v0.2沒有改好自己沒發現(根本不能通過編譯:P),我舊版本都會留著,作為成長的見證,有興趣看的再到我的網站上看舊版本。。。。。

以上題外話,以下說明一下基本原理。

在一個多人連線伺服器中,我們要有一個伺服端執行緒負責傾聽是否有客戶端連線,如果有客戶端連線,就指派一個客戶端執行緒專門應付這個客戶端連線,並在客戶端佇列中記錄它,然後進入下一個傾聽。

一個客戶端執行緒的工作,就是讀取客戶連線端的使用者輸入訊息,它不負責回應訊息,而是將讀到的訊息加入訊息佇列中,此外在我們的範例中,客戶端執行緒也負責自己的連線狀態,如果使用者中斷連線,客戶端執行緒會負責將自己從客戶端佇列中清除。

廣播執行緒負責取出訊息佇列中的訊息,然後將之一一傳送訊息給客戶端佇列中尚存在的客戶端執行緒。

伺服器主程式如下(java MultiChatServer):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.net.*;
import java.io.*;
import java.util.*;
 
public class MultiChatServer {
    public static void main(String[] args) {
        BufferedReader user = null;
        String sysopMessage = null;
        int port = 0;
 
        System.out.println("多人連線聊天伺服器v0.2....");
        System.out.print("連接埠: ");
        try {
            user = new BufferedReader(new InputStreamReader(System.in));
            port = Integer.parseInt(user.readLine());
 
            // 啟動伺服器執行緒
            ServerThread server = new ServerThread(port);
            System.out.println("伺服器正在啟動......");
            System.out.print("繫結至連結埠 " + port + " ......");
            server.start();
            System.out.println("Ok!");
 
            // 後台的站台廣播
            while((sysopMessage = user.readLine()) != null) {
                if(sysopMessage.equals("shutdown")) {
                    System.out.println("伺服器即將於5秒後關閉!");
                    server.addSysopMessage("伺服器廣播: 網站即將於5秒後關閉!");
 
                    try {
                        for(int i = 0; i < 5; i++) {
                            Thread.sleep(1000);
                            System.out.print(".");
                        }
                    }
                    catch(InterruptedException e) {}
 
                    break;
                }
                else
                    server.addSysopMessage("站台廣播: " + sysopMessage);
            }
        }
        catch(IOException e) {
            System.out.println(e.toString());
        }
 
        System.out.println("伺服器關閉!");
    }
}


伺服器緒行緒:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import java.net.*;
import java.io.*;
import java.util.*;
 
// 伺服執行緒
public class ServerThread extends Thread {
    private ServerSocket _serverSkt;
    private BroadCastThread _broadCastThread; // 負責廣播
 
    public ServerThread(int port) {
        setDaemon(true);
        // 啟動廣播執行緒
        _broadCastThread = new BroadCastThread();
        _broadCastThread.start();
 
        try {
            _serverSkt = new ServerSocket(port);
        }
        catch(IOException e) {
            System.out.println(e.toString());
        }
    }
 
    public void addSysopMessage(String message) {
        _broadCastThread.addMessage(message);
    }
 
    public void run() {
        Socket clientSkt = null; // 客戶端Socket
        ClientThread client = null; // 客戶端連線
 
        try {
            while(true) {
                System.out.println("傾聽客戶端......");
                clientSkt = _serverSkt.accept();
                System.out.println(clientSkt.getInetAddress() + "連線......");
 
                // 啟動一個客戶端執行緒,第二個參數指定廣播執行緒物件
                client = new ClientThread(clientSkt, _broadCastThread);
                client.start();
                
                // 將客戶端加入廣播執行緒中管理
                _broadCastThread.addClientThread(client);
            }
        }
        catch(IOException e) {
             System.out.println(e.toString());
        }
    }
 
    public void finallize() {
        try {
            _serverSkt.close();
        }
        catch(IOException e) {
            System.out.println(e.toString());
        }
    }
}


客戶端執行緒:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import java.net.*;
import java.io.*;
import java.util.*;
 
// Client執行緒
public class ClientThread extends Thread {
    private Socket _skt;
    private BroadCastThread _broadCastThread;  // 廣播執行緒
    private PrintStream _printStream;  // 輸出串流
    private static int _clientNum = 0; // 客戶端連線數
 
    public ClientThread(Socket skt, BroadCastThread broad) {
        setDaemon(true);
        _skt = skt;
        _broadCastThread = broad;
 
        try {
            _printStream = new PrintStream(_skt.getOutputStream());
        }
        catch(IOException e) {
            e.printStackTrace();
        }
        _clientNum++;
    }
 
    public void sendMessage(String message) {
        _printStream.println(message);
    }
 
    public void run() {
        BufferedReader buf = null;
        String userMessage = null;
        String nickName = null;  // 使用者名稱
       
        try {
            buf = new BufferedReader(new
                       InputStreamReader(_skt.getInputStream()));
            sendMessage("連線成功!請輸入使用者名稱......");
 
            nickName = buf.readLine();
            if(nickName == null)
                nickName = "guest";
 
            sendMessage(nickName + "歡迎您!目前有 " + _clientNum + " 人在線上......");
 
            _broadCastThread.addMessage(":: " + nickName + "進入聊天室^_^" );
 
            // 讀取客戶端訊息
            while((userMessage = buf.readLine()) != null) {
                // 離線指令為 /bye
                if(userMessage.equals("/bye"))
                    break;
 
                 // add至訊息佇列
                _broadCastThread.addMessage(nickName + ">>" + userMessage);
            }
        }
        catch(IOException e) {
        }
        finally {
            // 連線終止
            _clientNum--; // 客戶連線數減一
            _broadCastThread.addMessage(nickName + "出了聊天室 ^o^_Y..");
            _broadCastThread.removeClientThread(this);
            try {
                _skt.close();
            }
            catch(IOException e) {
            }
        }
    }
}


廣播執行緒:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.net.*;
import java.io.*;
import java.util.*;
 
// 廣播執行緒
public class BroadCastThread extends Thread {
    private Vector _clientVector; // 儲存連線的客戶端
    private Vector _messageVector; // 儲存廣播訊息
 
    public BroadCastThread() {
       setDaemon(true);
       _clientVector = new Vector();
       _messageVector = new Vector();
    }
 
    public void addClientThread(ClientThread client) {
        // 將客戶端add至處理佇列
        _clientVector.addElement(client);
    }
 
    public void removeClientThread(ClientThread client) {
        // 移除客戶端
        _clientVector.removeElement(client);
    }
 
    public void addMessage(String message) {
        _messageVector.addElement(message);
    }
 
    public void run() {
        ClientThread client = null;
        String message = null;
        try {
            while(true) {
                // 每兩秒廣播一次
                Thread.sleep(2000);
 
                // 取出要廣播的訊息
                // 目前沒有訊息就不處理接下來的內容
                if(_messageVector.isEmpty())
                    continue;
 
                message = (String) _messageVector.firstElement();
                _messageVector.removeElement(message);
 
                // 將訊息一個一個丟給客戶端
                for(int i = 0; i < _clientVector.size(); i++) {
                    client = (ClientThread) _clientVector.elementAt(i);
                    client.sendMessage(message);
                }
            }
        }
        catch(InterruptedException e) {
            System.out.println(e.toString());
        }
    }
}


客戶端程式如下,這是一對一聊天程式改變而來的(java MultiChatClient):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
 
// 視窗介面
public class MultiChatClient extends Frame {
    private Button clientBtn, serverBtn;
    private TextArea textArea;
    private TextField tfAddress, tfPort, tfType;
    private ChatClientSocket clientSkt;  // 客戶端連線處理執行緒
 
    public MultiChatClient() {
        clientBtn = new Button("Connect");
        textArea = new TextArea("", 10, 50, TextArea.SCROLLBARS_BOTH);
        tfAddress = new TextField("localhost");  // IP欄位
        tfPort = new TextField("port");  // 連接埠欄位
        tfType = new TextField(50);   // 文字輸入欄位
 
        tfType.addKeyListener(new TFListener()); // 註冊事件
        textArea.setEditable(false);
 
        setLayout(new FlowLayout());  // 版面配置
        add(tfAddress);
        add(tfPort);
        add(clientBtn);
        add(textArea);
        add(tfType);
        setSize(400, 300);
        setTitle("MultiChatClient");
 
        // 按下「Connect」按鈕的事件處理
        clientBtn.addActionListener(
            new ActionListener() {  // 匿名類別
                public void actionPerformed(ActionEvent e) {
                    setClient();
                }
            }
        );
 
        addWindowListener(  // 按下關閉鈕時結束
            new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            }
        );
        show();
    }
 
    public void setGUIState(boolean state) {
        tfAddress.setEnabled(state);
        tfPort.setEnabled(state);
        clientBtn.setEnabled(state);
    }
 
    // 取得訊息並顯示在GUI上
    public void update() {
            textArea.append(clientSkt.getMessage());
    }
 
    public static void main(String args[]) {
        MultiChatClient frm = new MultiChatClient();
    }
 
    // 設定客戶端
    private void setClient() {
        // 取得指定的IP與連接埠
        int port = Integer.parseInt(tfPort.getText());
        // 建立客戶端連線執行緒
        clientSkt = new ChatClientSocket(tfAddress.getText(), port);
        clientSkt.setMessageObserver(this);
        // 啟動執行緒進行連線
        clientSkt.start();
    }
 
    // 事件處理
    private class TFListener implements KeyListener {
        public void keyPressed(KeyEvent e) {}
        public void keyTyped(KeyEvent e) {}
        public void keyReleased(KeyEvent e) {
            // 如果按下的是「Enter」鍵
            if(e.getKeyCode() == KeyEvent.VK_ENTER) {
                // 將資料透過連線執行緒送出
                clientSkt.dataOutput(tfType.getText());
                // 清除下方文字欄位內容
                tfType.setText("");
            }
        }       
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import java.net.*;
import java.io.*;
import javax.swing.JOptionPane;
 
// 處理客戶端連線的Thread
public class ChatClientSocket extends Thread {
    private Socket skt;        // 客戶端連線Socket物件
    private InetAddress host;  // 指定的伺服端IP
    private int port;          // 指定的伺服端連接埠
 
    private BufferedReader theInputStream;
    private PrintStream theOutputStream;
    private String message;     // 伺服端傳回的資料
 
    private MultiChatClient chatBox; // 聊天程式介面
 
    public ChatClientSocket(String ip, int port) {
        try {
            // 取得伺服端的InetAddress物件、通訊連接埠
            host = InetAddress.getByName(ip);
            this.port = port;
        }
        catch (IOException e) {
            JOptionPane.showMessageDialog(null, e.toString(),
                            "錯誤", JOptionPane.ERROR_MESSAGE);
        }
    }
 
    // 指定這個Socket的訊息觀察者
    public void setMessageObserver(MultiChatClient box) {
        chatBox = box;
    }
 
    // 取得訊息
    public String getMessage() {
        return message;
    }
 
    public void run() {
        try {
            message = "嘗試連線......";
            chatBox.update();
 
            // 建立Socket物件並嘗試連線
            skt = new Socket(host, port);
            message = "連線成功\n";
            chatBox.update();
 
            // 從InputStream建立讀取緩衝區
            theInputStream = new BufferedReader(
                new InputStreamReader(skt.getInputStream()));
            // 從OutputStream中建立PrintStream物件
            theOutputStream = new PrintStream(skt.getOutputStream());
 
            // 讀取資料迴圈
            while((message = theInputStream.readLine()) != null) {
                // 將之顯示在Applet的TextArea上
                message = ": " + message + "\n";
                chatBox.update();
            }
 
            if(message == null) {
                skt.close();
                message = "連線中斷!\n";
                chatBox.update();
                chatBox.setGUIState(true);
            }
        }
        catch (IOException e) {
            message = e.toString();
            chatBox.update();
        }
    }
 
    // 客戶端丟出資料的方法
    public void dataOutput(String data) {
        theOutputStream.println(data);
    }
}


caterpillar edited on 2005-12-24 22:40
reply to postreply to post
良葛格學習筆記
作者 Re:[分享]多人聊天伺服器 v0.3 [Re:caterpillar]
popcorny

Jakarta 2%

版主

發文: 752
積分: 20
於 2004-03-02 22:45 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
其實我不太懂的是為什麼broadcast要有一個獨立的thread負責
有什麼好處嘛?
如果是要做async的model
建議可以用wait/notify..
會比較即時


reply to postreply to post
作者 Re:[分享]多人聊天伺服器 v0.3 [Re:caterpillar]
caterpillar

良葛格

版主

發文: 2613
積分: 70
於 2004-03-02 23:50 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
就像Producer-Consumer嗎?我覺得這與這個程式架構是類似的,就Message佇列的維護來說,ClientThread就像是Producer,而Consumer就是BroadCastThread,只不過是BroadCastThread還負責維護所有的ClientThread,這比較像登記複印的感覺,有個學生交了一份筆記給複印室,複印室將之複印並給全班(這樣的比喻看得懂嗎?)。。。。

不過您提到用wait/notify是對的,我應該用這來取代這個部份比較即時且有效率:
1
2
3
                    // 目前沒有訊息就不處理接下來的內容
                    if(_messageVector.isEmpty())
                        continue


reply to postreply to post
良葛格學習筆記
作者 Re:[分享]多人聊天伺服器 v0.3 [Re:caterpillar]
popcorny

Jakarta 2%

版主

發文: 752
積分: 20
於 2004-03-03 01:02 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
caterpillar wrote:
就像Producer-Consumer嗎?我覺得這與這個程式架構是類似的,就Message佇列的維護來說,ClientThread就像是Producer,而Consumer就是BroadCastThread,只不過是BroadCastThread還負責維護所有的ClientThread,這比較像登記複印的感覺,有個學生交了一份筆記給複印室,複印室將之複印並給全班(這樣的比喻看得懂嗎?)。。。。

不過您提到用wait/notify是對的,我應該用這來取代這個部份比較即時且有效率:

恩..沒錯..就是producer-consumer...
不過如果更簡單的寫法
BroadCastThread這個根本不用是thread
而直接把addMessage改成sendBroadcastMessage
內容就是把這個message送給所有的人就好啦...
多一個thread好像沒有那麼必要
當然...client如果很多
又想要在呼叫sendBroadcastMessage之後馬上回來...
那可以用此種async的方式做..

另外...
你的BroadCastThread中的Queue是用Vector去做
建議改用LinkedList..
因為在Vector中做remove第一個element可能會很花時間
當然..人少的時候感覺不到
但是人多的話就會有差別了...


reply to postreply to post
作者 Re:[分享]多人聊天伺服器 v0.3 [Re:popcorny]
ahan





發文: 12
積分: 0
於 2004-03-03 18:26 user profilesend a private message to usersend email to ahanreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
popcorny wrote:
另外...
你的BroadCastThread中的Queue是用Vector去做
建議改用LinkedList..
因為在Vector中做remove第一個element可能會很花時間
當然..人少的時候感覺不到
但是人多的話就會有差別了...


Vector會比較慢…是它內部就已實作同步的判斷
而LinkedList則是無實作,所以無需浪費同步的判斷,所以比較快囉
==若有錯誤,還請大家指正囉==


reply to postreply to post
======= 今夕何夕,水落無痕 =======
作者 Re:[分享]多人聊天伺服器 v0.3 [Re:ahan]
caterpillar

良葛格

版主

發文: 2613
積分: 70
於 2004-03-03 18:49 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
ahan wrote:
Vector會比較慢…是它內部就已實作同步的判斷
而LinkedList則是無實作,所以無需浪費同步的判斷,所以比較快囉
==若有錯誤,還請大家指正囉==


我採用Vector的原因也是這個,當然!用LinkedList的話,也可以自行實作同步,只是不知這樣的話,與Vector的效率比起來又是哪個好呢?


reply to postreply to post
良葛格學習筆記
作者 Re:[分享]多人聊天伺服器 v0.3 [Re:caterpillar]
ahan





發文: 12
積分: 0
於 2004-03-03 19:11 user profilesend a private message to usersend email to ahanreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
caterpillar wrote:
我採用Vector的原因也是這個,當然!用LinkedList的話,也可以自行實作同步,只是不知這樣的話,與Vector的效率比起來又是哪個好呢?

既然Vector已有實作同步判斷,那程式中對Vector add remove的動作,應該就不需用synchronized包起來,因會多此一舉囉
若要比較自己判斷同步比較快 還是交由jvm判斷的話,應該個寫個run個一萬個數據的來比較,應該就比的出來哩 ^^


reply to postreply to post
======= 今夕何夕,水落無痕 =======
作者 Re:[分享]多人聊天伺服器 v0.3 [Re:ahan]
caterpillar

良葛格

版主

發文: 2613
積分: 70
於 2004-03-03 19:23 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
ahan wrote:
既然Vector已有實作同步判斷,那程式中對Vector add remove的動作,應該就不需用synchronized包起來,因會多此一舉囉
若要比較自己判斷同步比較快 還是交由jvm判斷的話,應該個寫個run個一萬個數據的來比較,應該就比的出來哩 ^^


哈!沒有改到。。。。之前我已經去掉幾個了,沒注意到還有兩個地方沒有去掉。。。。Tongue

3/3程式已修正。。。


reply to postreply to post
良葛格學習筆記
作者 Re:[分享]多人聊天伺服器 v0.3 [Re:caterpillar]
popcorny

Jakarta 2%

版主

發文: 752
積分: 20
於 2004-03-03 19:25 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
caterpillar wrote:
我採用Vector的原因也是這個,當然!用LinkedList的話,也可以自行實作同步,只是不知這樣的話,與Vector的效率比起來又是哪個好呢?

喔...當然不是因為synchronized的問題
而是內部實作的問題
LinkedList對於addFirst/removeFirst這種operation是O(1)
但是Vector會是O(n)
差別是在這裡


browser edited on 2004-03-03 19:26
reply to postreply to post
作者 Re:[分享]多人聊天伺服器 v0.3 [Re:popcorny]
caterpillar

良葛格

版主

發文: 2613
積分: 70
於 2004-03-03 19:30 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
popcorny wrote:
喔...當然不是因為synchronized的問題
而是內部實作的問題
LinkedList對於addFirst/removeFirst這種operation是O(1)
但是Vector會是OThumbs down
差別是在這裡


嗯!對Message來說,確實是使用LinkedList比較快,我當初只想到Vector已經實作同步的好處,倒是忘了查找效能的差異了。。。。

Big-O竟然自動變成喝倒彩的符號了,真是巧合。。。。


reply to postreply to post
良葛格學習筆記
作者 Re:[分享]多人聊天伺服器 v0.3 [Re:popcorny]
ahan





發文: 12
積分: 0
於 2004-03-04 01:05 user profilesend a private message to usersend email to ahanreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
popcorny wrote:
喔...當然不是因為synchronized的問題
而是內部實作的問題
LinkedList對於addFirst/removeFirst這種operation是O(1)
但是Vector會是O(n)
差別是在這裡

耶 又學到一項
LinkedList是O(1)是因為它是鏈結串列為底實作的
Vector是O(n)則是因為它是陣列為底實作的
是這樣嗎 ??
謝謝囉


browser edited on 2004-03-04 01:08
reply to postreply to post
======= 今夕何夕,水落無痕 =======
作者 Re:[網路]多人聊天伺服器 v0.3 [Re:caterpillar]
genjen62





發文: 6
積分: 0
於 2004-03-26 13:42 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
請問... 不好意思, 我是 Java 新手, 沒什麼實作經驗,
只知道一個大概而已, 因為我到日本的 Yahoo 聊天室,
發現他們的 Applet 聊天室除了可以傳 message , 還可以傳聲音 & webcam,
我也想自己寫像那樣的 Applet 的聊天室, 所以最近開始蒐集資料, 我把上面 caterpillar 版主的 sample code 拿來 Compile, 有些會過, 有些會錯在 import java.net.*;
請問是為什麼呢? 是我 Classpath 沒設好的關係嗎?
我用的是 Netbeans 附的 j2sdk1.4.2 , 謝謝各位.
如果我 po 錯地方還請各位原諒 :p


reply to postreply to post
Welcome to AKIRA's Blog
作者 Re:[網路]多人聊天伺服器 v0.3 [Re:caterpillar]
caterpillar

良葛格

版主

發文: 2613
積分: 70
於 2004-03-31 18:19 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
近來在整理多執行緒的一些心得,結果。。。。哈!發現這個程式有個非常好笑的錯誤,不過我不修正,看有沒有人可以找出來的並公佈修正版本的!修正錯誤的可以加分喔!(加分作什麼?問站長吧!反正先加再說,以後有利多時就用的上了。。。。Big Smile

提示:如果有很多訊息時就很好笑了。。。。不是之前討論的LinkedList與Vector的問題。。。。。


reply to postreply to post
良葛格學習筆記
作者 Re:[網路]多人聊天伺服器 v0.3 [Re:caterpillar]
T55555

Java, Ruby, Haskell

版主

發文: 1026
積分: 24
於 2004-03-31 23:48 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
caterpillar wrote:
近來在整理多執行緒的一些心得,結果。。。。哈!發現這個程式有個非常好笑的錯誤,不過我不修正,看有沒有人可以找出來的並公佈修正版本的!修正錯誤的可以加分喔!(加分作什麼?問站長吧!反正先加再說,以後有利多時就用的上了。。。。Big Smile

提示:如果有很多訊息時就很好笑了。。。。不是之前討論的LinkedList與Vector的問題。。。。。


個人淺見:
我不知道這是不是個錯誤. 我只是覺得會有這現象...
In BroadCastThread.run()
the thread is sleep 2 seconds and broadcast a message to all clients. (only one message at a time)
If there are many clients, and many messages need to be broadcast,
the message may wait long long time to broadcase.

For example, if the message vector is empty, and during the 2 seconds (thread sleep time) there are 10 clients, each one types 50 messages, ==> Ooops,
if you type a new message, then you have to wait, at least, 10 * 50 * 2 = 1000 seconds = ~17 minutes later then your message will be broadcast.

More and more clients, more and more messages waiting, more and more this phenomena worst.

(And of course, if there is a "robot" to type quickly the message in secondes, make the Vector out of limited or Out of memory error ... Ooops...)

Solution: Make a MAX_SIZE to allows in message vector,
check and return boolean result or throw exception,
let the client know about the server is in "overflow" state,
please wait and retry later to post new message ...

(Idea: And it is maybe good too, like, fix a maximum number of active client connected allowed. And/Or do something like "time out disconnect" if the client do not post a new message during an amount of time ...)

( BTW: 我 13分很久了... Smile )


T55555 edited on 2004-04-01 00:21
reply to postreply to post
作者 Re:[網路]多人聊天伺服器 v0.3 [Re:caterpillar]
caterpillar

良葛格

版主

發文: 2613
積分: 70
於 2004-04-01 00:28 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
哈!對一半,您指出了原因,不過解決方案可以有更好的方法,popcorny說過了喔!。。。。。我的回應也提到了。。。。

不過您的方案是也可以解決問題,且可以不更動程式架構,先獎勵,看有沒有人能再改進的,我心中的方案要稍微更動一下message queue的維護方式。。。。


reply to postreply to post
良葛格學習筆記
作者 Re:[網路]多人聊天伺服器 v0.3 [Re:caterpillar]
T55555

Java, Ruby, Haskell

版主

發文: 1026
積分: 24
於 2004-04-01 01:28 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
caterpillar wrote:
哈!對一半,您指出了原因,不過解決方案可以有更好的方法,popcorny說過了喔!。。。。。我的回應也提到了。。。。

不過您的方案是也可以解決問題,且可以不更動程式架構,先獎勵,看有沒有人能再改進的,我心中的方案要稍微更動一下message queue的維護方式。。。。


I just try to give you a quick solution. (No need big change)
I do not read the entire program and try to ameliorate yet.

Again, this time, I just quick see the program, and got a quick idea: (maybe it is not good at all)

If you do not want the Asyn mode, you could do:
No need the message queue!
Got a message, broadcast immediately.

In BroadCastThread,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private String _message;
 
public synchronized void addMessage(String message) {
  _message = message;
  notifyAll();  // wakes up the BroadCast thread...
}
 
public synchronized void run() {
  ...
  while(true) {
      ...  // no need sleep
      ...  // send _message to all clients
      try {
        wait(); // wait for next message
      }
      catch (InterruptedException) {
      ...
      }
  } 
}    


This is the "got one message immediately send to all clients" synchronized design.
If you like Asyn mode, then more work to do.

BTW: I do find out this design has "trouble" too.
You click the enter-key to post new message, it may "block" long time,
until the broadcast thread send the previous message to all clients,
and you got the chance first get the monitor control ...

Asyn mode seems good way to fix this problem ...

========
剛剛看了一下 popcorny 的 post, 我這方法就如 popcorny 所說
干脆, 就不要 BroadCastThread, 把 addMessage, 轉成
sendBoradCastMessage ( and synchronized it ).
==> 當然這也發生了我所提到的 "block" client 現象. (check my next post for Asyn-Mode ...)


T55555 edited on 2004-04-01 04:46
reply to postreply to post
作者 Re:[網路]多人聊天伺服器 v0.3 [Re:caterpillar]
T55555

Java, Ruby, Haskell

版主

發文: 1026
積分: 24
於 2004-04-01 02:10 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
OK, immediately think about quick solution for Asyn-mode.
(Don't blame to me, this is "quick", so it maybe have many troubles...)

You could use LinkedList instead of Vector, because we syncrhonized by ourself in the BroadCastThread.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    private List _message = new LinkedList(); // use LinkedList instead of Vector, we do synchronization our own
  
    public void addMessage(String message) {
      synchronized(_message) {
          _message.add(message);  // append at the end of LinkedList
          notifyAll();            // wakes up the broadcast thread
      }
    }
  
    public void run() {
        String message = null;
        ...
        while (true) {
            try {
                synchronized(_message) {
                    if (_message.isEmpty()) {
                        wait(); // wait for new message ...
                    }
                    else {
                        message = (String) _message.get(0); // only get the first message and immediately exit the
                        _message.remove(0);                 // syncrhonized block to allow other client addMessage.
                    }
                }
            } catch (InterruptedException e) {
                continue;
            }
  
            ...   // boadcast to all clients, and this time it is not in sycrhonized
            ...   // block to allow during broadcast other client can addMessage...
  
        }
    }


Please note, this is "quick and less modification" to fix the problem.
I do not review entire application yet, so, maybe like popcorny said, it is possible that we even do not need the BroadCastThread at all ...

=========

other version, send all messages to all clients,
instead sychronized to get only one message ...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    private List _message = new LinkedList(); // use LinkedList instead of Vector, we do synchronization our own
  
    public void addMessage(String message) {
      synchronized(_message) {
          _message.add(message);  // append at the end of LinkedList
          notifyAll();            // wakes up the broadcast thread
      }
    }
  
    public void run() {
        List messages = null;
        ...
        while (true) {
            try {
                synchronized(_message) {
                    if (_message.isEmpty()) {
                        wait(); // wait for new message ...
                    }
                    else {
                        messages = new ArrayList(_message); // copy all messages
                        _message.clear();
                    }
                }
            } catch (InterruptedException e) {
                continue;
            }
                  // iterator throgh messages list, send to all clients ...
            ...   // boadcast to all clients, and this time it is not in sycrhonized
            ...   // block to allow during broadcast other client can addMessage...
            messages = null;
        }
    }


T55555 edited on 2004-04-02 08:44
reply to postreply to post
作者 Re:[網路]多人聊天伺服器 v0.3 [Re:caterpillar]
contagious





發文: 12
積分: 2
於 2004-04-01 14:42 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
我是傳染..

有關 message 很久才會送到的問題..
何不在每一個 broadcast 的 loop 裡就把所有已接到的message 一次送出去呢?
程式碼大概如下..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    public void run() {
        ClientThread client = null;
        String message = null;
        Object[] message_tmp =null;
 
        try {
            while(true) {
                // 每兩秒廣播一次
                Thread.sleep(2000);
 
                // 取出要廣播的訊息
                // 目前沒有訊息就不處理接下來的內容
                if(_messageVector.isEmpty())
                    continue;
    
                synchronized(_messageVector){  
                       message_tmp = _messageVector.toArray();
                       _messageVector.clear();      
                }
    
               // 將訊息一個一個丟給客戶端
               for(int j=0;j<message_tmp.length;j++){
                    for(int i = 0; i < _clientVector.size(); i++) {
                         client = (ClientThread) _clientVector.elementAt(i);
                         client.sendMessage( (String)message_tmp[i]);
                    }
                }
            }
        }


還有一個問題..是有關 Vector 的 synchronized
Vector 有做 synchronized 是指說對這個 Vector 的操作都是 atomic 的..
並不是用了 Vector 就可以不管 synchronized 的問題..

我們考慮一個情況..
在 broadcast thread 中的 run裡有一段
1
2
3
4
5
for(int i = 0; i < _clientVector.size(); i++) {
    client = (ClientThread) _clientVector.elementAt( i );
    //可能會發生 race condition
    client.sendMessage(message);
}


在我標註的地方
若執行到這裡時發生 thread switch(..奇怪的詞..看懂就好)
剛剛被取得的這個 client 被使用者關掉了..
那接下來這個 client.sendMessage(message); 這一句就會出現 exception

不過在這個程式裡發生這個 exception 是沒關係的..
反正 client 都關了..message 沒送到也是正常的..

另外 LinkedList 也有 synchronized 的版本..
1
List list = Collections.synchronizedList(new LinkedList());


傳染走了


contagious edited on 2004-04-01 15:36
reply to postreply to post
作者 Re:[網路]多人聊天伺服器 v0.3 [Re:caterpillar]
blowfish

Ultraman Forever



發文: 33
積分: 0
於 2004-05-28 16:27 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
最近也要寫個server, 所以很認真的看caterpillar的大作, 想偷學點東西..呵.
努力中...

不過有個問題想請教一下, 我發現ClientThread在Server中產生後, 到最後好像都沒有被設成null, 所以是不是在一段時間後, 會自動被GC清除, 但如果連線數一多時, 會不會造成Server有問題呢?


blowfish edited on 2004-05-28 17:17
reply to postreply to post
Some might say that sunshine follows thunder.


-= Blowfish's Place =-
作者 Re:[網路]多人聊天伺服器 v0.3 [Re:blowfish]
caterpillar

良葛格

版主

發文: 2613
積分: 70
於 2004-05-28 19:15 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
我是這麼想的,當客戶端連線終止時,會用_broadCastThread.removeClientThread(this); ,這時被移出的物件沒有任何名稱參考至它,最後會被自動回收。。。。

應該是這樣吧!。。。。(O_O?) ,不知道有沒有想錯。。。。


reply to postreply to post
良葛格學習筆記
作者 setMessageObserver()請問觀察者是什麼意思??? [Re:caterpillar]
object0625





發文: 6
積分: 0
於 2010-06-03 00:21 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
我在練習板大多人聊天的時候發現這個方法setMessageObserver()請問觀察者是什麼意思???
為什麼要用MultiChatClient宣告一個chatbox 然後在setMessageObserver建構子裡面再用MultiChatClient 宣告一個box 再把它丟進chatbox裡面 不懂 麻煩解說一下 謝謝


reply to postreply to post
作者 Re:[網路]多人聊天伺服器 v0.3 [Re:caterpillar]
summerdan





發文: 2
積分: 0
於 2010-11-05 13:10 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
我試著跟朋友連看看
但是好像都連不上 原因是?


reply to postreply to post
» JWorld@TW »  Java 程式分享區 » Networking

reply to topicthreaded modego to previous topicgo to next topic
  已讀文章
  新的文章
  被刪除的文章
Jump to the top of page

JWorld@TW 本站商標資訊

Powered by Powerful JuteForum® Version Jute 1.5.8