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

前言
-
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連接建立完成 = 可以開始發送數據
- 三次握手期間任何一次未收到對面回復都要重發。
- 最后一個確認報文段發送完畢以后,客戶端和服務器端都進入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(可以雙向互發信息)
- 服務器端和客戶端都是在Android實現
- 需要兩臺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