Android 基于TCP的 Socket 編程實(shí)現(xiàn)(結(jié)合 okio)

前言

? ? ? ?兩個(gè)進(jìn)程如果要進(jìn)行通訊最基本的一個(gè)前提就是能夠唯一的標(biāo)識(shí)一個(gè)進(jìn)程掸屡,在本地進(jìn)程通訊中我們可以使用 PID 來(lái)唯一標(biāo)識(shí)一個(gè)進(jìn)程,但 PID 只在本地是唯一的规哲,網(wǎng)絡(luò)中兩個(gè)進(jìn)程 PID 沖突幾率很大,這時(shí)我們就需要通過(guò)其他手段來(lái)唯一標(biāo)識(shí)網(wǎng)絡(luò)中的進(jìn)程了,我們知道 IP 層的 ip 地址可以唯一標(biāo)示主機(jī)谣蠢,而 TCP 層協(xié)議和端口號(hào)結(jié)合就可以唯一標(biāo)示主機(jī)的一個(gè)進(jìn)程了。

能夠唯一標(biāo)示網(wǎng)絡(luò)中的進(jìn)程后,它們就可以利用 Socket 進(jìn)行通信了眉踱,什么是 Socket 呢挤忙?我們經(jīng)常把 Socket 翻譯為套接字(為什么翻譯成套接字),Socket 是在應(yīng)用層和傳輸層之間的一個(gè)抽象層谈喳,它把 TCP/IP 層復(fù)雜的操作抽象為幾個(gè)簡(jiǎn)單的接口供應(yīng)用層調(diào)用册烈,從而實(shí)現(xiàn)進(jìn)程在網(wǎng)絡(luò)中通信。


原理圖

相關(guān)類

? ? ? ?這里提到的 Socket 為廣義上的 Socket 編程婿禽,它可以基于 TCP 或者 UDP 實(shí)現(xiàn)赏僧,Java為 Socket 編程封裝了幾個(gè)重要的類,如下:

Socket (TCP)

? ? ? Socket 類實(shí)現(xiàn)了一個(gè)客戶端 Socket扭倾,作為兩臺(tái)機(jī)器通信的終端淀零,默認(rèn)采用的傳輸層協(xié)議為 TCP 可靠傳輸協(xié)議。Socket 類除了構(gòu)造函數(shù)返回一個(gè) socket 外膛壹,還提供了 connect , getOutputStream, getInputStream 和 close 方法驾中。connect 方法用于請(qǐng)求一個(gè) socket 連接,getOutputStream 用于獲得寫 socket的輸出流模聋,getInputStream 用于獲得讀 socket 的輸入流哀卫,close 方法用于關(guān)閉一個(gè)流。

DatagramSocket (UDP)

? ? ? ?DatagramSocket 類實(shí)現(xiàn)了一個(gè)發(fā)送和接收數(shù)據(jù)報(bào)的 socket撬槽,傳輸層協(xié)議使用 UDP此改,不能保證數(shù)據(jù)報(bào)的可靠傳輸。DataGramSocket 主要有 send, receive 和 close 三個(gè)方法侄柔。send 用于發(fā)送一個(gè)數(shù)據(jù)報(bào)共啃,Java 提供了 DatagramPacket 對(duì)象用來(lái)表達(dá)一個(gè)數(shù)據(jù)報(bào)。receive 用于接收一個(gè)數(shù)據(jù)報(bào)暂题,調(diào)用該方法后移剪,一直阻塞接收到直到數(shù)據(jù)報(bào)或者超時(shí)。close 是關(guān)閉一個(gè) socket薪者。

ServerSocket

? ? ? ?ServerSocket 類實(shí)現(xiàn)了一個(gè)服務(wù)器 socket纵苛,一個(gè)服務(wù)器 socke t等待客戶端網(wǎng)絡(luò)請(qǐng)求,然后基于這些請(qǐng)求執(zhí)行操作言津,并返回給請(qǐng)求者一個(gè)結(jié)果攻人。ServerSocket 提供了 bind、accept 和 close 三個(gè)方法悬槽。bind 方法為ServerSocket 綁定一個(gè)IP地址和端口怀吻,并開(kāi)始監(jiān)聽(tīng)該端口。accept 方法為 ServerSocket 接受請(qǐng)求并返回一個(gè) Socket 對(duì)象初婆,accept 方法調(diào)用后蓬坡,將一直阻塞直到有請(qǐng)求到達(dá)猿棉。close 方法關(guān)閉一個(gè) ServerSocket 對(duì)象。

SocketAddress

? ? ? ?SocketAddress 提供了一個(gè) socket 地址屑咳,不關(guān)心傳輸層協(xié)議萨赁。這是一個(gè)虛類,由子類來(lái)具體實(shí)現(xiàn)功能兆龙、綁定傳輸協(xié)議杖爽。它提供了一個(gè)不可變的對(duì)象,被 socket 用來(lái)綁定详瑞、連接或者返回?cái)?shù)值掂林。

InetSocketAddress

? ? ? ?InetSocketAddress 實(shí)現(xiàn)了IP地址的 SocketAddress,也就是有 IP 地址和端口號(hào)表達(dá) Socket 地址坝橡。如果不制定具體的 IP 地址和端口號(hào)泻帮,那么 IP 地址默認(rèn)為本機(jī)地址,端口號(hào)隨機(jī)選擇一個(gè)计寇。

DatagramPacket(UDP)

? ? ? ? DatagramSocket 是面向數(shù)據(jù)報(bào) socket 通信的一個(gè)可選通道锣杂。數(shù)據(jù)報(bào)通道不是對(duì)網(wǎng)絡(luò)數(shù)據(jù)報(bào) socket 通信的完全抽象。socket通信的控制由DatagramSocket 對(duì)象實(shí)現(xiàn)番宁。DatagramPacket 需要與 DatagramSocket 配合使用才能完成基于數(shù)據(jù)報(bào)的 socket 通信元莫。

基于TCP的 Socket

? ? ? ?基于 TCP 的 Socket可以實(shí)現(xiàn)客戶端—服務(wù)器間的雙向?qū)崟r(shí)通信。上面提到的 java.NET包中定義的兩個(gè)類 Socket 和 ServerSocket蝶押,分別用來(lái)實(shí)現(xiàn)雙向連接的 client 和 server 端踱蠢。


通信模型

實(shí)現(xiàn)

客戶端連接:demo


android端效果

客戶端發(fā)送:消息給服務(wù)端


向服務(wù)端發(fā)數(shù)據(jù)

服務(wù)端代碼:

'''

public class SocketTest {

? ? ? ? ? private static final int PORT =9999;

? ? ? ? ? private List mList =newArrayList();

? ? ? ? ? private ServerSocket server =null;

? ? ? ? ? private ExecutorService mExecutorService =null;

? ? ? ? ? private String receiveMsg;

? ? ? ? ? private String sendMsg;

? ? ? ? ? public static void main(String[] args) {

? ? ? ? ? ? ? ? ? ? ? newSocketTest();

? ? ? ? ? }

? ? ? ? ?public Socket Test() {

? ? ? ? ? ? ? ? ? ?try{

? ? ? ? ? ? ? ? ? ? ? ? ?server =newServerSocket(PORT);

? ? ? ? ? ? ? ? ? ? ? ? ?mExecutorService = Executors.newCachedThreadPool();

? ? ? ? ? ? ? ? ? ? ? ? ?System.out.println("服務(wù)器已啟動(dòng)...");

? ? ? ? ? ? ? ? ? ? ? ? ?Socket client =null;

? ? ? ? ? ? ? ? ? ? ? ? ?while(true) {

? ? ? ? ? ? ? ? ? ? ? ? ?client = server.accept();

? ? ? ? ? ? ? ? ? ? ? ? ?mList.add(client);

? ? ? ? ? ? ? ? ? ? ? ? ?mExecutorService.execute(new Service(client));

? ? ? ? ? ? ? ? ? ? ?}

? ? ? ? ? ? ?}catch(Exception e) {

? ? ? ? ? ? ? ? ?e.printStackTrace();

? ? ? ? ? ? }

? ? ? }

class Service implements Runnable {

private Socket socket;

private BufferedReader in=null;

private PrintWriter printWriter=null;

public Service(Socket socket) {

this.socket = socket;try{

printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter( socket.getOutputStream(),"UTF-8")),true);

in=new BufferedReader(new InputStreamReader(

socket.getInputStream(),"UTF-8"));

printWriter.println("成功連接服務(wù)器"+"(服務(wù)器發(fā)送)");

System.out.println("成功連接服務(wù)器");

}catch(IOException e) {

e.printStackTrace();

}

}

@Override

public void run() {

try{

while(true) {

if((receiveMsg =in.readLine())!=null) {

System.out.println("receiveMsg:"+receiveMsg);

if(receiveMsg.equals("0")) {

System.out.println("客戶端請(qǐng)求斷開(kāi)連接");

printWriter.println("服務(wù)端斷開(kāi)連接"+"(服務(wù)器發(fā)送)");

mList.remove(socket);

in.close();

socket.close();

break;

}else{

sendMsg ="我已接收:"+ receiveMsg +"(服務(wù)器發(fā)送)";

printWriter.println(sendMsg);

}

}

}

}catch(Exception e) {

e.printStackTrace();

}

}

}

}

'''

服務(wù)端使用線程池實(shí)現(xiàn)多客戶端連接,server.accept() 表示等待客戶端連接棋电,當(dāng)有客戶端連接時(shí)新建一個(gè)線程去處理茎截,其中涉及到的方法之前都提到過(guò),不再贅述赶盔。

客戶端代碼:

'''

public class SocketActivity extends AppCompatActivity{

private EditText mEditText;

private TextView mTextView;

private static final String TAG ="TAG";

private static final String HOST ="192.168.23.1";

private static final int PORT =9999;

private PrintWriter printWriter;

private BufferedReader in;

private ExecutorService mExecutorService =null;

private String receiveMsg;

@Override

protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);

setContentView(R.layout.activity_socket);

mEditText = (EditText) findViewById(R.id.editText);

mTextView = (TextView) findViewById(R.id.textView);

mExecutorService = Executors.newCachedThreadPool();

}

public void connect(View view) {

mExecutorService.execute(newconnectService());

}

public void send(View view) {

String sendMsg = mEditText.getText().toString();

mExecutorService.execute(newsendService(sendMsg));

}

public void disconnect(View view) {

mExecutorService.execute(newsendService("0"));

}

private class sendService implements Runnable{

privateString msg;

sendService(String msg) {

this.msg = msg;

}

@Override

public void run() {

printWriter.println(this.msg);

}

}

private class connectService implements Runnable{

@Override

public void run() {try{

Socket socket =newSocket(HOST, PORT);

socket.setSoTimeout(60000);

printWriter =newPrintWriter(new BufferedWriter(new OutputStreamWriter(

socket.getOutputStream(),"UTF-8")),true);

in =new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));

receiveMsg();

}catch(Exception e) {

Log.e(TAG, ("connectService:"+ e.getMessage()));

}

}

}

private void receiveMsg() {

try{

while(true) {

if((receiveMsg = in.readLine()) !=null) {

Log.d(TAG,"receiveMsg:"+ receiveMsg);

runOnUiThread(new Runnable() {

@Override

public void run() {

mTextView.setText(receiveMsg +"\n\n"+ mTextView.getText());

}

});

}

}

}catch(IOException e) {

Log.e(TAG,"receiveMsg: ");

e.printStackTrace();

}

}

}

'''

客戶端同樣使用了線程池進(jìn)行管理企锌,把連接和發(fā)送分割為兩個(gè) Runnable 易于調(diào)用,當(dāng)發(fā)送 “0” 且服務(wù)端收到時(shí)關(guān)閉連接于未。

okio 實(shí)現(xiàn)

到這里一個(gè)簡(jiǎn)單的 Socket 通信就完成了撕攒,其中對(duì)于 Socket 的信息流使用的是 java.io,之前學(xué)習(xí) okio 時(shí)烘浦,了解到 okio 可以替代 java.io抖坪,okio是一個(gè)由square公司開(kāi)發(fā)的開(kāi)源庫(kù),它彌補(bǔ)了Java.io和java.nio的不足谎倔,能夠更方便快速的讀取柳击、存儲(chǔ)和處理數(shù)據(jù)(了解更多請(qǐng)點(diǎn)擊Okio源碼分析),下面就嘗試用 okio 替換 java.io片习。

直接上代碼:

服務(wù)端代碼:

'''

public class SocketTest {

private static final int PORT =9999;

private List mList =newArrayList();

private ServerSocket server =null;

private ExecutorService mExecutorService =null;

private String receiveMsg;

private String sendMsg;

public static void main(String[] args) {

newSocketTest();

}

public SocketTest() {

try{

server =new ServerSocket(PORT);

mExecutorService = Executors.newCachedThreadPool();

System.out.println("服務(wù)器已啟動(dòng)...");

Socket client =null;

while(true) {

client = server.accept();

mList.add(client);

mExecutorService.execute(new Service(client));

}

}catch(Exception e) {

e.printStackTrace();

}

}

class Service implements Runnable {

private Socket socket;

private BufferedSink mSink;

private BufferedSource mSource;

public Service(Socket socket) {

this.socket = socket;

try{

mSink = Okio.buffer(Okio.sink(socket));

mSource = Okio.buffer(Okio.source(socket));

sendMsg="成功連接服務(wù)器"+"(服務(wù)器發(fā)送)";

mSink.writeUtf8(sendMsg+"\n");

mSink.flush();

System.out.println("成功連接服務(wù)器");

}catch(IOException e) {

e.printStackTrace();

}

}

@Override

public void run() {

try{

while(true) {

for(String receiveMsg; (receiveMsg = mSource

.readUtf8Line()) !=null;) {

System.out.println("receiveMsg:"+ receiveMsg);

if(receiveMsg.equals("0")) {

System.out.println("客戶端請(qǐng)求斷開(kāi)連接");

mSink.writeUtf8("服務(wù)端斷開(kāi)連接"+"(服務(wù)器發(fā)送)");

mSink.flush();

mList.remove(socket);

socket.close();

break;

}else{

sendMsg ="我已接收:"+ receiveMsg +"(服務(wù)器發(fā)送)";

mSink.writeUtf8(sendMsg+"\n");

mSink.flush();

}

}

}

}catch(Exception e) {

e.printStackTrace();

}

}

}

}

'''


客戶端代碼:

'''

public class SocketActivity extends AppCompatActivity{

private EditText mEditText;

private TextView mTextView;

private static final String TAG ="TAG";

private static final String HOST ="192.168.23.1";

private static final int PORT =9999;

private BufferedSink mSink;

private BufferedSource mSource;

private ExecutorService mExecutorService =null;

@Override

protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);

setContentView(R.layout.activity_socket);

mEditText = (EditText) findViewById(R.id.editText);

mTextView = (TextView) findViewById(R.id.textView);

mExecutorService = Executors.newCachedThreadPool();

}publicvoidconnect(View view) {

mExecutorService.execute(new connectService());

}

public void send(View view) {

String sendMsg = mEditText.getText().toString();

mExecutorService.execute(new sendService(sendMsg));

}

public void disconnect(View view) {

mExecutorService.execute(new sendService("0"));

}

private class sendService implements Runnable{

private String msg;

sendService(String msg) {

this.msg = msg;

}

@Override

public void run() {

try{

mSink.writeUtf8(this.msg+"\n");

mSink.flush();

}catch(IOException e) {

e.printStackTrace();

}

}

}

private class connectService implements Runnable{

@Override

public void run() {

try{

Socket socket =newSocket(HOST, PORT);

mSink = Okio.buffer(Okio.sink(socket));

mSource = Okio.buffer(Okio.source(socket));

receiveMsg();

}catch(Exception e) {

Log.e(TAG, ("connectService:"+ e.getMessage()));

}

}

}

private void receiveMsg() {

try{

while(true) {

for(String receiveMsg; (receiveMsg = mSource.readUtf8Line()) !=null; ) {

Log.d(TAG,"receiveMsg:"+ receiveMsg);finalString finalReceiveMsg = receiveMsg;

runOnUiThread(new Runnable() {

@Override

public void run() {

mTextView.setText(finalReceiveMsg +"\n\n"+ mTextView.getText());

}

});

}

}

}catch(IOException e) {

Log.e(TAG,"receiveMsg: ");

e.printStackTrace();

}

}

}

'''

這里有一個(gè)很坑的地方:

mSink.writeUtf8(this.msg+"\n");

mSink.flush();

起初沒(méi)有加 “\n” 時(shí)捌肴,調(diào)用 flush 方法后消息是無(wú)法發(fā)送成功的,除非調(diào)用 sink.close 方法后才會(huì)發(fā)送成功藕咏,但是我們不能每發(fā)送一次就 close 掉状知,對(duì)比 printWriter.println 方法,嘗試加上一個(gè)換行符孽查,果真發(fā)送成功饥悴。

總結(jié)

android有兩種通信方式,一種是常用的基于 HTTP 協(xié)議方式盲再,另一種就是基于 TCP/UDP 協(xié)議的 Socket 方式西设。雖然大部分需求都可通過(guò) HTTP 實(shí)現(xiàn),實(shí)現(xiàn)起來(lái)也較為簡(jiǎn)單答朋,但某些情景下需要使用 Socket 方式贷揽,這時(shí)永遠(yuǎn)不要放棄去使用最佳的工具來(lái)解決問(wèn)題的機(jī)會(huì)。本文主要通過(guò) Socket 實(shí)現(xiàn)了 Android 基于 TCP 協(xié)議的通信梦碗,后面將 Socket 的輸入輸出流處理由 java.io 替換為 Okio 實(shí)現(xiàn)禽绪,雖然說(shuō) Okio 彌補(bǔ)了Java.io和 java.nio 的不足,能夠更方便快速的讀取洪规、存儲(chǔ)和處理數(shù)據(jù)印屁,但是實(shí)際性能并沒(méi)測(cè)試過(guò),這里主要是為了復(fù)習(xí)一下 Okio 的使用斩例,另外就是在Okio源碼分析中沒(méi)有涉及到 Socket 的內(nèi)容雄人,這里正好填補(bǔ)一下知識(shí)漏洞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末念赶,一起剝皮案震驚了整個(gè)濱河市础钠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晶乔,老刑警劉巖珍坊,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異正罢,居然都是意外死亡阵漏,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門翻具,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)履怯,“玉大人,你說(shuō)我怎么就攤上這事裆泳√局蓿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵工禾,是天一觀的道長(zhǎng)运提。 經(jīng)常有香客問(wèn)我蝗柔,道長(zhǎng),這世上最難降的妖魔是什么民泵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任癣丧,我火速辦了婚禮,結(jié)果婚禮上栈妆,老公的妹妹穿的比我還像新娘胁编。我一直安慰自己,他們只是感情好鳞尔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布嬉橙。 她就那樣靜靜地躺著,像睡著了一般寥假。 火紅的嫁衣襯著肌膚如雪市框。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天昧旨,我揣著相機(jī)與錄音拾给,去河邊找鬼。 笑死兔沃,一個(gè)胖子當(dāng)著我的面吹牛蒋得,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乒疏,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼额衙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了怕吴?” 一聲冷哼從身側(cè)響起窍侧,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎转绷,沒(méi)想到半個(gè)月后伟件,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡议经,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年斧账,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片煞肾。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咧织,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出籍救,到底是詐尸還是另有隱情习绢,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布蝙昙,位于F島的核電站闪萄,受9級(jí)特大地震影響梧却,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桃煎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一篮幢、第九天 我趴在偏房一處隱蔽的房頂上張望大刊。 院中可真熱鬧为迈,春花似錦、人聲如沸缺菌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)伴郁。三九已至耿战,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間焊傅,已是汗流浹背剂陡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狐胎,地道東北人鸭栖。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像握巢,于是被迫代替她去往敵國(guó)和親晕鹊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容