登录 立即注册
安币:

查看: 2257|回复: 10

android Socket实现简单聊天功能以及文件传输,android socket长连接

[复制链接]

308

主题

798

帖子

705

安币

手工艺人

发表于 2017-12-4 12:23:10 | 显示全部楼层 |阅读模式

        干程序是一件枯燥重复的事,每当感到内心浮躁的时候,我就会找小说来看。我从小就喜爱看武侠小说,一直有着武侠梦。从金庸,古龙,梁羽生系列到凤歌(昆仑),孙晓(英雄志)以及萧鼎的(诛仙)让我领略着不一样的江湖。

        如果你有好看的武侠系列小说,给我留言哦。题外话就扯这么多了,接着还是上技术。

        看看今天实现的功能效果图:

        

        可以这里使用多台手机进行通讯,我采用的服务器发送消息。

        是不是只有发送消息,有些显得太单调了。好,在发送消息的基础上增加文件传输。后期会增加视频,音频的传输,增加表情包。那一起来看看图文消息的效果图,带领大家一起来实现通讯的简易聊天功能。

        

        需要解决的难点:

        如何判断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。

        套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。步骤如下:



  

    1. 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求


  

    2. 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。


  

    3. 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

        jdk socket

        在java.net包下有两个类:socket和serversocket。serversocket用于服务器端,socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是socket还是serversocket它们的工作都是通过socketimpl类及其子类完成的。接着了解下socket和serversocket的构造方法。

        socket

        socket的构造方法:

[Java] 查看源文件 复制代码
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

        参数含义:



  

    1. address 双向连接中另一方的ip地址


  

    2. port 端口号


  

    3. localport 本地主机端口号


  

    4. localaddr 本地机器地址


  

    5. impl 是socket的父类,既可以用来创建serversocket又可以用来创建socket

        注意:我们在选取端口号的时候需要特别注意,每一个端口提供一种特定的服务,只有给出正确的端口,才能获得相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23。本文选取的端口号为30003

        socket的几个重要方法:

[Java] 查看源文件 复制代码
public inputstream getinputstream(); //方法获得网络连接输入,同时返回一个iutputstream对象实例
public outputstream getoutputstream(); //方法连接的另一端将得到输入,同时返回一个outputstream对象实例
public socket accept(); //用于产生"阻塞",直到接受到一个连接,并且返回一个客户端的socket对象实例。

        对流的操作,操作完记得处理和关闭。以及对流异常的处理。

        serversocket

        serversocket的构造方法:

[Java] 查看源文件 复制代码
serversocket(int port); //创建绑定到特定端口的服务器套接字
serversocket(int port,int backlog); //利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号
serversocket(int port,int backlog, inetaddress bindaddr); //使用指定的端口、侦听 backlog 和要绑定到的本地 ip地址创建服务器

        接着我们一起来看看案例。

        发送和接收消息

        首先来实现一个简单的案例,服务器端一直监听某个端口,等待客户端连接请求。客户端根据ip地址和端口号连接服务器端,接着客服端通过控制台向服务端发送消息,服务端接收到消息并且展示出来。下面来看看具体实现的步骤:

        clientsocket客服端:

[Java] 查看源文件 复制代码
    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地址:

[Java] 查看源文件 复制代码
   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服务端:

[Java] 查看源文件 复制代码
    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接收的数据是字符串还是流?

        定义协议

        为了保证接收到的数据类型统一(数据是字符串还是流),需要定义协议。定义协议的方式有很多种:



  

    1. 发送一个握手信号。 根据握手信号来确定发送的是字符串还是流


  

    2. 定义了header(头)和body(实体),头是固定大小的,用来告诉接收者数据的格式、用途、长度等信息,接收者根据header来接受body。


  

    3. 自定义协议

        我这里采用的自定义协议,原理跟前面两种类似。我传输的是json数据,根据字段标识传输的是字符串还是流,接收者根据标识去解析数据即可。

        协议的实体类(transmission):

[Java] 查看源文件 复制代码
  //文件名称
  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)

        客户端发送文件的代码如下:

[Java] 查看源文件 复制代码
  /**
   * 文件路径
   *
   * @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();
    }
  }

        文章结尾处我会附上源码。

[Java] 查看源文件 复制代码
    trans.content = base64utils.encode(bytes);
    mprintwriter.write(mgson.tojson(trans) + "\r\n");
    mprintwriter.flush();

        把字节流转换成字符串传输base64utils.encode(bytes),接收方把字符串解析成字节流并写入文件。

        注意:在android程序中运行,记得添加网络文件读写的权限。

        服务端(serverthread)

        服务端接收文件的代码如下:

[Java] 查看源文件 复制代码
    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程序。


9

主题

9281

帖子

1827

安币

Android大神

Rank: 6Rank: 6

QQ达人

发表于 2017-12-4 17:15:50 | 显示全部楼层
每次我都积极回帖的,想要安币~

8

主题

9131

帖子

3560

安币

码皇(巴士元老)

Rank: 8Rank: 8

发表于 2017-12-5 00:11:56 | 显示全部楼层
支持楼主,支持安卓巴士!

1

主题

9227

帖子

2565

安币

Android大神

Rank: 6Rank: 6

发表于 2017-12-5 07:48:01 | 显示全部楼层
楼主是好人,回个帖会有安币吗?

11

主题

9253

帖子

1001

安币

Android大神

Rank: 6Rank: 6

发表于 2017-12-5 13:33:12 | 显示全部楼层
支持楼主,支持安卓巴士!

465

主题

9767

帖子

827

安币

代码手工艺人

Rank: 4

发表于 2017-12-5 23:40:11 | 显示全部楼层
安卓巴士是个不错的网站,我来顶个贴~

10

主题

9684

帖子

868

安币

代码手工艺人

Rank: 4

发表于 2017-12-6 17:13:04 | 显示全部楼层
感谢大神~

0

主题

182

帖子

1160

安币

Android大神

Rank: 6Rank: 6

发表于 2017-12-12 13:13:03 | 显示全部楼层
学习学习!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

站长推荐

通过邮件订阅最新安卓weekly信息
上一条 /4 下一条

下载安卓巴士客户端

全国最大的安卓开发者社区

联系我们
关闭
合作电话:
15618560077
Email:
805941275@qq.com
商务市场合作/投稿
问题反馈及帮助
联系我们

广告投放| 下载客户端|申请友链|手机版|站点统计|安卓巴士 ( 粤ICP备15117877号 )

快速回复 返回顶部 返回列表