Android的IPC機制(七)—— Socket的原理簡析與使用
綜述
在前面的幾篇文章中,我們介紹了許多在Android中有關程式間通訊的方式,但都是在一個裝置上進行的程式間通訊,而這時候我們兩個應用在不同的裝置上的時候,在這個時候我們就不能通過前方介紹的那些方法來解決了。但是我們通過網路進行通訊來處理這個問題。今天就來介紹一下Android中網路通訊的其中一種方式——Socket。Socket翻譯為中文為套接字,而現在套接字也成為了作業系統中的一部分。下面我們就來看一下如何使用這個套接字的。
TCP/IP介紹
這裡有一點需要說明一下,在Internet所使用的各種協議中,最重要的和最著名的就是TCP和IP兩個協議。而我們現在經常提到的TCP/IP並不一定單指TCP和IP這兩個具體的協議,而往往表示Internet所使用的整個TCP/IP協議族。
TCP/IP的體系結構
TCP/IP協議可以為各式各樣的應用提供服務,同時TCP/IP協議也允許IP協議在各式各樣的網路構成的網際網路上執行。TCP/IP協議是一個四層的體系結構,它包含應用層、傳輸層、網路層、網路介面層。在傳輸層中主要使用的有兩種協議:TCP(Transmission Control Protocol,傳輸控制協議)和UDP(User Datagram Protocol,使用者資料包協議)。下面是TCP/IP體系結構示意圖。
傳輸控制協議TCP
TCP是TCP/IP體系中非常複雜的一個協議,在這裡我們簡單看一下TCP協議的特點,對於TCP協議的詳細內容,可以檢視一些計算機網路的相關書籍。
1. TCP是面向連線傳輸協議,也就是說在我們的應用程式在使用TCP協議之前,必須先建立起TCP連線。在傳送資料完畢後,必須釋放已經建立的TCP連線。就像我們打電話一樣,打電話之前首先需要撥號進行建立連線,等通話結束後再結束通話釋放連線。
2. 每一條TCP連線只能有兩個端點,每一條TCP連線只能是點對點的。
3. TCP提供了一個可靠交付的服務,也就是說通過TCP連線傳送的資料,無差錯,不丟失,不重複,並且按序到達。
4. TCP提供全雙工通訊,它允許通訊雙方的應用程式在任何時候都能夠傳送資料。
5. TCP通訊中是面向位元組流的,其中的“流”指的是流入到程式或從程式流出的位元組序列。
使用者資料包協議UDP
使用者資料包協議UDP只是在IP的資料服務上增加了很少的一點功能(複用、分用的功能以及差錯檢測的功能)。在這裡簡單說一下UDP的特點。
1. UDP是無連線,也就是在傳送資料之前是不需要建立連線的,也就減少了開銷和傳送資料之間的延時。
2. UDP它只能是盡最大努力地交付,也就是不能夠保證可靠交付。
3. UDP它是面向報文的。傳送方的UDP對應用程式交下來的報文,再新增首部後就向下交付給IP層。
4. UDP它沒有擁塞控制,也就是說在網路出現擁塞的情況下不會使源主機的傳送速率降低。
5. UDP支援一對一,一對多,多對一和多對多的互動通訊。
Socket在TCP/IP中的作用
Socket是工作於TCP/IP協議中應用層和傳輸層之間的一種抽象(不屬於應用層也不屬於傳輸層)。在Android系統中,它可以分為流套接字(streamsocket)和資料包套接字(datagramsocket)。而Socket中的流套接字將TCP協議作為其端對端協議,提供了一個可信賴的位元組流服務;資料包套接字使用UDP協議,提供資料打包傳送服務。
在網路程式設計的時候,我們經常把Socket作為應用程式和傳輸層協議之間的介面。在下面圖中表示了這樣一個概念。在圖中我們假定了運輸層使用的是TCP協議(如果使用的是UDP協議,情況也是類似的,只是UDP是無連線的通訊的兩端依然可以用兩個套接字來標誌)。並且現在套接字已經成為作業系統核心的一部分。
不過有一點我們要注意,在套接字以上的程式是受應用程式控制的,而在套接字以下的的傳輸層協議軟體則是由計算機作業系統控制。因此,只要我們的應用程式使用TCP/IP協議進行通訊,它就必須通過Socket與作業系統互動並請求服務。從這裡可以看出來,我們對Socket以上的應用程式具有完全的控制,但對Socket以下的傳輸層卻只有很少的控制。例如,我們可以選擇傳輸層協議(TCP或UDP)和一些傳輸層的引數(如最大快取空間和最大報文長度)。
Socket使用案例
在這裡我們選擇傳輸層協議為TCP協議,也就是我們將使用流套接字作為例子進行舉例說明。現在我們現在做一個聊天室功能。在這裡我們建立兩個應用程式,分別執行在兩個不同的裝置上。首先看一下效果圖。
演示
客戶端程式碼
package com.example.ljd.socketclient;
import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.sql.Date;
import java.text.SimpleDateFormat;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends AppCompatActivity{
private static final int RECEIVE_NEW_MESSAGE = 1;
private static final int SOCKET_CONNECT_SUCCESS = 2;
private static final int SOCKET_CONNECT_FAIL = 3;
@Bind(R.id.msg_edit_text)
EditText mMessageEditText;
@Bind(R.id.show_linear)
LinearLayout mShowLinear;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
private boolean mIsConnectServer = false;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case RECEIVE_NEW_MESSAGE:
TextView textView = new TextView(MainActivity.this);
textView.setText((String)msg.obj);
mShowLinear.addView(textView);
break;
case SOCKET_CONNECT_SUCCESS:
Toast.makeText(MainActivity.this,"連線服務端成功",Toast.LENGTH_SHORT).show();
break;
case SOCKET_CONNECT_FAIL:
Toast.makeText(MainActivity.this,"連線服務端失敗,請重新嘗試",Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@Override
protected void onDestroy() {
ButterKnife.unbind(this);
disConnectServer();
super.onDestroy();
}
@OnClick({R.id.send_btn,R.id.connect_btn,R.id.disconnect_btn})
public void onClickButton(View v) {
switch (v.getId()){
case R.id.send_btn:
sendMessageToServer();
break;
case R.id.connect_btn:
new Thread() {
@Override
public void run() {
connectServer();
}
}.start();
break;
case R.id.disconnect_btn:
Toast.makeText(MainActivity.this,"已經斷開連線",Toast.LENGTH_SHORT).show();
disConnectServer();
break;
}
}
private String getTime(long time) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
private void connectServer() {
if (mIsConnectServer)
return;
int count = 0;
while (mClientSocket == null) {
try {
mClientSocket = new Socket("10.10.14.160", 8088);
mPrintWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(mClientSocket.getOutputStream())), true);
mIsConnectServer = true;
mHandler.obtainMessage(SOCKET_CONNECT_SUCCESS).sendToTarget();
} catch (IOException e) {
SystemClock.sleep(1000);
count++;
if (count == 5){
mHandler.obtainMessage(SOCKET_CONNECT_FAIL).sendToTarget();
return;
}
}
}
try {
// 接收伺服器端的訊息
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
mClientSocket.getInputStream()));
while (!MainActivity.this.isFinishing()) {
String msg = bufferedReader.readLine();
if (msg != null) {
String time = getTime(System.currentTimeMillis());
final String showedMsg = "server " + time + ":" + msg;
mHandler.obtainMessage(RECEIVE_NEW_MESSAGE, showedMsg)
.sendToTarget();
}
}
mPrintWriter.close();
bufferedReader.close();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void disConnectServer(){
mIsConnectServer = false;
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
mClientSocket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void sendMessageToServer(){
if (!mIsConnectServer){
Toast.makeText(this,"沒有連線上服務端,請重新連線",Toast.LENGTH_SHORT).show();
return;
}
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
mPrintWriter.println(msg);
mMessageEditText.setText("");
String time = getTime(System.currentTimeMillis());
final String showedMsg = "client " + time + ":" + msg;
TextView textView = new TextView(this);
textView.setText(showedMsg);
mShowLinear.addView(textView);
}
}
}
- 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
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
客戶端佈局程式碼
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp"
>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<LinearLayout
android:id="@+id/show_linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/msg_edit_text"
android:layout_width="0dp"
android:layout_gravity="bottom"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
<Button
android:id="@+id/send_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="傳送" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/connect_btn"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="連線"/>
<Button
android:id="@+id/disconnect_btn"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="斷開連線"/>
</LinearLayout>
</LinearLayout>
- 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
服務端程式碼
package com.example.ljd.socketserver;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.Date;
import java.text.SimpleDateFormat;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends AppCompatActivity{
@Bind(R.id.show_linear)
LinearLayout mShowLinear;
@Bind(R.id.msg_edit_text)
EditText mMessageEditText;
private ServerSocket mServerSocket;
private PrintWriter mPrintWriter;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0){
TextView textView = new TextView(MainActivity.this);
textView.setText((String)msg.obj);
mShowLinear.addView(textView);
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
try {
mServerSocket = new ServerSocket(8088);
} catch (IOException e) {
e.printStackTrace();
}
new Thread(new AcceptClient()).start();
}
@Override
public void onDestroy() {
ButterKnife.unbind(this);
if (mServerSocket != null){
try {
mServerSocket.close();
mServerSocket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
@OnClick(R.id.send_btn)
public void onClickButton() {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
//將訊息寫入到流中
mPrintWriter.println(msg);
mMessageEditText.setText("");
String time = getTime(System.currentTimeMillis());
final String showedMsg = "server " + time + ":" + msg;
TextView textView = new TextView(this);
textView.setText(showedMsg);
mShowLinear.addView(textView);
}
}
private String getTime(long time) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
class AcceptClient implements Runnable{
@Override
public void run() {
try {
Socket clientSocket = null;
while (clientSocket == null){
clientSocket = mServerSocket.accept();
mPrintWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(clientSocket.getOutputStream())), true);
}
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
clientSocket.getInputStream()));
while (!MainActivity.this.isFinishing()) {
//讀取客戶端發來的訊息
String msg = bufferedReader.readLine();
if (msg != null) {
String time = getTime(System.currentTimeMillis());
final String showedMsg = "client " + time + ":" + msg;
mHandler.obtainMessage(0, showedMsg)
.sendToTarget();
}
}
bufferedReader.close();
clientSocket.close();
mPrintWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 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
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
服務端佈局程式碼
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp"
>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<LinearLayout
android:id="@+id/show_linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/msg_edit_text"
android:layout_width="0dp"
android:layout_gravity="bottom"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
<Button
android:id="@+id/send_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="傳送" />
</LinearLayout>
</LinearLayout>
- 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
新增許可權
在客戶端與服務端應用中我們還需要新增一些許可權
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
- 1
- 2
總結
在網路通訊中我們除了socket我們最常用的還有一種方式那就是http通訊,而http連線採用的是”請求—響應方式“,也就是說只有當客戶端發出請求時,服務端才能夠向客戶端返回資料。在這裡我們就不在詳細介紹。對於Android的IPC機制就說到這裡了,當然還有其他方式可以進行跨程式通訊,我們可以自行研究了。
原始碼下載
相關文章
- Android IPC機制(三):淺談Binder的使用Android
- Android IPC 機制分析Android
- Android Handler與Looper原理簡析AndroidOOP
- 詳解 Android 中的 IPC 機制:基礎篇Android
- 02.Android之IPC機制問題Android
- Android快取機制-LRU cache原理與用法Android快取
- Binder通訊機制與IPC通訊.md
- 簡析Windows訊息機制Windows
- 藉助 AIDL 理解 Android Binder 機制——AIDL 的使用和原理分析AIAndroid
- Android 流暢度檢測原理簡析Android
- Android中mmap原理及應用簡析Android
- 詳解Java Socket的工作機制Java
- ObjC中Category的原理簡析OBJGo
- Android10_原理機制系列_事件傳遞機制Android事件
- Android RollBack機制實現原理剖析Android
- Netty之DefaultAttributeMap與AttributeKey的機制和原理Netty
- 學習筆記(2)IPC機制筆記
- Android Socket連線,使用Socket進行通訊(Android)Android
- Android Handler MessageQueue Looper 訊息機制原理AndroidOOP
- Android Handler機制之Message的傳送與取出Android
- 遊戲中的牛頓力學,簡析遊戲物理機制遊戲
- 多型的機制原理多型
- 淺析反向代理的原理與作用
- 簡單案例淺析JS執行緒機制JS執行緒
- 簡析網路競技遊戲匹配機制遊戲
- Android 3G/4G流量上網原理簡析Android
- Android V1及V2簽名原理簡析Android
- Android-Handler訊息機制實現原理Android
- 淺析JavaScript的事件迴圈機制JavaScript事件
- JavaScript的事件迴圈機制淺析JavaScript事件
- socket.io client + socketio-netty server簡析clientNettyServer
- IO多路複用與epoll機制淺析
- Android事件分發機制簡單理解Android事件
- Android進階:七、Retrofit2.0原理解析之最簡流程Android
- Android IPC 之AIDLAndroidAI
- 短文1:使用 php-socket 簡述 http 伺服器原理PHPHTTP伺服器
- 短文1.1:使用 php-socket 簡述 http 伺服器原理PHPHTTP伺服器
- springMVC 的工作原理和機制SpringMVC
- Android LeakCanary的使用和原理Android