使用c语言实现的http get post请求

背景

我目前需要解决一个需求,将一个c工程中的特定数据转发到VUE前端框架上做界面展示,且该框架已经有后端为flask框架。所以得考虑如何将c工程中的数据发送到python中。容易知道,进程间通信的方式有管道、信号量、消息队列、共享内存、套接字等。为了简易实现上述功能和尽量不影响他们两边原先进程的功能,使用套接字发送封装的数据做http请求给flask后端,这样来实现数据转发。
HTTP(超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。具体区别这篇博客讲的很详细。总而言之http连接=以http协议为通信协议的tcp连接,http协议可以由tcp协议封装报文而来,现在要解决的就是c的套接字如何封装成符合http协议的get/post请求。

参考案例

最开始找了网上很多案例,tcp套接字细节此处不赘述。http请求就是其tcp传输附上对应http请求的报文,但是实际测试不对,没有相应返回。猜想到可能测试环境不同封装格式也要改变,所以使用wireshark抓包软件抓了个具体的数据包来分析。
使用的get、post请求的html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>GET and POST</title>
</head>
<body>
    <form action = "http://localhost:5000" method = "get">
         <table>
            <tr>
                <td>Name</td>
                <td><input type ="text" name ="username"></td>
            </tr>
            <tr>
                <td>Password</td>
                <td><input type ="password" name ="password"></td>
            </tr>
            <tr>
                <td><input type = "get submit"></td>
            </tr>
        </table>
    </form>
    <form action = "http://localhost:5000" method = "post">
        <table>
           <tr>
               <td>Name</td>
               <td><input type ="text" name ="username"></td>
           </tr>
           <tr>
               <td>Password</td>
               <td><input type ="password" name ="password"></td>
           </tr>
           <tr>
               <td><input type = "post submit"></td>
           </tr>
   </table>
     </form>
</body>
</html>

后端flask接收代码

from flask import Flask, request
app = Flask(__name__)

@app.route('/', methods=['GET'])
def index():
    username = request.args.get('username')
    password = request.args.get('password')
    if username == "xugaoxiang" and password == "xugaoxiang":
        return f"<html><body>Welcome {username}</body></html>"
    else:
        return f"<html><body>Welcome!</body></html>"

@app.route('/', methods=['POST'])
def index():
    username = request.form['username']
    password = request.form['password']
    if username == "xugaoxiang" and password == "xugaoxiang":
        return f"<html><body>Welcome {username}</body></html>"
    else:
        return f"<html><body>Welcome!</body></html>"

if __name__ == '__main__':
    app.run(debug=True)

测试案例取自此教程,贴出教程源码链接:https://github.com/xugaoxiang/FlaskTutorial
其抓包结果如下所示:
只需要关注http数据包中的tcp报文内容即可。
在这里插入图片描述

具体实现

注:为了解决大小端和数据位数不统一的问题,我是将所有数据转为字符串来发送。如果想要发送json等数据格式同样用抓包看下具体如何封装即可,此处简易的实现先不考虑那些功能。
有了上面的数据样本,进行下面c代码的封装转发。使用环境不同,部分函数可能有所变化。这里只展示基础模板。
真正通用的是下面这段补齐信令的函数

void addget(const char* str1)                 //补齐get信令数据
{
    strcat(str1, "Host: 127.0.0.1\r\n");//cname记录不影响连接,此处不做修改
    strcat(str1, "Content-Type: text/html\r\n\r\n");
}

void addpost(char* str1, const char* data)    //补齐post信令数据
{
    char postlength[128];

    sprintf(postlength, "%d\r\n", strlen(data + 1));
    strcat(str1, "Host: 127.0.0.1\r\n");//cname记录不影响连接,此处不做修改
    strcat(str1, "Content-Type: application/x-www-form-urlencoded\r\n");
    strcat(str1, "Content-Length: ");
    strcat(str1, postlength);
    strcat(str1, "\r\n\r\n");
    strcat(str1, data + 1);		
}
/* 调用方式
		//data即为传输而来的数据
        case get_test:{	        // 封装成http的get请求 
            strcpy(str1, "GET /getsometing?");
            strcat(str1, data + 1);
            strcat(str1, " HTTP/1.1\r\n");
            addget(str1);
            break;
        }
        case post_test:{	    // 封装成http的post请求 
            strcpy(str1, "POST /postsometing HTTP/1.1\r\n");
            addpost(str1, data);
            break;						
        }
*/

请求代码模板

只讨论http请求方面内容,展示基础的tcp套接字绑定及封装http请求流程

#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <windows.h>
#include <pthread.h>  
#include <assert.h>

#define PORT 5000	// 设定发送端口
#define BUFSIZE 1024
#define DATASIZE 2000

enum send_flask_type {
    start=1, 
    accomplish, 
    get_test,
    post_test
};

// 实现函数,flaskip为http请求的ip地址 此处为环回地址127.0.0.1 data为传输数据(例如type=start) send_type设定传输方式
int HandleFlask(const char* flaskip, const char* data, int send_type)
{
	int i, sockfd_flask;
	fd_set   t_set1;
	struct timeval  timeset;
    struct sockaddr_in flaskaddr;
    char str1[2 * DATASIZE], buf[BUFSIZE];

    //* 创建flask连接套接字 *//
	if ((sockfd_flask = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
		printf("创建网络连接失败,本线程即将终止!\n");
		return -1;
	}
	flaskaddr.sin_family = AF_INET;
	flaskaddr.sin_addr.s_addr = inet_addr(flaskip);
	flaskaddr.sin_port = htons(PORT);
	memset(&flaskaddr.sin_zero, 0, 8);
	if (connect(sockfd_flask, (struct sockaddr *)&flaskaddr, sizeof(flaskaddr)) < 0){
		printf("连接到flask服务器失败!\n");
		return -1;
	}
    // printf("连接Flask服务器成功\n");

    switch(send_type) {
        case get_test:{	        /* 封装成http的get请求 */
            strcpy(str1, "GET /getsometing?");
            // if(SplitStr(data + 1, str1) < 0) { //将data中的tpye数字类型转成字符串并添加至str1
            //     printf("需要转发的报文格式有误\n");
            //     return -1;
            // }
            strcat(str1, data + 1);
            strcat(str1, " HTTP/1.1\r\n");
            addget(str1);
            break;
        }
        case post_test:{	    /* 封装成http的post请求 */ 
            strcpy(str1, "POST /postsometing HTTP/1.1\r\n");
            addpost(str1, data);
            break;						
        }
        default:{
            printf("接收到无效格式,舍弃\n");
            return 0;
        }
    }

    i = send(sockfd_flask, str1, strlen(str1), 0);
    if (i < 0) {
        // printf("发送失败!错误代码是%d,错误信息是'%s'\n",errno, strerror(errno));
        printf("发送数据给flask失败!错误代码是%d\n", WSAGetLastError());//windows获取erron
        closesocket(sockfd_flask);
        return -1;
    }
    else {
        // printf("消息发送至flask成功,共发送了%d个字节!send_type=%d \n", i, send_type);
    }

    // python安装插件eventlet后,外部http访问后flask不会立即关闭套接字
    // (即falsk return后不会发送空的tcp报文),
    // 所以此修改为不考虑复杂场景只接收一次flask的http返回数据后就关闭套接字    
    FD_ZERO(&t_set1);
    FD_SET(sockfd_flask, &t_set1);
    timeset.tv_sec= 0;
    timeset.tv_usec= 100000;                //扫描堵塞时间100ms
    i= select(sockfd_flask +1, &t_set1, NULL, NULL, &timeset);

    if (i == 0) {
        // printf("长时间未接收到flask http响应,跳过\n");
        // continue;
        // break;
    }
    else if (i < 0) {
        printf("在读取flask数据报文时SELECT检测到异常,该异常导致线程终止!\n");
        closesocket(sockfd_flask);
        return -1;
    }
    else {
        memset(buf, 0, sizeof(buf) );
        i = recv(sockfd_flask, buf, sizeof(buf), 0);
        if (i <= 0) {
            closesocket(sockfd_flask);
            if (i  == 0) {  
                // printf("与flask通信的http套接字关闭\n");
                return 0;
            }
            else {
                printf("接收flask数据报出现错误!错误代码是%d\n", WSAGetLastError());    //windows获取erron
                return -1;
            }
        }
        else {  //对http返回值进行处理
            // printf("flask返回值%s\n", buf);
            // continue;
            // break;       
        }
    }
    closesocket(sockfd_flask);
	return 0;
}

flask接收示例

			***flask后端测试用****
@app.route('/getsometing', methods=['GET'])
def gettest():
    type = request.args.get('type')
    src = request.args.get('src')
    dst = request.args.get('dst')
    print(f"get data: type={type},src={src},dst={dst}")
    return f"<html><body>get return: type={type},src={src},dst={dst}</body></html>"

@app.route('/postsometing', methods=['POST'])
def posttest():
    type = request.form['type']
    src = request.form['src']
    dst = request.form['dst']
    print(f"post data: type={type},src={src},dst={dst}")
    return f"<html><body>post return: type={type},src={src},dst={dst}</body></html>"

#如果数据量比较大可以这样写

 @app.route('/getsometing', methods=['GET'])
def gettest():
    get_data=request.args.to_dict()
    type = get_data['type']
    cur_time=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 

    if type == 'Start' : #自己定义的发送数据的type
		pass
    elif type == 'Accomplish' :
		pass 
    elif type == 'change' :
		pass
    else :
        print ("接收到无效数据,将其丢弃")
        with open("log.txt", "a+") as f:
            f.write('\n# ' + cur_time + ' ---------- get error:\n' + json.dumps(get_data))
        return "<html><body> Flask access invalid data </body></html>"

    return "<html><body> data access </body></html>"


@app.route('/postsometing', methods=['POST'])
def posttest():
    post_data = request.form.to_dict()
    print (post_data)
    type = post_data['type']
    cur_time=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 

    if type == "change":
		pass
    else :
        print ("接收到无效数据,将其丢弃")
        with open("log.txt", "a+") as f:
            f.write('\n# ' + cur_time + ' ---------- post error:\n' + json.dumps(post_data))
        return "<html><body> Flask access invalid data </body></html>"

    return "<html><body> data access </body></html>"
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Windows 平台上,可以使用 Winsock 库来实现 HTTP 下载文件。以下是一个简单的 C 语言程序,演示如何实现 HTTP 下载文件: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #define BUFFER_SIZE 1024 int main(int argc, char *argv[]) { if (argc < 2) { printf("usage: %s url\n", argv[0]); return 1; } // 初始化 Winsock 库 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { perror("WSAStartup failed"); return 1; } // 解析 URL char *url = argv[1]; char *host = strtok(url, "/"); char *path = strtok(NULL, ""); if (path == NULL) { path = "/"; } // 建立 TCP 连接 struct hostent *server = gethostbyname(host); if (server == NULL) { perror("gethostbyname failed"); return 1; } SOCKET sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockfd == INVALID_SOCKET) { perror("socket failed"); return 1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(80); memcpy(&addr.sin_addr.s_addr, server->h_addr, server->h_length); if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) { perror("connect failed"); return 1; } // 发送 HTTP 请求 char request[BUFFER_SIZE]; sprintf(request, "GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n", path, host); if (send(sockfd, request, strlen(request), 0) == SOCKET_ERROR) { perror("send failed"); return 1; } // 接收 HTTP 响应 char response[BUFFER_SIZE]; int n = recv(sockfd, response, BUFFER_SIZE, 0); if (n == SOCKET_ERROR) { perror("recv failed"); return 1; } response[n] = '\0'; // 解析响应头 char *content_length = strstr(response, "Content-Length:"); if (content_length == NULL) { printf("unknown file size\n"); return 1; } int file_size = atoi(content_length + strlen("Content-Length:")); printf("file size: %d\n", file_size); // 写入文件 FILE *file = fopen("file.txt", "wb"); char buffer[BUFFER_SIZE]; int total = 0; while (total < file_size) { n = recv(sockfd, buffer, BUFFER_SIZE, 0); if (n == SOCKET_ERROR) { perror("recv failed"); return 1; } fwrite(buffer, sizeof(char), n, file); total += n; } fclose(file); // 关闭连接 closesocket(sockfd); // 卸载 Winsock 库 WSACleanup(); printf("file downloaded\n"); return 0; } ``` 注意:以上代码仅供参考,实际使用时需要添加错误处理和其他必要的检查。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值