Android Socket通信實現(xiàn)簡單聊天室
socket通信是基于底層TCP/IP協(xié)議實現(xiàn)的。這種服務端不需要任何的配置文件和tomcat就可以完成服務端的發(fā)布,使用純java代碼實現(xiàn)通信。socket是對TCP/IP的封裝調(diào)用,本身并不是一種協(xié)議,我們通過socket來調(diào)用協(xié)議來跟服務端進行通信和數(shù)據(jù)的傳輸。socket就像客戶端與服務端之間的一條信息通道,每一個不同的客戶端都會建立一個獨立的socket,雙方都沒有關閉連接的話,連接—也就是建立好的這條socket通道將一直保持,服務端要跟那一個客戶端通信只需要找到對應的socket對象就可以進行數(shù)據(jù)傳遞。
第一次握手:客戶端發(fā)送syn包(syn=j)到服務器,并進入SYN_SEND狀態(tài),等待服務器確認;
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發(fā)送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態(tài);
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發(fā)送確認包ACK(ack=k+1),此包發(fā)送完畢,客戶端和服務器進入ESTABLISHED狀態(tài),完成三次握手。
一. 服務端:在客戶端跟服務端通信之前,服務端必須先開啟。首先來看一下服務端Socket的編寫吧。服務端就是一個簡單的java項目,由于聊天室可能會有多個客戶端同時連接并發(fā)送消息,我們這里使用線程池來處理客戶端的請求。
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() + ":服務端就緒。"); Socket client = null; while (true) {//為每一個連接到服務器的客戶端分配一個線程進行消息的接收和發(fā)送 client = serverSocket.accept(); list.add(client); executorService.execute(new Service(client)); } }
首先我們創(chuàng)建了一個大小為5的固定大小線程池,并創(chuàng)建端口號為12345的服務端socket接收客戶端請求,通過一個while循環(huán)不斷輪詢來自服務端的連接請求,在while循環(huán)里面調(diào)用了serverSocket.accept();是線程進入阻塞狀態(tài),也就是說在沒有接收到客戶端的請求時,程序?qū)⒁恢蓖A粼谶@里,當有客戶端連接服務端是,代碼開始往下走,我們把接收到的客戶端socket放入list里面,這樣我們就把所有連接到服務端的socket保存下來了,這樣就使得我們可以隨時對任一客戶端進行數(shù)據(jù)傳遞。之后就是線程池調(diào)用execute執(zhí)行一個線程,把連接過來的socket作為參數(shù)傳進去。接下來分析下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 = "用戶:" + client.getInetAddress() + "加入了聊天室,當前人數(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 = "用戶:" + client.getInetAddress() + "離開了聊天室,當前人數(shù):" + list.size(); sendMsg() ; client.close() ; break ; }else{ msg = client.getInetAddress() + "說:" + msg; sendMsg() ; } } } } catch (Exception e) { e.printStackTrace(); } } public void sendMsg() {//為每一個用戶發(fā)送這個消息: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ù)傳進來的socket,在里面我們通過這個socket獲取輸入流包裝成一個BufferedReader,br = new BufferedReader(new InputStreamReader(client.getInputStream()));這里我們是主要是針對聊天,所以使用的是字符流進行數(shù)據(jù)的傳輸,這個類里面聲明了一個成員變量msg,通過這個變量來給每個客戶端發(fā)送信息。下面看下sendMsg方法:
public void sendMsg() {//為每一個用戶發(fā)送這個消息: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(); } } }
這里我們通過遍歷list里面的每個socket獲得它的的輸出流并且包裝成PrintWriter 向客戶端發(fā)送信息, pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())));向每一個客戶端發(fā)送成員變量msg內(nèi)容,在PrintWriter調(diào)用print之后一定要調(diào)用flush刷新輸出流進行數(shù)據(jù)的傳遞,否則客戶端無法接收到服務端發(fā)送的數(shù)據(jù)。接下來看這個類的住方法 run:
public void run() { try { while (true) { if ((msg = br.readLine()) != null) { if(msg.equals("bye")){ list.remove(this.client) ; br.close() ; msg = "用戶:" + client.getInetAddress() + "離開了聊天室,當前人數(shù):" + list.size(); sendMsg() ; client.close() ; break ; }else{ msg = client.getInetAddress() + "說:" + msg; sendMsg() ; } } } } catch (Exception e) { e.printStackTrace(); } }
與前面類似,也是通過一個while進行無限循環(huán)進行讀取socket的輸入流,如果內(nèi)容不為空就調(diào)用sendmsg對每一個客戶端進行信息發(fā)送,有個小小的處理就是如果發(fā)送過來的信息是bye的時候就斷開對應socket的鏈接,退出聊天室。以上是對服務端的分析,接下來我們來看Android客戶端。
二. 客戶端:客戶端基本與服務端一樣,我們直接上代碼吧。
//首先還是貼出成員變量 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)絡連接----- 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一個thread來建立與服務端的連接,因為主線程不能訪問網(wǎng)絡,由于我們客戶端肯定是只有當前這一個socket的,所以只有一個線程,不用跟服務端一樣使用線程池了。連接一旦建立,獲取socket的輸入輸出流來包裝成對應的BufferedReader和PrintWriter:br = new BufferedReader(new InputStreamReader(client.getInputStream()));pw = new PrintWriter(new BufferedWriter( new OutputStreamWriter(client.getOutputStream())));由于這里只會使用一個socket,所以這里的相關變量都可以使用成員變量。然后走下來就是對send按鈕的監(jiān)聽,點擊發(fā)送的話,把內(nèi)容發(fā)送給服務端,服務端接收到之后發(fā)送給每一個保持著鏈接的客戶端。這個activity也是實現(xiàn)了runnable接口的,接下來看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(); } } } }
這里跟服務端一樣,通過一個while無限循環(huán)讀取來自服務端的信息,一旦讀取到信息之后就通過handler從子線程發(fā)送消息到主線程,主線程進行數(shù)據(jù)的更新,其實就是向顯示聊天室內(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; } }; };
總體來說客戶端還是比服務端容易點,沒有涉及到并發(fā),只需要做當前這個客戶端對應的socket通信就行了。以上就是對socket的一個簡單總結(jié)和在安卓里面的簡單應用實現(xiàn)聊天室功能。效果圖:
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- Android Socket實現(xiàn)多個客戶端聊天布局
- android使用Socket通信實現(xiàn)多人聊天應用
- Android使用Websocket實現(xiàn)聊天室
- 基于Socket.IO實現(xiàn)Android聊天功能代碼示例
- android Socket實現(xiàn)簡單聊天小程序
- android socket聊天室功能實現(xiàn)
- android Socket實現(xiàn)簡單聊天功能以及文件傳輸
- Android 基于Socket的聊天室實例
- Android基于socket實現(xiàn)的簡單C/S聊天通信功能
- Android Socket實現(xiàn)多個客戶端即時通信聊天
相關文章
android使用 ScrollerView 實現(xiàn) 可上下滾動的分類欄實例
本篇文章主要介紹了android使用 ScrollerView 實現(xiàn) 可上下滾動的分類欄實例,具有一定的參考價值,有興趣的可以了解一下。2017-01-01Android 數(shù)據(jù)庫文件存取至儲存卡的方法
這篇文章主要介紹了Android 數(shù)據(jù)庫文件存取至儲存卡的方法的相關資料,需要的朋友可以參考下2016-03-03Android SeekBar 自定義thumb旋轉(zhuǎn)動畫效果
某些音樂播放或者視頻播放的界面上,資源還在加載時,進度條的原點(thumb)會顯示一個轉(zhuǎn)圈的效果。這篇文章主要介紹了Android SeekBar 自定義thumb thumb旋轉(zhuǎn)動畫效果,需要的朋友可以參考下2021-11-11Android 6.0 無法在SD卡創(chuàng)建目錄的方法
今天小編就為大家分享一篇Android 6.0 無法在SD卡創(chuàng)建目錄的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08Android手機號碼輸入框(滿11位自動跳到下個輸入框)實例代碼
這篇文章主要介紹了Android手機號碼輸入框(滿11位自動跳到下個輸入框)實例代碼,需要的朋友可以參考下2017-10-10Android 模擬新聞APP顯示界面滑動優(yōu)化實例代碼
所謂滑動優(yōu)化就是滑動時不加載圖片,停止才加載,第一次進入時手動加載。下面通過本文給大家介紹android 模擬新聞app顯示界面滑動優(yōu)化實例代碼,需要的朋友可以參考下2017-03-03