【网络编程】Linux自带socket库构建HTTP客户端发送数据(POST方式)

以上传图片到服务器为例子,详细的代码已经上传到github

客户端

1. 初始化socket对象

(1) 创建socket对象(AF_INET, SOCK_STREAM, 0)

int socket_fd = socket(AF_INET, SOCK_STREAM, 0);

(2) 与服务器进行连接connect,需要知道服务器的IP和PORT,IP地址的格式(AF_INET),同时要把IP地址和PORT从主机字节序转换为网络字节序。

sockaddr_in servaddr;
servaddr.sin_addr.s_addr = inet_addr((this->mServerIP).c_str());//将主机字节序转为网络字节序
servaddr.sin_port = htons(this->mPort); 
std::cout << htons(this->mPort) << std::endl;
servaddr.sin_family = AF_INET;
/*connect操作,返回0则success*/
int socket_init_status = connect(socket_fd, (sockaddr*)&servaddr, sizeof(sockaddr))

2. 用nlohmann把图像从cv::mat转为base64的数据格式,并存到json对象中。

3.发送Json数据(POST方式)

(1) 先发送HTTPHEADER,自己构造头部,一般用到的才加上去,正常来说比如要用到的有:
POST /router HTTP/1.1
CONTENT_TYPE: application/json
CONTENT_LENGTH: xxx
如果上述header中存在参数,则可以用sprintf函数去填写,如以下代码:

	const char* header_template = 
        // Request Line
        "POST %s HTTP/1.1\r\n" 
        "User-Agent: HANDSOMEBOY 1.0\r\n"
        "Connection: Close\r\n"
        "Content-Type: application/json\r\n"
        "Content-Length: %lu\r\n"
        "\r\n";
    char header[2048] = { 0 };
    sprintf(header, header_template, router.c_str(), data_size);

(2) 发送data,将Json数据send过去,若每次send数据的大小 > SEND_BUFFER_SIZE,则可以多次发送,如下代码

		if ( data_size < SEND_BUFFER_SIZE) { //在发送data,size小于send_buffer_size
	        send_size = send(this->socket_fd, json_data.c_str(), data_size, 0);
	        std::cout << TAG_INFO << "Send Size: " << send_size << " Byte(s)." << std::endl;
	    } else {
	        /*
	        函数c_str()就是将C++的string转化为C的字符串数组,c_str()生成一个constchar*指针,指向字符串的首地址
	        */
	        int total_times = data_size / SEND_BUFFER_SIZE + ((data_size % SEND_BUFFER_SIZE > 0) ? 1 : 0);//计算send次数
	        for (int i = 0; i < total_times; i++) {
	            if (i == (total_times-1))
	                send_size = send(this->socket_fd, json_data.c_str()+i*SEND_BUFFER_SIZE, (data_size-i*SEND_BUFFER_SIZE), 0);    
	            else
	                send_size = send(this->socket_fd, json_data.c_str()+i*SEND_BUFFER_SIZE, SEND_BUFFER_SIZE, 0);	            
	        }
   		 }

4. 接收来自服务端的数据

(1) 使用recv(cli_sockfd, recv_buffer, buffer_size)函数

(2) 处理recv_buffer的数据,通常返回来的数据报报头和data通常由’\r\n\r\n’分割,所以将光标移动到’\r\n\r\n’的后一位上,此时光标将是json数据开始的位置

Content-Type: application/json
Content-Length: 361
Server: Werkzeug/1.0.0 Python/3.6.10
Date: Wed, 08 Apr 2020 04:00:18 GMT
(这里即是 \r\n\r\n)
{
	"image_path":"./resources/image/upload/A81F25130C843811393C714496E86062.jpg\r\n.",
	"status_code":200
}

(3) 新建一个buffer来存储data,这里我命名为content_buffer,用strncpy()函数把光标起始位置,到recv_buffer最后位置的数据copy到content_buffer中。

char content_buffer[RECV_BUFFER_SIZE] = {0};
/*strncpy参数:
    param1: dataBuffer
    param2:取数据地址起始
    param3:取数据长度
*/
strncpy(content_buffer, message_ptr+pos, message_size-pos); 
//message_ptr是指向recv_buffer开头的指针,而pos是让指针往后移动到data的第一位,
//message_size-pos是指需要截取数据的长度

(4) 通过nlohmann中的parse函数,将content_buffer转为json对象,通过json对象中的status_code字段来确定是否请求成功,若请求成功则正常返回数据并做后续处理

nlohmann::json json_obj = nlohmann::json::parse(content_buffer);
if (200 == json_obj["status_code"].get<int>()) {
    find_200 = true;
}
else {
    std::cout << "[HTTP ERROR] status_code: " << json_obj["status_code"].get<int>() << 
        ", message: "<<json_obj["message"].get<std::string>()<< std::endl;
}

//Request success and Receive success
if (find_200) {
    return json_obj;    
}

服务端(使用的是flask框架)

直接返回return_json似乎有点问题,nlohnmann处理不到,所以return时调用了jsonify,当然也建议使用flask框架返回json数据时使用jsonify。

@app.route('/upload_image', methods=['POST'])
def upload_image():
    return_json = {}

    if request.method != 'POST':
        return_json['status_code'] = 1001
        return_json['message'] = 'Request method is Wrong.'
        return jsonify(return_json)
    # receive data
    print('request.content_length:', request.content_length)
    print('request.accept_mimetypes:', request.accept_mimetypes)
    print('request.user_agent:', request.user_agent)

    if request.json is None:
        return_json['status_code'] = 1002
        return_json['message'] = 'Json data is None.'
        return jsonify(return_json)
    if request.json['img_num'] <= 0:
        return_json['status_code'] = 1003
        return_json['message'] = 'Argument \'img_num\' of Json date is zero.'
        return jsonify(return_json)
    # Json data is null or not null
    for i in range(request.json['img_num']):
        if str(i) + '_img_base64' not in request.json.keys() or request.json[str(i) + '_img_base64'].replace(' ', '') == '':
            return_json['status_code'] = 1004
            return_json['message'] = 'Argument' + str(i) + '\'_img_base64\' of Json date does not exists' \
                                                           ' or Invalid base64 string.'
            return jsonify(return_json)
    # Get json data from request
    image_path = ''
    for i in range(request.json['img_num']):
        path = base64_to_image(request.json[str(i)+'_img_base64'])
        image_path = image_path + path + '\r\n'
    return_json['status_code'] = 200
    return_json['image_path'] = image_path
    return jsonify(return_json)

最近用C++代码把服务端给写了一下,算是对上面偷懒用python写服务端的小完善,链接在这这是该篇文章的姊妹篇,C++代码编写HTTP服务端

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值