| Master The Pico WiFi: Client Sockets |
| Written by Harry Fairhead and Mike James | ||||
| Tuesday, 18 November 2025 | ||||
Page 2 of 3
Setting Up SocketsLwIP sockets work as described above, but before you can start to make use of them you have to get things setup correctly. It is assumed that you have FreeRTOS setup correctly as described in the previous chapter. To enable sockets you have to add #define LWIP_SOCKET 1 to the start of the lwipopts.h file. This makes the beginning read: #ifndef __LWIPOPTS_H__ #define __LWIPOPTS_H__ #define NO_SYS 0 #define LWIP_SOCKET 1 To the end of the file you need to add: #define LWIP_TIMEVAL_PRIVATE 0 #if !NO_SYS #define TCPIP_THREAD_STACKSIZE 1024 #define TCPIP_MBOX_SIZE 8 #endif#define LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT 2000 Some of these are required to make Free RTOS work. You need to add LWIP_TIMEVAL_PRIVATE to stop lwIP adding its own time functions and so clashing with those provided by the SDK. LWIP_POSIX_SOCKETS_IO_NAMES allows you to use the standard POSIX socket function names. The lwIP library adds lwip_ to the start of each of the socket functions, for example write() becomes lwip_write. The reason is to allow lwIP sockets to coexist with other implementations of the functions. If you set LWIP_POSIX_SOCKETS_IO_NAMES to 1 you can use the standard POSIX names and this is the convention adopted for the following examples. The LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT parameter sets the time that the system will wait for more data before closing the connection. By default it is set to 20 seconds but in most cases 2 seconds is adequate. A Web ClientWe now have enough information to implement our first socket program, client.c, a web client. The first thing we have to do is create a socket and the TCP needed for an HTTP transaction: int sockfd = socket(AF_INET, SOCK_STREAM, 0); To allow this to work you have to add: #include "lwip/sockets.h" Next we need to get the address of the server we want to connect to. For the web this would usually be done using a DNS lookup on a domain name. To make things simple, we will skip the lookup and use a known IP address. Example.com is a domain name provided for use by examples and you can find its address by pinging it. At the time of writing it was hosted at: 23.192.228.80 This could change so check before concluding that "nothing works". There are three fields in the address structure. The first is: struct sockaddr_in addr; Then comes sin_family, which is set to: addr.sin_family = AF_INET; to indicate an IPv4 address. The next field is the port number of the IP address, but you can't simply use: addr.sin_port = 80; because the bit order used on the Internet isn't the same as used on most processors. Instead you have to use a utility function that will ensure the correct bit order: addr.sin_port = htons(80); The function name stands for “host to network short” and there are other similarly named functions. The actual address is defined in the in_addr field. This is a struct with only one field, s_addr, a 32-bit representation of an IP address. The format is fairly simple. Regard the 32-bit value as four bytes with each byte coding one value of the "dotted" IP address. That is, if the IP address is w.x.y.z then w, x, y and z are the bytes of s_addr. For example, the IP address of example.com is 23.192.228.80 and converting each value into its byte equivalent in hex gives 17.C0.E4.50, which would be the hex value we have to store in s_addr if it wasn't for the fact that the bytes are stored in reverse order. So, the hex equivalent of the IP address is 0x50E4C0 and this can be used to initialize the address struct: addr.sin_addr.s_addr = 0x50E4C0; Fortunately we don’t have to do this as there is a conversion function: addr.sin_addr.s_addr = inet_addr("23.192.228.80");
To make all this work you need to add: #include "lwip/inet.h" With the address worked out and safely stored we can now make the connection: connect(sockfd, &addr, sizeof (addr)); This will return 0 if it successfully connects and we do need to test for this condition. You will also get a type warning because the pointer to the addr structure isn't as defined in the function. In fact there are many variations which you could pass and it is the standard idiom to cast them to the function's pointer type: connect(sockfd, (struct sockaddr *) &addr, By default there are only four sockets allowed, but a socket can be reused to make multiple connections. Simply close the socket and use connect. You can also increase the number of sockets available, but this is usually not a good idea. As long as there is no error, we can start to send and receive data. But what data? As always, the answer is that it all depends on the protocol you are using. There is nothing about a socket that tells you what to send. It is a completely general I/O mechanism. You can send anything, but if you don't send what the server is expecting you won’t get very far. As we are creating an HTTP client we need to send a set of HTTP headers. As we discovered in Chapter 2. the simplest header that works is: char header[] = "GET /index.htm HTTP/1.1\r\n
HOST:example.org\r\n\r\n";
which corresponds to the headers: GET /index.html HTTP/1.1 HOST:example.com Always remember that an HTTP request has to end with a blank line. If you don't send the blank line then you will get no response from most servers. In addition, the HOST header has to have the domain name with no additional syntax - no slashes and no http: or similar. With the headers defined, we can send our first HTTP request using write as if the socket was just another file to write data to: int n = write(sockfd, header, strlen(header)); We can read the response just as if the socket was a file: char buffer[2048];
n = read(sockfd, buffer, 2048);
printf("%s", buffer);
The read function blocks until there is some data in the receive buffer and returns as soon as there is any data to read. What this means is that you may have to keep reading data until the buffer is empty for a timeout period.
|
||||
| Last Updated ( Tuesday, 18 November 2025 ) |
