java与c 通信_Java与C之间的socket通信
最近正在開(kāi)發(fā)一個(gè)基于指紋的音樂(lè)檢索應(yīng)用,算法部分已經(jīng)完成,所以嘗試做一個(gè)Android App。Android與服務(wù)器通信通常采用HTTP通信方式和Socket通信方式。由于對(duì)web服務(wù)器編程了解較少,而且后臺(tái)服務(wù)器已經(jīng)采用原始socket實(shí)現(xiàn)與c客戶端通信,這就要求Android客戶端也采用socket實(shí)現(xiàn)。所以在開(kāi)發(fā)Android app時(shí)采用了原始socket進(jìn)行編程。
由于算法是用C語(yǔ)言實(shí)現(xiàn)的,而Android應(yīng)用一般是Java開(kāi)發(fā),這就不可避免得涉及Java和C語(yǔ)言之間的通信問(wèn)題。一種方案是在客戶端采用JNI方式,上層UI用Java開(kāi)發(fā),但是底層通信還是用C的socket完成。這種方案需要掌握J(rèn)NI編程,對(duì)不少Java開(kāi)發(fā)者是個(gè)障礙。為了減小開(kāi)發(fā)難度,最好的方案是直接用Java socket與C socket進(jìn)行通信。但是這種方案也有問(wèn)題,最大的問(wèn)題在于API和數(shù)據(jù)格式的不統(tǒng)一。本人在本科曾嘗試?yán)肑ava和c的socket進(jìn)行通信,發(fā)現(xiàn)根本無(wú)法傳遞數(shù)據(jù),一度認(rèn)為這兩種socket之間無(wú)法通信。今天重拾舊問(wèn)題,必須一次性地完美地解決Java和C之間的socket通信問(wèn)題。在此可以先將實(shí)現(xiàn)總結(jié)為1句話:通信全部用字節(jié)實(shí)現(xiàn)。
在介紹Java和c之間的socket通信之前,首先將音樂(lè)檢索大概介紹一下,更詳細(xì)的內(nèi)容可參考基于指紋的音樂(lè)檢索。基于指紋的音樂(lè)檢索就是讓用戶錄制一段正在播放的音樂(lè)上傳服務(wù)器,服務(wù)器通過(guò)提取指紋進(jìn)行檢索獲得相應(yīng)的歌名返回給用戶,就這么簡(jiǎn)單。簡(jiǎn)單的工作原理如圖一。所以在該應(yīng)用中,socket通信主要涉及兩個(gè)方面:客戶端向服務(wù)器發(fā)送文件和服務(wù)器向客戶端發(fā)送結(jié)果兩部分。下面先介紹服務(wù)器部分。
圖1 音樂(lè)檢索的簡(jiǎn)單工作原理示意圖
1 服務(wù)器設(shè)計(jì)
服務(wù)器端采用C socket進(jìn)行通信,同時(shí)為了能響應(yīng)多用戶請(qǐng)求,服務(wù)器端需要采用多線程編程。為了專注于socket通信,已經(jīng)將無(wú)關(guān)代碼去掉,首先看main方法。
typedef struct
{
int client_sockfd;
……
}client_arg;
void get_ip_address(unsigned long address,char* ip)
{
sprintf(ip,"%d.%d.%d.%d",address>>24,(address&0xFF0000)>>24,(address&0xFF00)>>24,address&0xFF);
}
int main()
{
int server_sockfd;
int server_len;
struct sockaddr_in server_address;
int result;
server_sockfd=socket(AF_INET,SOCK_STREAM,0);
server_address.sin_family=AF_INET;
server_address.sin_addr.s_addr=htonl(INADDR_ANY);
server_address.sin_port=htons(9527);
server_len=sizeof(server_address);
bind(server_sockfd,(struct sockaddr*)&server_address,server_len);
listen(server_sockfd,MAX_THREAD);
while(true)
{
int client_sockfd;
struct sockaddr_in client_address;
int client_len;
char ip_address[16];
client_arg* args;
client_len=sizeof(client_address);
client_sockfd=accept(server_sockfd,(struct sockaddr*)&client_address,(socklen_t*)&client_len);
args=(client_arg*)malloc(sizeof(client_arg));
args->client_sockfd=client_sockfd;
get_ip_address(ntohl(client_address.sin_addr.s_addr),ip_address);
printf("get connection from %s\n",ip_address);
//create a thread to process the query/
pthread_t client_thread;
pthread_attr_t thread_attr;
int res;
res=pthread_attr_init(&thread_attr);
if(res !=0)
{
perror("Attribute creation failed");
free(args);
close(client_sockfd);
continue;
}
res=pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
if(res !=0)
{
perror("Setting detached attribute failed");
free(args);
close(client_sockfd);
continue;
}
res=pthread_create(&client_thread,&thread_attr,one_query,(void*)args);
if(res !=0)
{
perror("Thread creation failed");
free(args);
close(client_sockfd);
continue;
}
pthread_attr_destroy(&thread_attr);
}
return 0;
}服務(wù)器端采用標(biāo)準(zhǔn)的TPC(threadper connection)架構(gòu),即服務(wù)器每獲得一個(gè)客戶端請(qǐng)求,都會(huì)創(chuàng)建一個(gè)新的線程負(fù)責(zé)與客戶端通信,具體的任務(wù)都在每一個(gè)線程中完成。這種方式有個(gè)缺點(diǎn),就是存在線程的頻繁創(chuàng)建和刪除,所以還可以將accept函數(shù)放入每一個(gè)線程中進(jìn)行獨(dú)立監(jiān)聽(tīng)(這種方式需要加鎖)。需要注意的是我們需要設(shè)置線程屬性為detached,表示主線程不等待子線程。下面介紹每個(gè)線程具體完成的任務(wù):
void get_time(char* times)
{
time_t timep;
struct tm* p;
timep=time(NULL);
p=gmtime(&timep);
sprintf(times,"%d-%02d-%02d-%02d-%02d-%02d",p->tm_year+1900,p->tm_mon+1,p->tm_mday,
p->tm_hour+8,p->tm_min,p->tm_sec);
}
int recv_file(char* path,int client_sockfd,int file_length)
{
FILE* fp;
int read_length;
char buffer[1024];
fp=fopen(path,"wb");
if(fp==NULL)
{
perror("Open file failed");
return -1;
}
while((read_length=recv(client_sockfd,buffer,1023,0))>0)
{
buffer[read_length]='\0';
fwrite(buffer,1,read_length,fp);
file_length-=read_length;
if(!file_length)
{
fclose(fp);
printf("write to file %s\n",path);
return 0;
}
}
return 0;
}
void* one_query(void* arg)
{
char file_name[32];
char path[64]="./recv_data/";
char length[10];
int file_length=0;
client_arg* args=(client_arg*)arg;
int sockfd=args->client_sockfd;
get_time(file_name);
strcat(file_name,".wav");
strcat(path,file_name);
/1.receive file length//
recv(sockfd,length,10,0);
file_length=atoi(length);
printf("file length is %d\n",file_length);
/2.receive file content//
if(recv_file(path,sockfd,file_length)==-1)
{
perror("receive file failed");
close(sockfd);
pthread_exit(NULL);
}
result* list;
//3.search the fingerprint library, and get the expected music id//
int count=match(&list);
char result_to_client[2000];
for(int i=0;i
{
if(list[i].confidence>0.4)
{
memset(length,0,sizeof(length));
memset(result_to_client,0,sizeof(result_to_client));
/4. retrieve the database to get detailed information //
MYSQL_RES* res=select_music_based_on_id(list[i].id);
row_result* row_res=fetch_row(res);
sprintf(result_to_client,"%s,%s,%s,%d,%d,%lf",row_res->name,row_res->artist,row_res->album,list[i].score,list[i].start_time,list[i].confidence);
/5. Send a retrieval flag(1:success,0:fail)//
sprintf(length,"%d",1);
send(sockfd,length,10,0);
/6. Send the result
send(sockfd,result_to_client,2000,0);
free_result(res);
free_row(row_res);
}
else
{
memset(length,0,sizeof(length));
sprintf(length,"%d",0);
send(sockfd,length,10,0);
}
}
free(list);
close(sockfd);
pthread_exit(NULL);
}
one_query函數(shù)實(shí)現(xiàn)了每個(gè)線程與客戶端通信的代碼。代碼核心的部分可以表示為六步:1. 從客戶端讀取錄制音頻的長(zhǎng)度;2. 讀取實(shí)際的音頻,并保存到文件,文件以當(dāng)前時(shí)間命名;3. 檢索指紋服務(wù)器,獲得檢索的音樂(lè)id;4. 如果檢索結(jié)果置信度高,則利用檢索到的id訪問(wèn)數(shù)據(jù)庫(kù)獲得更加詳細(xì)的音樂(lè)信息;5. 給用戶發(fā)送一個(gè)成功/失敗標(biāo)注;6. 如果檢索成功,發(fā)送具體的音樂(lè)信息。
1.1 讀取文件長(zhǎng)度
在第一步讀取音頻長(zhǎng)度時(shí),我們采用了原始socket中的recv函數(shù)。該函數(shù)原型為:
Int recv(intsocket, void *buff, int length, int flags)
接收數(shù)據(jù)用void* 獲取,我們可以用char數(shù)組按照字節(jié)來(lái)讀取,讀取之后再解析。需要注意的一點(diǎn)是參數(shù)中傳遞的長(zhǎng)度必須大于客戶端可能傳遞過(guò)來(lái)的長(zhǎng)度,在此我們用10字節(jié)來(lái)表示傳遞的上限(int型最大約為4*109,需要10位,加上’\0’需要11位,但是音頻長(zhǎng)度遠(yuǎn)小于最大的int值,所以只分配10位)。讀到的char數(shù)組之后利用atoi轉(zhuǎn)化為實(shí)際的int型整數(shù)。網(wǎng)上很多博客在介紹Java和C之間的socket通信時(shí)會(huì)涉及復(fù)雜的大小端問(wèn)題,由于我們將所有的數(shù)據(jù)都轉(zhuǎn)成字節(jié)數(shù)組傳遞,所以不存在這個(gè)問(wèn)題。
1.2 讀取音頻文件
音頻文件的讀取在recv_file中實(shí)現(xiàn)。讀取的核心還是按照字節(jié)流來(lái)完成,每次讀取1023字節(jié)的數(shù)據(jù),然后寫入文件。這里有兩點(diǎn)需要注意:首先recv讀取的長(zhǎng)度和我們指定的長(zhǎng)度可能不一致,也即返回的長(zhǎng)度小于1023,我們需要以返回的長(zhǎng)度為準(zhǔn);分配的數(shù)組長(zhǎng)度是1024,但是我們每次讀取的數(shù)據(jù)最長(zhǎng)只能為1023,這是因?yàn)槲覀冃枰谧x取數(shù)據(jù)的最后添加一個(gè)’\0’標(biāo)記,用來(lái)標(biāo)記數(shù)據(jù)的末尾。讀取結(jié)束的標(biāo)志是達(dá)到之前傳遞過(guò)來(lái)的文件長(zhǎng)度。
1.3 檢索指紋庫(kù)
該步驟在獲得完整的音頻文件之后,就對(duì)該文件提取指紋然后檢索指紋庫(kù),原理可參考基于指紋的音樂(lè)檢索,在此不再贅述。檢索的結(jié)果是一個(gè)音樂(lè)的top5列表。每一項(xiàng)結(jié)果都有檢索得到的音樂(lè)id和相應(yīng)的置信度。
1.4 訪問(wèn)數(shù)據(jù)庫(kù)
該步驟在top 5列表中有置信度大于0.4的音樂(lè)時(shí)執(zhí)行。利用檢索得到的id去訪問(wèn)數(shù)據(jù)庫(kù),獲得音樂(lè)的名字和作者等信息。
1.5 發(fā)送flag標(biāo)記
在發(fā)送具體的信息之前先發(fā)送一個(gè)標(biāo)記,表示此次檢索是成功還是失敗,方便客戶端顯示。如果成功,發(fā)送標(biāo)記‘1’,失敗則發(fā)送標(biāo)記‘0’。發(fā)送時(shí),并不是直接發(fā)送一個(gè)int型的整數(shù),而是首先利用sprintf將整型變?yōu)閏har型字符串,交給客戶端去解析。發(fā)送函數(shù)采用原始socket中的send函數(shù),原型為:
Int send(int socket, const void * buff, int length, int flags)
1.6 發(fā)送音樂(lè)信息
當(dāng)檢索到對(duì)應(yīng)的音樂(lè)時(shí),則把具體的音樂(lè)信息發(fā)送給客戶端。這里還是利用sprintf將信息都打印到字符串中。可以看出,為了與Javasocket通信,所有的數(shù)據(jù)傳遞都被轉(zhuǎn)換成char*字符串。
2 客戶端實(shí)現(xiàn)
在介紹客戶端之前,先把代碼貼出來(lái):
import java.io.*;
import java.net.*;
public class Client
{
void query(String file,String ip,int port)
{
FileInputStream fileInputStream;
DataInputStream netInputStream;
DataOutputStream netOutputStream;
Socket sc;
int fileLength;
byte[] buffer=new byte[1023];
byte[] readLen=new byte[10];
byte[] readResult=new byte[2000];
int len;
int result_count=0;
File f=new File(file);
if(f.exists())
{
fileLength=(int)f.length();
}
else
{
System.out.println("No such file");
return;
}
try
{
fileInputStream=new FileInputStream(file);
sc=new Socket(ip,port);
netInputStream=new DataInputStream(sc.getInputStream());
netOutputStream=new DataOutputStream(sc.getOutputStream());
/1.send file length//
netOutputStream.write(Integer.toString(fileLength).getBytes());
/2. send file///
while((len=fileInputStream.read(buffer))>0)
{
netOutputStream.write(buffer,0,len);
}
3. read result symbol///
netInputStream.read(readLen);
while(((char)readLen[0])=='1')
{
/4. Read result//
netInputStream.read(readResult);
String result=new String(readResult);
String[] ss=result.split(",");
int score=Integer.parseInt(ss[3]);
int startTime=Integer.parseInt(ss[4]);
double confidence=Double.parseDouble(ss[5]);
System.out.println("name:"+ss[0].trim());
System.out.println("artist:"+ss[1].trim());
System.out.println("album:"+ss[2].trim());
System.out.println("score:"+score);
System.out.println("startTime:"+startTime);
System.out.println("confidence:"+confidence);
result_count++;
netInputStream.read(readLen);
}
if(result_count==0)
{
System.out.println("No match music");
}
fileInputStream.close();
netInputStream.close();
netOutputStream.close();
sc.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
Client client=new Client();
client.query(args[0],args[1],9527);
}
}
與服務(wù)器端相對(duì)應(yīng),客戶端的流程主要分為四步:1. 發(fā)送文件長(zhǎng)度;2. 發(fā)送文件內(nèi)容;3. 讀取標(biāo)記;4. 讀取檢索結(jié)果。在此,讀取文件采用FileInputStream流,網(wǎng)絡(luò)通信采用DataInputStream和DataOutputStream。
2.1 發(fā)送文件長(zhǎng)度
Java在發(fā)送int型時(shí),也需要轉(zhuǎn)換成字符串,在此我們先用Integer封裝類獲取int型的字符串表示,然后利用String類的getBytes函數(shù)獲得其字節(jié)數(shù)組。最后利用DataOutputStream的write函數(shù)發(fā)送給服務(wù)器。
2.2 發(fā)送文件
發(fā)送文件的過(guò)程是:首先從文件中讀取固定長(zhǎng)度的內(nèi)容,然后再利用write函數(shù)發(fā)送同等長(zhǎng)度的字節(jié)數(shù)組。
2.3 讀取標(biāo)記
發(fā)送完文件之后,客戶端就等著從服務(wù)器端獲取檢索結(jié)果。服務(wù)器首先返回一個(gè)0/1標(biāo)記。由于該標(biāo)記有效內(nèi)容只有一個(gè)字節(jié),所以我們可以通過(guò)讀取第0個(gè)字節(jié)的內(nèi)容來(lái)判斷檢索是否成功。讀取是通過(guò)DataInputStream的read函數(shù)完成,讀取的內(nèi)容會(huì)放在原始的字節(jié)數(shù)組中。
2.4 讀取音樂(lè)信息
如果檢索成功,服務(wù)器在發(fā)送成功標(biāo)記之后還會(huì)將完整的音樂(lè)信息發(fā)送過(guò)來(lái)。讀取還是利用DataInputStream的read函數(shù)。讀取的內(nèi)容比較復(fù)雜,我們首先將字節(jié)數(shù)組轉(zhuǎn)換成字符串,然后利用split函數(shù)解析出每一部分內(nèi)容。之后就可以在Android UI界面中顯示。
3 總結(jié)
在親自完成Java和c之間的socket通信之后,感覺(jué)也沒(méi)有那么復(fù)雜。其實(shí)核心就一點(diǎn):所有的數(shù)據(jù)類型都轉(zhuǎn)換成字節(jié)數(shù)組進(jìn)行傳遞。C端用recv和send函數(shù)就行,Java端用read和write就行,就這么簡(jiǎn)單。
總結(jié)
以上是生活随笔為你收集整理的java与c 通信_Java与C之间的socket通信的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python echo(msg) 字符串
- 下一篇: java求阶乘的程序_按要求编写Java