Socket communication

Sockets are the standard way to access transport-layer communication from network applications. The Berkeley socket model, later standardized by POSIX, includes a naming standard for functions and components, and the behavior in a UNIX operating system. If the TCP/IP stack is integrated in the operating system, the scheduler can provide a mechanism to suspend the caller while waiting for a specific input, and the socket call API can be implemented to match POSIX specifications. In a bare-metal event-based application, however, the synchronization with the sockets is done using callbacks, as previously mentioned, in order to follow the event-based model of the main loop.

The interface provided by lwIP for bare-metal socket communication, also called the raw socket API, consists of custom calls, each specifying a callback whenever an event is expected from the stack. When the specific event occurs, lwIP will call the callback from the main loop function.

The description of a TCP socket in lwIP is contained in a TCP-specific protocol control-block structure, tcp_pcb. To allocate a new control block for the listening TCP socket, the following function is used:

struct tcp_pcb *tcp_new(void);

To accept a TCP connection, a bare-metal lwIP TCP server would first call:

err_t tcp_bind(struct tcp_pcb *pcb, ip_addr_t *ipaddr, u16_t port);
err_t tcp_listen(struct tcp_pcb *pcb);

These non-blocking functions bind the socket to a local address and put it into a listening state.

At this point, a POSIX application would call the blocking accept function, which would wait indefinitely for the next incoming connection on the socket. A lwIP bare-metal application instead calls:

void tcp_accept(struct tcp_pcb *pcb, err_t (* accept)(void *arg, 
struct tcp_pcb *newpcb,
err_t err)
);

This simply indicates that the server is ready to accept new connections, and wants to be called back to the address of the accept function call that has been passed as a parameter when a new incoming connection is established.

Using the same mechanism, to receive the next data segment, the application calls:

void tcp_recv(struct tcp_pcb *pcb, err_t (* recv)(void *arg, 
struct tcp_pcb *tpcb,
struct pbuf *p, err_t err)
);

This indicates to the TCP/IP stack that the application is ready to receive the next segment over the TCP connection, and the operation can be performed when a new buffer is available because the stack calls the actual recv function that has been specified as the argument when tcp_recv has been called.

Similarly, picoTCP associates one callback with each socket object. The callback is a common point to react to any socket-related events, such as a new incoming TCP connection, new data to be read on the socket buffer, or the end of the previous write operation.

The callback is specified when the socket is created:

struct pico_socket *pico_socket_open(uint16_t net, uint16_t proto,
void (*wakeup)(uint16_t ev, struct pico_socket *s)
);

The preceding function creates a new socket object for use in the specified network and transport protocol context, the net and proto arguments respectively, and reacts to all socket events by calling the wakeup function that is provided by the application. Using this mechanism, picoTCP successfully detects half-closed socket connections, and other events that are not specifically related to the current operation in progress but may occur due to a state change in the socket communication model.

A TCP socket server can be configured on the newly created socket using these functions:

int pico_socket_bind(struct pico_socket *s, void *local_addr, 
uint16_t *port);
int pico_socket_listen(struct pico_socket *s, int backlog);

At this point, the application has to wait for the incoming connections without calling accept. An event is generated, which calls the wakeup function, whenever a new incoming connection is established, and the application can finally call accept to generate the new socket object, corresponding to the incoming connection:

struct pico_socket *pico_socket_accept(struct pico_socket *s,
void *orig,
uint16_t *local_port);

The first argument passed to the picoTCP wakeup callback is a bitmask indicating the event types that occurred on the socket. Events may be:

  • EV_RD: Indicating that there is data to read on the incoming data buffer.
  • EV_CONN: Indicating that a new connection has been established, after calling connect, or while waiting in listening state, before calling accept.
  • EV_CLOSE: Triggered when the other side of the connection sent a FIN TCP segment, indicating that it has finished its transmission. The socket is in the CLOSE_WAIT state, meaning that the application may still send data before terminating the connection.
  • EV_FIN: Indicating that the socket has been closed, and it is not usable anymore after returning from syscall.
  • EV_ERR: An error occurred.

The callback interface provided by the TCP/IP stacks may be a little obscure to use at the beginning, but it is a very efficient way to achieve higher throughput when correctly implemented in the application.

Both the TCP/IP stacks we analyzed are capable of providing more standardized APIs only in combination with an operating system, by running the TCP/IP library main loop in a separate thread, and providing access to the sockets using system calls.

Socket communication is only one of the APIs exposed by the TCP/IP stacks. Other protocols implemented by the stack provide their own function signatures; they are described in both libraries' manuals.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.200.197