Cannot loop send() when trying to send large data in https_client sample on nRF9160

Hi everyone,

I want to send large data with https protocol.

I edited the https_client sample split the data and looped send() for sending the large data.

But when I did send() once, I got error 128. Did the socket close by itself?

After I run send() once, I run close() -> tls_setup() ->connect() again. Then I can do send() again.

This is very time consuming, in a single loop, reconnecting takes 2 or 3 seconds in my environment. So I need a different solution.

Is it possible to loop send() continuously with the socket open? In the past, there have been similar case with the http protocol, but it seems that closing the socket with every send() is the solution.

https://devzone.nordicsemi.com/f/nordic-q-a/46086/nrf9160-dk-http-post-to-my-webserver

Is this still not improving? If I can send quickly even if I close the socket, it may be a solution.

HW:nRF9160DK

FW:modem v1.3.1

SDK v1.9.1

My LTE connection is LTE-M.

Best Regards,

Yukio Oyama

Parents
  • Hello

    Looking at the https client sample, it looks like the code is supposed to support sending multiple packets before closing the socket.

    I assume the changes you've made is to limit how many bytes you're allowed to send in each packet? Can you show me the exact changes you've made to the sample?

    Best regards,

    Einar

  • Hello Einar-san,

    This is the minimum set with errors. (Some APN information was hidden.)

    To find the cause, I searched for the smallest change where the error occurred. When sending twice (not loop), an error will occur on the second sending.

    This may be due to server behavior rather than device, but I'm not familiar with either.

    If a socket has been a timeout from open or access, can it be controlled by a parameter in the code?

    4263.prj.conf

    /*
     * Copyright (c) 2020 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #include <string.h>
    #include <zephyr.h>
    #include <stdlib.h>
    #include <net/socket.h>
    #include <modem/nrf_modem_lib.h>
    #include <net/tls_credentials.h>
    #include <modem/lte_lc.h>
    #include <modem/modem_key_mgmt.h>
    
    #define _HTTPS_POST_      1	// Send https to the test server.(1:enable)
    #define _SIM_SOFTBANK_EN_ 1 // Use softbank sim.(1:enable)
    #define _SECOND_SEND_     1 // Second transmission.(1:enable)
    
    #define HTTPS_PORT 443
    
    #if _HTTPS_POST_
    #define HTTPS_HOSTNAME "httpbin.org"
    #else
    #define HTTPS_HOSTNAME "example.com"
    #endif
    
    #if _HTTPS_POST_
    #define HTTP_HEAD                      \
    	"POST /post HTTP/1.1\r\n"              \
    	"Host: " HTTPS_HOSTNAME ":443\r\n" \
    	"Content-Type: text/html\r\n"	   \
    	"Content-Length: 5\r\n"            \
    	"\r\nhello\r\n"
    #else
    #define HTTP_HEAD                                                              \
    	"HEAD / HTTP/1.1\r\n"                                                  \
    	"Host: " HTTPS_HOSTNAME ":443\r\n"                                     \
    	"Connection: close\r\n\r\n"
    #endif
    
    
    #define HTTP_HEAD_LEN (sizeof(HTTP_HEAD) - 1)
    
    #define HTTP_HDR_END "\r\n\r\n"
    
    #define RECV_BUF_SIZE 2048
    #define TLS_SEC_TAG 42
    
    static const char send_buf[] = HTTP_HEAD;
    static char recv_buf[RECV_BUF_SIZE];
    
    /* Certificate for `example.com` */
    static const char cert[] = {
    #if _HTTPS_POST_
    	#include "../cert/startfield_class2.pem"	// "httpbin.org"
    #else
    	#include "../cert/DigiCertGlobalRootCA.pem"
    #endif
    };
    
    BUILD_ASSERT(sizeof(cert) < KB(4), "Certificate too large");
    
    #if _SIM_SOFTBANK_EN_
    #define CMD_CGDCONT "AT+CGDCONT=0,\"IPV4V6\",\"APN_name\""
    #define CMD_CGAUTH  "AT+CGAUTH=0,1,\"username\",\"password\""
    #define AT_MAX_CMD_LEN          2048
    static uint8_t at_buf[AT_MAX_CMD_LEN];
    #endif
    
    /* Provision certificate to modem */
    int cert_provision(void)
    {
    	int err;
    	bool exists;
    	int mismatch;
    
    	/* It may be sufficient for you application to check whether the correct
    	 * certificate is provisioned with a given tag directly using modem_key_mgmt_cmp().
    	 * Here, for the sake of the completeness, we check that a certificate exists
    	 * before comparing it with what we expect it to be.
    	 */
    	err = modem_key_mgmt_exists(TLS_SEC_TAG, MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN, &exists);
    	if (err) {
    		printk("Failed to check for certificates err %d\n", err);
    		return err;
    	}
    
    	if (exists) {
    		mismatch = modem_key_mgmt_cmp(TLS_SEC_TAG,
    					      MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN,
    					      cert, strlen(cert));
    		if (!mismatch) {
    			printk("Certificate match\n");
    			return 0;
    		}
    
    		printk("Certificate mismatch\n");
    		err = modem_key_mgmt_delete(TLS_SEC_TAG, MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN);
    		if (err) {
    			printk("Failed to delete existing certificate, err %d\n", err);
    		}
    	}
    
    	printk("Provisioning certificate\n");
    
    	/*  Provision certificate to the modem */
    	err = modem_key_mgmt_write(TLS_SEC_TAG,
    				   MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN,
    				   cert, sizeof(cert) - 1);
    	if (err) {
    		printk("Failed to provision certificate, err %d\n", err);
    		return err;
    	}
    
    	return 0;
    }
    
    /* Setup TLS options on a given socket */
    int tls_setup(int fd)
    {
    	int err;
    	int verify;
    
    	/* Security tag that we have provisioned the certificate with */
    	const sec_tag_t tls_sec_tag[] = {
    		TLS_SEC_TAG,
    	};
    
    #if defined(CONFIG_SAMPLE_TFM_MBEDTLS)
    	err = tls_credential_add(tls_sec_tag[0], TLS_CREDENTIAL_CA_CERTIFICATE, cert, sizeof(cert));
    	if (err) {
    		return err;
    	}
    #endif
    
    	/* Set up TLS peer verification */
    	enum {
    		NONE = 0,
    		OPTIONAL = 1,
    		REQUIRED = 2,
    	};
    
    //	verify = REQUIRED;
    	verify = NONE;
    
    	err = setsockopt(fd, SOL_TLS, TLS_PEER_VERIFY, &verify, sizeof(verify));
    	if (err) {
    		printk("Failed to setup peer verification, err %d\n", errno);
    		return err;
    	}
    
    	/* Associate the socket with the security tag
    	 * we have provisioned the certificate with.
    	 */
    	err = setsockopt(fd, SOL_TLS, TLS_SEC_TAG_LIST, tls_sec_tag,
    			 sizeof(tls_sec_tag));
    	if (err) {
    		printk("Failed to setup TLS sec tag, err %d\n", errno);
    		return err;
    	}
    
    	err = setsockopt(fd, SOL_TLS, TLS_HOSTNAME, HTTPS_HOSTNAME, sizeof(HTTPS_HOSTNAME) - 1);
    	if (err) {
    		printk("Failed to setup TLS hostname, err %d\n", errno);
    		return err;
    	}
    	return 0;
    }
    
    void main(void)
    {
    	int err;
    	int fd;
    	char *p;
    	int bytes;
    	size_t off;
    	struct addrinfo *res;
    	struct addrinfo hints = {
    		.ai_family = AF_INET,
    		.ai_socktype = SOCK_STREAM,
    	};
    
    	printk("HTTPS client sample started\n\r");
    
    #if _SIM_SOFTBANK_EN_
    	err = nrf_modem_at_cmd(recv_buf, sizeof(recv_buf), "%s", CMD_CGDCONT);
    	printk("AT+CGDCONT err:%x, %s\n\r",err, recv_buf);
    	err = nrf_modem_at_cmd(recv_buf, sizeof(recv_buf), "%s", CMD_CGAUTH);
    	printk("AT+CGDCONT err:%x, %s\n\r",err, recv_buf);
    #endif	//_SIM_SOFTBANK_EN_
    
    #if !defined(CONFIG_SAMPLE_TFM_MBEDTLS)
    	/* Provision certificates before connecting to the LTE network */
    	err = cert_provision();
    	if (err) {
    		return;
    	}
    #endif
    
    	printk("Waiting for network.. ");
    	err = lte_lc_init_and_connect();
    	if (err) {
    		printk("Failed to connect to the LTE network, err %d\n", err);
    		return;
    	}
    	printk("OK\n");
    
    	err = getaddrinfo(HTTPS_HOSTNAME, NULL, &hints, &res);
    	if (err) {
    		printk("getaddrinfo() failed, err %d\n", errno);
    		return;
    	}
    
    	((struct sockaddr_in *)res->ai_addr)->sin_port = htons(HTTPS_PORT);
    
    	if (IS_ENABLED(CONFIG_SAMPLE_TFM_MBEDTLS)) {
    		fd = socket(AF_INET, SOCK_STREAM | SOCK_NATIVE_TLS, IPPROTO_TLS_1_2);
    	} else {
    		fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2);
    	}
    	if (fd == -1) {
    		printk("Failed to open socket!\n");
    		goto clean_up;
    	}
    
    	/* Setup TLS socket options */
    	err = tls_setup(fd);
    	if (err) {
    		goto clean_up;
    	}
    
    	printk("Connecting to %s\n", HTTPS_HOSTNAME);
    	err = connect(fd, res->ai_addr, sizeof(struct sockaddr_in));
    	if (err) {
    		printk("connect() failed, err: %d\n", errno);
    		goto clean_up;
    	}
    
    	off = 0;
    	do {
    		bytes = send(fd, &send_buf[off], HTTP_HEAD_LEN - off, 0);
    		if (bytes < 0) {
    			printk("send() failed, err %d\n", errno);
    			goto clean_up;
    		}
    		off += bytes;
    	} while (off < HTTP_HEAD_LEN);
    
    	printk("Sent %d bytes\n", off);
    
    	off = 0;
    	do {
    		bytes = recv(fd, &recv_buf[off], RECV_BUF_SIZE - off, 0);
    		if (bytes < 0) {
    			printk("recv() failed, err %d\n", errno);
    			goto clean_up;
    		}
    		off += bytes;
    	} while (bytes != 0 /* peer closed connection */);
    
    	printk("Received %d bytes\n", off);
    
    	/* Print HTTP response */
    	p = strstr(recv_buf, "\r\n");
    	if (p) {
    		off = p - recv_buf;
    		recv_buf[off + 1] = '\0';
    		printk("\n>\t %s\n\n", recv_buf);
    	}
    
    #if _SECOND_SEND_
    	off = 0;
    	do {
    		bytes = send(fd, &send_buf[off], HTTP_HEAD_LEN - off, 0);
    		if (bytes < 0) {
    			printk("send() failed, err %d\n", errno);
    			goto clean_up;
    		}
    		off += bytes;
    	} while (off < HTTP_HEAD_LEN);
    	printk("Sent %d bytes\n", off);
    
    	off = 0;
    	do {
    		bytes = recv(fd, &recv_buf[off], RECV_BUF_SIZE - off, 0);
    		if (bytes < 0) {
    			printk("recv() failed, err %d\n", errno);
    			goto clean_up;
    		}
    		off += bytes;
    	} while (bytes != 0 /* peer closed connection */);
    
    	printk("Received %d bytes\n", off);
    
    	p = strstr(recv_buf, "\r\n");
    	if (p) {
    		off = p - recv_buf;
    		recv_buf[off + 1] = '\0';
    		printk("\n>\t %s\n\n", recv_buf);
    	}
    
    
    #endif
    
    	printk("Finished, closing socket.\n");
    
    clean_up:
    	freeaddrinfo(res);
    	(void)close(fd);
    
    	lte_lc_power_off();
    }
    

    In my experiment, send_buff [] is fixed and small in size. Finally, in my project I want to split the image file every 2KB (or less) and upload it by looping the send() .

    Best Regards,

    Yukio Oyama

  • Hm it seems you are correct, using https it looks like the modem will send all the data at once when you're done queueing data to send and call recv.

    You might want to consider using websockets or something similar instead of https requests unless https requests are a crucial part of your application.

    If you have to use https requests then it seems like you'll have to close the socket each time.

    -Einar

  • Hi Einar-san,

    In my application, https is more flexible because the connection destination is not fixed.

    In the thread below, he created a blocking send and put a wait after send(). And it succeeds in repeating send(). Is this logical as a solution?

    https://devzone.nordicsemi.com/f/nordic-q-a/51336/need-the-nrf9160-example-to-transfer-large-data-by-tcp

    On the other hand, I was looking for a quick way to reconnect instead of repeating send ().

    I'm sorry I'm not familiar with network connections.

    Looking at the thread below, does caching the session make it easier to resume the session?

    https://devzone.nordicsemi.com/f/nordic-q-a/55335/dtls-session-resumption-on-nrf9160-modem-fw-v1-1-0

    But it seems that it can only be used with nrf_setsockopt().

    Can't I cache the session with setsockopt()? Can I change setsockopt() to nrf_setsockopt()?

    And does it help me?

    Best Regards,

    Yukio Oyama

  • Yes, if using TCP is an option for your application, then something like in the first thread you linked could be a good solution I believe.

    If you want to send large data I would choose a protocol that allows this without having to reconnect after each packet, and TCP is a good option for this.

    -Einar

  • Hi Einar-san,

    The thread quoted uses TCP, but I don't want to use TCP. I wanted to know if blocking transmissions and wait inserts are effective.

    Do you think these are logically effective in HTTPS with nRF9160?

    Or do you think the only solution is to leave HTTPS and use a different protocol? Is it a difficult problem to send sliced data continuously using HTTPS with nRF9160?

    Best Regards,

    Yukio Oyama

  • Hi

    Ok I misunderstood then, It's very possible that the same approach could work for https.

    it could be that you just need to put a wait after each send for it to work.

    Based on how data is sent in the https_client example I would expect this solution to work, you might just be overflowing the modem with packets when you're not waiting.

    Let me know if you're still having problems.

    -Einar

Reply
  • Hi

    Ok I misunderstood then, It's very possible that the same approach could work for https.

    it could be that you just need to put a wait after each send for it to work.

    Based on how data is sent in the https_client example I would expect this solution to work, you might just be overflowing the modem with packets when you're not waiting.

    Let me know if you're still having problems.

    -Einar

Children
  • Hi Einar-san,

    Can I know a modem overflow? Or can I know that the packet has been sent?

    Is it closed in the modem library?

    Best Regards,

    Yukio Oyama

  • Hi

    There is the AT monitor library which can handle AT notifications from the modem. It would probably be possible to receive a notification when a packet is sent by using this library.

    There is also a function in the modem library that can give you information on the TX memory region, maybe that could be useful.

    -Einar

  • Hi Einar-san,

    I was able to repeat Send() over 4KB by inserting a wait after Send().

    However, it seems that the required waiting time differs depending on the sending data size. My sending data size is not fixed, it is dangerous to set the waiting time to a fixed value.

    Can I receive AT notifications after using send()?

    Or can it be received only when using the AT command with nrf_modem_at_cmd() etc.? If the AT command is required, does the chunk mode of AT#XHTTPCREQ deal with the buffer problem for repeated sending without any countermeasure? 

    Is there an example of AT command execution in chunk mode? It helps me when I change the way suddenly,

    Best Regards,

    Yukio Oyama

  • Hi Einar-san,

    I haven't answered the previous question, but I have additional questions.

    I was focusing on send() to send large data quickly, but I found that recv() also takes time.

    When I run recv(), it takes 5-7 seconds to respond.

    Looking at the receive buffer, the arrival of data by recv() is fast enough that I think the time extension to the disconnect operation.

    I don't expect a disconnect with recv() because the server is keep-alive, but you said the disconnect after recv() was fine.

    However, I feel that the time it takes to disconnect is long.(5-7 seconds)

    Is this correct? Or is it extended with some error due to an unexpected disconnection?

    If due to an error, is there a function that actively disconnects to avoid the error and reduce its time?

    Best Regards,

    Yukio Oyama

  • My sending data size is not fixed, it is dangerous to set the waiting time to a fixed value.

    One solution then would be to always wait as if you're sending the longest packet you'll be sending, but I agree this is not optimal.

    Can I receive AT notifications after using send()?

    I believe the send() function is actually sending AT-commands to the modem under the hood, which would mean the AT notifications are also there, you'd just have to catch them in your application.

    does the chunk mode of AT#XHTTPCREQ deal with the buffer problem for repeated sending without any countermeasure?

    Have you tried using this AT command? I thought these SLM-specific AT commands were specifically for the Serial LTE Modem application. Though if it works it works, never hurts to try.

    Is there an example of AT command execution in chunk mode?

    Not that I know of.

    When I run recv(), it takes 5-7 seconds to respond.

    Sounds strange, but I could imagine it being due to something like the client not expecting the server to be keep-alive, and waiting for it to terminate the connection?

    Do you know exactly what function call is taking long? Is it recv()?

    You can probably set the timeout to be shorter somehow, maybe with a flag in the recv command.

    It might also be the case that you could set an option for the socket to prevent recv from waiting for a message if no message is available:

    If no messages are available at the socket and O_NONBLOCK is not set on the socket's file descriptor, recv() shall block until a message arrives.

    Best regards,

    Einar

Related