TCP客户端的模型

HTTP POST协议

#ifndef _TUOBAO_TCP_CLIENT_ 
#define _TUOBAO_TCP_CLIENT_

#include <netinet/in.h>  
#include <sys/socket.h>

typedef struct _tuobao_tcpclient{ 
    int socket;
    int remote_port;     
    char remote_ip[16];  
    struct sockaddr_in _addr; 
    int connected;       
} tuobao_tcpclient;

int tuobao_tcpclient_create(tuobao_tcpclient *,const char *host, int port);
int tuobao_tcpclient_conn(tuobao_tcpclient *);
int tuobao_tcpclient_recv(tuobao_tcpclient *,char **lpbuff,int size);
int tuobao_tcpclient_send(tuobao_tcpclient *,char *buff,int size);
int tuobao_tcpclient_close(tuobao_tcpclient *);

#endif

上面的代码应该也不用说太多,光看这个这些函数的命名也差不多知道嘛意思了
定义一个结构体,有远程IP及端口,连接状态(注:连接状态是为了保持长连接用的,但事实上post请求的时候,根本就没有保持,所以,基本上没用)
然后是五个tcp函数
tuobao_tcpclient_create:根据服务器IP或者域名,以及端口填充tcpclient结构,主要是申请个socket及填一下sockaddr_in
tuobao_tcpclient_conn:连接到远程端口,并修改tcpclient的连接状态
tuobao_tcpclient_recv:接收远程指定字节数的数据,并分配空间到lpBuf,如果size为0,就阻塞直到连接关闭
tuobao_tcpclient_send:发送buf,size为buf长度
tuobao_tcpclient_close:关闭socket并且设置连接状态

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netdb.h>

#include "tuobao_tcpclient.h"

#define BUFFER_SIZE 1024


int tuobao_tcpclient_create(tuobao_tcpclient *pclient,const char *host, int port){
    struct hostent *he;

    if(pclient == NULL) return -1;
    memset(pclient,0,sizeof(tuobao_tcpclient));

    if((he = gethostbyname(host))==NULL){
        return -2;
    }

    pclient->remote_port = port;
    strcpy(pclient->remote_ip,inet_ntoa( *((struct in_addr *)he->h_addr) ));

    pclient->_addr.sin_family = AF_INET;
    pclient->_addr.sin_port = htons(pclient->remote_port);
    pclient->_addr.sin_addr = *((struct in_addr *)he->h_addr);

    if((pclient->socket = socket(AF_INET,SOCK_STREAM,0))==-1){
        return -3;
    }

    /*TODO:是否应该释放内存呢?*/

    return 0;
}

int tuobao_tcpclient_conn(tuobao_tcpclient *pclient){
    if(pclient->connected)
        return 1;

    if(connect(pclient->socket, (struct sockaddr *)&pclient->_addr,sizeof(struct sockaddr))==-1){
        return -1;
    }

    pclient->connected = 1;

    return 0;
}

int tuobao_tcpclient_recv(tuobao_tcpclient *pclient,char **lpbuff,int size){
    int recvnum=0,tmpres=0;
    char buff[BUFFER_SIZE];

    *lpbuff = NULL;

    while(recvnum < size || size==0){
        tmpres = recv(pclient->socket, buff,BUFFER_SIZE,0);
        if(tmpres <= 0)
            break;
        recvnum += tmpres;

        if(*lpbuff == NULL){
            *lpbuff = (char*)malloc(recvnum);
            if(*lpbuff == NULL)
                return -2;
        }else{
            *lpbuff = (char*)realloc(*lpbuff,recvnum);
            if(*lpbuff == NULL)
                return -2;
        }

        memcpy(*lpbuff+recvnum-tmpres,buff,tmpres);
    }

    return recvnum;
}

int tuobao_tcpclient_send(tuobao_tcpclient *pclient,char *buff,int size){
    int sent=0,tmpres=0;

    while(sent < size){
        tmpres = send(pclient->socket,buff+sent,size-sent,0);
        if(tmpres == -1){
            return -1;
        }
        sent += tmpres;
    }
    return sent;
}

int tuobao_tcpclient_close(tuobao_tcpclient *pclient){
    close(pclient->socket);
    pclient->connected = 0;
}

现在来看看http协议的头吧
虽然搞好几年的asp.net,但是因为微软为我们封装得太好了,一直没有关注http协议的post头应该是怎么样?
想想,其实很简单,看看人家IE是怎么发出去的就知道了
ubuntu下,先用nc工作监听一下,win下面随便发个请求

<FORM METHOD=POST ACTION="http://192.168.1.103/a.b">
<INPUT TYPE="text" NAME="input1">
<INPUT TYPE="submit">    
 </FORM>

看nc的响应
linbc@cheng-ubuntu:~/workspace/httpclient$ sudo nc -l -p 80
[sudo] password for linbc:
POST /a.b HTTP/1.1
Accept: image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-ms-xbap, application/x-shockwave-flash, application/vnd.ms-excel, application/msword, application/vnd.ms-powerpoint, /
Accept-Language: zh-cn
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30618)
Host: 192.168.1.103
Content-Length: 12
Connection: Keep-Alive
Cache-Control: no-cache
input1=hello

好了,基本上知道post请求的http头
下面直接看代码

#include "tuobao_tcpclient.h"

int http_post(tuobao_tcpclient *pclient,char *page,char *request,char **response){

    char post[300],host[100],content_len[100];
    char *lpbuf,*ptmp;
    int len=0;

    lpbuf = NULL;
    const char *header2="User-Agent: Tuobao Http 0.1\r\nCache-Control: no-cache\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: */*\r\n";

    sprintf(post,"POST %s HTTP/1.0\r\n",page);
    sprintf(host,"HOST: %s:%d\r\n",pclient->remote_ip,pclient->remote_port);
    sprintf(content_len,"Content-Length: %d\r\n\r\n",strlen(request));

    len = strlen(post)+strlen(host)+strlen(header2)+strlen(content_len)+strlen(request)+1;
    lpbuf = (char*)malloc(len);
    if(lpbuf==NULL){
        return -1;
    }

    strcpy(lpbuf,post);
    strcat(lpbuf,host);
    strcat(lpbuf,header2);
    strcat(lpbuf,content_len);
    strcat(lpbuf,request);

    if(!pclient->connected){
        tuobao_tcpclient_conn(pclient);
    }

    if(tuobao_tcpclient_send(pclient,lpbuf,len)<0){
        return -1;
    }
printf("发送请求:\n%s\n",lpbuf);

    /*释放内存*/
    if(lpbuf != NULL) free(lpbuf);
    lpbuf = NULL;

    /*it's time to recv from server*/
    if(tuobao_tcpclient_recv(pclient,&lpbuf,0) <= 0){
        if(lpbuf) free(lpbuf);
        return -2;
    }
printf("接收响应:\n%s\n",lpbuf);

    /*响应代码,|HTTP/1.0 200 OK|
     *从第10个字符开始,第3位
     * */
    memset(post,0,sizeof(post));
    strncpy(post,lpbuf+9,3);
    if(atoi(post)!=200){
        if(lpbuf) free(lpbuf);
        return atoi(post);
    }


    ptmp = (char*)strstr(lpbuf,"\r\n\r\n");
    if(ptmp == NULL){
        free(lpbuf);
        return -3;
    }
    ptmp += 4;/*跳过\r\n*/

    len = strlen(ptmp)+1;
    *response=(char*)malloc(len);
    if(*response == NULL){
        if(lpbuf) free(lpbuf);
        return -1;
    }
    memset(*response,0,len);
    memcpy(*response,ptmp,len-1);

    /*从头域找到内容长度,如果没有找到则不处理*/
    ptmp = (char*)strstr(lpbuf,"Content-Length:");
    if(ptmp != NULL){
        char *ptmp2;
        ptmp += 15;
        ptmp2 = (char*)strstr(ptmp,"\r\n");
        if(ptmp2 != NULL){
            memset(post,0,sizeof(post));
            strncpy(post,ptmp,ptmp2-ptmp);
            if(atoi(post)<len)
                (*response)[atoi(post)] = '\0';
        }
    }

    if(lpbuf) free(lpbuf);

    return 0;
}

int main(){

    tuobao_tcpclient client;

    char *response = NULL;
    printf("开始组包\n");
    tuobao_tcpclient_create(&client,"127.0.0.1",80);

    if(http_post(&client,"/i.php","f1=hello",&response)){
        printf("失败!\n");
        exit(2);
    }
    printf("响应:\n%d:%s\n",strlen(response),response);

    free(response);
    return 0;
}

写个简单的makefile

objects = tuobao_tcpclient.o httppost.o
httppost: $(objects)
    cc -o httppost $(objects)
tuobao_tcpclient.o: tuobao_tcpclient.h
httppost.o:
.PHONY : clean
clean :
    @echo 正在移除
    -rm httppost $(objects)