溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android Socket通信實(shí)現(xiàn)簡(jiǎn)單聊天室

發(fā)布時(shí)間:2020-10-18 06:02:35 來源:腳本之家 閱讀:147 作者:cvil 欄目:移動(dòng)開發(fā)

socket通信是基于底層TCP/IP協(xié)議實(shí)現(xiàn)的。這種服務(wù)端不需要任何的配置文件和tomcat就可以完成服務(wù)端的發(fā)布,使用純java代碼實(shí)現(xiàn)通信。socket是對(duì)TCP/IP的封裝調(diào)用,本身并不是一種協(xié)議,我們通過socket來調(diào)用協(xié)議來跟服務(wù)端進(jìn)行通信和數(shù)據(jù)的傳輸。socket就像客戶端與服務(wù)端之間的一條信息通道,每一個(gè)不同的客戶端都會(huì)建立一個(gè)獨(dú)立的socket,雙方都沒有關(guān)閉連接的話,連接—也就是建立好的這條socket通道將一直保持,服務(wù)端要跟那一個(gè)客戶端通信只需要找到對(duì)應(yīng)的socket對(duì)象就可以進(jìn)行數(shù)據(jù)傳遞。

第一次握手:客戶端發(fā)送syn包(syn=j)到服務(wù)器,并進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器確認(rèn);
第二次握手:服務(wù)器收到syn包,必須確認(rèn)客戶的SYN(ack=j+1),同時(shí)自己也發(fā)送一個(gè)SYN包(syn=k),即SYN+ACK包,此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài);
第三次握手:客戶端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認(rèn)包ACK(ack=k+1),此包發(fā)送完畢,客戶端和服務(wù)器進(jìn)入ESTABLISHED狀態(tài),完成三次握手。

一. 服務(wù)端:在客戶端跟服務(wù)端通信之前,服務(wù)端必須先開啟。首先來看一下服務(wù)端Socket的編寫吧。服務(wù)端就是一個(gè)簡(jiǎn)單的java項(xiàng)目,由于聊天室可能會(huì)有多個(gè)客戶端同時(shí)連接并發(fā)送消息,我們這里使用線程池來處理客戶端的請(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ù)器的客戶端分配一個(gè)線程進(jìn)行消息的接收和發(fā)送
  client = serverSocket.accept();
  list.add(client);
  executorService.execute(new Service(client));
 }
 }

首先我們創(chuàng)建了一個(gè)大小為5的固定大小線程池,并創(chuàng)建端口號(hào)為12345的服務(wù)端socket接收客戶端請(qǐng)求,通過一個(gè)while循環(huán)不斷輪詢來自服務(wù)端的連接請(qǐng)求,在while循環(huán)里面調(diào)用了serverSocket.accept();是線程進(jìn)入阻塞狀態(tài),也就是說在沒有接收到客戶端的請(qǐng)求時(shí),程序?qū)⒁恢蓖A粼谶@里,當(dāng)有客戶端連接服務(wù)端是,代碼開始往下走,我們把接收到的客戶端socket放入list里面,這樣我們就把所有連接到服務(wù)端的socket保存下來了,這樣就使得我們可以隨時(shí)對(duì)任一客戶端進(jìn)行數(shù)據(jù)傳遞。之后就是線程池調(diào)用execute執(zhí)行一個(gè)線程,把連接過來的socket作為參數(shù)傳進(jìn)去。接下來分析下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() + "加入了聊天室,當(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 = "用戶:" + client.getInetAddress() + "離開了聊天室,當(dāng)前人數(shù):" + list.size();
    sendMsg() ;
    client.close() ;
    break ;
   }else{
    msg = client.getInetAddress() + "說:" + msg;
    sendMsg() ;
   }
   }
  }
  } catch (Exception e) {
  e.printStackTrace();
  }
 }

 public void sendMsg() {//為每一個(gè)用戶發(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)來的socket,在里面我們通過這個(gè)socket獲取輸入流包裝成一個(gè)BufferedReader,br = new BufferedReader(new InputStreamReader(client.getInputStream()));這里我們是主要是針對(duì)聊天,所以使用的是字符流進(jìn)行數(shù)據(jù)的傳輸,這個(gè)類里面聲明了一個(gè)成員變量msg,通過這個(gè)變量來給每個(gè)客戶端發(fā)送信息。下面看下sendMsg方法:

public void sendMsg() {//為每一個(gè)用戶發(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();
  }
  }
 }

這里我們通過遍歷list里面的每個(gè)socket獲得它的的輸出流并且包裝成PrintWriter 向客戶端發(fā)送信息, pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())));向每一個(gè)客戶端發(fā)送成員變量msg內(nèi)容,在PrintWriter調(diào)用print之后一定要調(diào)用flush刷新輸出流進(jìn)行數(shù)據(jù)的傳遞,否則客戶端無法接收到服務(wù)端發(fā)送的數(shù)據(jù)。接下來看這個(gè)類的住方法 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() + "離開了聊天室,當(dāng)前人數(shù):" + list.size();
   sendMsg() ;
   client.close() ;
    break ;
   }else{
   msg = client.getInetAddress() + "說:" + msg;
  sendMsg() ;
  }
   }
  }
  } catch (Exception e) {
  e.printStackTrace();
  }
 }

與前面類似,也是通過一個(gè)while進(jìn)行無限循環(huán)進(jìn)行讀取socket的輸入流,如果內(nèi)容不為空就調(diào)用sendmsg對(duì)每一個(gè)客戶端進(jìn)行信息發(fā)送,有個(gè)小小的處理就是如果發(fā)送過來的信息是bye的時(shí)候就斷開對(duì)應(yīng)socket的鏈接,退出聊天室。以上是對(duì)服務(wù)端的分析,接下來我們來看Android客戶端。

二. 客戶端:客戶端基本與服務(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來建立與服務(wù)端的連接,因?yàn)橹骶€程不能訪問網(wǎng)絡(luò),由于我們客戶端肯定是只有當(dāng)前這一個(gè)socket的,所以只有一個(gè)線程,不用跟服務(wù)端一樣使用線程池了。連接一旦建立,獲取socket的輸入輸出流來包裝成對(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)變量都可以使用成員變量。然后走下來就是對(duì)send按鈕的監(jiān)聽,點(diǎn)擊發(fā)送的話,把內(nèi)容發(fā)送給服務(wù)端,服務(wù)端接收到之后發(fā)送給每一個(gè)保持著鏈接的客戶端。這個(gè)activity也是實(shí)現(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();
  }
  }
 }
 }

這里跟服務(wù)端一樣,通過一個(gè)while無限循環(huán)讀取來自服務(wù)端的信息,一旦讀取到信息之后就通過handler從子線程發(fā)送消息到主線程,主線程進(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;
  }
 };
 };

總體來說客戶端還是比服務(wù)端容易點(diǎn),沒有涉及到并發(fā),只需要做當(dāng)前這個(gè)客戶端對(duì)應(yīng)的socket通信就行了。以上就是對(duì)socket的一個(gè)簡(jiǎn)單總結(jié)和在安卓里面的簡(jiǎn)單應(yīng)用實(shí)現(xiàn)聊天室功能。效果圖:

Android Socket通信實(shí)現(xiàn)簡(jiǎn)單聊天室 

Android Socket通信實(shí)現(xiàn)簡(jiǎn)單聊天室

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI