安卓Java Web Socket简单使用

安卓Java Web Socket简单使用

android使用javaWebSocket实现跨设备设备通讯的方式之一。

demo地址:https://gitee.com/sixu_Java/java-web-socket

只要关注onOpenonMessageonCloseonError这四个方法,然后server端启动,client端通过server端ip与端口去连接。即可完成简单的连接demo。

而server端与client端的通讯关注onMessage

注意:操作界面需要在UI线程,如将接收到的message更新到页面上,可借助Handler。如果直接在onMessage直接更新页面,则不会走onMessage,会走onError

断线重连必定会走onClose方法,所以根据client的onClose方法断线的原因,调用reconnect或者reconnectBlocking即可完成重连动作。

准备

  1. 首先需要创建两个项目,作为通讯的serve端与client端。

  2. 引入Java Web Socket依赖

    implementation "org.java-websocket:Java-WebSocket:1.4.0"
    

    Java-WebSocket的github地址https://github.com/TooTallNate/Java-WebSocket

  3. 给client端和serve端在AndroidManifest.xml添加联网权限

    <uses-permission android:name="android.permission.INTERNET" />
    

Client端

继承WebSocketClient

新建类继承WebSocketClient,并实现4个方法。我们所有关于通讯的操作基本上都围绕着这个类。

    @Override
    public void onOpen(ServerHandshake handshakedata) {
    }

    @Override
    public void onMessage(String message) {
    }

    @Override
    public void onMessage(ByteBuffer bytes) {
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
    }

    @Override
    public void onError(Exception ex) {
    }
  1. onOpen:当连接上serve端时调用。

  2. onMessage:接收到消息时调用。

  3. onClose:连接断开时调用。code表示断开的原因,remote表示断开是否是由于serve的原因。

    code可在org.java_websocket.framing.CloseFrame中查看类型,根据code可以决定是否需要进行重连。

    如code=1,则表示client与server从来没有连接过,则不需要重连。

    code = 1000(Normal),表示是正常断开,一般情况下已经完成了要做的事情,用户手动断开,无需重连。

    code= 1001 (GOING_AWAY),服务器可能由于自身原因导致断开,可重连。

  4. onError:连接出错时调用,之后会调用onClose。

代码:

public class JWSClient extends WebSocketClient {
    private static final String TAG = "JWSClient";
    private JWSCallBack mCallBack;

    public JWSClient(URI serverUri, JWSCallBack callBack) {
        this(serverUri);
        mCallBack = callBack;
    }

    private JWSClient(URI serverUri) {
        super(serverUri);
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        Log.d(TAG, "onOpen: ");
        mCallBack.onOpen(handshakedata);
    }

    @Override
    public void onMessage(String message) {
        Log.d(TAG, "onMessage: ");
        mCallBack.onMessage(message);
    }

    @Override
    public void onMessage(ByteBuffer bytes) {
        super.onMessage(bytes);
        Log.d(TAG, "onMessage: ");
        mCallBack.onMessage(bytes);
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        Log.d(TAG, "onClose: code = " + code + ", reason = " + reason + ", remote = " + remote);
        if (code==-1){
            return;
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    reconnectBlocking();
                } catch (InterruptedException exception) {
                    exception.printStackTrace();
                }
            }
        }).start();
    }

    @Override
    public void onError(Exception ex) {
        Log.d(TAG, "onError: " + ex.getMessage());
    }

    interface JWSCallBack {
        void onOpen(ServerHandshake handshakedata);

        void onMessage(String message);

        void onMessage(ByteBuffer bytes);
    }

}

连接

ws://ip地址:端口生成一个WebSocketClient对象,然后调用connect方法,即尝试连接serve。

ip地址表示serve的ip地址,端口需要与serve约定,保持一致。

以ws开头表示未加密,wws开头表示加密。

connect方法不会阻塞,connectBlocking会阻塞当前线程。

连接成功后,调用WebSocketClient的send方法,即可向serve发送数据。

send的参数是byte[]类型,则serve的onMessage收到的也是byte[]。

send的参数是string类型,则serve的onMessage收到的也是string。

代码

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private TextView mTextViewReceive;
    private EditText mEditTextIP;
    private EditText mEditTextSend;
    private Button mButtonConnect;
    private Button mButtonSend;

    private JWSClient mJWSClient;
    private JWSClient.JWSCallBack mJWSCallBack = new JWSClient.JWSCallBack() {
        @Override
        public void onOpen(ServerHandshake handshakedata) {

        }

        @Override
        public void onMessage(String message) {
            Message msg = Message.obtain();
            msg.what = 0;
            msg.obj = message;
            mUIHandler.sendMessage(msg);
        }

        @Override
        public void onMessage(ByteBuffer bytes) {
            Message msg = Message.obtain();
            msg.what = 0;
            msg.obj = getString(bytes);
            mUIHandler.sendMessage(msg);
        }
    };

    private Handler mUIHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    mTextViewReceive.setText((String) msg.obj);
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        mEditTextIP = findViewById(R.id.edit_ip);
        mEditTextSend = findViewById(R.id.edit_send);
        mButtonConnect = findViewById(R.id.btn_connect);
        mButtonSend = findViewById(R.id.btn_send);
        mTextViewReceive = findViewById(R.id.text_receive);
    }

    public void connectServe(View view) {
        String ip = mEditTextIP.getText().toString();
        if (ip != null && ip.startsWith("192.168.")) {
            try {
                mJWSClient = new JWSClient(new URI("ws://" + ip + ":13333"), mJWSCallBack);
                mJWSClient.setConnectionLostTimeout(5);
                mJWSClient.connectBlocking(3, TimeUnit.SECONDS);
            } catch (URISyntaxException | InterruptedException exception) {
                exception.printStackTrace();
                Toast.makeText(MainActivity.this, "连接失败", Toast.LENGTH_SHORT).show();
            }
        } else {
            Toast.makeText(MainActivity.this, "请输入正确IP", Toast.LENGTH_SHORT).show();
        }
    }

    public void send(View view) {
        String msg = mEditTextSend.getText().toString();
        if (msg != null && !"".equals(msg)) {
            if (mJWSClient != null && mJWSClient.isOpen()) {
                Log.d(TAG, "send: ");
                mJWSClient.send(msg);
            }
        } else {
            Toast.makeText(MainActivity.this, "发送不能为空", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 将ByteBuffer转换为String
     *
     * @param buffer
     * @return
     */
    public static String getString(ByteBuffer buffer) {
        Charset charset = null;
        CharsetDecoder decoder = null;
        CharBuffer charBuffer = null;
        try {
            charset = Charset.forName("UTF-8");
            decoder = charset.newDecoder();
            // charBuffer = decoder.decode(buffer);//用这个的话,只能输出来一次结果,第二次显示为空
            charBuffer = decoder.decode(buffer.asReadOnlyBuffer());
            return charBuffer.toString();
        } catch (Exception ex) {
            ex.printStackTrace();
            return "";
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mJWSClient != null) {
            mJWSClient.close();
        }
    }
}

serve端

继承WebSocketServer

与client差不多,只不过会多一个WebSocket参数,每当有一个client去连接serve端时,就会生成一个WebSocket对象,可以获取client的ip地址。

onClose时,无法通过WebSocket获取地址。

service端启动

传入端口创建WebSocketServer对象,调用start方法即可。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private TextView mTextViewReceive;
    private TextView mTextConnectClient;
    private JWSServe mJWSServe;
    private List<String> mClientIpList = new ArrayList<>();

    private JWSServe.JWSCallBack mJWSCallBack = new JWSServe.JWSCallBack() {
        @Override
        public void onOpen(ClientHandshake handshake) {

        }

        @Override
        public void onMessage(String message) {
            Message msg = Message.obtain();
            msg.what = 0;
            msg.obj = message;
            mUIHandler.sendMessage(msg);
        }

        @Override
        public void onMessage(ByteBuffer bytes) {
            Message msg = Message.obtain();
            msg.what = 0;
            msg.obj = getString(bytes);
            mUIHandler.sendMessage(msg);
        }

        @Override
        public void updateSocketClient(HashMap<WebSocket, String> socketHashMap) {
            mClientIpList = new ArrayList<>(socketHashMap.values());
            mUIHandler.sendEmptyMessage(1);
        }
    };

    private Handler mUIHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    mTextViewReceive.setText((String) msg.obj);
                    break;
                case 1:
                    StringBuffer stringBuffer = new StringBuffer();
                    if (mClientIpList.size() < 1) {
                        stringBuffer.append("当前无设备连接\n");
                    } else {
                        for (String ip : mClientIpList) {
                            stringBuffer.append(ip + ":已连接\n");
                        }
                    }
                    mTextConnectClient.setText(stringBuffer.toString());
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextViewReceive = findViewById(R.id.text_receive);
        mTextConnectClient = findViewById(R.id.text_connect_client);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mJWSServe = new JWSServe(13333, mJWSCallBack);
        mJWSServe.setConnectionLostTimeout(5);
        mJWSServe.start();
    }

    public void send(View view) {
        String msg = ((EditText) findViewById(R.id.edit_send)).getText().toString();
        if (msg != null && !"".equals(msg)) {
            mJWSServe.send(mClientIpList, msg);
        } else {
            Toast.makeText(MainActivity.this, "发送不能为空", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 将ByteBuffer转换为String
     *
     * @param buffer
     * @return
     */
    public static String getString(ByteBuffer buffer) {
        Charset charset = null;
        CharsetDecoder decoder = null;
        CharBuffer charBuffer = null;
        try {
            charset = Charset.forName("UTF-8");
            decoder = charset.newDecoder();
            // charBuffer = decoder.decode(buffer);//用这个的话,只能输出来一次结果,第二次显示为空
            charBuffer = decoder.decode(buffer.asReadOnlyBuffer());
            return charBuffer.toString();
        } catch (Exception ex) {
            ex.printStackTrace();
            return "";
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        try {
            mJWSServe.stop();
        } catch (IOException exception) {
            exception.printStackTrace();
        } catch (InterruptedException exception) {
            exception.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

JavaWebSocket的心跳机制

client和server的重连是依靠PingPong

如client调用ping方法,如果server收到消息会,会立刻给client回复一个Pong。则client的Pong方法会立刻被调用。

实际上实现使用JavaWebSocket并不需要如此复杂,在server端启动或client端连接前,通过setConnectionLostTimeout设置一个连接超时的时间(未设置则默认时间一分钟,设置为0或负数表示关闭超时检测)。

serve会根据设置时间检测client列表有哪些已超时,则会走onClose。

client设置时间后,serve会每隔固定式见向client发送ping,如果client超时没有收到pong,则走onError和onClose。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值