Introduction
TCP Client&Server establishes a two-way connection between a server and a client. It is the most common communication model used by applications such as HTTP, Telnet, FTP, SSH and others
LwIP is a free light-weight TCP/IP stack in MCUXpresso SDK. It has three application programming interfaces (API):
RAW API: it is the native API of LwIP. It enables the development of applications using event callbacks.
Netconn API: it is a high level sequential API that requires the services of a real-time system (RTOS), The Netconn API enables multi-threaded operations.
BSD Socket API: it is developed on top of the Netconn API.
In this article, I will introduce how to implement a TCP client & Server demo with LwIP socket API. One EVK-RT1060 acts as TCP server, and others boards act as TCP clients. They are connected through an Ethernet router. They communicate via TCP protocol. If you press SW8 on the client board, it will toggle a LED on the Server board.
This article is for beginners to understand the Socket API on NXP MCUXpresso SDK.
2. Hardware configuration
PHY settings:
In this demo, we use phyksz8081, that is the default PHY in EVK-RT1060 board. ENET port 0.
/*! @brief The ENET PHY address. */
#define BOARD_ENET0_PHY_ADDRESS (0x02U) /* Phy address of enet port 0. */
/* Address of PHY interface. */
#define EXAMPLE_PHY_ADDRESS BOARD_ENET0_PHY_ADDRESS
/* MDIO operations. */
#define EXAMPLE_MDIO_OPS enet_ops
/* PHY operations. */
#define EXAMPLE_PHY_OPS phyksz8081_ops
/* ENET clock frequency. */
#define EXAMPLE_CLOCK_FREQ CLOCK_GetFreq(kCLOCK_IpgClk)
Configure the external PHY, pull up the ENET_INT before RESET.
GPIO_PinInit(GPIO1, 9, &gpio_config);
GPIO_PinInit(GPIO1, 10, &gpio_config);
/* pull up the ENET_INT before RESET. */
GPIO_WritePinOutput(GPIO1, 10, 1);
GPIO_WritePinOutput(GPIO1, 9, 0);
delay();
GPIO_WritePinOutput(GPIO1, 9, 1);
MAC settings:
In this demo, MAC address is defined in Macro configMAC_ADDR
/* MAC address configuration. */
#define configMAC_ADDR \
{ \
0x02, 0x12, 0x13, 0x10, 0x15, 0x11 \
}
3. Starting a network interface
To create a new network interface, the user allocates space for a new struct netif (but does not initialize any part of it) and calls netifapi_netif_add:
IP4_ADDR(&fsl_netif0_ipaddr, configIP_ADDR0, configIP_ADDR1, configIP_ADDR2, configIP_ADDR3);
IP4_ADDR(&fsl_netif0_netmask, configNET_MASK0, configNET_MASK1, configNET_MASK2, configNET_MASK3);
IP4_ADDR(&fsl_netif0_gw, configGW_ADDR0, configGW_ADDR1, configGW_ADDR2, configGW_ADDR3);
netifapi_netif_add(&fsl_netif0, &fsl_netif0_ipaddr, &fsl_netif0_netmask, &fsl_netif0_gw, &fsl_enet_config0,
ethernetif0_init, tcpip_input);
Pass tcpip_input API to netif_add API as input callback function that is called to pass ingress packets up in the protocol layer stack
next we need to bring the interface up
An interface that is “up” is available to your application for input and output, and “down” is the opposite state. Therefore, before you can use the interface, you must bring it up. This can be accomplished depending on how the interface gets its IP address. We can use static IP address or DHCP.
Set the network interface as the default network interface
netifapi_netif_set_default(&fsl_netif0);
Bring the interface up, available for processing
netifapi_netif_set_up(&fsl_netif0);
4. LwIP Initialization
Call tcpip_init to create a tcpip_thread, this thread has exclusive access to LwIP core functions. Other threads communicate with this thread using message boxes. It also starts all the timers to make sure they are running in the right thread context.
tcpip_init(NULL, NULL);
5. TCP Client & Server Implementation
For TCP communications, one host listens for incoming connection requests. When a request arrives, the server accepts it and data is transferred between the server and the client. The sequence of function calls for the client and a server participating in a TCP connection is show in below picture.
The steps for establishing a TCP socket on the client side are the following:
Create a socket using the socket() function.
Connect the socket to the address of the server using the connect() function.
Send and receive data by means fo the recv() and send() functions.
Close the connection by means of the close() function.
The steps in establishing a TCP socket on the server side are as follows
Create a socket with socket() function;
Bind the socket to an address using the bind() function.
Listen for connections with the listen() function.
Accept a connection with the accept() function, this blocks until a client connects.
Send and receive data by means of send() and receive().
Close the connection by means of the close() function.
4.1 TCP SERVER IMPLEMENTATION
Socket is a standard set of function calls used at application level. When user calls the function socket(), it creates a socket and returns references a number for the socket.
In this demo, a socket structure was created and filled in this way:
/* Create a new socket. */
server_sock = socket(AF_INET, SOCK_STREAM, 0);
LWIP_ASSERT("server_sock >= 0 ", server_sock >= 0);
server_addr.sin_family = AF_INET;
server_addr.sin_port = PP_HTONS(TCP_CUSTOM_PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
Bind function allows user to associate a socket with a particular local port and IP address.
err = bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
Listen() function prepares the given socket to accept incoming TCP requests.
/* Tell connection to go into listening mode. */
err = listen(server_sock, 0);
Accept() function detects incoming connection requests on the listening socket. This call will cause a task to wait until a connection request is received.
/* Grab new connection. */
client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_len);
The recv() function receives a message from a socket.
The send() function initiates transmission of a message from the specified socket to its peer.
In this demo, if server board receives a ‘TOGGLE’ message from the client board, it will toggle a LED. (GPIO1/3, need to connect a LED manually)
while ( recv(client_sock, rxbuf, rx_len, 0 ) > 0 )
{
uint8_t *tcp_rx_buf;
delay();
tcp_rx_buf = (void *)rxbuf;
tcp_rx_buf[rx_len]= '\0';
PRINTF("Received : %s \r\n", rxbuf);
if ( (tcp_rx_buf[0] == 'T') || (tcp_rx_buf[0] == 't') )
{
PRINTF("LED was toggled from client\r\n");
GPIO_PortToggle(EXAMPLE_LED_GPIO, 1u << EXAMPLE_LED_GPIO_PIN);
/*send a message to client*/
send(client_sock, SendReply, sizeof(SendReply), 0);
}
}
4.2 TCP CLIENT IMPLEMENTATION
For the client side, the socket structure is created:
client_sock = socket(AF_INET, SOCK_STREAM, 0);
connect(): when a user issues a connect command, the stack creates a connection with another host. Before connect can instruct the stack to establish a connection, the user must pass a socket and a sockaddr_in structure containing the destination IP address and port. In TCP, the handshaking packets will be exchanged.
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = PP_HTONS(TCP_CUSTOM_PORT);
server_addr.sin_addr.s_addr = server_ip_addr.addr;
err = connect(client_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
LWIP_ASSERT("connect fail, please start TCP server first ! ", err == 0);
In this demo, a GPIO is initialized and interrupt is enabled. When SW8 is pressed, the client board will send ‘Toggle’ command to the TCP server.
while (1)
{
if (g_InputSignal)
{
delay();
if (1 == GPIO_PinRead(EXAMPLE_SW_GPIO, EXAMPLE_SW_GPIO_PIN))
{
PRINTF("%s is turned on.\r\n", EXAMPLE_SW_NAME);
err = send(server_sock, Sendtext, sizeof(Sendtext) / sizeof(Sendtext[0]), 0);
}
/* Reset state of switch. */
g_InputSignal = false;
} //end of if (g_InputSignal)
}
View full article