Android Socket通信實(shí)現(xiàn)簡(jiǎn)單聊天室
socket通信是基于底層TCP/IP協(xié)議實(shí)現(xiàn)的。這種服務(wù)端不需要任何的配置文件和tomcat就可以完成服務(wù)端的發(fā)布,使用純java代碼實(shí)現(xiàn)通信。socket是對(duì)TCP/IP的封裝調(diào)用,本身并不是一種協(xié)議,我們通過(guò)socket來(lái)調(diào)用協(xié)議來(lái)跟服務(wù)端進(jìn)行通信和數(shù)據(jù)的傳輸。socket就像客戶(hù)端與服務(wù)端之間的一條信息通道,每一個(gè)不同的客戶(hù)端都會(huì)建立一個(gè)獨(dú)立的socket,雙方都沒(méi)有關(guān)閉連接的話(huà),連接—也就是建立好的這條socket通道將一直保持,服務(wù)端要跟那一個(gè)客戶(hù)端通信只需要找到對(duì)應(yīng)的socket對(duì)象就可以進(jìn)行數(shù)據(jù)傳遞。
第一次握手:客戶(hù)端發(fā)送syn包(syn=j)到服務(wù)器,并進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器確認(rèn);
第二次握手:服務(wù)器收到syn包,必須確認(rèn)客戶(hù)的SYN(ack=j+1),同時(shí)自己也發(fā)送一個(gè)SYN包(syn=k),即SYN+ACK包,此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài);
第三次握手:客戶(hù)端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認(rèn)包ACK(ack=k+1),此包發(fā)送完畢,客戶(hù)端和服務(wù)器進(jìn)入ESTABLISHED狀態(tài),完成三次握手。
一. 服務(wù)端:在客戶(hù)端跟服務(wù)端通信之前,服務(wù)端必須先開(kāi)啟。首先來(lái)看一下服務(wù)端Socket的編寫(xiě)吧。服務(wù)端就是一個(gè)簡(jiǎn)單的java項(xiàng)目,由于聊天室可能會(huì)有多個(gè)客戶(hù)端同時(shí)連接并發(fā)送消息,我們這里使用線(xiàn)程池來(lái)處理客戶(hù)端的請(qǐng)求。
List<Socket> list = new ArrayList<Socket>(); ExecutorService executorService; BufferedReader br; private static final int PORT = 12345; private static final int POOL_SIZE = 5 ; public Socket_Server() throws IOException { executorService = Executors.newFixedThreadPool(POOL_SIZE); ServerSocket serverSocket = new ServerSocket(PORT); System.out.println(serverSocket.getInetAddress().getHostAddress() + ":服務(wù)端就緒。"); Socket client = null; while (true) {//為每一個(gè)連接到服務(wù)器的客戶(hù)端分配一個(gè)線(xiàn)程進(jìn)行消息的接收和發(fā)送 client = serverSocket.accept(); list.add(client); executorService.execute(new Service(client)); } }
首先我們創(chuàng)建了一個(gè)大小為5的固定大小線(xiàn)程池,并創(chuàng)建端口號(hào)為12345的服務(wù)端socket接收客戶(hù)端請(qǐng)求,通過(guò)一個(gè)while循環(huán)不斷輪詢(xún)來(lái)自服務(wù)端的連接請(qǐng)求,在while循環(huán)里面調(diào)用了serverSocket.accept();是線(xiàn)程進(jìn)入阻塞狀態(tài),也就是說(shuō)在沒(méi)有接收到客戶(hù)端的請(qǐng)求時(shí),程序?qū)⒁恢蓖A粼谶@里,當(dāng)有客戶(hù)端連接服務(wù)端是,代碼開(kāi)始往下走,我們把接收到的客戶(hù)端socket放入list里面,這樣我們就把所有連接到服務(wù)端的socket保存下來(lái)了,這樣就使得我們可以隨時(shí)對(duì)任一客戶(hù)端進(jìn)行數(shù)據(jù)傳遞。之后就是線(xiàn)程池調(diào)用execute執(zhí)行一個(gè)線(xiàn)程,把連接過(guò)來(lái)的socket作為參數(shù)傳進(jìn)去。接下來(lái)分析下service的內(nèi)容:
class Service implements Runnable { Socket client; BufferedReader br; String msg = ""; public Service(Socket client) { this.client = client; try { br = new BufferedReader(new InputStreamReader( client.getInputStream())); msg = "用戶(hù):" + client.getInetAddress() + "加入了聊天室,當(dāng)前人數(shù):" + list.size(); sendMsg(); } catch (Exception e) { e.printStackTrace(); } } public void run() { try { while (true) { if ((msg = br.readLine()) != null) { if(msg.equals("bye")){ list.remove(this.client) ; br.close() ; msg = "用戶(hù):" + client.getInetAddress() + "離開(kāi)了聊天室,當(dāng)前人數(shù):" + list.size(); sendMsg() ; client.close() ; break ; }else{ msg = client.getInetAddress() + "說(shuō):" + msg; sendMsg() ; } } } } catch (Exception e) { e.printStackTrace(); } } public void sendMsg() {//為每一個(gè)用戶(hù)發(fā)送這個(gè)消息:msg PrintWriter pw; System.out.println(msg); for (Socket client : list) { try { pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter( client.getOutputStream()))); pw.println(msg); pw.flush() ; } catch (Exception e) { e.printStackTrace(); } } } }
在service的構(gòu)造方法中使用了作為參數(shù)傳進(jìn)來(lái)的socket,在里面我們通過(guò)這個(gè)socket獲取輸入流包裝成一個(gè)BufferedReader,br = new BufferedReader(new InputStreamReader(client.getInputStream()));這里我們是主要是針對(duì)聊天,所以使用的是字符流進(jìn)行數(shù)據(jù)的傳輸,這個(gè)類(lèi)里面聲明了一個(gè)成員變量msg,通過(guò)這個(gè)變量來(lái)給每個(gè)客戶(hù)端發(fā)送信息。下面看下sendMsg方法:
public void sendMsg() {//為每一個(gè)用戶(hù)發(fā)送這個(gè)消息:msg PrintWriter pw; System.out.println(msg); for (Socket client : list) { try { pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter( client.getOutputStream()))); pw.println(msg); pw.flush() ; } catch (Exception e) { e.printStackTrace(); } } }
這里我們通過(guò)遍歷list里面的每個(gè)socket獲得它的的輸出流并且包裝成PrintWriter 向客戶(hù)端發(fā)送信息, pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())));向每一個(gè)客戶(hù)端發(fā)送成員變量msg內(nèi)容,在PrintWriter調(diào)用print之后一定要調(diào)用flush刷新輸出流進(jìn)行數(shù)據(jù)的傳遞,否則客戶(hù)端無(wú)法接收到服務(wù)端發(fā)送的數(shù)據(jù)。接下來(lái)看這個(gè)類(lèi)的住方法 run:
public void run() { try { while (true) { if ((msg = br.readLine()) != null) { if(msg.equals("bye")){ list.remove(this.client) ; br.close() ; msg = "用戶(hù):" + client.getInetAddress() + "離開(kāi)了聊天室,當(dāng)前人數(shù):" + list.size(); sendMsg() ; client.close() ; break ; }else{ msg = client.getInetAddress() + "說(shuō):" + msg; sendMsg() ; } } } } catch (Exception e) { e.printStackTrace(); } }
與前面類(lèi)似,也是通過(guò)一個(gè)while進(jìn)行無(wú)限循環(huán)進(jìn)行讀取socket的輸入流,如果內(nèi)容不為空就調(diào)用sendmsg對(duì)每一個(gè)客戶(hù)端進(jìn)行信息發(fā)送,有個(gè)小小的處理就是如果發(fā)送過(guò)來(lái)的信息是bye的時(shí)候就斷開(kāi)對(duì)應(yīng)socket的鏈接,退出聊天室。以上是對(duì)服務(wù)端的分析,接下來(lái)我們來(lái)看Android客戶(hù)端。
二. 客戶(hù)端:客戶(hù)端基本與服務(wù)端一樣,我們直接上代碼吧。
//首先還是貼出成員變量 private Button send; private EditText edt_input; private TextView txt_content; private static final String SERVER_PATH = "172.16.10.18"; private static final int PORT = 12345; private Socket client; private BufferedReader br; private PrintWriter pw; private StringBuffer content = new StringBuffer(); private void initView() { send = (Button) findViewById(R.id.send); edt_input = (EditText) findViewById(R.id.input); txt_content = (TextView) findViewById(R.id.chat_content); // --------發(fā)起網(wǎng)絡(luò)連接----- new Thread() { public void run() { try { client = new Socket(SERVER_PATH, PORT); br = new BufferedReader(new InputStreamReader( client.getInputStream())); pw = new PrintWriter(new BufferedWriter( new OutputStreamWriter(client.getOutputStream()))); } catch (Exception e) { e.printStackTrace(); } } }.start(); send.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (client != null && client.isConnected() && !client.isOutputShutdown()) { String input = edt_input.getText().toString(); pw.println(input); pw.flush(); ((EditText)findViewById(R.id.input)).setText(""); } } }); new Thread(this).start(); }
首先還是傳統(tǒng)的new一個(gè)thread來(lái)建立與服務(wù)端的連接,因?yàn)橹骶€(xiàn)程不能訪(fǎng)問(wèn)網(wǎng)絡(luò),由于我們客戶(hù)端肯定是只有當(dāng)前這一個(gè)socket的,所以只有一個(gè)線(xiàn)程,不用跟服務(wù)端一樣使用線(xiàn)程池了。連接一旦建立,獲取socket的輸入輸出流來(lái)包裝成對(duì)應(yīng)的BufferedReader和PrintWriter:br = new BufferedReader(new InputStreamReader(client.getInputStream()));pw = new PrintWriter(new BufferedWriter( new OutputStreamWriter(client.getOutputStream())));由于這里只會(huì)使用一個(gè)socket,所以這里的相關(guān)變量都可以使用成員變量。然后走下來(lái)就是對(duì)send按鈕的監(jiān)聽(tīng),點(diǎn)擊發(fā)送的話(huà),把內(nèi)容發(fā)送給服務(wù)端,服務(wù)端接收到之后發(fā)送給每一個(gè)保持著鏈接的客戶(hù)端。這個(gè)activity也是實(shí)現(xiàn)了runnable接口的,接下來(lái)看run方法:
public void run() { while (true) { if (client != null && client.isConnected() && !client.isInputShutdown()) { try { String response; if ((response = br.readLine()) != null) { content.append(response + "\n"); mHandler.sendEmptyMessage(UPDATE_CONTENT); } } catch (Exception e) { e.printStackTrace(); } } } }
這里跟服務(wù)端一樣,通過(guò)一個(gè)while無(wú)限循環(huán)讀取來(lái)自服務(wù)端的信息,一旦讀取到信息之后就通過(guò)handler從子線(xiàn)程發(fā)送消息到主線(xiàn)程,主線(xiàn)程進(jìn)行數(shù)據(jù)的更新,其實(shí)就是向顯示聊天室內(nèi)容的textview追加聊天內(nèi)容并且setText上去:
Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case UPDATE_CONTENT: txt_content.append(content); break; default: break; } }; };
總體來(lái)說(shuō)客戶(hù)端還是比服務(wù)端容易點(diǎn),沒(méi)有涉及到并發(fā),只需要做當(dāng)前這個(gè)客戶(hù)端對(duì)應(yīng)的socket通信就行了。以上就是對(duì)socket的一個(gè)簡(jiǎn)單總結(jié)和在安卓里面的簡(jiǎn)單應(yīng)用實(shí)現(xiàn)聊天室功能。效果圖:
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android Socket實(shí)現(xiàn)多個(gè)客戶(hù)端聊天布局
- android使用Socket通信實(shí)現(xiàn)多人聊天應(yīng)用
- Android使用Websocket實(shí)現(xiàn)聊天室
- 基于Socket.IO實(shí)現(xiàn)Android聊天功能代碼示例
- android Socket實(shí)現(xiàn)簡(jiǎn)單聊天小程序
- android socket聊天室功能實(shí)現(xiàn)
- android Socket實(shí)現(xiàn)簡(jiǎn)單聊天功能以及文件傳輸
- Android 基于Socket的聊天室實(shí)例
- Android基于socket實(shí)現(xiàn)的簡(jiǎn)單C/S聊天通信功能
- Android Socket實(shí)現(xiàn)多個(gè)客戶(hù)端即時(shí)通信聊天
相關(guān)文章
android使用 ScrollerView 實(shí)現(xiàn) 可上下滾動(dòng)的分類(lèi)欄實(shí)例
本篇文章主要介紹了android使用 ScrollerView 實(shí)現(xiàn) 可上下滾動(dòng)的分類(lèi)欄實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01Android?貝塞爾曲線(xiàn)繪制一個(gè)波浪球
當(dāng)?flutter?的現(xiàn)有組件無(wú)法滿(mǎn)足產(chǎn)品要求的UI效果時(shí),我們就需要通過(guò)自繪組件的方式來(lái)進(jìn)行實(shí)現(xiàn)了。本文章就來(lái)介紹如何用貝塞爾曲線(xiàn)實(shí)現(xiàn)一個(gè)帶文本的波浪球,需要的可以參考一下2022-05-05Android實(shí)現(xiàn)滑動(dòng)屏幕切換圖片
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)滑動(dòng)屏幕切換圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08Android 數(shù)據(jù)庫(kù)文件存取至儲(chǔ)存卡的方法
這篇文章主要介紹了Android 數(shù)據(jù)庫(kù)文件存取至儲(chǔ)存卡的方法的相關(guān)資料,需要的朋友可以參考下2016-03-03Android SeekBar 自定義thumb旋轉(zhuǎn)動(dòng)畫(huà)效果
某些音樂(lè)播放或者視頻播放的界面上,資源還在加載時(shí),進(jìn)度條的原點(diǎn)(thumb)會(huì)顯示一個(gè)轉(zhuǎn)圈的效果。這篇文章主要介紹了Android SeekBar 自定義thumb thumb旋轉(zhuǎn)動(dòng)畫(huà)效果,需要的朋友可以參考下2021-11-11Android 6.0 無(wú)法在SD卡創(chuàng)建目錄的方法
今天小編就為大家分享一篇Android 6.0 無(wú)法在SD卡創(chuàng)建目錄的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08Android手機(jī)號(hào)碼輸入框(滿(mǎn)11位自動(dòng)跳到下個(gè)輸入框)實(shí)例代碼
這篇文章主要介紹了Android手機(jī)號(hào)碼輸入框(滿(mǎn)11位自動(dòng)跳到下個(gè)輸入框)實(shí)例代碼,需要的朋友可以參考下2017-10-10Android 模擬新聞APP顯示界面滑動(dòng)優(yōu)化實(shí)例代碼
所謂滑動(dòng)優(yōu)化就是滑動(dòng)時(shí)不加載圖片,停止才加載,第一次進(jìn)入時(shí)手動(dòng)加載。下面通過(guò)本文給大家介紹android 模擬新聞app顯示界面滑動(dòng)優(yōu)化實(shí)例代碼,需要的朋友可以參考下2017-03-03