国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

android Socket實現簡單聊天功能以及文件傳輸

2019-12-12 03:39:34
字體:
來源:轉載
供稿:網友

干程序是一件枯燥重復的事,每當感到內心浮躁的時候,我就會找小說來看。我從小就喜愛看武俠小說,一直有著武俠夢。從金庸,古龍,梁羽生系列到鳳歌(昆侖),孫曉(英雄志)以及蕭鼎的(誅仙)讓我領略著不一樣的江湖。

如果你有好看的武俠系列小說,給我留言哦。題外話就扯這么多了,接著還是上技術。

看看今天實現的功能效果圖:

可以這里使用多臺手機進行通訊,我采用的服務器發送消息。

是不是只有發送消息,有些顯得太單調了。好,在發送消息的基礎上增加文件傳輸。后期會增加視頻,音頻的傳輸,增加表情包。那一起來看看圖文消息的效果圖,帶領大家一起來實現通訊的簡易聊天功能。

需要解決的難點:

如何判斷socket接收的數據是字符串還是流?

如果你已是一名老司機,還請留言給出寶貴意見。帶著這個疑問我們接著往下看。

Socket概述

Socket我們稱之為"套接字",用于消息通知系統(如:激光推送),時事通訊系統(如:環信)等等。用于描述IP地址和端口,是一個通信鏈的句柄。網絡上的兩個程序通過一個雙向的通訊連接實現數據的交換,這個雙向鏈路的一端稱為一個Socket,一個Socket由一個IP地址和一個端口號唯一確定(如:ServerSocket)。應用程序通常通過"套接字"向網絡發出請求或者應答網絡請求。Socket是TCP/IP協議的一個十分流行的編程界面,但是,Socket所支持的協議種類也不光TCP/IP一種,因此兩者之間是沒有必然聯系的。在Java環境下,Socket編程主要是指基于TCP/IP協議的網絡編程。

java.net包下有兩個類:Socket和ServerSocket,基于TCP協議。

本文針對Socket和ServerSocket作主要講解。

socket連接

建立Socket連接至少需要一對套接字,其中一個運行于客戶端,稱為ClientSocket ,另一個運行于服務器端,稱為ServerSocket。

套接字之間的連接過程分為三個步驟:服務器監聽,客戶端請求,連接確認。步驟如下:

  • 服務器監聽:服務器端套接字并不定位具體的客戶端套接字,而是處于等待連接的狀態,實時監控網絡狀態,等待客戶端的連接請求
  • 客戶端請求:指客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。為此,客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然后就向服務器端套接字提出連接請求。
  • 連接確認:當服務器端套接字監聽到或者說接收到客戶端套接字的連接請求時,就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接字的描述發給客戶端,一旦客戶端確認了此描述,雙方就正式建立連接。而服務器端套接字繼續處于監聽狀態,繼續接收其他客戶端套接字的連接請求。

JDK Socket

在java.net包下有兩個類:Socket和ServerSocket。ServerSocket用于服務器端,Socket是建立網絡連接時使用的。在連接成功時,應用程序兩端都會產生一個Socket實例,操作這個實例,完成所需的會話。對于一個網絡連接來說,套接字是平等的,并沒有差別,不因為在服務器端或在客戶端而產生不同級別。不管是Socket還是ServerSocket它們的工作都是通過SocketImpl類及其子類完成的。接著了解下Socket和ServerSocket的構造方法。

Socket

Socket的構造方法:

Socket(InetAddress address,int port); //創建一個流套接字并將其連接到指定 IP 地址的指定端口號Socket(String host,int port); //創建一個流套接字并將其連接到指定主機上的指定端口號Socket(InetAddress address,int port, InetAddress localAddr,int localPort); //創建一個套接字并將其連接到指定遠程地址上的指定遠程端口Socket(String host,int port, InetAddress localAddr,int localPort); //創建一個套接字并將其連接到指定遠程主機上的指定遠程端口Socket(SocketImpl impl); //使用用戶指定的 SocketImpl 創建一個未連接 Socket

參數含義:

  • address 雙向連接中另一方的IP地址
  • port 端口號
  • localPort 本地主機端口號
  • localAddr 本地機器地址
  • impl 是socket的父類,既可以用來創建serverSocket又可以用來創建Socket

注意:我們在選取端口號的時候需要特別注意,每一個端口提供一種特定的服務,只有給出正確的端口,才能獲得相應的服務。0~1023的端口號為系統所保留,例如http服務的端口號為80,telnet服務的端口號為21,ftp服務的端口號為23。本文選取的端口號為30003

Socket的幾個重要方法:

public InputStream getInputStream(); //方法獲得網絡連接輸入,同時返回一個IutputStream對象實例public OutputStream getOutputStream(); //方法連接的另一端將得到輸入,同時返回一個OutputStream對象實例public Socket accept(); //用于產生"阻塞",直到接受到一個連接,并且返回一個客戶端的Socket對象實例。

對流的操作,操作完記得處理和關閉。以及對流異常的處理。

ServerSocket

ServerSocket的構造方法:

ServerSocket(int port); //創建綁定到特定端口的服務器套接字ServerSocket(int port,int backlog); //利用指定的 backlog 創建服務器套接字并將其綁定到指定的本地端口號ServerSocket(int port,int backlog, InetAddress bindAddr); //使用指定的端口、偵聽 backlog 和要綁定到的本地 IP地址創建服務器

接著我們一起來看看案例。

發送和接收消息

首先來實現一個簡單的案例,服務器端一直監聽某個端口,等待客戶端連接請求。客戶端根據IP地址和端口號連接服務器端,接著客服端通過控制臺向服務端發送消息,服務端接收到消息并且展示出來。下面來看看具體實現的步驟:

ClientSocket客服端:

    try {      Socket socket = new Socket("173.1.1.121", 30004);      //獲取控制臺輸入的內容      BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));      System.out.print("請輸入發送的字符串:");      String str = bufferedReader.readLine();      //給服務端發送消息      PrintWriter printWriter = new PrintWriter(socket.getOutputStream());      printWriter.write(str + "/r/n");      printWriter.flush();      //關閉資源      bufferedReader.close();      printWriter.close();      socket.close();    } catch (IOException e) {      e.printStackTrace();    }

Socket兩個參數分別是IP地址和端口號,可以通過以下代碼獲取IP地址:

   InetAddress ia = null;   try {     ia = ia.getLocalHost();     String localname = ia.getHostName();     String localip = ia.getHostAddress();     System.out.println("本機名稱是:" + localname);     System.out.println("本機的ip是 :" + localip);   } catch (Exception e) {     // TODO Auto-generated catch block     e.printStackTrace();   }

注意:關閉多余的網絡適配,只保留當前的網絡連接。關閉防火墻,安全軟件。不然可能導致連接不上。

ServerSocket服務端:

    try {      mServerSocket = new java.net.ServerSocket(30004);      Socket socket = mServerSocket.accept();      BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));      String content = null;      while ((content=bufferedReader.readLine() )!= null) {        System.out.println("接收到客服端發來的消息:" +content);      }      //關閉連接      bufferedReader.close();      socket.close();    } catch (IOException e) {      e.printStackTrace();    }

服務端和客服端的端口號必須保持一致。下面我們運行兩個 java 程序看看效果:

可能有些童鞋還不知道,怎么在AndroidStudio下面運行java程序,請看下面截圖:

傳輸文件

Socket只能通過流去讀取消息,傳輸文件需要解決文章開始提出的問題, 如何判斷socket接收的數據是字符串還是流?

定義協議

為了保證接收到的數據類型統一(數據是字符串還是流),需要定義協議。定義協議的方式有很多種:

  • 發送一個握手信號。 根據握手信號來確定發送的是字符串還是流
  • 定義了Header(頭)和Body(實體),頭是固定大小的,用來告訴接收者數據的格式、用途、長度等信息,接收者根據Header來接受Body。
  • 自定義協議

我這里采用的自定義協議,原理跟前面兩種類似。我傳輸的是JSON數據,根據字段標識傳輸的是字符串還是流,接收者根據標識去解析數據即可。

協議的實體類(Transmission):

  //文件名稱  public String fileName;  //文件長度  public long fileLength;  //傳輸類型  public int transmissionType;  //傳輸內容  public String content;  //傳輸的長度  public long transLength;  //發送還是接受類型  1發送 2接收  public int itemType = 1;  //0 文本 1 圖片  public int showType;

根據字段transmissionType去標識傳輸(序列化)或接收(反序列化)的類型。傳輸的過程中始終都是以JSON的格式存在的。傳輸文件時需要把流轉換成字符串(方式很多種我用的是Base64加密與解密)。

客戶端(ClientThread)

客戶端發送文件的代碼如下:

  /**   * 文件路徑   *   * @param filePath   */  private void sendFile(String filePath) {    FileInputStream fis = null;    File file = new File(filePath);    try {      mSendHandler.sendEmptyMessage(Constants.PROGRESS);      fis = new FileInputStream(file);      Transmission trans = new Transmission();      trans.transmissionType = Constants.TRANSFER_FILE;      trans.fileName = file.getName();      trans.fileLength = file.length();      trans.transLength = 0;      byte[] bytes = new byte[1024];      int length = 0;      while ((length = fis.read(bytes, 0, bytes.length)) != -1) {        trans.transLength += length;        trans.content = Base64Utils.encode(bytes);        mPrintWriter.write(mGson.toJson(trans) + "/r/n");        mPrintWriter.flush();        //更新進度        Message message = new Message();        message.what = Constants.PROGRESS;        message.obj = 100 * trans.transLength / trans.fileLength;        mSendHandler.sendMessage(message);      }      fis.close();    } catch (FileNotFoundException e) {      e.printStackTrace();      if (fis != null) {        try {          fis.close();        } catch (IOException e1) {          e1.printStackTrace();        }      }    } catch (IOException e) {      e.printStackTrace();      mPrintWriter.close();    }  }

文章結尾處我會附上源碼。

    trans.content = Base64Utils.encode(bytes);    mPrintWriter.write(mGson.toJson(trans) + "/r/n");    mPrintWriter.flush();

把字節流轉換成字符串傳輸Base64Utils.encode(bytes),接收方把字符串解析成字節流并寫入文件。

注意:在Android程序中運行,記得添加網絡文件讀寫的權限。

服務端(ServerThread)

服務端接收文件的代碼如下:

    long fileLength = trans.fileLength;    long transLength = trans.transLength;    if (mCreateFile) {      mCreateFile = false;      fos = new FileOutputStream(new File("d:/" + trans.fileName));    }    byte[] b = Base64Utils.decode(trans.content.getBytes());    fos.write(b, 0, b.length);    System.out.println("接收文件進度" + 100 * transLength / fileLength + "%...");    if (transLength == fileLength) {      mCreateFile = true;      fos.flush();      fos.close();    }

服務端接收到文件,并存儲到了d盤。注意文件傳輸結束后關閉流。

源碼地址:SocketDemo_jb51.rar

下載源碼后,請先替換Constants類中HOST地址,然后運行MyServer的java程序,最后運行MainActivity的Android程序。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 连南| 彰化市| 长寿区| 南漳县| 昌黎县| 湘潭市| 顺义区| 旬阳县| 长沙市| 泰宁县| 晋宁县| 阳泉市| 尚志市| 永德县| 盐山县| 外汇| 乐亭县| 威信县| 赤水市| 那曲县| 镇安县| 德保县| 彭州市| 延寿县| 黔江区| 香港| 婺源县| 冕宁县| 翼城县| 武城县| 松潘县| 汤原县| 仙居县| 游戏| 江安县| 淮南市| 岑巩县| 礼泉县| 乐平市| 吉安县| 象山县|