Page 1 of 4 Servers usually have to be encrypted but a simple HTTP server is a good place to start. This is an extract from our intermediate level book on the Pico's Wifi capabilities.
Master the Raspberry Pi Pico in C: WiFiwith lwIP & mbedtls
By Harry Fairhead & Mike James
Buy from Amazon.
Contents
Preface
- The Pico WiFi Stack
- Introduction To TCP
Extract: Simplest HTTP Client
- More Advanced TCP
- SSL/TLS and HTTPS
Extract: Simplest HTTPS Client
- Details of Cryptography
Extract: Random Numbers
- Servers
Extract: HTTP Server NEW!!
- UDP For Speed
Extract: Basic UDP
- SNTP For Time-Keeping
- SMTP For Email
- MQTT For The IoT
Appendix 1 Getting Started In C
<ASIN:B0C247QJLK>
The only difference between a server and a client is the ability to accept a connection. A client actively seeks a connection with a server, but a server has to just sit waiting patiently for a client to connect. The server also has the problem of having to deal with the possibility of having to deal with more than one client at a time and hence multiple simultaneous connections. The basics are the same - only the way server allows clients to connect is different.
If you are also going to create a secure server with TLS support then there is another difference. A client doesn’t need a certificate, but a server does. When a client connects the server has to provide its certificate to the client to allow it to set up encrypted data exchange and to verify the server’s identity. This means we need to know how to create and use a certificate if we are going to create a server.
As we already know how to use the Application Layered Module for adding TLS and making other modifications, it makes sense to carry on using it. There are, however, raw TCP functions corresponding to each of the altcp_ functions we use in this chapter. If you don’t want to support TLS you could just use the raw TCP functions, but the efficiency gains are small.
A Basic TCP Server
A server is set up in exactly the same way as a client. You need to use altcp_new to create a PCB Protocol Control Block and fill it in with the appropriate callbacks:
struct altcp_pcb *pcb = altcp_new(NULL);
What the callbacks are will be explained later.
A difference is that you have to bind a server to particular network ports and a single machine might have more than one:
err_t altcp_bind(struct altcp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port)
You can use bind with a client to limit the network connections and hence IP addresses it can use, but this usually isn’t necessary as you generally don’t care what IP address a client has. You do generally care what IP address a server has because the client has to know it to connect. The binding is to a particular IP address and port number. As usual, you can use the IP address 0.0.0.0 to mean “any”.
At this point the PCB is bound to the IP address and you could use altcp_connect to connect to a server as we have done in previous chapters. However, for a server we don’t connect to anything - we simply listen for incoming connections:
cyw43_arch_lwip_begin();
pcb=altcp_listen(pcb);
cyw43_arch_lwip_end();
The altcp_listen function returns a new PCB which uses less memory than the original one, which is deallocated.
What is going to happen if a client tries to connect while the server is listening? The answer is that a new callback, accept, is called to deal with the connection. You can set the callback in the PCB in the usual way:
altcp_accept(pcb,accept);
and accept has to have the signature:
err_t accept(void *arg, struct altcp_pcb *pcb, err_t err);
As before the arg parameter is a custom parameter passed to all callbacks. What is different is that the pcb parameter is a brand new PCB that you can use to work with the connection to the client. That is, it isn’t the PCB that you used to listen on. A new PCB is created for each client and the accept function has to add callbacks that will deal with client events, for example:
static err_t accept(void *arg, struct altcp_pcb *pcb, err_t err) { altcp_recv(pcb,recv); printf("connect!\n"); return ERR_OK; }
The recv callback deals with the data, i.e. the request that the client has sent to the server. This needs to be examined and then the altcp_write function is used to send the response back to the client.
Putting this together setting up the server is just a matter of:
struct altcp_pcb *pcb = altcp_new(NULL);
altcp_accept(pcb,accept); altcp_bind(pcb, IP_ADDR_ANY, 80); cyw43_arch_lwip_begin(); pcb=altcp_listen(pcb); cyw43_arch_lwip_end(); while (true) { sleep_ms(500); }
The simplest accept function is:
static err_t accept(void *arg, struct altcp_pcb *pcb, err_t err) { altcp_recv(pcb,recv); return ERR_OK; }
and the recv function is the same as used in previous examples:
err_t recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err) { if (p != NULL) { printf("recv total %d this buffer %d next %d err %d\n", p->tot_len, p->len, p->next, err); pbuf_copy_partial(p, myBuff, p->tot_len, 0); myBuff[p->tot_len] = 0; printf("Buffer= %s\n", myBuff); altcp_recved(pcb, p->tot_len); pbuf_free(p); } return ERR_OK; }
|