1對1全雙工連線
把兩套單工通訊設備或程式結合在一起,使得兩個方向的資料和應答訊號均可同時傳輸。1對1通訊效率高,控制簡單,相對地成本也較高。需要有4條獨立的線路,採用四線制或用多工技術在一對線上製作。多對多全雙工連線
建立1個交換設備或是程式,將不同方向的資料用任何方式讓不同的客戶端讀取。多對多通訊效率不一定高,但控制簡困難,但相對地使用成本也較低。多對多全雙工通訊的方法很多,大致上可分成輪詢或廣播兩種。輪詢是指用非同步的方式,將資料儲存在交換設備或程式等待客戶端讀取,即使客戶端連線中斷,仍然可以再下次連線時讀取。廣播的方式則是以同步的方式,所有的客戶端都必須與交換設備連線。理論上每筆訊息都所有客戶端都可以看的到,透過程式的過濾機制選擇想要的訊息。建立多對多全雙工連線機制
這系列的第10個例子相對前面的例子相對複雜,因此需要利用封裝(Package)的方式加以區別。ChatThread
監聽客戶端socket的執行緒,接受 ChatThreadManager 管理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 | package chat; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.Socket; public class ChatThread extends Thread { Socket socket; public ChatThread(Socket s) { this .socket = s; } public void out(String out) { try { socket.getOutputStream().write(out.getBytes( "UTF-8" )); } catch (Exception e) { } } public void run() { try { BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8" )); String line = "" ; while ((line = br.readLine()) != null ) { System.out.println(line); ChatThreadManager.getChartManager().publish( this , line); } br.close(); } catch (Exception e) { // TODO: handle exception } } } |
此時出現錯誤,請點選Create class ChatThreadManager...並直接 Finish 即可。
ChatThreadManager
管理多個ChatThread,用於通知其他客戶端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 | package chat; import java.util.Vector; public class ChatThreadManager { private ChatThreadManager() { } private static final ChatThreadManager Cm = new ChatThreadManager(); public static ChatThreadManager getChartManager() { return Cm; } Vector<ChatThread> ChatSocket_vector = new Vector<ChatThread>(); public void add(ChatThread cs) { ChatSocket_vector.add(cs); } public void publish(ChatThread cs, String msg) { for ( int i = 0 ; i < ChatSocket_vector.size(); i++) { ChatThread csTemp = ChatSocket_vector.get(i); if (!cs.equals(csTemp)) { csTemp.out(msg + "\n" ); } } } } |
此時出現2個錯誤...
第一個錯誤找不到MyClientWindow 請先用WindowBuilder 建立名為MyClientWindow的視窗。
第二個錯誤請直接點選Create Class ConnectionManager..Finish即可。
ConnectionManager
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 | package chat; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; public class ConnectionManager { private ConnectionManager() {} private static final ConnectionManager instance = new ConnectionManager(); public static ConnectionManager getChatManager() { return instance; } MyClientWindow window; // 傳入視窗介面 Socket socket; private String IP; BufferedReader bReader; PrintWriter pWriter; public void setWindow(MyClientWindow window) { this .window = window; } public void connect(String ip) { this .IP = ip; new Thread() { @Override public void run() { // 建立Socket try { socket = new Socket(IP, 23456 ); // 建立輸出串流 pWriter = new PrintWriter( new OutputStreamWriter(socket.getOutputStream())); // 建立輸入串流 bReader = new BufferedReader( new InputStreamReader(socket.getInputStream())); String line = null ; // 如果讀到新訊息 while ((line = bReader.readLine()) != null ) { window.appendText(line); } // 讀完數據之後要關閉 pWriter.close(); bReader.close(); pWriter = null ; bReader = null ; } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }.start(); } public void send(String sendMsg) { if (pWriter != null ) { pWriter.write(sendMsg + "\n" ); pWriter.flush(); } else { window.appendText( "結束連線..." ); } } } |
MyClientWindow
建立JFrame後依絕對位置建立視窗,並且設置2個按鈕。1個用於執行連接,另外1個用於傳送訊息。JTextField物件用途是連接的主機位置,JTextArea物件的用途是顯示訊息。
操作介面,視窗中包括交換機IP、連線按鈕、接收訊息方塊、發送訊息,以及發送按鈕。
MyClientWindowl 已自動完成大部份的程式碼,但仍需要手動加入下列方法。
請在最後1個括號前加上appendText() 方法
123public
void
appendText(String in) {
info.append(
"\n"
+ in);
}
連線按鈕中的程式碼
1ConnectionManager.getChatManager().connect(ip.getText());
發送按鈕中的程式碼
123ConnectionManager.getChatManager().send(name.getText()+
": "
+ msg.getText());
appendText(name.getText()+
": "
+ msg.getText());
msg.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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | package chat; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.net.InetAddress; import java.net.UnknownHostException; import javax.swing.GroupLayout; import javax.swing.GroupLayout.Alignment; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.LayoutStyle.ComponentPlacement; import javax.swing.border.EmptyBorder; public class MyClientWindow extends JFrame { private static final long serialVersionUID = 1L; private JPanel contentPane; private JTextArea info; private JTextField ip; private JTextField msg; private JTextField name; public MyClientWindow() throws UnknownHostException { setAlwaysOnTop( true ); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds( 100 , 100 , 450 , 300 ); contentPane = new JPanel(); contentPane.setBorder( new EmptyBorder( 5 , 5 , 5 , 5 )); setContentPane(contentPane); info = new JTextArea(); info.setColumns( 5 ); info.setText( "凖備連線" ); JScrollPane jsp = new JScrollPane(info); ip = new JTextField(); ip.setText( "" ); ip.setColumns( 10 ); msg = new JTextField(); msg.setText( "hello" ); msg.setColumns( 10 ); name = new JTextField(); name.setText(InetAddress.getLocalHost().getHostAddress()); name.setColumns( 10 ); JButton btnConnect = new JButton( "連接" ); btnConnect.addMouseListener( new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { ConnectionManager.getChatManager().connect(ip.getText()); } }); JButton btnSend = new JButton( "發送" ); btnSend.addMouseListener( new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { ConnectionManager.getChatManager().send(name.getText()+ ": " + msg.getText()); appendText(name.getText()+ ": " + msg.getText()); msg.setText( "" ); } }); GroupLayout gl_contentPane = new GroupLayout(contentPane); gl_contentPane.setHorizontalGroup( gl_contentPane.createParallelGroup(Alignment.TRAILING) .addGroup(gl_contentPane.createSequentialGroup() .addGroup(gl_contentPane.createParallelGroup(Alignment.LEADING) .addGroup(Alignment.TRAILING, gl_contentPane.createSequentialGroup() .addGap( 5 ) .addComponent(name, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addGap( 10 ) .addComponent(msg, GroupLayout.PREFERRED_SIZE, 188 , GroupLayout.PREFERRED_SIZE) .addPreferredGap(ComponentPlacement.RELATED) .addComponent(btnSend, GroupLayout.PREFERRED_SIZE, 109 , GroupLayout.PREFERRED_SIZE)) .addGroup(gl_contentPane.createSequentialGroup() .addComponent(ip, GroupLayout.PREFERRED_SIZE, 294 , GroupLayout.PREFERRED_SIZE) .addPreferredGap(ComponentPlacement.RELATED) .addComponent(btnConnect, GroupLayout.DEFAULT_SIZE, 114 , Short.MAX_VALUE)) .addComponent(jsp, GroupLayout.DEFAULT_SIZE, 414 , Short.MAX_VALUE)) .addContainerGap()) ); gl_contentPane.setVerticalGroup( gl_contentPane.createParallelGroup(Alignment.LEADING) .addGroup(gl_contentPane.createSequentialGroup().addGroup(gl_contentPane.createParallelGroup(Alignment.BASELINE) .addComponent(ip, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addComponent(btnConnect)).addPreferredGap(ComponentPlacement.RELATED) .addComponent(jsp, GroupLayout.DEFAULT_SIZE, 193 , Short.MAX_VALUE) .addGroup(gl_contentPane.createParallelGroup(Alignment.LEADING, false ).addGroup(gl_contentPane.createSequentialGroup() .addPreferredGap(ComponentPlacement.RELATED) .addGroup(gl_contentPane.createParallelGroup(Alignment.TRAILING) .addComponent(btnSend) .addComponent(msg, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))) .addGroup(Alignment.TRAILING, gl_contentPane.createSequentialGroup() .addPreferredGap(ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(name, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addGap( 1 )))) ); contentPane.setLayout(gl_contentPane); } // 客戶端發送的內容添加到中間的JTextArea物件中 public void appendText(String in) { info.append( "\n" + in); } } |
StartClient
程式進入點,產生視窗啟動監聽...1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package chat; import java.awt.EventQueue; public class StartClient { public static void main(String[] args) { EventQueue.invokeLater( new Runnable() { @Override public void run() { try { MyClientWindow frame= new MyClientWindow(); frame.setVisible( true ); ConnectionManager.getChatManager().setWindow(frame); } catch (Exception e) { } } }); } } |
ServerListener
監聽指定連接埠,當接收到 socket 連線,就為該 socket 開啟一個 ChatThread 執行緒 並將 ChatThread 執行緒保存到 Vector1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package chat; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import javax.swing.JOptionPane; public class ServerListener extends Thread { public void run() { try { ServerSocket serversocket = new ServerSocket( 23456 ); while ( true ) { Socket socket = serversocket.accept(); // 程式會暫停,直到有socket連接進來才會往下執行 System.out.println( "one client has connected" ); ChatThread cs = new ChatThread(socket); cs.start(); ChatThreadManager.getChartManager().add(cs); } } catch (IOException e) { e.printStackTrace(); } } } |
StartServer
Server 程式的進入點。1 2 3 4 5 6 7 | package chat; public class StartServer { public static void main(String[] args) { new ServerListener().start(); } } |
沒有留言:
張貼留言