Android開發:Socket的簡介與使用解析

ty391255 9年前發布 | 17K 次閱讀 Socket 安卓開發 Android開發 移動開發

前言

  • Socket的使用在Android的網絡編程中非常重要

  • 今天我將帶大家全面了解Socket及其使用方法

目錄

目錄

1.網絡基礎

1.1 計算機網絡分層

計算機網絡分為五層:物理層、數據鏈路層、網絡層、運輸層、應用層

計算機網絡

其中:

  • 網絡層:負責根據IP找到目的地址的主機
  • 運輸層:通過端口把數據傳到目的主機的目的進程,來實現進程與進程之間的通信

1.2 端口號(PORT)

端口號規定為16位,即允許一個IP主機有2的16次方65535個不同的端口。其中:

  • 0~1023:分配給系統的端口號

    我們不可以亂用

  • 1024~49151:登記端口號,主要是讓第三方應用使用

    但是必須在IANA(互聯網數字分配機構)按照規定手續登記,

  • 49152~65535:短暫端口號,是留給客戶進程選擇暫時使用,一個進程使用完就可以供其他進程使用。

在Socket使用時,可以用1024~65535的端口號

1.3 C/S結構

  • 定義:即客戶端/服務器結構,是軟件系統體系結構
  • 作用:充分利用兩端硬件環境的優勢,將任務合理分配到Client端和Server端來實現,降低了系統的通訊開銷。

    Socket正是使用這種結構建立連接的,一個套接字接客戶端,一個套接字接服務器。

如圖:

Socket架構

可以看出,Socket的使用可以基于TCP或者UDP協議。

1.4 TCP協議

  • 定義:Transmission Control Protocol,即傳輸控制協議,是一種傳輸層通信協議

    基于TCP的應用層協議有FTP、Telnet、SMTP、HTTP、POP3與DNS。

  • 特點:面向連接、面向字節流、全雙工通信、可靠

    • 面向連接:指的是要使用TCP傳輸數據,必須先建立TCP連接,傳輸完成后釋放連接,就像打電話一樣必須先撥號建立一條連接,打完后掛機釋放連接。

    • 全雙工通信:即一旦建立了TCP連接,通信雙方可以在任何時候都能發送數據。

    • 可靠的:指的是通過TCP連接傳送的數據,無差錯,不丟失,不重復,并且按序到達。

    • 面向字節流:流,指的是流入到進程或從進程流出的字符序列。簡單來說,雖然有時候要傳輸的數據流太大,TCP報文長度有限制,不能一次傳輸完,要把它分為好幾個數據塊,但是由于可靠性保證,接收方可以按順序接收數據塊然后重新組成分塊之前的數據流,所以TCP看起來就像直接互相傳輸字節流一樣,面向字節流。

  • TCP建立連接

    必須進行 三次握手

    :若A要與B進行連接,則必須

    • 第一次握手:建立連接。客戶端發送連接請求報文段,將SYN位置為1,Sequence Number為x;然后,客戶端進入SYN_SEND狀態,等待服務器的確認。即A發送信息給B
    • 第二次握手:服務器收到客戶端的SYN報文段,需要對這個SYN報文段進行確認。即B收到連接信息后向A返回確認信息
    • 第三次握手:客戶端收到服務器的(SYN+ACK)報文段,并向服務器發送ACK報文段。即A收到確認信息后再次向B返回確認連接信息

      此時,A告訴自己上層連接建立;B收到連接信息后告訴上層連接建立。

TCP三次握手

這樣就完成TCP三次握手 = 一條TCP連接建立完成 = 可以開始發送數據

  1. 三次握手期間任何一次未收到對面回復都要重發。
  2. 最后一個確認報文段發送完畢以后,客戶端和服務器端都進入ESTABLISHED狀態。

為什么TCP建立連接需要三次握手?

答:防止服務器端因為接收了 早已失效的連接請求報文 從而一直等待客戶端請求,從而浪費資源

  • “已失效的連接請求報文段”的產生在這樣一種情況下:Client發出的第一個連接請求報文段并沒有丟失,而是在某個網絡結點長時間的滯留了,以致延誤到連接釋放以后的某個時間才到達server。
  • 這是一個早已失效的報文段。但Server收到此失效的連接請求報文段后,就誤認為是Client再次發出的一個新的連接請求。
  • 于是就向Client發出確認報文段,同意建立連接。
  • 假設不采用“三次握手”:只要Server發出確認,新的連接就建立了。
  • 由于現在Client并沒有發出建立連接的請求,因此不會向Server發送數據。
  • 但Server卻以為新的運輸連接已經建立,并一直等待Client發來數據。>- 這樣,Server的資源就白白浪費掉了。

采用“三次握手”的辦法可以防止上述現象發生:

  • Client不會向Server的確認發出確認
  • Server由于收不到確認,就知道Client并沒有要求建立連接
  • 所以Server不會等待Client發送數據,資源就沒有被浪費
  • TCP釋放連接

    TCP釋放連接需要 四次揮手 過程,現在假設A主動釋放連接:(數據傳輸結束后,通信的雙方都可釋放連接)

    • 第一次揮手:A發送釋放信息到B;(發出去之后,A->B發送數據這條路徑就斷了)
    • 第二次揮手:B收到A的釋放信息之后,回復確認釋放的信息:我同意你的釋放連接請求

    • 第三次揮手:B發送“請求釋放連接“信息給A

    • 第四次揮手:A收到B發送的信息后向B發送確認釋放信息:我同意你的釋放連接請求

      B收到確認信息后就會正式關閉連接;

      A等待2MSL后依然沒有收到回復,則證明B端已正常關閉,于是A關閉連接

TCp四次握手

為什么TCP釋放連接需要四次揮手?

為了保證雙方都能通知對方“需要釋放連接”,即在釋放連接后都無法接收或發送消息給對方

  • 需要明確的是:TCP是全雙工模式,這意味著是雙向都可以發送、接收的
  • 釋放連接的定義是:雙方都無法接收或發送消息給對方,是雙向的
  • 當主機1發出“釋放連接請求”(FIN報文段)時,只是表示主機1已經沒有數據要發送 / 數據已經全部發送完畢;

    但是,這個時候主機1還是可以接受來自主機2的數據。

  • 當主機2返回“確認釋放連接”信息(ACK報文段)時,表示它已經知道主機1沒有數據發送了

    但此時主機2還是可以發送數據給主機1

  • 當主機2也發送了FIN報文段時,即告訴主機1我也沒有數據要發送了

    此時,主機1和2已經無法進行通信:主機1無法發送數據給主機2,主機2也無法發送數據給主機1,此時,TCP的連接才算釋放

1.5 UDP協議

  • 定義:User Datagram Protocol,即用戶數據報協議,是一種傳輸層通信協議。

    基于UDP的應用層協議有TFTP、SNMP與DNS。

  • 特點:無連接的、不可靠的、面向報文、沒有擁塞控制

    • 無連接的:和TCP要建立連接不同,UDP傳輸數據不需要建立連接,就像寫信,在信封寫上收信人名稱、地址就可以交給郵局發送了,至于能不能送到,就要看郵局的送信能力和送信過程的困難程度了。

    • 不可靠的:因為UDP發出去的數據包發出去就不管了,不管它會不會到達,所以很可能會出現丟包現象,使傳輸的數據出錯。

    • 面向報文:數據報文,就相當于一個數據包,應用層交給UDP多大的數據包,UDP就照樣發送,不會像TCP那樣拆分。

    • 沒有擁塞控制 :擁塞,是指到達通信子網中某一部分的分組數量過多,使得該部分網絡來不及處理,以致引起這部分乃至整個網絡性能下降的現象,嚴重時甚至會導致網絡通信業務陷入停頓,即出現死鎖現象,就像交通堵塞一樣。TCP建立連接后如果發送的數據因為信道質量的原因不能到達目的地,它會不斷重發,有可能導致越來越塞,所以需要一個復雜的原理來控制擁塞。而UDP就沒有這個煩惱,發出去就不管了。
  • 應用場景

    很多的實時應用(如IP電話、實時視頻會議、某些多人同時在線游戲等)要求源主機以很定的速率發送數據,并且允許在網絡發生擁塞時候丟失一些數據,但是要求不能有太大的延時,UDP就剛好適合這種要求。所以說,只有不適合的技術,沒有真正沒用的技術。

1.6 HTTP協議

詳情請看我寫的另外一篇文章 你需要了解的HTTP知識都在這里了!

2. Socket的定義

  • 即套接字,是其中 計算機網絡中運輸層和應用層之間的一種一個中間抽象層,也是一個編程接口
  • 成對出現,一對套接字Socket的 組成 就是 Socket ={(IP地址1:PORT端口號),(IP地址2:PORT端口號)}

3. Socket具體使用

Socket可以基于TCP或者UDP協議,由于TCP比UDP常用,所以下面實例中的Socket將基于TCP協議

3.1 實例Demo1

下面是一個簡單的、基于TCP協議的Socket連接demo

客戶端:AndroidStudio實現

服務器端:Eclipse實現

服務器端(用eclipse編譯):

package scut;
import java.io.BufferedReader;  
import java.io.InputStream;  
import java.io.InputStreamReader;  
import java.io.OutputStream;  
import java.net.ServerSocket;  
import java.net.Socket;  

public class Sock {


      /** 
       * 服務器端 
       *  
       * @author Administrator 
       *  
       */  


          // 程序入口  
          public static void main(String args[]) {  

              try {  

                  // 建立一個ServletSocket ,監聽對應的端口,用于監聽客戶端的連接請求  
                  ServerSocket serverSocket = new ServerSocket(40004);  

                  while (true) { // 循環不斷接收客戶端的請求  

                      System.out.println("等待客戶端請求....");  
                      Socket socket = serverSocket.accept(); // 等待接收  
                      System.out.println("收到請求,服務器建立連接...");  

                      // 返回數據  
                      OutputStream os = socket.getOutputStream();  
                      String msg = "服務器已連接成功...";  
                      os.write(msg.getBytes("utf-8"));  



                      os.close();  
                      socket.close();  
                  }  

              } catch (Exception e) {  
                  e.printStackTrace();  
              }  
          }  


}

輸出

收到請求,服務器建立連接...
等待客戶端請求....
收到請求,服務器建立連接...
等待客戶端請求....

客戶端(用Android studio編譯):

package scut.myserversocket;

import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MainActivity extends AppCompatActivity {
    private TextView tv;
    String str;
    boolean running = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.TV);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                running = !running;
                new Thread(){
                    //建一個線程防止阻塞UI線程
                    public void run(){
                        super.run();
                        while (running){

                            try {
                                Socket socket = new Socket("192.168.56.1",40004);
                                //建立連接,因為genymotion的模擬器的本地ip不同于一般的模擬器,所以ip地址要用這個
                                sleep(1000);
                                // 獲取服務器返回的數據
                                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                                System.out.println("服務器數據:" + (str = br.readLine()));
                                os.close();
                                br.close();
                                socket.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }

                }.start();
                tv.setText(str);
            }
        });
    }


}

最后還要在Mainifest

輸出:

服務器數據:服務器已連接成功...
服務器數據:服務器已連接成功...
服務器數據:服務器已連接成功...

3.2 實例Demo2

一個較復雜、基于TCP的Socket通信demo(可以雙向互發信息)

  1. 服務器端和客戶端都是在Android實現
  2. 需要兩臺Android手機連到同一個wifi,使它們處于同一個網段,才能用Socket訪問IP地址實現客戶端和服務器端的連接通信。

服務器端代碼.

xml代碼:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="scut.serversocket.MainActivity">


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="未連接"
        android:id="@+id/tvIP"
        android:layout_gravity="center_vertical" />

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="未連接"
        android:textSize="30sp"/>

    <EditText
        android:id="@+id/etSend"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="請輸入要發送的內容" />

    <Button
        android:id="@+id/btnAccept"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="建立" />

    <Button
        android:id="@+id/btnSend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="發送" />
</LinearLayout>

MainActivity.java:

package scut.serversocket;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;

public class MainActivity extends AppCompatActivity {
    private TextView tv = null;
    private EditText et = null;
    private TextView IPtv = null;
    private Button btnSend = null;
    private Button btnAcept = null;
    private Socket socket;
    private ServerSocket mServerSocket = null;
    private boolean running = false;
    private AcceptThread mAcceptThread;
    private ReceiveThread mReceiveThread;
    private Handler mHandler = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.tv);
        et = (EditText) findViewById(R.id.etSend);
        IPtv = (TextView) findViewById(R.id.tvIP);
        btnAcept = (Button) findViewById(R.id.btnAccept);
        btnSend = (Button) findViewById(R.id.btnSend);
        mHandler = new MyHandler();
        btnSend.setEnabled(false);//設置發送按鍵為不可見
        btnAcept.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //開始監聽線程,監聽客戶端連接
                mAcceptThread = new AcceptThread();
                running = true;
                mAcceptThread.start();
                btnSend.setEnabled(true);//設置發送按鍵為可見
                IPtv.setText("等待連接");
                btnAcept.setEnabled(false);

            }
        });
        //發送數據按鈕
        btnSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                OutputStream os = null;
                try {
                    os = socket.getOutputStream();//獲得socket的輸出流
                    String msg = et.getText().toString()+"\n";
//                    System.out.println(msg);
                    os.write(msg.getBytes("utf-8"));//輸出EditText的內容
                    et.setText("");//發送后輸入框清0
                    os.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }catch (NullPointerException e) {
                    displayToast("未連接不能輸出");//防止服務器端關閉導致客戶端讀到空指針而導致程序崩潰
                    }
            }
        });
    }
    //定義監聽客戶端連接的線程
    private class AcceptThread extends Thread{
        @Override
        public void run() {
//            while (running) {
                try {
                    mServerSocket = new ServerSocket(40012);//建立一個ServerSocket服務器端
                    socket = mServerSocket.accept();//阻塞直到有socket客戶端連接
//                System.out.println("連接成功");
                    try {
                        sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    Message msg = mHandler.obtainMessage();
                    msg.what = 0;
                    msg.obj = socket.getInetAddress().getHostAddress();//獲取客戶端IP地址
                    mHandler.sendMessage(msg);//返回連接成功的信息
                    //開啟mReceiveThread線程接收數據
                    mReceiveThread = new ReceiveThread(socket);
                    mReceiveThread.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
//            }
        }
    }

    //定義接收數據的線程
    private class ReceiveThread extends Thread{
        private InputStream is = null;
        private String read;
        //建立構造函數來獲取socket對象的輸入流
        public ReceiveThread(Socket sk){
            try {
                is = sk.getInputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void run() {
            while (running) {
                try {
                    sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                BufferedReader br = null;
                try {
                    br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                try {
                    //讀服務器端發來的數據,阻塞直到收到結束符\n或\r
                    read = br.readLine();
                    System.out.println(read);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NullPointerException e) {
                    running = false;//防止服務器端關閉導致客戶端讀到空指針而導致程序崩潰
                    Message msg2 = mHandler.obtainMessage();
                    msg2.what = 2;
                    mHandler.sendMessage(msg2);//發送信息通知用戶客戶端已關閉
                    e.printStackTrace();
                    break;
                }
                //用Handler把讀取到的信息發到主線程
                Message msg = mHandler.obtainMessage();
                msg.what = 1;
                msg.obj = read;
                mHandler.sendMessage(msg);

            }
        }
    }

    private void displayToast(String s)
    {
        Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
    }

    class MyHandler extends Handler{//在主線程處理Handler傳回來的message
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    String str = (String) msg.obj;
                    tv.setText(str);
                    break;
                case 0:
                    IPtv.setText("客戶端"+msg.obj+"已連接");
                    displayToast("連接成功");
                    break;
                case 2:
                    displayToast("客戶端已斷開");
                    //清空TextView
                    tv.setText(null);//
                    IPtv.setText(null);
                    try {
                        socket.close();
                        mServerSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    btnAcept.setEnabled(true);
                    btnSend.setEnabled(false);
                    break;
            }
        }
    }

    @Override
    protected void onDestroy() {
        mHandler.removeCallbacksAndMessages(null);//清空消息隊列,防止Handler強引用導致內存泄漏
    }
}

客戶端代碼:

xml代碼:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    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"
    android:orientation="vertical"
    tools:context="scut.myserversocket.MainActivity"
    tools:showIn="@layout/activity_main">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="3">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="未連接"
            android:id="@+id/TV"
            android:textSize="30sp"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1">
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="請輸入IP要連接服務器端地址"
            android:textSize="20sp"
            android:id="@+id/IPet"/>

    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="開啟連接"
            android:id="@+id/btnStart"
            android:layout_weight="1"
            />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="發送消息"
            android:id="@+id/btnSend"
            android:layout_weight="1"
            />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="取消連接"
            android:id="@+id/btnStop"
            android:layout_weight="1"
            />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1">
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="請輸入要發送的數據"
            android:id="@+id/et"
            />
    </LinearLayout>
</LinearLayout>

MainActivity.java:

package scut.clientsocket;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private TextView tv;
    private EditText et;
    private EditText IPet;
    private Handler myhandler;
    private Socket socket;
    private String str = "";
    boolean running = false;
    private Button btnSend;
    private Button btnStart;
    private Button btnStop;
    private StartThread st;
    private ReceiveThread rt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.TV);
        et = (EditText) findViewById(R.id.et);
        IPet = (EditText) findViewById(R.id.IPet);

        btnSend = (Button) findViewById(R.id.btnSend);
        btnStart = (Button) findViewById(R.id.btnStart);
        btnStop = (Button) findViewById(R.id.btnStop);

        setButtonOnStartState(true);//設置按鍵狀態為可開始連接

        btnSend.setOnClickListener(this);
        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);

        myhandler = new MyHandler();//實例化Handler,用于進程間的通信

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btnStart:
                //按下開始連接按鍵即開始StartThread線程
                st = new StartThread();
                st.start();
                setButtonOnStartState(false);//設置按鍵狀態為不可開始連接

                break;
            case R.id.btnSend:
                // 發送請求數據
                OutputStream os = null;
                try {

                    os = socket.getOutputStream();//得到socket的輸出流
                    //輸出EditText里面的數據,數據最后加上換行符才可以讓服務器端的readline()停止阻塞
                    os.write((et.getText().toString()+"\n").getBytes("utf-8"));
                    et.setText("");//發送后輸入框清0
//                    System.out.println(et.getText().toString()+"\n");
                } catch (IOException e) {
                    e.printStackTrace();
                }

                break;
            case R.id.btnStop:
                running = false;
                setButtonOnStartState(true);//設置按鍵狀態為不可開始連接
                try {
                    socket.close();
                } catch (NullPointerException e) {
                    e.printStackTrace();
                    displayToast("未連接成功");
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
        }

    }
private class StartThread extends Thread{
    @Override
    public void run() {
        try {

            socket = new Socket(IPet.getText().toString(),40012);//連接服務端的IP
            //啟動接收數據的線程
            rt = new ReceiveThread(socket);
            rt.start();
            running = true;
            System.out.println(socket.isConnected());
            if(socket.isConnected()){//成功連接獲取socket對象則發送成功消息
                Message msg0 = myhandler.obtainMessage();
                msg0.what=0;
                myhandler.sendMessage(msg0);
                }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

    private class ReceiveThread extends Thread{
        private InputStream is;
        //建立構造函數來獲取socket對象的輸入流
        public ReceiveThread(Socket socket) throws IOException {
            is = socket.getInputStream();
        }
        @Override
        public void run() {
            while (running) {
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                try {
                    //讀服務器端發來的數據,阻塞直到收到結束符\n或\r
                    System.out.println(str = br.readLine());

                } catch (NullPointerException e) {
                    running = false;//防止服務器端關閉導致客戶端讀到空指針而導致程序崩潰
                    Message msg2 = myhandler.obtainMessage();
                    msg2.what = 2;
                    myhandler.sendMessage(msg2);//發送信息通知用戶客戶端已關閉
                    e.printStackTrace();
                    break;

                } catch (IOException e) {
                    e.printStackTrace();
                }

                //用Handler把讀取到的信息發到主線程
                Message msg = myhandler.obtainMessage();


                msg.what = 1;
//                }
                msg.obj = str;
                myhandler.sendMessage(msg);
                try {
                    sleep(400);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            Message msg2 = myhandler.obtainMessage();
            msg2.what = 2;
            myhandler.sendMessage(msg2);//發送信息通知用戶客戶端已關閉

        }
    }

    private void displayToast(String s)//Toast方法
    {
        Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
    }

    private void setButtonOnStartState(boolean flag){//設置按鈕的狀態
        btnSend.setEnabled(!flag);
        btnStop.setEnabled(!flag);
        btnStart.setEnabled(flag);
        IPet.setEnabled(flag);
    }


class MyHandler extends Handler{//在主線程處理Handler傳回來的message
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
            String str = (String) msg.obj;
            System.out.println(msg.obj);
            tv.setText(str);//把讀到的內容更新到UI
            break;
            case 0:
                displayToast("連接成功");
                break;
            case 2:
                displayToast("服務器端已斷開");
                tv.setText(null);
                setButtonOnStartState(true);//設置按鍵狀態為可開始
                break;

        }

    }
}

}

PS:在操作Socket的輸入輸出流的時候一定不能close()關閉,一關閉的話就會導致整個Socket關閉,這里搞了我好久。

效果圖:

操作:首先服務端點建立按鈕,建立ServerSocket,然后客戶端輸入服務端的IP地址,點開始連接,提示連接成功之后可以發送消息,點取消連接之后回復到初始狀態

服務端界面(未連接):

客戶端界面(未連接):

服務端界面(連接):

客戶端界面(連接):

服務端界面(接收到消息):

客戶端界面(接收到消息):

 

4. 總結

  • 相信大家已經非常了解關于Socket的使用

  • 接下來,我會繼續介紹Android中其他相關知識,有興趣可以繼續關注 Carson_Ho的安卓開發筆記

 

 

來自:http://www.jianshu.com/p/089fb79e308b

 

 本文由用戶 ty391255 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!