您好,登錄后才能下訂單哦!
socket通信是基于底層TCP/IP協議實現的。這種服務端不需要任何的配置文件和tomcat就可以完成服務端的發布,使用純java代碼實現通信。socket是對TCP/IP的封裝調用,本身并不是一種協議,我們通過socket來調用協議來跟服務端進行通信和數據的傳輸。socket就像客戶端與服務端之間的一條信息通道,每一個不同的客戶端都會建立一個獨立的socket,雙方都沒有關閉連接的話,連接—也就是建立好的這條socket通道將一直保持,服務端要跟那一個客戶端通信只需要找到對應的socket對象就可以進行數據傳遞。
第一次握手:客戶端發送syn包(syn=j)到服務器,并進入SYN_SEND狀態,等待服務器確認;
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
一. 服務端:在客戶端跟服務端通信之前,服務端必須先開啟。首先來看一下服務端Socket的編寫吧。服務端就是一個簡單的java項目,由于聊天室可能會有多個客戶端同時連接并發送消息,我們這里使用線程池來處理客戶端的請求。
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) {//為每一個連接到服務器的客戶端分配一個線程進行消息的接收和發送 client = serverSocket.accept(); list.add(client); executorService.execute(new Service(client)); } }
首先我們創建了一個大小為5的固定大小線程池,并創建端口號為12345的服務端socket接收客戶端請求,通過一個while循環不斷輪詢來自服務端的連接請求,在while循環里面調用了serverSocket.accept();是線程進入阻塞狀態,也就是說在沒有接收到客戶端的請求時,程序將一直停留在這里,當有客戶端連接服務端是,代碼開始往下走,我們把接收到的客戶端socket放入list里面,這樣我們就把所有連接到服務端的socket保存下來了,這樣就使得我們可以隨時對任一客戶端進行數據傳遞。之后就是線程池調用execute執行一個線程,把連接過來的socket作為參數傳進去。接下來分析下service的內容:
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() + "加入了聊天室,當前人數:" + 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() + "離開了聊天室,當前人數:" + list.size(); sendMsg() ; client.close() ; break ; }else{ msg = client.getInetAddress() + "說:" + msg; sendMsg() ; } } } } catch (Exception e) { e.printStackTrace(); } } public void sendMsg() {//為每一個用戶發送這個消息: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的構造方法中使用了作為參數傳進來的socket,在里面我們通過這個socket獲取輸入流包裝成一個BufferedReader,br = new BufferedReader(new InputStreamReader(client.getInputStream()));這里我們是主要是針對聊天,所以使用的是字符流進行數據的傳輸,這個類里面聲明了一個成員變量msg,通過這個變量來給每個客戶端發送信息。下面看下sendMsg方法:
public void sendMsg() {//為每一個用戶發送這個消息: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 向客戶端發送信息, pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())));向每一個客戶端發送成員變量msg內容,在PrintWriter調用print之后一定要調用flush刷新輸出流進行數據的傳遞,否則客戶端無法接收到服務端發送的數據。接下來看這個類的住方法 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() + "離開了聊天室,當前人數:" + list.size(); sendMsg() ; client.close() ; break ; }else{ msg = client.getInetAddress() + "說:" + msg; sendMsg() ; } } } } catch (Exception e) { e.printStackTrace(); } }
與前面類似,也是通過一個while進行無限循環進行讀取socket的輸入流,如果內容不為空就調用sendmsg對每一個客戶端進行信息發送,有個小小的處理就是如果發送過來的信息是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); // --------發起網絡連接----- 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(); }
首先還是傳統的new一個thread來建立與服務端的連接,因為主線程不能訪問網絡,由于我們客戶端肯定是只有當前這一個socket的,所以只有一個線程,不用跟服務端一樣使用線程池了。連接一旦建立,獲取socket的輸入輸出流來包裝成對應的BufferedReader和PrintWriter:br = new BufferedReader(new InputStreamReader(client.getInputStream()));pw = new PrintWriter(new BufferedWriter( new OutputStreamWriter(client.getOutputStream())));由于這里只會使用一個socket,所以這里的相關變量都可以使用成員變量。然后走下來就是對send按鈕的監聽,點擊發送的話,把內容發送給服務端,服務端接收到之后發送給每一個保持著鏈接的客戶端。這個activity也是實現了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無限循環讀取來自服務端的信息,一旦讀取到信息之后就通過handler從子線程發送消息到主線程,主線程進行數據的更新,其實就是向顯示聊天室內容的textview追加聊天內容并且setText上去:
Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case UPDATE_CONTENT: txt_content.append(content); break; default: break; } }; };
總體來說客戶端還是比服務端容易點,沒有涉及到并發,只需要做當前這個客戶端對應的socket通信就行了。以上就是對socket的一個簡單總結和在安卓里面的簡單應用實現聊天室功能。效果圖:
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。