最近想使用JNI完成http请求,由于之前有C语言基础,就来了兴致研究了一番。
请务必理解http的传输层数据格式,\r\n和\r\n\r\n,例:
#define HTTP_POST "POST /%s HTTP/1.1\r\nHOST: %s:%d\r\nAccept: */*\r\n"\
"Content-Type:application/x-www-form-urlencoded\r\nContent-Length: %zu%s\r\n\r\n%s"
#define HTTP_GET "GET /%s HTTP/1.1\r\nHOST: %s:%d\r\nAccept: */*%s\r\n\r\n"
提供给各位完成http请求的两种方案:
- 使用自带的socket.h完成http请求;
- 使用curl网络库完成http请求;
我使用了第一种,网上有几位的代码我参考了一下,基本上可以实现http网络请求,不过都会存在一些问题:
- socket read/rev阻塞,每次读取1024字节,循环读取,打印出来数据已经接收完成,但循环依旧阻塞在read/rev方法那里,直到服务器端超时关闭socket,才会执行完。我看了其他的帖子:①有说用非阻塞的,但是非阻塞不适合http请求的场景,服务器不会一直返回数据,不需要while死循环读取;②有说判断read/rev状态的,但阻塞就是阻塞,结束才有状态,所以也不可行。我的实现思路是:读取http的content-length数据长度,然后每次循环截取/r/n/r/n后面的数据,对比两个长度,如果一样,则证明数据接收完成,直接返回,关闭socket。
- http数据大小是不能确定的,有可能是任意大小,所以使用了buffer保存,最后在转换为char*数组。
关键代码(读取http响应内容):
//自定义Read函数
//参数一:自己程序的socket
//返回值:成功返回指向存储有相应内容的动态内存的指针,失败返回NULL
//注意:1)返回的动态内存指针在不使用的时候应该通过free释放;2)如果不是真的有问题,请不要动,如果读取数据出现问题,请优先检查data2.c中的函数
char *Read(int socket) {
int length = 0, return_length;
char buffer[BUFFER_SIZE];
char *Data;
PBUFFER header, nowBuffer;//nowBuffer指向正在使用的BUFFER节点
if (NULL != (header = create_EmptyBufferLink()))//创建BUFFER表头
{
if (NULL == (nowBuffer = append_Buffer_Node(header)))//创建第一个存储响应的BUFFER节点
{
LOGD("\nappend_Buffer_Node() fail in http.c Read()\n");//节点添加失败直接返回
free_Buffer_Link(header);
return NULL;
}
} else {
LOGD("\ncreate_EmptyBufferLink() fail in http.c Read()\n");//头结点创建失败直接返回
return NULL;
}
int content_length = -1,header_length=-1;
//每次读取1024个节点存储到buffer中,然后再通过strncpy复制到BUFFER节点当中
while ((return_length = recv(socket, buffer, BUFFER_SIZE,0)) > 0) {
if (return_length == -1) {
LOGD("\nreceive wrong!\n");
free_Buffer_Link(header);
header = NULL;
break;
} else {
if (length >= 50176)//如果节点已经快要存满,则新建节点,将相应内容存到新建立的节点当中
{
nowBuffer->data[length] = '\0';
if (NULL == (nowBuffer = append_Buffer_Node(header))) {
LOGD("\nappend_Buffer_Node() fail in http.c Read()\n");//节点添加失败直接返回
free_Buffer_Link(header);
break;
}
length = 0;
strncpy(nowBuffer->data + length, buffer, return_length);
length += return_length;
} else {
strncpy(nowBuffer->data + length, buffer, return_length);
length += return_length;
}
//得到content-length所在位置,通过atoi函数得到请求数据的长度
if (content_length==-1){
char *ptmp = (char *) strstr(buffer, "Content-Length");
if (!ptmp) {
LOGD("本次数据不包含Conteng-length");
}
content_length = atoi(ptmp + strlen("Content-Length: "));
LOGD("Conteng-length数据长度为:%d,指针ptmp:%d", content_length,ptmp);
}
nowBuffer->data[length] = '\0';
Data = get_All_Buffer(header);
LOGD("哈哈:%s",Data);
char *p = strstr(Data,"\r\n\r\n");
int now_length = strlen(Data) - (p-Data+4);
LOGD("测试指针下标位置为:%d;数据位置为:%d;实际数据长度为:%d",p-Data+1,p-Data+4,now_length);
//通过得到\r\n\r\n的位置,计算响应内容的长度。
// 如果计算得到的长度和content-length相等,则认为http请求结束。
if (now_length >= content_length){
//释放BUFFER链表
if (header != NULL) {
free_Buffer_Link(header);
header = NULL;
}
//申请响应内容的内存,用于存放数据。
char *response = (char *) malloc(now_length+1);
if (!response) {
LOGD("malloc failed \n");
return NULL;
}
LOGD("终于该拷贝了...,指针p:%d",p);
strcpy(response, p + 4);
return response;
}
}
}
//释放BUFFER链表
if (header != NULL) {
free_Buffer_Link(header);
header = NULL;
}
return NULL;//返回指向存储有响应内容的动态内存的指针(可能为空)
}
http代码参见:https://github.com/VcStrong/CHttpDemo/tree/master/common/src/main/cpp