开发环境:ubuntu10.10 32位
服务端:ok6410 烧入linux系统
客户端:android手机
在ok6410上编写tcp服务端程序是用c语言编写,android的应用程序是用java语言编写的,因此要想实现与linux c语言的通信则必须通过JNI来实现,在本次设计中,tcp客户端程序是用c语言写的,通过NDK编译生成库文件,在android的应用程序中调用c语言程序来实现与linux c的通信,目前本程序只是实现了简单的通信。
1.服务端程序
/* net_select.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 1234
#define MAX_QUE_CONN_NM 5
#define MAX_SOCK_FD FD_SETSIZE
#define BUFFER_SIZE 1024 //缓存区
struct user_info
{
char userName[20];
char userPasswd[20];
char serverIP[20];
};
int main()
{
struct sockaddr_in server_sockaddr, client_sockaddr;
int sin_size, count;
fd_set inset, tmp_inset;
int sockfd, client_fd, fd;
char buf[BUFFER_SIZE];
char clientIP[20];
struct user_info userInfo;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}//建立socket链接,流式socket,IPv4协议
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = INADDR_ANY;//0.0.0.0不确定地址
bzero(&(server_sockaddr.sin_zero), 8);//填充0以保持与struct sockaddr同样大小
int i = 1;/* 允许重复使用本地地址与套接字进行绑定*/
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr)) == -1)
{
perror("bind");
exit(1);
}
printf("bind success!\n");
if(listen(sockfd, MAX_QUE_CONN_NM) == -1)
{
perror("listen");
exit(1);
}
printf("listening....\n");
/*将调用socket()函数的描述符作为文件描述符*/
FD_ZERO(&inset); //清空inset文件描述集
FD_SET(sockfd, &inset); //将sockfd加入inset文件描述集
while(1)
{
tmp_inset = inset;
sin_size=sizeof(struct sockaddr_in);
memset(buf, 0, sizeof(buf)); //初始化buf缓存区
/*调用select()函数*/
if (!(select(MAX_SOCK_FD, &tmp_inset, NULL, NULL, NULL) > 0))
{
perror("select");
close(sockfd);
exit(1);
}
for (fd = 0; fd < MAX_SOCK_FD; fd++)
{
if (FD_ISSET(fd, &tmp_inset) > 0)
{
if (fd == sockfd)
{ /* 服务端接收客户端的链接请求 */
if ((client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, &sin_size))== -1)
{
perror("accept");
exit(1);
}
memset(clientIP, 0 ,sizeof(clientIP));
inet_ntop(AF_INET, (void *)(&client_sockaddr.sin_addr), clientIP, sizeof(clientIP));
FD_SET(client_fd, &inset);
printf("New connection from %d(socket)\n", client_fd);
printf("the client IP is :%s\n",clientIP);
}
else /* 处理从客户端发来的消息*/
{
if ((count = recv(fd, &userInfo, sizeof(struct user_info), 0)) > 0)
{
printf("Received a message from %d\n", fd);
printf("%s,%s,%s\n",userInfo.serverIP, userInfo.userName, userInfo.userPasswd);
strcpy(buf, "success");
send(fd, buf, strlen(buf), 0);
}
else
{
close(fd);
FD_CLR(fd, &inset);
printf("Client %d(socket) has left\n", fd);
}
}
} /* end of if FD_ISSET*/
} /* end of for fd*/
} /* end if while while*/
close(sockfd);
exit(0);
}
在服务端中,是用select方式来实现多路复用的,即实现多个客户端连接服务端,程序中定义了user_info结构体用来存放从客户端传入的数据,这是因为以后要用到sqlite数据库,因此才定义的这个结构体,另外,在服务端中,我们通过客户端的socket连接来获取客户端的ip地址,也可以在客户端获取本地ip,将ip发送给服务端,服务端接收到数据后即发送“success”字符串给客户端。
客户端:
/*client.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <jni.h>
#include <android/log.h>
#define PORT 1234
#define BUFFER_SIZE 20
typedef struct
{
char userName[20];
char userPasswd[20];
char serverIP[20];
} user_info;
char* Jstring2CStr(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env,"java/lang/String");
jstring strencode = (*env)->NewStringUTF(env,"GB2312");
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312");
jsize alen = (*env)->GetArrayLength(env,barr);
jbyte* ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE);
if(alen > 0)
{
rtn = (char*)malloc(alen+1); //new char[alen+1]; "\0"
memcpy(rtn,ba,alen);
rtn[alen]=0;
}
(*env)->ReleaseByteArrayElements(env,barr,ba,0); //释放内存
return rtn;
}
jint
Java_com_videoclient_forlinx_VideoMonitor_LoginFromJNI(JNIEnv *env,
jobject thiz, jstring serverIP, jstring userName, jstring userPasswd)
{
int sockfd, sendbytes, recvbytes;
char buf[BUFFER_SIZE];
struct hostent *host;
struct sockaddr_in serv_addr;
user_info userInfo;
__android_log_print(ANDROID_LOG_INFO, "Project Name", "call success");
memset(buf, 0, sizeof(buf));
strcpy(userInfo.serverIP, Jstring2CStr(env, serverIP));
strcpy(userInfo.userName, Jstring2CStr(env, userName));
strcpy(userInfo.userPasswd, Jstring2CStr(env, userPasswd));
__android_log_print(ANDROID_LOG_INFO, "Project Name", userInfo.serverIP);
__android_log_print(ANDROID_LOG_INFO, "Project Name", userInfo.userName);
__android_log_print(ANDROID_LOG_INFO, "Project Name", userInfo.userPasswd);
__android_log_print(ANDROID_LOG_INFO, "Project Name", userInfo.userIP);
//创建socket
if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("socket");
exit(1);
}
__android_log_print(ANDROID_LOG_INFO, "Project Name", "create socket success");
//设置socketaddr_in结构体中相关参数
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = inet_addr(userInfo.serverIP);
bzero(&(serv_addr.sin_zero), 8);
__android_log_print(ANDROID_LOG_INFO, "Project Name", "set serv_addr param");
//调用connect函数主动发起对服务器端的连接
if(connect(sockfd,(struct sockaddr *)&serv_addr, sizeof(struct sockaddr))== -1)
{
perror("connect");
exit(1);
}
__android_log_print(ANDROID_LOG_INFO, "Project Name", "connect success");
//发送消息给服务端
if ((sendbytes = send(sockfd, &userInfo, sizeof(user_info), 0)) == -1)
{
perror("send");
exit(1);
}
__android_log_print(ANDROID_LOG_INFO, "Project Name", "send success");
memset(buf, 0, sizeof(buf));
if(recvbytes = recv(sockfd, buf, BUFFER_SIZE, 0) < 0)
{
perror("recv");
exit(1);
}
__android_log_print(ANDROID_LOG_INFO, "Project Name", "recv success");
printf("Recvived a result:%s\n", buf);
sleep(3);
close(sockfd);
if(strncmp(buf, "success",7) == 0)
{
return 1;
}
else
{
return 0;
}
return 0;
}
在客户端中,同样定义了user_info结构体,在LoginFromJNI函数中用到了前面提到的java与c之间的数据传递,这里传递的是string类型的数据,用到Jstring2CStr这个函数将java中的string数据转成c语言中的string数据,直接使用的话,数据是乱码,suer_info中的数据是从java的应用程序中传递过来的。
3. android 应用程序端
static
{
try
{
System.loadLibrary("client");
}
catch(UnsatisfiedLinkError ulink)
{
Log.i("client", "can not be loaded");
ulink.printStackTrace();
}
}
加载生成的库。
public native int LoginFromJNI(String serverIP, String userName, String userPasswd, String userIP);
public int Login()
{
return this.LoginFromJNI(this.serverIP, this.userName, this.userPasswd, this.userIP);
}
声明函数,调用该函数。最后为应用程序添加访问internet权限,至此,即可实现android与ok6410通信。