test websocket demo for Android

This commit is contained in:
shuxin   zheng 2020-05-08 14:43:30 +08:00
parent 2bd1024971
commit 3250875984
14 changed files with 214 additions and 138 deletions

View File

@ -36,16 +36,16 @@
<output-test url="file://$MODULE_DIR$/build/intermediates/javac/debugUnitTest/compileDebugUnitTestJavaWithJavac/classes" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/renderscript_source_output_dir/debug/compileDebugRenderscript/out" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/renderscript_source_output_dir/debug/compileDebugRenderscript/out" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/aidl_source_output_dir/debugAndroidTest/compileDebugAndroidTestAidl/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/renderscript_source_output_dir/debugAndroidTest/compileDebugAndroidTestRenderscript/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/aidl_source_output_dir/debugAndroidTest/compileDebugAndroidTestAidl/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/test/debug" isTestSource="true" generated="true" />

View File

@ -121,8 +121,8 @@ if(CMAKE_SYSTEM_NAME MATCHES "Android")
set(lib_all ${lib_acl_cpp} ${lib_protocol} ${lib_acl} -lz)
endif()
add_library(native-lib SHARED ${lib_src})
target_link_libraries(native-lib ${lib_all} ${log-lib})
add_library(ws SHARED ${lib_src})
target_link_libraries(ws ${lib_all} ${log-lib})
###############################################################################

View File

@ -1,54 +0,0 @@
//
// Created by shuxin   zheng on 2020-03-29.
//
#include "stdafx.h"
#include "util.h"
#include "websocket.h"
extern "C" JNIEXPORT void JNICALL
Java_com_example_http_HttpClient_WebsocketStart(
JNIEnv*,
jobject)
{
log_info("START!\r\n");
websocket_run();
log_info("FINISH!\r\n");
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_http_HttpClient_HttpGet(
JNIEnv* env,
jobject me,
jstring addr,
jstring host,
jstring url)
{
std::string server_addr, server_host, server_url;
JString2String(env, addr, server_addr);
JString2String(env, host, server_host);
JString2String(env, url, server_url);
acl::http_request request(server_addr.c_str());
acl::http_header& header = request.request_header();
header.set_url(server_url.c_str())
.set_host(server_host.c_str())
.accept_gzip(true)
.set_keep_alive(false);
log_info("addr=%s, host=%s, url=%s", server_addr.c_str(),
server_host.c_str(), server_url.c_str());
if (!request.request(NULL, 0)) {
log_error("send http request error=%s", acl::last_serror());
return NULL;
}
acl::string body;
if (!request.get_body(body)) {
log_error("get body error: %s", acl::last_serror());
return NULL;
}
return String2JString(env, body);
}

View File

@ -0,0 +1,37 @@
//
// Created by shuxin   zheng on 2020-03-29.
//
#include "stdafx.h"
#include "util.h"
#include "websocket.h"
static void websocket_callback(void* env_ctx, void* obj_ctx, const char* msg)
{
JNIEnv* env = (JNIEnv*) env_ctx;
jobject obj = (jobject) obj_ctx;
jclass clz = env->GetObjectClass(obj);
if (clz) {
jmethodID mID = env->GetMethodID(clz, "onMessage", "(Ljava/lang/String;)V");
if (mID) {
jstring s = String2JString(env, msg);
env->CallVoidMethod(obj, mID, s);
}
}
}
extern "C" JNIEXPORT void JNICALL
Java_com_example_http_HttpClient_WebsocketStart(
JNIEnv* env,
jobject obj,
jstring addr)
{
log_open();
log_info("START!\r\n");
std::string server_addr;
JString2String(env, addr, server_addr);
websocket_run(server_addr.c_str(), websocket_callback, env, obj);
log_info("FINISH!\r\n");
}

View File

@ -30,4 +30,56 @@ void log_error(const char* fmt, ...)
va_end(ap);
}
static void write_callback(void*, const char* fmt, va_list ap)
{
char buf[512];
vsnprintf(buf, sizeof(buf), fmt, ap);
__android_log_print(ANDROID_LOG_INFO, "dns-debug", "%s", buf);
}
static bool __opened = false;
static int open_callback(const char*, void*)
{
return 0;
}
static void logger_hook(void (*write_callback)(void*, const char* fmt, va_list ap))
{
if (__opened) {
return;;
}
__opened = true;
acl_msg_register(open_callback, NULL, write_callback, NULL);
acl_msg_open("dummy.txt", "dummy");
logger("acl lib init ok!");
}
static void logger_unhook(void)
{
if (!__opened) {
return;;
}
__opened = false;
acl_msg_unregister();
}
void log_open(void)
{
if (__opened) {
return;
}
__opened = true;
log_info("call logger_hook\r\n");
logger_hook(write_callback);
log_info("call logger_hook ok\r\n");
}
void log_close(void)
{
if (__opened) {
logger_unhook();
__opened = false;
}
}

View File

@ -2,5 +2,8 @@
void JString2String(JNIEnv *env, jstring js, std::string &out);
jstring String2JString(JNIEnv *env, const char *s);
void log_open(void);
void log_close(void);
void log_info(const char* fmt, ...);
void log_error(const char* fmt, ...);

View File

@ -9,9 +9,13 @@ static acl::atomic_long __aio_refer = 0;
class websocket_client : public acl::http_aclient
{
public:
websocket_client(acl::aio_handle& handle, const char* host)
websocket_client(acl::aio_handle& handle, const char* host,
void (*callback)(void*, void*, const char*), void* env, void* obj)
: http_aclient(handle, NULL)
, host_(host)
, callback_(callback)
, env_(env)
, obj_(obj)
, debug_(false)
, compressed_(false)
{
@ -50,6 +54,7 @@ protected:
{
log_info("%s(%d): websocket_client will be deleted!\r\n",
__FUNCTION__, __LINE__);
on_message("websocket will be deleted");
delete this;
}
@ -58,8 +63,10 @@ protected:
bool on_connect(void)
{
log_info("--------------- connect server ok ------------\r\n");
on_message("connect server ok");
show_ns_addr();
log_info(">>> begin ws_handshake\r\n");
on_message("begin ws handshake");
this->ws_handshake();
return true;
@ -72,6 +79,8 @@ protected:
reqhdr.build_request(buf);
log_info("---------------websocket request header---------\r\n");
log_info("[%s]\r\n", buf.c_str());
on_message("ws request header");
on_message(buf.c_str());
}
// @override
@ -79,18 +88,21 @@ protected:
{
log_info("%s(%d): disconnect from server\r\n",
__FUNCTION__, __LINE__);
on_message("disconnect from server");
}
// @override
void on_ns_failed(void)
{
log_info("dns lookup failed\r\n");
on_message("dns lookup failed");
}
// @override
void on_connect_timeout(void)
{
log_info("connect timeout\r\n");
on_message("connect timeout");
show_ns_addr();
}
@ -98,6 +110,7 @@ protected:
void on_connect_failed(void)
{
log_info("connect failed\r\n");
on_message("connect failed");
show_ns_addr();
}
@ -105,6 +118,7 @@ protected:
bool on_read_timeout(void)
{
log_info("read timeout\r\n");
on_message("read timeout");
return true;
}
@ -119,6 +133,8 @@ protected:
log_info("-----------%s: response header----\r\n", __FUNCTION__);
log_info("[%s]\r\n", buf.c_str());
on_message("response header");
on_message(buf.c_str());
return true;
}
@ -153,6 +169,7 @@ protected:
bool on_ws_handshake(void)
{
log_info(">>> websocket handshake ok\r\n");
on_message("websocket handshake ok");
char buf[128];
snprintf(buf, sizeof(buf), "hello, myname is zsx\r\n");
@ -162,6 +179,8 @@ protected:
return false;
}
log_info("send ok\r\n");
on_message("send ok");
// 开始进入 websocket 异步读过程
this->ws_read_wait(5);
return true;
@ -171,26 +190,30 @@ protected:
void on_ws_handshake_failed(int status)
{
log_info(">>> websocket handshake failed, status=%d\r\n", status);
on_message("websocket handshake failed");
}
// @override
bool on_ws_frame_text(void)
{
log_info(">>> got frame text type\r\n");
log_info(">>> got frame text\r\n");
on_message("got fame text");
return true;
}
// @override
bool on_ws_frame_binary(void)
{
log_info(">>> got frame binaray type\r\n");
log_info(">>> got frame binaray\r\n");
on_message("got frame binary");
return true;
}
// @override
void on_ws_frame_closed(void)
{
log_info(">>> got frame closed type\r\n");
log_info(">>> got frame closed\r\n");
on_message("got frame closed");
}
// @override
@ -199,6 +222,7 @@ protected:
acl::string buf;
buf.copy(data, dlen);
log_info("%s", buf.c_str());
on_message(buf.c_str());
//(void) write(1, data, dlen);
return true;
@ -208,20 +232,35 @@ protected:
bool on_ws_frame_finish(void)
{
log_info(">>> frame finish\r\n");
on_message("frame finished");
return true;
}
private:
acl::string host_;
void (*callback_)(void*, void*, const char*);
void* env_;
void* obj_;
bool debug_;
bool compressed_;
void on_message(const char* fmt, ...)
{
acl::string buf;
va_list ap;
va_start(ap, fmt);
buf.vformat(fmt, ap);
va_end(ap);
callback_(env_, obj_, buf.c_str());
}
};
bool websocket_run(void)
bool websocket_run(const char* addr, void (*callback)(void*, void*, const char*),
void* env, void* obj)
{
int conn_timeout = 5, rw_timeout = 5;
acl::string addr("10.110.28.210:8885");
acl::string host("www.baidu.com");
acl::string host("www.test.com");
std::vector<acl::string> name_servers;
bool debug = false;
@ -243,9 +282,9 @@ bool websocket_run(void)
// 开始异步连接远程 WEB 服务器
websocket_client* conn = new websocket_client(handle, host);
websocket_client* conn = new websocket_client(handle, host, callback, env, obj);
if (!conn->open(addr, conn_timeout, rw_timeout)) {
log_info("connect %s error\r\n", addr.c_str());
log_info("connect %s error\r\n", addr);
delete conn;
return false;
@ -279,6 +318,7 @@ bool websocket_run(void)
}
}
// 再检测一次,以便于将可能漏掉的非阻塞连接对象释放
handle.check();
return true;
}

View File

@ -1,3 +1,12 @@
#pragma once
bool websocket_run(void);
/**
* Websocket
* @param addr {const char*} Websocket
* @param callback {void (*)(void*, void*, const char*)}
* @param env {void*}
* @param obj {void*}
* @return {bool}
*/
bool websocket_run(const char* addr, void (*callback)(void*, void*, const char*),
void* env, void* obj);

View File

@ -1,28 +1,31 @@
package com.example.http;
public final class HttpClient {
private HttpHandler handler;
private String serverAddr;
public HttpClient(String addr) {
public HttpClient(HttpHandler handler, String addr) {
this.handler = handler;
this.serverAddr = addr;
}
public String get(String host, String url) {
try {
String body = HttpGet(serverAddr, host, url);
return body;
} catch (UnsatisfiedLinkError e) {
System.out.println("HttpGet error=" + e.toString());
return null;
}
/**
* 启动并测试 Websocket 客户端
*/
public void websocketStart() {
WebsocketStart(serverAddr);
}
public void websocketStart() {
WebsocketStart();
/**
* jni 中回调此方法
* @param msg {String}
*/
public void onMessage(String msg) {
handler.onData(msg);
}
////////////////////////////////////////////////////////////////////////////
// Jni Native method
private native String HttpGet(String serverAddr, String host, String url);
private native void WebsocketStart();
private native void WebsocketStart(String addr);
}

View File

@ -4,8 +4,7 @@ import android.os.Handler;
import android.os.Message;
public final class HttpHandler extends Handler {
final public static int BODY_OK = 0;
final public static int BODY_ERR = 1;
final public static int DATA_OK = 0;
private final MainActivity mainActivity;
public HttpHandler(MainActivity mainActivity) {
@ -15,11 +14,8 @@ public final class HttpHandler extends Handler {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case BODY_OK:
mainActivity.onBody((String) message.obj);
break;
case BODY_ERR:
mainActivity.onError((String) message.obj);
case DATA_OK:
mainActivity.onData((String) message.obj);
break;
default:
break;
@ -27,26 +23,13 @@ public final class HttpHandler extends Handler {
}
/**
* 成功获得 HTTP 数据体的回调方法
* @param body {String} 响应的 HTTP 数据体
* Websocket 线程空间中的回调方法用来传递消息
* @param data {String} 响应的 HTTP 数据体
*/
public void onBody(String body) {
public void onData(String data) {
Message message = new Message();
message.what = BODY_OK;
message.obj = body;
this.sendMessage(message);
}
/**
* 获取 HTTP 结果失败时的回调方法
* @param domain {String} 请求的域名
* @param url {String} 请求的 URL
*/
public void onError(String domain, String url) {
String error = "Domain: domain, " + "url: url";
Message message = new Message();
message.what = BODY_ERR;
message.obj = error;
message.what = DATA_OK;
message.obj = data;
this.sendMessage(message);
}
}

View File

@ -3,20 +3,15 @@ package com.example.http;
public final class HttpThread extends Thread {
private HttpHandler handler;
private String serverAddr;
private String host;
private String url;
public HttpThread(HttpHandler handler, String serverAddr,
String host, String url) {
this.handler = handler;
public HttpThread(HttpHandler handler, String serverAddr) {
this.handler = handler;
this.serverAddr = serverAddr;
this.host = host;
this.url = url;
}
@Override
public void run() {
HttpClient httpClient = new HttpClient(serverAddr);
HttpClient httpClient = new HttpClient(handler, serverAddr);
httpClient.websocketStart();
}
}

View File

@ -3,6 +3,7 @@ package com.example.http;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
@ -11,10 +12,11 @@ import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private EditText domain;
private TextView result;
StringBuffer text = new StringBuffer();
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
System.loadLibrary("ws");
}
/**
@ -22,7 +24,6 @@ public class MainActivity extends AppCompatActivity {
* @param info
*/
public void onError(String info) {
StringBuffer text = new StringBuffer();
text.append("httpGet error\r\n");
text.append(info + "\r\n");
result.setText(text);
@ -30,30 +31,34 @@ public class MainActivity extends AppCompatActivity {
/**
* 获得 HTTP 数据体成功的回调方法
* @param body
* @param data
*/
public void onBody(String body) {
result.setText(body);
public void onData(String data) {
result.append(data);
result.append("\r\n");
int offset = result.getLineCount() * result.getLineHeight();
if (offset > result.getHeight()){
result.scrollTo(0,offset - result.getHeight());
}
}
/**
* HTTP 服务器获得数据
* HTTP Websocket 服务器获得数据
*/
private void httpGet() {
String host = domain.getText().toString().trim();
if (host.isEmpty()) {
private void websocketStart() {
String addr = domain.getText().toString().trim();
if (addr.isEmpty()) {
return;
}
System.out.println("host is " + host);
System.out.println("addr is " + addr);
try {
HttpHandler handler = new HttpHandler(this);
String addr = host + ":80", url = "/";
HttpThread httpThread = new HttpThread(handler, addr, host, url);
HttpThread httpThread = new HttpThread(handler, addr);
httpThread.start();
} catch (Exception e) {
e.printStackTrace();
onError(host);
onError(addr);
}
}
@ -64,13 +69,15 @@ public class MainActivity extends AppCompatActivity {
domain = (EditText) findViewById(R.id.domain);
result = findViewById(R.id.result);
result.setMovementMethod(ScrollingMovementMethod.getInstance());
result.setScrollbarFadingEnabled(false);
// 绑定 HTTP 请求事件
Button get = (Button) findViewById(R.id.http_get);
get.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
httpGet();
websocketStart();
}
});

View File

@ -23,15 +23,16 @@
android:layout_centerVertical="true"
android:text="域名:"
android:textSize="20sp" />
<EditText
android:id="@+id/domain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/domain_lable"
android:ems="10"
android:inputType="textPersonName"
android:layout_toRightOf="@+id/domain_lable"
android:layout_centerVertical="true"
android:text="www.baidu.com"
android:text="10.110.28.210|8885"
android:verticalScrollbarPosition="defaultPosition"
tools:layout_editor_absoluteX="88dp"
tools:layout_editor_absoluteY="97dp" />
@ -76,6 +77,7 @@
android:paddingBottom="40dp"
android:scrollbarStyle="outsideInset"
android:scrollbars="vertical"
android:fillViewport="true"
android:scrollHorizontally="true"
android:text="TextView"
android:visibility="visible"

View File

@ -381,26 +381,25 @@ bool http_aclient::handle_websocket(void)
}
}
// 在 SSL 握手阶段,该方法会多次调用,直至 SSL 握手成功或失败
// SSL 握手和读 Websocket 数据帧阶段,该方法都会被多次调用。
// 当启用 SSL 模式时,在 SSL 握手阶段,该方法会被多次调用,直至 SSL 握手成功
// 或失败;在读取 Websocket 数据帧阶段,该方法也会被多次调用,用来一直读取数据帧
bool http_aclient::read_wakeup(void)
{
#if defined(HAS_POLARSSL_DLL) || defined(HAS_POLARSSL)
// 如果 websocket 非 NULL则说明进入到 websocket 通信方式,
// 该触发条件在 http_res_hdr_cllback 中注册
switch (status_) {
case HTTP_ACLIENT_STATUS_WS_READING:
acl_assert(ws_in_);
return handle_websocket();
#if defined(HAS_POLARSSL_DLL) || defined(HAS_POLARSSL)
case HTTP_ACLIENT_STATUS_SSL_HANDSHAKE:
return handle_ssl_handshake();
#endif
default:
logger_error("invalid status=%u", status_);
return false;
}
#else
logger_error("shouldn't come here in no SSL mode!");
return false;
#endif
}
bool http_aclient::handle_ssl_handshake(void)