日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

java与c 通信_Java与C之间的socket通信

發(fā)布時(shí)間:2024/1/23 java 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java与c 通信_Java与C之间的socket通信 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

最近正在開(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)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。