Android 基于TCP协议的Socket编程(自定义协议)

1.Socket简介

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

Socket的英文原义是“孔”或“插座”。通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。

理论上两台主机能互相ping通,则可以互相socket通信。

2.需求描述

一台具有固定ip的android设备(作为Socket服务端),客户端(可以多个)需要给它发送一些命令来即时播放文本、图片、视频等信息。

3.需求分析

一般来说,Android开发时像一般简单的需求可以通过http或者webservice进行通信,对即时性要求不高的通信也可以折中选择轮询服务的机制(但耗流量)。但是要实现上述需求所说的即时播放多媒体信息的话,显然用Socket来实现最佳。

服务端: Java中能接收其他通信实体连接请求的类是ServerSocket,ServerSocket对象用来监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状态。ServerSocket类提供如下构造器:

  • ServerSocket(int port); 使用指定端口来创建,port范围:0-65535。注意,在选择端口时,必须小心。每一个端口提供一种特定的服务,只有给出正确的端口,才能获得相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23, 所以我们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。
  • ServerSocket(int port, int backlog); 增加一个用来改变连接队列长度的参数backlog。
  • ServerSocket(int port, int backlog, InetAddress bindAddr);在机器存在多个IP地址的情况下,bindAddr参数指定将ServerSocket绑定到指定IP。

客户端 通常使用Socket来连接指定服务器,Socket类提供如下构造器:

  • Socket(InetAddress/String remoteAddress, int port); 创建连接到指定远程主机、远程端口的Socket,本地IP地址和端口使用默认值。
  • Socket(InetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort); 绑定本地IP地址和端口,适用于本地主机有多个IP地址的情形。

Socket通信需要服务端和客户端,刚开始分析时把Android端当客户端,发现这样行不通啊,Android设备是负责监听某个端口,有消息来时就作出响应(播放多媒体信息),这应该是服务端做的事情。所以让Android端作为服务端。

需要自定义协议来播放文本、图片、视频消息。

4.编程实现

4.1自定义协议很简单,按字节来定义如下(编码utf-8):
开始标志(1byte)     业务数据包   结束标志(1byte)     
0x02                            0x03            
业务数据包定义:顺序定义(各域间用"|"分隔,第一个域为消息类型)
1)文本通知
2001|优先级|每次时长(秒)|播放次数(默认1)|字体大小|通知内容
2)图片通知
2071|优先级|每次时长(秒)|播放次数(默认1)|是否全屏|图片URL
5)视频通知(1为全屏播放)
2051|优先级|播放次数|是否全屏|视频URL

通知内容中不能包含分隔符|,字体大小默认60(大于0才有效)
播放次数:<=0则为默认1次,大于0才有效
优先级:同类通知中数字越大表示优先级越高,<=0表示放到队列末尾。
     普通文本>图片>视频 

返回客户端:
OK -- 成功
ERROR:MSG -- 失败  例如 ERROR:消息类型不正确
4.2 服务端实现

Android设备作为服务端,需要开一个端口监听。很容易想到用Service来实现,这里采用它的一个子类IntentService来实现(该类的销毁由系统管理,非常方便),为了可以多个客户端连接,需要启动多个线程分别与客户端建立连接。

4.2.1 负责监听端口的服务类SocketService.java
import android.app.IntentService;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.util.Log;

import com.jykj.departure.util.SocketHelper;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SocketService extends IntentService {
   
    private ServerSocket server;
    private static final int PORT = 54321;
    private List<Socket> mList = new ArrayList<>();

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public SocketService(String name) {
        super(name);
    }

    public SocketService() {
        super("Socket Service");
        System.out.println("Create SocketService!");

    }
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        ExecutorService es;
        try {
            server = new ServerSocket(PORT);
            server.setReuseAddress(true);
            es = Executors.newCachedThreadPool();
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        Log.e("WS", "begin client connected");
        Socket client;
        while (true) {
            Log.e("WS", "线程连接数:" + mList.size());
            try {
                client = server.accept();
                Log.e("WS", "client connected:" + client.getInetAddress() + ":" + client.getPort());
                mList.add(client);
                es.execute(new ServiceThread(client));
            } catch (IOException e) {
                e.printStackTrace();
                break;
            }
        }

    }
    //每个客户端连接开启一个线程处理
    class ServiceThread implements Runnable {
        private Socket socket;

        ServiceThread(Socket s) {
            socket = s;
        }

        @Override
        public void run() {
            try {
                InputStream is = socket.getInputStream();
                byte[] bytes = SocketHelper.readBytes(is);

                Log.e("WS", "字节长度:" + bytes.length + "," + new String(bytes, SocketHelper.CHARSET));
                String s = SocketHelper.checkBytes(bytes);
                Log.e("WS", "校验结果:" + s);
                if (SocketHelper.RETURN_OK.equals(s)) {
                    Intent i = new Intent(MainActivity.ACTION_SOCKET);
                    i.putExtra(MainActivity.EXTRA_INFO, SocketHelper.getContent(bytes));
                    sendBroadcast(i);//校验成功将 消息命令 通过广播发送给主界面MainActivity
                }
                mList.remove(socket);
                PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),SocketHelper.CHARSET), true);
                writer.println(s);
                writer.flush();
                writer.close();
                is.close();
                socket.close();//记得关闭socket(关闭与客户端的连接)
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
4.2.2 与协议有关的辅助类SocketHelper.java
import android.util.Log;

import com.jykj.departure.entity.Notice;
import com.jykj.departure.entity.Pictures;
import com.jykj.departure.entity.Videos;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;

/**
 * socket协议接口
 */
public class SocketHelper {
   
    public static final String CHARSET = "UTF-8";
    private static final byte BEGIN_BYTE=0x02;
    private static final byte END_BYTE=0x03;
    public static final String SPLITOR = "\\|";//分隔符 "|"
    public static final String RETURN_OK = "OK";
    private static final String RETURN_ERROR = "ERROR:";
    public static final String TYPE_GENERAL="2001";//普通消息
    public static final String TYPE_VIDEO_GENERAL = "2051";//视频普通
    public static final String TYPE_PICTURE_GENERAL = "2071";//图片普通 
    //从输入流中读取所有字节
    public static byte[] readBytes(InputStream is) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(is);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buff = new byte[128];
        int len = -1;
        boolean flag = true;
        while (flag&&(len=bis.read(buff))!=-1){
            for(int i=len-1;i>=0;i--){
                if(buff[i]==END_BYTE){
                    flag = false;
                    len = i+1;
                    break;
                }
            }
            baos.write(buff,0,len);
        }
        byte[] b = baos.toByteArray();

        baos.close();
        //bis.close();//此处如果把bis关闭的话(则会顺带关闭底层is),这样会将socket关闭,这样就不能写出数据了!!!

        return b;
    }
    /**
     * 校验 消息格式
     * 目前只有两种消息格式 播放次数为0表示停止播放,为-1表示一直播放
     * 优先级:数字越大表示优先级越高,<=0表示放到队列末尾,班次通知列表优先级最高
音,发车时间,车牌号,车型名称,经停,晚点
     * @param bytes 字节流
     * @return OK
     */
    public static String checkBytes(byte[] bytes){
        if(bytes==null||bytes.length<=0) return RETURN_ERROR+"未读取到数据";
        byte b1= bytes[0];//起始标志
        byte b2 = bytes[bytes.length-1];//结束标志
        if(b1!=BEGIN_BYTE) return RETURN_ERROR+"协议起始标志不对";
        if(b2!=END_BYTE) return RETUR
  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值