2017年7月6日 星期四

Android:使用Socket的簡單聊天室範例

之前有在Java裡寫了Server-Client介紹,這篇就來使用Android來寫一個簡單的對話App,使用Socket和ServerSocket來連結Server和Client端,並且透過I/O傳遞資料

Client端:

 Server端:





Client端:
activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.and_sv.MainActivity" >

    <EditText
        android:id="@+id/edtname"
        android:layout_width="100dp"
        android:layout_height="wrap_content" >
        
    </EditText>

    <EditText
        android:id="@+id/edttext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/edtname"
        android:layout_marginLeft="20dp"
        android:layout_toRightOf="@+id/edtname"
        android:ems="10" >
    </EditText>

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignRight="@+id/edttext"
        android:layout_below="@+id/edttext"
        android:text="送出" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/edtname"
        android:layout_below="@+id/button1" />

</RelativeLayout>

MainActivity.java:

public class MainActivity extends Activity {
 private EditText edtname,edttext;
 private TextView textview1;
 private Button button1;
 String tmp;                // 暫存文字訊息
    Socket clientSocket;
    
    public static Handler mHandler = new Handler();
    
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  edtname = (EditText)findViewById(R.id.edtname);
  edttext = (EditText)findViewById(R.id.edttext);
  button1 = (Button)findViewById(R.id.button1);
  textview1 = (TextView)findViewById(R.id.textView1);
  button1.setOnClickListener(btnlistener);
  
        Thread t = new Thread(readData);
         // 啟動執行緒
         t.start();

 }
 private OnClickListener btnlistener = new Button.OnClickListener(){

  @Override
  public void onClick(View v) {
   // TODO Auto-generated method stub
   if(clientSocket.isConnected()){
     
                BufferedWriter bw;

                try {
                    // 取得網路輸出串流
                    bw = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream()));

                    // 寫入訊息
                    bw.write(edtname.getText()+":"+edttext.getText()+"\n");

                    // 立即發送
                    bw.flush();
                } catch (IOException e) {

                }
                // 將文字方塊清空
                edttext.setText("");
            }    
  }
  
 };
  
     // 顯示更新訊息
     private Runnable updateText = new Runnable() {
         public void run() {
             // 加入新訊息並換行
             textview1.append(tmp + "\n");
         }
     };
  
     // 取得網路資料
     private Runnable readData = new Runnable() {
         public void run() {
             // server端的IP
             InetAddress serverIp;
             try {
                 // 以內定(本機電腦端)IP為Server端
                 serverIp = InetAddress.getByName("10.0.2.2");
                 int serverPort = 5050;
                 clientSocket = new Socket(serverIp, serverPort);
  
                 // 取得網路輸入串流
                 BufferedReader br = new BufferedReader(new InputStreamReader(
                         clientSocket.getInputStream()));
  
                 // 當連線後
                 while (clientSocket.isConnected()) {
                     // 取得網路訊息
                     tmp = br.readLine();
  
                     // 如果不是空訊息則
                     if(tmp!=null)
                         // 顯示新的訊息
                         mHandler.post(updateText);
                 }
  
             } catch (IOException e) {
  
             }
         }
     };
  
 }

Server端:

public class Server {
 
 private static int serverport = 5050;
    private static ServerSocket serverSocket;
    private static int count=0;
 
    // 用串列來儲存每個client
    private static ArrayList players=new ArrayList();
 
    // 程式進入點
    public static void main(String[] args) {
     
        try {
            serverSocket = new ServerSocket(serverport);
            System.out.println("Server is start.");
            // 顯示等待客戶端連接
            System.out.println("Waiting for clinet connect");
            // 當Server運作中時
            while (!serverSocket.isClosed()) {
                
                // 呼叫等待接受客戶端連接
                waitNewPlayer();
            }
 
        } catch (IOException e) {
            System.out.println("Server Socket ERROR");
        }
 
    }
 
    // 等待接受客戶端連接
    public static void waitNewPlayer() {
        try {
            Socket socket = serverSocket.accept();
            ++count;
            System.out.println("現在使用者個數:"+count);
 
            // 呼叫創造新的使用者
            createNewPlayer(socket);
            
        } catch (IOException e) {
 
        }
 
    }
 
    // 創造新的使用者
    public static void createNewPlayer(final Socket socket) throws IOException {
     
     
        // 以新的執行緒來執行
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 增加新的使用者
                    players.add(socket);
                    // 取得網路串流 
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(socket.getInputStream()));
                    // 當Socket已連接時連續執行
                    while (socket.isConnected()) {
                        // 取得網路串流的訊息
                        String msg= br.readLine();
                        
                        if(msg==null){
                         System.out.println("Client Disconnected!");
                         break;
                        }
 
                        //輸出訊息 
                        System.out.println(msg);
                        
                        // 廣播訊息給其它的客戶端
                        castMsg(msg);

                    }
                    
 
                } catch (IOException e) {
                 e.getStackTrace();
                }
                finally{
                 // 移除客戶端
              players.remove(socket);
              --count;
              System.out.println("現在使用者個數:"+count);
                }
             
            }
        });
 
        // 啟動執行緒
        t.start();
    }
 
    // 廣播訊息給其它的客戶端
    public static void castMsg(String Msg){
        // 創造socket陣列
        Socket[] ps=new Socket[players.size()]; 
 
        // 將players轉換成陣列存入ps
        players.toArray(ps);
 
        // 走訪ps中的每一個元素
        for (Socket socket : ps ) {
            try {
                // 創造網路輸出串流
                BufferedWriter bw;
                bw = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream()));
 
                // 寫入訊息到串流
                bw.write(Msg+"\n");
 
                // 立即發送
                bw.flush();
            } catch (IOException e) {
 
            }
        }
    }

}

PS:因為要持續的更新Textview所以必須使用new Handler().post(runnable)
在Android裡只有(Main Thread或UI Thread)主線程負責UI的操作或事件,子線程不可以插手有關UI的事,但是可以透過子線程去處理複雜的事情,再透過Handler去通知主線程更新UI

5 則留言:

  1. 您好
    我使用您的程式來學習Android Socket
    然而當我使用Android 8.0手機直接app時,可以順利連線,但是要送出內容時,app就閃退了,請問如何解決?謝謝您。

    回覆刪除
    回覆
    1. 你好,請問你的Server端程式是放在哪裡呢?
      因為我這支server端程式是放在自己的電腦,serverIp = InetAddress.getByName("10.0.2.2");
      10.0.2.2是local端的ip,所以外面是連不進去的。還有server端程式要保持運作

      刪除
  2. 我的也在案出送出後會閃退,可以用虛擬IP嗎?PORT需要開啟嗎

    回覆刪除
  3. 遇到相同問題, 改了幾個地方, 供參考
    private View.OnClickListener btnlistener = new Button.OnClickListener(){
    @Override
    public void onClick(View v) {
    // TODO Auto-generated method stub
    if(clientSocket!=null && clientSocket.isConnected()){
    Thread t = new Thread(sendData);
    t.start();
    // 將文字方塊清空
    edttext.setText("");
    }
    }
    };
    private Runnable sendData = new Runnable() {
    public void run() {
    BufferedWriter bw;

    try {
    // 取得網路輸出串流
    bw = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream()));

    // 寫入訊息
    bw.write(edtname.getText()+":"+edttext.getText()+"\n");

    // 立即發送
    bw.flush();
    } catch (IOException e) {

    }
    }
    };

    回覆刪除