全雙工連線應用

1對1全雙工連線

把兩套單工通訊設備或程式結合在一起,使得兩個方向的資料和應答訊號均可同時傳輸。1對1通訊效率高,控制簡單,相對地成本也較高。需要有4條獨立的線路,採用四線制或用多工技術在一對線上製作。




多對多全雙工連線

建立1個交換設備或是程式,將不同方向的資料用任何方式讓不同的客戶端讀取。多對多通訊效率不一定高,但控制簡困難,但相對地使用成本也較低。多對多全雙工通訊的方法很多,大致上可分成輪詢或廣播兩種。輪詢是指用非同步的方式,將資料儲存在交換設備或程式等待客戶端讀取,即使客戶端連線中斷,仍然可以再下次連線時讀取。廣播的方式則是以同步的方式,所有的客戶端都必須與交換設備連線。理論上每筆訊息都所有客戶端都可以看的到,透過程式的過濾機制選擇想要的訊息。




建立多對多全雙工連線機制

這系列的第10個例子相對前面的例子相對複雜,因此需要利用封裝(Package)的方式加以區別。

ChatThread

監聽客戶端socket的執行緒,接受 ChatThreadManager 管理
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,用於通知其他客戶端
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


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() 方法
public void appendText(String in) {
  info.append("\n" + in);
 }

連線按鈕中的程式碼
ConnectionManager.getChatManager().connect(ip.getText());

發送按鈕中的程式碼
ConnectionManager.getChatManager().send(name.getText()+": " + msg.getText());
appendText(name.getText()+": " + msg.getText());
msg.setText("");


完整的程式碼如下

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

程式進入點,產生視窗啟動監聽...
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 執行緒保存到 Vector 集合內,交給ChatThreadManager管理,

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 程式的進入點。
package chat;

public class StartServer {
 public static void main(String[] args) {
  new ServerListener().start();
 }
}

沒有留言:

張貼留言