終於建了一個自己個人小站:https://huangtianyu.gitee.io,以後優先更新小站博客,歡迎進站,O(∩_∩)O~~
不多說,先上代碼。記得點擊star哦,代碼地址是:SocketDemo
上一篇文章寫了如何通過Java層實現Socket和服務器的Socket進行通信,這一篇繼續深究,寫個如何通過native層實現socket和服務器進行通信。服務器端代碼和前一篇博客 代碼一致,主要看下Android端的代碼。首先看下Main2Activity的代碼:
Main2Activity.java
package com.zqc.socketdemo;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class Main2Activity extends Activity implements View.OnClickListener {
private TextView tv;
private Button bt;
Handler handler;
private Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
tv = (TextView) findViewById(R.id.textView);
bt = (Button) findViewById(R.id.button);
bt.setOnClickListener(this);
mContext = this;
handler = new Handler(Looper.getMainLooper());
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
new Thread(){//網絡連接需要在線程中實現
@Override
public void run() {
SocketJNI.connect(mContext, "10.18.73.62", 6868);//和服務器進行socket通信
}
}.start();
break;
}
}
}
對應的xml佈局是activity_main2.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main2"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.zqc.socketdemo.Main2Activity">
<TextView
android:text="TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/button"
android:layout_alignStart="@+id/button"
android:layout_marginTop="69dp"
android:id="@+id/textView" />
<Button
android:text="Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="11dp"
android:id="@+id/button"
android:layout_marginStart="27dp"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true" />
</RelativeLayout>
其中SocketJNI.java的代碼很簡單,代碼如下:package com.zqc.socketdemo;
import android.content.Context;
/**
* Created by zhangqianchu on 2017/8/2.
*/
public class SocketJNI {
static {
System.loadLibrary("mysocket");
}
public native static void connect(Context mContext, String ip, int port);//這裏定義了一個jni方法,在native層實現socket連接
}
在SocketJNI.java類中定義了一個jni方法,即提供一個Java層的調用,具體代碼在native層實現。下面看看native層的代碼#include <jni.h>
#include <string.h>
#include "socketutil.h"
void showToast(JNIEnv *env, jobject context, jstring str) {
jclass tclss = (*env)->FindClass(env,"android/widget/Toast");
jmethodID mid = (*env)->GetStaticMethodID(env,tclss,"makeText","(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;");
jobject job = (*env)->CallStaticObjectMethod(env,tclss,mid,context,str);
jmethodID showId = (*env)->GetMethodID(env,tclss,"show","()V");
(*env)->CallVoidMethod(env,job,showId,context,str);
}
JNIEXPORT void JNICALL Java_com_zqc_socketdemo_SocketJNI_connect
(JNIEnv *env, jclass jcz, jobject context, jstring addr, jint port) {
jstring msg = (*env)->NewStringUTF(env,"start");
//showToast(env,context,msg);//show的時候會出現暫時卡頓,原因不明
LOGI("connect socket.");
const char* response = connectRemote("10.18.73.62", 6868); //response就是服務端傳給客戶端的內容
release();
LOGI("connect socket end.");
//msg = (*env)->NewStringUTF(env,response);
//showToast(env,context,msg);/
}
這裏將socket的具體實現放到socketutil.h裏面進行實現,避免代碼耦合。socketutil.h
#ifndef SOCKET_UTIL
#define SOCKET_UTIL
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "mysocket"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
void release();
const char* connectRemote(const char*,const int);
#endif
socketutil.c#include <jni.h>
#include <stdio.h>
#include <string.h>
#include <android/log.h>
#include "socketutil.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <android/log.h>
#define MAXSIZE 4096
struct sockaddr_in sock_add;
struct sockaddr_in server_addr;
int sockfd = 0;
void release(){
close(sockfd);
}
const char* connectRemote(const char* addr,const int port) {
sockfd = socket(AF_INET, SOCK_STREAM, 0); //ipv4,TCP數據連接
LOGI("step1");
if (sockfd < 0) {
return "socket error";
}
//服務器地址
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
LOGI("step2");
if( inet_pton(AF_INET, addr, &server_addr.sin_addr) < 0){ //設置ip地址
LOGI("address error");
return "address error";
}
socklen_t server_addr_length = sizeof(server_addr);
int connfd = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); //連接到服務器
LOGI("step3");
if (connfd < 0) {
return "connect error";
}
//發送數據
char *msg = "i send a data to server. \n";
int sdf = send(sockfd, msg , strlen(msg), 0); //發送一行數據到服務器
LOGI("step4.");
char buff[4096];
int n = recv(sockfd, buff, MAXSIZE ,0); //這地方應該需要循環獲取數據,目前服務器只響應了一條很短的字符串。
buff[n] = 0;
LOGI("step5.");
return buff;
}
這裏的socket連接使用的是最基本的Socket連接方法,首先設置協議(IPV4協議),然後設置ip地址和port端口號,之後便嘗試連接服務器,調用的連接方法是sys/socket.h庫中的函數,連接成功後會返回一個int值,若該值小於0則表示連接出錯。上述代碼編寫好之後,再編寫個Android.mk,然後利用ndk-build命令將c代碼編譯爲so庫,放入到工程的libs目錄下。其中Android.mk代碼如下:
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION :=cpp #針對C++的支持,標記c++文件的擴展名名稱
LOCAL_MODULE := mysocket
LOCAL_SRC_FILES := socketutil.c \
socketjni.c
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
爲了少編譯些版本,也可以加入一個Application.mk,代碼如下:Application.mk
#APP_MODULES := mysocket //so庫的名稱,在Android.mk裏面設置了
#APP_STL:=stlport_static
APP_ABI := armeabi #編譯的版本有哪些
#APP_OPIM :=debug
利用ndk-build命令進行編譯,ndk-build命令會直接將so庫放入到libs目錄下,然後運行工程,點擊裏面的Button按鈕,即可查看連接結果。注意,手機要和服務端在同一個局域網裏面,而且在運行的時候要修改下代碼裏面的ip地址。運行後輸出的Log如下:
上面的step1到step5表示運行到了哪一步,方便查看具體是在哪一步出錯了。
服務端輸出如下: