docs: provide CN translation for api-guides/lwip.rst

This commit is contained in:
caixinying-git 2023-05-31 16:56:11 +08:00
parent 1af5e870f1
commit 6ee5bbf3a6
2 changed files with 694 additions and 159 deletions

View File

@ -1,6 +1,8 @@
lwIP
====
:link_to_translation:`zh_CN:[中文]`
ESP-IDF uses the open source `lwIP lightweight TCP/IP stack`_. The ESP-IDF version of lwIP (`esp-lwip`_) has some modifications and additions compared to the upstream project.
Supported APIs
@ -16,17 +18,17 @@ Adapted APIs
.. warning::
When using any lwIP API (other than `BSD Sockets API`_), please make sure that it is thread safe. To check if a given API call is safe, enable :ref:`CONFIG_LWIP_CHECK_THREAD_SAFETY` and run the application. This way lwIP asserts the TCP/IP core functionality to be correctly accessed; the execution aborts if it is not locked properly or accessed from the correct task (`lwIP FreeRTOS Task`_).
The general recommendation is to use :doc:`/api-reference/network/esp_netif` component to interact with lwIP.
When using any lwIP API other than the `BSD Sockets API`_, please make sure that the API is thread-safe. To check if a given API call is thread-safe, enable the :ref:`CONFIG_LWIP_CHECK_THREAD_SAFETY` configuration option and run the application. This enables lwIP to assert the correct access of the TCP/IP core functionality. If the API is not accessed or locked properly from the appropriate `lwIP FreeRTOS Task`_, the execution will be aborted. The general recommendation is to use the :doc:`/api-reference/network/esp_netif` component to interact with lwIP.
Some common lwIP "app" APIs are supported indirectly by ESP-IDF:
Some common lwIP app APIs are supported indirectly by ESP-IDF:
- DHCP Server & Client are supported indirectly via the :doc:`/api-reference/network/esp_netif` functionality
- Simple Network Time Protocol (SNTP) is also supported via the :doc:`/api-reference/network/esp_netif`, or directly via the :component_file:`lwip/include/apps/esp_sntp.h` functions that provide thread-safe API to :component_file:`lwip/lwip/src/include/lwip/apps/sntp.h` functions (see also :ref:`system-time-sntp-sync`)
- ICMP Ping is supported using a variation on the lwIP ping API. See :doc:`/api-reference/protocols/icmp_echo`.
- NetBIOS lookup is available using the standard lwIP API. :example:`protocols/http_server/restful_server` has an option to demonstrate using NetBIOS to look up a host on the LAN.
- mDNS uses a different implementation to the lwIP default mDNS (see :doc:`/api-reference/protocols/mdns`), but lwIP can look up mDNS hosts using standard APIs such as ``gethostbyname()`` and the convention ``hostname.local``, provided the :ref:`CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES` setting is enabled.
- The PPP implementation in lwIP can be used to create PPPoS (PPP over serial) interface in ESP-IDF. Please refer to the documentation of :doc:`/api-reference/network/esp_netif` component to create and configure a PPP network interface, by means of the ``ESP_NETIF_DEFAULT_PPP()`` macro defined in :component_file:`esp_netif/include/esp_netif_defaults.h`. Additional runtime settings are provided via the :component_file:`esp_netif/include/esp_netif_ppp.h`. PPPoS interfaces are typically used to interact with NBIoT/GSM/LTE modems; more application level friendly API is supported by `esp_modem <https://components.espressif.com/component/espressif/esp_modem>`_ library, which uses this PPP lwIP module behind the scenes.
- Dynamic Host Configuration Protocol (DHCP) Server & Client are supported indirectly via the :doc:`/api-reference/network/esp_netif` functionality.
- Simple Network Time Protocol (SNTP) is also supported via the :doc:`/api-reference/network/esp_netif`, or directly via the :component_file:`lwip/include/apps/esp_sntp.h` functions, which also provide thread-safe API to :component_file:`lwip/lwip/src/include/lwip/apps/sntp.h` functions, see also :ref:`system-time-sntp-sync`.
- ICMP Ping is supported using a variation on the lwIP ping API, see :doc:`/api-reference/protocols/icmp_echo`.
- ICMPv6 Ping, supported by lwIP's ICMPv6 Echo API, is used to test IPv6 network connectivity. For more information, see :example:`protocols/sockets/icmpv6_ping`.
- NetBIOS lookup is available using the standard lwIP API. :example:`protocols/http_server/restful_server` has the option to demonstrate using NetBIOS to look up a host on the LAN.
- mDNS uses a different implementation to the lwIP default mDNS, see :doc:`/api-reference/protocols/mdns`. But lwIP can look up mDNS hosts using standard APIs such as ``gethostbyname()`` and the convention ``hostname.local``, provided the :ref:`CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES` setting is enabled.
- The PPP implementation in lwIP can be used to create PPPoS (PPP over serial) interface in ESP-IDF. Please refer to the documentation of the :doc:`/api-reference/network/esp_netif` component to create and configure a PPP network interface, by means of the ``ESP_NETIF_DEFAULT_PPP()`` macro defined in :component_file:`esp_netif/include/esp_netif_defaults.h`. Additional runtime settings are provided via :component_file:`esp_netif/include/esp_netif_ppp.h`. PPPoS interfaces are typically used to interact with NBIoT/GSM/LTE modems. More application-level friendly API is supported by the `esp_modem <https://components.espressif.com/component/espressif/esp_modem>`_ library, which uses this PPP lwIP module behind the scenes.
BSD Sockets API
---------------
@ -38,10 +40,10 @@ As implemented in ESP-IDF, lwIP supports all of the common usages of the BSD Soc
References
^^^^^^^^^^
A wide range of BSD Sockets reference material is available, including:
A wide range of BSD Sockets reference materials are available, including:
- `Single UNIX Specification BSD Sockets page <https://pubs.opengroup.org/onlinepubs/007908799/xnsix.html>`_
- `Berkeley Sockets Wikipedia page <https://en.wikipedia.org/wiki/Berkeley_sockets>`_
- `Single UNIX Specification - BSD Sockets page <https://pubs.opengroup.org/onlinepubs/007908799/xnsix.html>`_
- `Berkeley Sockets - Wikipedia page <https://en.wikipedia.org/wiki/Berkeley_sockets>`_
Examples
^^^^^^^^
@ -53,57 +55,63 @@ A number of ESP-IDF examples show how to use the BSD Sockets APIs:
- :example:`protocols/sockets/udp_server`
- :example:`protocols/sockets/udp_client`
- :example:`protocols/sockets/udp_multicast`
- :example:`protocols/http_request` (Note: this is a simplified example of using a TCP socket to send an HTTP request. The :doc:`/api-reference/protocols/esp_http_client` is a much better option for sending HTTP requests.)
- :example:`protocols/http_request`: this simplified example uses a TCP socket to send an HTTP request, but :doc:`/api-reference/protocols/esp_http_client` is a much better option for sending HTTP requests
Supported functions
Supported Functions
^^^^^^^^^^^^^^^^^^^
The following BSD socket API functions are supported. For full details see :component_file:`lwip/lwip/src/include/lwip/sockets.h`.
The following BSD socket API functions are supported. For full details, see :component_file:`lwip/lwip/src/include/lwip/sockets.h`.
- ``socket()``
- ``bind()``
- ``accept()``
- ``shutdown()``
- ``getpeername()``
- ``getsockopt()`` & ``setsockopt()`` (see `Socket Options`_)
- ``close()`` (via :doc:`/api-reference/storage/vfs`)
- ``read()``, ``readv()``, ``write()``, ``writev()`` (via :doc:`/api-reference/storage/vfs`)
- ``getsockopt()`` & ``setsockopt()``: see `Socket Options`_
- ``close()``: via :doc:`/api-reference/storage/vfs`
- ``read()``, ``readv()``, ``write()``, ``writev()``: via :doc:`/api-reference/storage/vfs`
- ``recv()``, ``recvmsg()``, ``recvfrom()``
- ``send()``, ``sendmsg()``, ``sendto()``
- ``select()`` (via :doc:`/api-reference/storage/vfs`)
- ``poll()`` (Note: on ESP-IDF, ``poll()`` is implemented by calling select internally, so using ``select()`` directly is recommended if a choice of methods is available.)
- ``fcntl()`` (see `fcntl`_)
- ``select()``: via :doc:`/api-reference/storage/vfs`
- ``poll()`` : on ESP-IDF, ``poll()`` is implemented by calling ``select()`` internally, so using ``select()`` directly is recommended, if a choice of methods is available
- ``fcntl()``: see `fcntl()`_
Non-standard functions:
- ``ioctl()`` (see `ioctls`_)
- ``ioctl()``: see `ioctl()`_
.. note:: Some lwIP application sample code uses prefixed versions of BSD APIs, for example ``lwip_socket()`` instead of the standard ``socket()``. Both forms can be used with ESP-IDF, but using standard names is recommended.
.. note::
Some lwIP application sample code uses prefixed versions of BSD APIs, e.g., ``lwip_socket()``, instead of the standard ``socket()``. Both forms can be used with ESP-IDF, but using standard names is recommended.
Socket Error Handling
^^^^^^^^^^^^^^^^^^^^^
BSD Socket error handling code is very important for robust socket applications. Normally the socket error handling involves the following aspects:
BSD Socket error handling code is very important for robust socket applications. Normally, socket error handling involves the following aspects:
- Detecting the error.
- Geting the error reason code.
- Handle the error according to the reason code.
- Detecting the error
- Getting the error reason code
- Handling the error according to the reason code
In lwIP, we have two different scenarios of handling socket errors:
In lwIP, we have two different scenarios for handling socket errors:
- Socket API returns an error. For more information, see `Socket API Errors`_.
- ``select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout)`` has exception descriptor indicating that the socket has an error. For more information, see `select() Errors`_.
- ``select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout)`` has an exception descriptor indicating that the socket has an error. For more information, see `select() Errors`_.
Socket API Errors
+++++++++++++++++
The error detection
**Error detection**
- We can know that the socket API fails according to its return value.
Get the error reason code
- When socket API fails, the return value doesn't contain the failure reason and the application can get the error reason code by accessing errno. Different values indicate different meanings. For more information, see <`Socket Error Reason Code`_>.
**Get the error reason code**
Example::
- When socket API fails, the return value does not contain the failure reason and the application can get the error reason code by accessing ``errno``. Different values indicate different meanings. For more information, see `Socket Error Reason Code`_.
Example:
.. code-block::
int err;
int sockfd;
@ -114,18 +122,24 @@ Example::
return err;
}
select() Errors
+++++++++++++++
``select()`` Errors
+++++++++++++++++++
The error detection
- Socket error when ``select()`` has exception descriptor
**Error detection**
Get the error reason code
- If the ``select`` indicates that the socket fails, we can't get the error reason code by accessing errno, instead we should call ``getsockopt()`` to get the failure reason code. Because ``select()`` has exception descriptor, the error code will not be given to errno.
- Socket error when ``select()`` has exception descriptor.
.. note:: ``getsockopt`` function prototype ``int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen)``. Its function is to get the current value of the option of any type, any state socket, and store the result in optval. For example, when you get the error code on a socket, you can get it by ``getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &optlen)``.
**Get the error reason code**
Example::
- If the ``select()`` indicates that the socket fails, we can not get the error reason code by accessing ``errno``, instead we should call ``getsockopt()`` to get the failure reason code. Since ``select()`` has exception descriptor, the error code is not given to ``errno``.
.. note::
The ``getsockopt()`` function has the following prototype: ``int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen)``. Its purpose is to get the current value of the option of any type, any state socket, and store the result in ``optval``. For example, when you get the error code on a socket, you can get it by ``getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &optlen)``.
Example:
.. code-block::
int err;
@ -144,66 +158,70 @@ Example::
Socket Error Reason Code
++++++++++++++++++++++++
Below is a list of common error codes. For more detailed list of standard POSIX/C error codes, please see `newlib errno.h <https://github.com/espressif/newlib-esp32/blob/master/newlib/libc/include/sys/errno.h>`_ and the platform-specific extensions :component_file:`newlib/platform_include/errno.h`
Below is a list of common error codes. For a more detailed list of standard POSIX/C error codes, please see `newlib errno.h <https://github.com/espressif/newlib-esp32/blob/master/newlib/libc/include/sys/errno.h>`_ and the platform-specific extensions :component_file:`newlib/platform_include/errno.h`.
+-----------------+-------------------------------------+
| Error code | Description |
+=================+=====================================+
| ECONNREFUSED | Connection refused |
+-----------------+-------------------------------------+
| EADDRINUSE | Address already in use |
+-----------------+-------------------------------------+
| ECONNABORTED | Software caused connection abort |
+-----------------+-------------------------------------+
| ENETUNREACH | Network is unreachable |
+-----------------+-------------------------------------+
| ENETDOWN | Network interface is not configured |
+-----------------+-------------------------------------+
| ETIMEDOUT | Connection timed out |
+-----------------+-------------------------------------+
| EHOSTDOWN | Host is down |
+-----------------+-------------------------------------+
| EHOSTUNREACH | Host is unreachable |
+-----------------+-------------------------------------+
| EINPROGRESS | Connection already in progress |
+-----------------+-------------------------------------+
| EALREADY | Socket already connected |
+-----------------+-------------------------------------+
| EDESTADDRREQ | Destination address required |
+-----------------+-------------------------------------+
| EPROTONOSUPPORT | Unknown protocol |
+-----------------+-------------------------------------+
.. list-table::
:header-rows: 1
:widths: 50 50
:align: center
* - Error code
- Description
* - ECONNREFUSED
- Connection refused
* - EADDRINUSE
- Address already in use
* - ECONNABORTED
- Software caused connection abort
* - ENETUNREACH
- Network is unreachable
* - ENETDOWN
- Network interface is not configured
* - ETIMEDOUT
- Connection timed out
* - EHOSTDOWN
- Host is down
* - EHOSTUNREACH
- Host is unreachable
* - EINPROGRESS
- Connection already in progress
* - EALREADY
- Socket already connected
* - EDESTADDRREQ
- Destination address required
* - EPROTONOSUPPORT
- Unknown protocol
Socket Options
^^^^^^^^^^^^^^
The ``getsockopt()`` and ``setsockopt()`` functions allow getting/setting per-socket options.
The ``getsockopt()`` and ``setsockopt()`` functions allow getting and setting per-socket options respectively.
Not all standard socket options are supported by lwIP in ESP-IDF. The following socket options are supported:
Common options
Common Options
++++++++++++++
Used with level argument ``SOL_SOCKET``.
- ``SO_REUSEADDR`` (available if :ref:`CONFIG_LWIP_SO_REUSE` is set, behavior can be customized by setting :ref:`CONFIG_LWIP_SO_REUSE_RXTOALL`)
- ``SO_REUSEADDR``: available if :ref:`CONFIG_LWIP_SO_REUSE` is set, whose behavior can be customized by setting :ref:`CONFIG_LWIP_SO_REUSE_RXTOALL`
- ``SO_KEEPALIVE``
- ``SO_BROADCAST``
- ``SO_ACCEPTCONN``
- ``SO_RCVBUF`` (available if :ref:`CONFIG_LWIP_SO_RCVBUF` is set)
- ``SO_RCVBUF``: available if :ref:`CONFIG_LWIP_SO_RCVBUF` is set
- ``SO_SNDTIMEO`` / ``SO_RCVTIMEO``
- ``SO_ERROR`` (this option is only used with ``select()``, see `Socket Error Handling`_)
- ``SO_ERROR``: only used with ``select()``, see `Socket Error Handling`_
- ``SO_TYPE``
- ``SO_NO_CHECK`` (for UDP sockets only)
- ``SO_NO_CHECK``: for UDP sockets only
IP options
IP Options
++++++++++
Used with level argument ``IPPROTO_IP``.
- ``IP_TOS``
- ``IP_TTL``
- ``IP_PKTINFO`` (available if :ref:`CONFIG_LWIP_NETBUF_RECVINFO` is set)
- ``IP_PKTINFO``: available if :ref:`CONFIG_LWIP_NETBUF_RECVINFO` is set
For multicast UDP sockets:
@ -213,7 +231,7 @@ For multicast UDP sockets:
- ``IP_ADD_MEMBERSHIP``
- ``IP_DROP_MEMBERSHIP``
TCP options
TCP Options
+++++++++++
TCP sockets only. Used with level argument ``IPPROTO_TCP``.
@ -222,15 +240,15 @@ TCP sockets only. Used with level argument ``IPPROTO_TCP``.
Options relating to TCP keepalive probes:
- ``TCP_KEEPALIVE`` (int value, TCP keepalive period in milliseconds)
- ``TCP_KEEPIDLE`` (same as ``TCP_KEEPALIVE``, but the value is in seconds)
- ``TCP_KEEPINTVL`` (int value, interval between keepalive probes in seconds)
- ``TCP_KEEPCNT`` (int value, number of keepalive probes before timing out)
- ``TCP_KEEPALIVE``: int value, TCP keepalive period in milliseconds
- ``TCP_KEEPIDLE``: same as ``TCP_KEEPALIVE``, but the value is in seconds
- ``TCP_KEEPINTVL``: int value, the interval between keepalive probes in seconds
- ``TCP_KEEPCNT``: int value, number of keepalive probes before timing out
IPv6 options
IPv6 Options
++++++++++++
IPv6 sockets only. Used with level argument ``IPPROTO_IPV6``
IPv6 sockets only. Used with level argument ``IPPROTO_IPV6``.
- ``IPV6_CHECKSUM``
- ``IPV6_V6ONLY``
@ -243,45 +261,47 @@ For multicast IPv6 UDP sockets:
- ``IPV6_MULTICAST_HOPS``
- ``IPV6_MULTICAST_LOOP``
fcntl
^^^^^
``fcntl()``
^^^^^^^^^^^
The ``fcntl()`` function is a standard API for manipulating options related to a file descriptor. In ESP-IDF, the :doc:`/api-reference/storage/vfs` layer is used to implement this function.
When the file descriptor is a socket, only the following ``fcntl()`` values are supported:
- ``O_NONBLOCK`` to set/clear non-blocking I/O mode. Also supports ``O_NDELAY``, which is identical to ``O_NONBLOCK``.
- ``O_RDONLY``, ``O_WRONLY``, ``O_RDWR`` flags for different read/write modes. These can read via ``F_GETFL`` only, they cannot be set using ``F_SETFL``. A TCP socket will return a different mode depending on whether the connection has been closed at either end or is still open at both ends. UDP sockets always return ``O_RDWR``.
- ``O_NONBLOCK`` to set or clear non-blocking I/O mode. Also supports ``O_NDELAY``, which is identical to ``O_NONBLOCK``.
- ``O_RDONLY``, ``O_WRONLY``, ``O_RDWR`` flags for different read or write modes. These flags can only be read using ``F_GETFL``, and cannot be set using ``F_SETFL``. A TCP socket returns a different mode depending on whether the connection has been closed at either end or is still open at both ends. UDP sockets always return ``O_RDWR``.
ioctls
^^^^^^
``ioctl()``
^^^^^^^^^^^
The ``ioctl()`` function provides a semi-standard way to access some internal features of the TCP/IP stack. In ESP-IDF, the :doc:`/api-reference/storage/vfs` layer is used to implement this function.
When the file descriptor is a socket, only the following ``ioctl()`` values are supported:
- ``FIONREAD`` returns the number of bytes of pending data already received in the socket's network buffer.
- ``FIONREAD`` returns the number of bytes of the pending data already received in the socket's network buffer.
- ``FIONBIO`` is an alternative way to set/clear non-blocking I/O status for a socket, equivalent to ``fcntl(fd, F_SETFL, O_NONBLOCK, ...)``.
Netconn API
-----------
lwIP supports two lower level APIs as well as the BSD Sockets API: the Netconn API and the Raw API.
lwIP supports two lower-level APIs as well as the BSD Sockets API: the Netconn API and the Raw API.
The lwIP Raw API is designed for single threaded devices and is not supported in ESP-IDF.
The lwIP Raw API is designed for single-threaded devices and is not supported in ESP-IDF.
The Netconn API is used to implement the BSD Sockets API inside lwIP, and it can also be called directly from ESP-IDF apps. This API has lower resource usage than the BSD Sockets API, in particular it can send and receive data without needing to first copy it into internal lwIP buffers.
The Netconn API is used to implement the BSD Sockets API inside lwIP, and it can also be called directly from ESP-IDF apps. This API has lower resource usage than the BSD Sockets API. In particular, it can send and receive data without firstly copying it into internal lwIP buffers.
.. important:: Espressif does not test the Netconn API in ESP-IDF. As such, this functionality is *enabled but not supported*. Some functionality may only work correctly when used from the BSD Sockets API.
.. important::
For more information about the Netconn API, consult `lwip/lwip/src/include/lwip/api.h <http://www.nongnu.org/lwip/2_0_x/api_8h.html>`_ and `this wiki page which is part of the unofficial lwIP Application Developers Manual <https://lwip.fandom.com/wiki/Netconn_API>`_.
Espressif does not test the Netconn API in ESP-IDF. As such, this functionality is **enabled but not supported**. Some functionality may only work correctly when used from the BSD Sockets API.
For more information about the Netconn API, consult `lwip/lwip/src/include/lwip/api.h <http://www.nongnu.org/lwip/2_0_x/api_8h.html>`_ and `part of the **unofficial** lwIP Application Developers Manual <https://lwip.fandom.com/wiki/Netconn_API>`_.
lwIP FreeRTOS Task
------------------
lwIP creates a dedicated TCP/IP FreeRTOS task to handle socket API requests from other tasks.
A number of configuration items are available to modify the task and the queues ("mailboxes") used to send data to/from the TCP/IP task:
A number of configuration items are available to modify the task and the queues (mailboxes) used to send data to/from the TCP/IP task:
- :ref:`CONFIG_LWIP_TCPIP_RECVMBOX_SIZE`
- :ref:`CONFIG_LWIP_TCPIP_TASK_STACK_SIZE`
@ -290,86 +310,89 @@ A number of configuration items are available to modify the task and the queues
IPv6 Support
------------
Both IPv4 and IPv6 are supported as a dual stack and are enabled by default. Both IPv6 and IPv4 may be disabled separately if they are not needed (see :ref:`lwip-ram-usage`).
IPv6 support is limited to *Stateless Autoconfiguration* only, *Stateful configuration* is not supported in ESP-IDF (not in upstream lwip).
Both IPv4 and IPv6 are supported in a dual-stack configuration and are enabled by default. Both IPv6 and IPv4 may be disabled separately if they are not needed, see :ref:`lwip-ram-usage`.
IPv6 support is limited to **Stateless Autoconfiguration** only. **Stateful configuration** is not supported in ESP-IDF, nor in upstream lwIP.
IPv6 Address configuration is defined by means of these protocols or services:
- **SLAAC** IPv6 Stateless Address Autoconfiguration (RFC-2462)
- **DHCPv6** Dynamic Host Configuration Protocol for IPv6 (RFC-8415)
None of these two types of address configuration is enabled by default, so the device uses only Link Local addresses or statically defined addresses.
None of these two types of address configuration is enabled by default, so the device uses only Link Local addresses or statically-defined addresses.
.. _lwip-ivp6-autoconfig:
Stateless Autoconfiguration Process
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To enable address autoconfiguration using Router Advertisement protocol please enable:
To enable address autoconfiguration using the Router Advertisement protocol, please enable:
- :ref:`CONFIG_LWIP_IPV6_AUTOCONFIG`
This configuration option enables IPv6 autoconfiguration for all network interfaces
(in contrast to the upstream lwIP, where the autoconfiguration needs to be explicitly enabled for each netif with ``netif->ip6_autoconfig_enabled=1``
This configuration option enables IPv6 autoconfiguration for all network interfaces, which differs from the upstream lwIP behavior, where the autoconfiguration needs to be explicitly enabled for each ``netif`` with ``netif->ip6_autoconfig_enabled=1``.
.. _lwip-ivp6-dhcp6:
DHCPv6
^^^^^^
DHCPv6 in lwIP is very simple and support only stateless configuration. It could be enabled using:
DHCPv6 in lwIP is very simple and supports only stateless configuration. It could be enabled using:
- :ref:`CONFIG_LWIP_IPV6_DHCP6`
Since the DHCPv6 works only in its stateless configuration, the :ref:`lwip-ivp6-autoconfig` has to be enabled, too, by means of :ref:`CONFIG_LWIP_IPV6_AUTOCONFIG`.
Moreover, the DHCPv6 needs to be explicitly enabled form the application code using
Since the DHCPv6 works only in its stateless configuration, the :ref:`lwip-ivp6-autoconfig` has to be enabled as well via :ref:`CONFIG_LWIP_IPV6_AUTOCONFIG`.
Moreover, the DHCPv6 needs to be explicitly enabled from the application code using:
.. code-block::
dhcp6_enable_stateless(netif);
DNS servers in IPv6 autoconfiguration
DNS Servers in IPv6 Autoconfiguration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In order to autoconfigure DNS server(s), especially in IPv6 only networks, we have these two options
In order to autoconfigure DNS server(s), especially in IPv6-only networks, we have these two options:
- Recursive domain name system -- this belongs to the Neighbor Discovery Protocol (NDP), uses :ref:`lwip-ivp6-autoconfig`.
Number of servers must be set :ref:`CONFIG_LWIP_IPV6_RDNSS_MAX_DNS_SERVERS`, this is option is disabled (set to 0) by default.
- Recursive Domain Name System (DNS): this belongs to the Neighbor Discovery Protocol (NDP) and uses :ref:`lwip-ivp6-autoconfig`.
- DHCPv6 stateless configuration -- uses :ref:`lwip-ivp6-dhcp6` to configure DNS servers. Note that the this configuration
assumes IPv6 Router Advertisement Flags (RFC-5175) to be set to
The number of servers must be set :ref:`CONFIG_LWIP_IPV6_RDNSS_MAX_DNS_SERVERS`, this option is disabled by default, i.e., set to 0.
- DHCPv6 stateless configuration, uses :ref:`lwip-ivp6-dhcp6` to configure DNS servers. Note that this configuration assumes IPv6 Router Advertisement Flags (RFC-5175) to be set to
- Managed Address Configuration Flag = 0
- Other Configuration Flag = 1
esp-lwip custom modifications
ESP-lwIP Custom Modifications
-----------------------------
Additions
^^^^^^^^^
The following code is added which is not present in the upstream lwIP release:
The following code is added, which is not present in the upstream lwIP release:
Thread-safe sockets
Thread-Safe Sockets
+++++++++++++++++++
It is possible to ``close()`` a socket from a different thread to the one that created it. The ``close()`` call will block until any function calls currently using that socket from other tasks have returned.
It is possible to ``close()`` a socket from a different thread than the one that created it. The ``close()`` call blocks, until any function calls currently using that socket from other tasks have returned.
It is, however, not possible to delete a task while it is actively waiting on ``select()`` or ``poll()`` APIs. It is always necessary that these APIs exit before destroying the task, as this might corrupt internal structures and cause subsequent crashes of the lwIP.
(These APIs allocate globally referenced callback pointers on stack, so that when the task gets destroyed before unrolling the stack, the lwIP would still hold pointers to the deleted stack)
It is, however, not possible to delete a task while it is actively waiting on ``select()`` or ``poll()`` APIs. It is always necessary that these APIs exit before destroying the task, as this might corrupt internal structures and cause subsequent crashes of the lwIP. These APIs allocate globally referenced callback pointers on the stack so that when the task gets destroyed before unrolling the stack, the lwIP could still hold pointers to the deleted stack.
On demand timers
On-Demand Timers
++++++++++++++++
lwIP IGMP and MLD6 features both initialize a timer in order to trigger timeout events at certain times.
lwIP IGMP and MLD6 feature both initialize a timer in order to trigger timeout events at certain times.
The default lwIP implementation is to have these timers enabled all the time, even if no timeout events are active. This increases CPU usage and power consumption when using automatic light sleep mode. ``esp-lwip`` default behaviour is to set each timer "on demand" so it is only enabled when an event is pending.
The default lwIP implementation is to have these timers enabled all the time, even if no timeout events are active. This increases CPU usage and power consumption when using automatic Light-sleep mode. ``ESP-lwIP`` default behavior is to set each timer ``on demand``, so it is only enabled when an event is pending.
To return to the default lwIP behaviour (always-on timers), disable :ref:`CONFIG_LWIP_TIMERS_ONDEMAND`.
To return to the default lwIP behavior, which is always-on timers, disable :ref:`CONFIG_LWIP_TIMERS_ONDEMAND`.
Lwip timers API
lwIP Timers API
+++++++++++++++
When users are not using WiFi, these APIs provide users with the ability to turn off LwIP timer to reduce power consumption.
When not using Wi-Fi, the lwIP timer can be turned off via the API to reduce power consumption.
The following API functions are supported. For full details see :component_file:`lwip/lwip/src/include/lwip/timeouts.h`.
The following API functions are supported. For full details, see :component_file:`lwip/lwip/src/include/lwip/timeouts.h`.
- ``sys_timeouts_init()``
- ``sys_timeouts_deinit()``
@ -377,21 +400,21 @@ The following API functions are supported. For full details see :component_file:
Additional Socket Options
+++++++++++++++++++++++++
- Some standard IPV4 and IPV6 multicast socket options are implemented (see `Socket Options`).
- Some standard IPV4 and IPV6 multicast socket options are implemented, see `Socket Options`_.
- Possible to set IPV6-only UDP and TCP sockets with ``IPV6_V6ONLY`` socket option (normal lwIP is TCP only).
- Possible to set IPV6-only UDP and TCP sockets with ``IPV6_V6ONLY`` socket option, while normal lwIP is TCP-only.
IP layer features
IP Layer Features
+++++++++++++++++
- IPV4 source based routing implementation is different.
- IPV4-source-based routing implementation is different
- IPV4 mapped IPV6 addresses are supported.
- IPV4-mapped IPV6 addresses are supported
Customized lwIP hooks
Customized lwIP Hooks
+++++++++++++++++++++
The original lwIP supports implementing custom compile-time modifications via ``LWIP_HOOK_FILENAME``. This file is already used by the IDF port layer, but IDF users could still include and implement any custom additions via a header file defined by the macro ``ESP_IDF_LWIP_HOOK_FILENAME``. Here is an exmaple of adding a custom hook file to the build process (the hook is called ``my_hook.h`` and located in the project's ``main`` folder):
The original lwIP supports implementing custom compile-time modifications via ``LWIP_HOOK_FILENAME``. This file is already used by the ESP-IDF port layer, but ESP-IDF users could still include and implement any custom additions via a header file defined by the macro ``ESP_IDF_LWIP_HOOK_FILENAME``. Here is an example of adding a custom hook file to the build process, and the hook is called ``my_hook.h``, located in the project's ``main`` folder:
.. code-block:: cmake
@ -402,11 +425,12 @@ The original lwIP supports implementing custom compile-time modifications via ``
Limitations
^^^^^^^^^^^
Calling ``send()`` or ``sendto()`` repeatedly on a UDP socket may eventually fail with ``errno`` equal to ``ENOMEM``. This is a limitation of buffer sizes in the lower layer network interface drivers. If all driver transmit buffers are full then UDP transmission will fail. Applications sending a high volume of UDP datagrams who don't wish for any to be dropped by the sender should check for this error code and re-send the datagram after a short delay.
Calling ``send()`` or ``sendto()`` repeatedly on a UDP socket may eventually fail with ``errno`` equal to ``ENOMEM``. This failure occurs due to the limitations of buffer sizes in the lower-layer network interface drivers. If all driver transmit buffers are full, the UDP transmission will fail. For applications that transmit a high volume of UDP datagrams and aim to avoid any dropped datagrams by the sender, it is advisable to implement error code checking and employ a retransmission mechanism with a short delay.
.. only:: esp32
Increasing the number of TX buffers in the :ref:`Wi-Fi <CONFIG_ESP_WIFI_TX_BUFFER>` or :ref:`Ethernet <CONFIG_ETH_DMA_TX_BUFFER_NUM>` project configuration (as applicable) may also help.
Increasing the number of TX buffers in the :ref:`Wi-Fi <CONFIG_ESP_WIFI_TX_BUFFER>` or :ref:`Ethernet <CONFIG_ETH_DMA_TX_BUFFER_NUM>` project configuration as applicable may also help.
.. only:: not esp32 and SOC_WIFI_SUPPORTED
@ -417,46 +441,48 @@ Calling ``send()`` or ``sendto()`` repeatedly on a UDP socket may eventually fai
Performance Optimization
------------------------
TCP/IP performance is a complex subject, and performance can be optimized towards multiple goals. The default settings of ESP-IDF are tuned for a compromise between throughput, latency, and moderate memory usage.
TCP/IP performance is a complex subject, and performance can be optimized toward multiple goals. The default settings of ESP-IDF are tuned for a compromise between throughput, latency, and moderate memory usage.
Maximum throughput
Maximum Throughput
^^^^^^^^^^^^^^^^^^
Espressif tests ESP-IDF TCP/IP throughput using the :example:`wifi/iperf` example in an RF sealed enclosure.
Espressif tests ESP-IDF TCP/IP throughput using the :example:`wifi/iperf` example in an RF-sealed enclosure.
The :example_file:`wifi/iperf/sdkconfig.defaults` file for the iperf example contains settings known to maximize TCP/IP throughput, usually at the expense of higher RAM usage. To get maximum TCP/IP throughput in an application at the expense of other factors then suggest applying settings from this file into the project sdkconfig.
The :example_file:`wifi/iperf/sdkconfig.defaults` file for the iperf example contains settings known to maximize TCP/IP throughput, usually at the expense of higher RAM usage. To get maximum TCP/IP throughput in an application at the expense of other factors, it is suggested to apply settings from this file into the project sdkconfig.
.. important:: Suggest applying changes a few at a time and checking the performance each time with a particular application workload.
.. important::
- If a lot of tasks are competing for CPU time on the system, consider that the lwIP task has configurable CPU affinity (:ref:`CONFIG_LWIP_TCPIP_TASK_AFFINITY`) and runs at fixed priority (18). Configure competing tasks to be pinned to a different core, or to run at a lower priority. See also :ref:`built-in-task-priorities`.
Suggest applying changes a few at a time and checking the performance each time with a particular application workload.
- If a lot of tasks are competing for CPU time on the system, consider that the lwIP task has configurable CPU affinity (:ref:`CONFIG_LWIP_TCPIP_TASK_AFFINITY`) and runs at fixed priority (18). To optimize CPU utilization, consider assigning competing tasks to different cores or adjusting their priorities to lower values. For additional details on built-in task priorities, please refer to :ref:`built-in-task-priorities`.
- If using ``select()`` function with socket arguments only, disabling :ref:`CONFIG_VFS_SUPPORT_SELECT` will make ``select()`` calls faster.
- If there is enough free IRAM, select :ref:`CONFIG_LWIP_IRAM_OPTIMIZATION` and :ref:`CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION` to improve TX/RX throughput
- If there is enough free IRAM, select :ref:`CONFIG_LWIP_IRAM_OPTIMIZATION` and :ref:`CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION` to improve TX/RX throughput.
.. only:: SOC_WIFI_SUPPORTED
If using a Wi-Fi network interface, please also refer to :ref:`wifi-buffer-usage`.
Minimum latency
Minimum Latency
^^^^^^^^^^^^^^^
Except for increasing buffer sizes, most changes which increase throughput will also decrease latency by reducing the amount of CPU time spent in lwIP functions.
Except for increasing buffer sizes, most changes that increase throughput also decrease latency by reducing the amount of CPU time spent in lwIP functions.
- For TCP sockets, lwIP supports setting the standard ``TCP_NODELAY`` flag to disable Nagle's algorithm.
.. _lwip-ram-usage:
Minimum RAM usage
Minimum RAM Usage
^^^^^^^^^^^^^^^^^
Most lwIP RAM usage is on-demand, as RAM is allocated from the heap as needed. Therefore, changing lwIP settings to reduce RAM usage may not change RAM usage at idle but can change it at peak.
Most lwIP RAM usage is on-demand, as RAM is allocated from the heap as needed. Therefore, changing lwIP settings to reduce RAM usage may not change RAM usage at idle, but can change it at peak.
- Reducing :ref:`CONFIG_LWIP_MAX_SOCKETS` reduces the maximum number of sockets in the system. This will also cause TCP sockets in the ``WAIT_CLOSE`` state to be closed and recycled more rapidly (if needed to open a new socket), further reducing peak RAM usage.
- Reducing :ref:`CONFIG_LWIP_TCPIP_RECVMBOX_SIZE`, :ref:`CONFIG_LWIP_TCP_RECVMBOX_SIZE` and :ref:`CONFIG_LWIP_UDP_RECVMBOX_SIZE` reduce memory usage at the expense of throughput, depending on usage.
- Reducing :ref:`CONFIG_LWIP_TCP_MSL`, :ref:`CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT` reduces the maximum segment lifetime in the system. This will also cause TCP sockets in the ``TIME_WAIT``, ``FIN_WAIT_2`` state to be closed and recycled more rapidly
- Disabling :ref:`CONFIG_LWIP_IPV6` can save about 39 KB for firmware size and 2KB RAM when the system is powered up and 7KB RAM when the TCPIP stack is running. If there is no requirement for supporting IPV6 then it can be disabled to save flash and RAM footprint.
- Disabling :ref:`CONFIG_LWIP_IPV4` can save about 26 KB of firmware size and 600B RAM on power up and 6 KB RAM when the TCP/IP stack is running. If the local network supports IPv6-only configuration then IPv4 can be disabled to save flash and RAM footprint.
- Reducing :ref:`CONFIG_LWIP_MAX_SOCKETS` reduces the maximum number of sockets in the system. This also causes TCP sockets in the ``WAIT_CLOSE`` state to be closed and recycled more rapidly when needed to open a new socket, further reducing peak RAM usage.
- Reducing :ref:`CONFIG_LWIP_TCPIP_RECVMBOX_SIZE`, :ref:`CONFIG_LWIP_TCP_RECVMBOX_SIZE` and :ref:`CONFIG_LWIP_UDP_RECVMBOX_SIZE` reduce RAM usage at the expense of throughput, depending on usage.
- Reducing :ref:`CONFIG_LWIP_TCP_MSL` and :ref:`CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT` reduces the maximum segment lifetime in the system. This also causes TCP sockets in the ``TIME_WAIT`` and ``FIN_WAIT_2`` states to be closed and recycled more rapidly.
- Disabling :ref:`CONFIG_LWIP_IPV6` can save about 39 KB for firmware size and 2 KB RAM when the system is powered up and 7 KB RAM when the TCP/IP stack is running. If there is no requirement for supporting IPV6, it can be disabled to save flash and RAM footprint.
- Disabling :ref:`CONFIG_LWIP_IPV4` can save about 26 KB of firmware size and 600 B RAM on power up and 6 KB RAM when the TCP/IP stack is running. If the local network supports IPv6-only configuration, IPv4 can be disabled to save flash and RAM footprint.
.. only:: SOC_WIFI_SUPPORTED
@ -468,17 +494,17 @@ Peak Buffer Usage
The peak heap memory that lwIP consumes is the **theoretically-maximum memory** that the lwIP driver consumes. Generally, the peak heap memory that lwIP consumes depends on:
- the memory required to create a UDP connection: lwip_udp_conn
- the memory required to create a TCP connection: lwip_tcp_conn
- the number of UDP connections that the application has: lwip_udp_con_num
- the number of TCP connections that the application has: lwip_tcp_con_num
- the TCP TX window size: lwip_tcp_tx_win_size
- the TCP RX window size: lwip_tcp_rx_win_size
- the memory required to create a UDP connection: ``lwip_udp_conn``
- the memory required to create a TCP connection: ``lwip_tcp_conn``
- the number of UDP connections that the application has: ``lwip_udp_con_num``
- the number of TCP connections that the application has: ``lwip_tcp_con_num``
- the TCP TX window size: ``lwip_tcp_tx_win_size``
- the TCP RX window size: ``lwip_tcp_rx_win_size``
**So, the peak heap memory that the LwIP consumes can be calculated with the following formula:**
**So, the peak heap memory that the lwIP consumes can be calculated with the following formula:**
lwip_dynamic_peek_memory = (lwip_udp_con_num * lwip_udp_conn) + (lwip_tcp_con_num * (lwip_tcp_tx_win_size + lwip_tcp_rx_win_size + lwip_tcp_conn))
Some TCP-based applications need only one TCP connection. However, they may choose to close this TCP connection and create a new one when an error (such as a sending failure) occurs. This may result in multiple TCP connections existing in the system simultaneously, because it may take a long time for a TCP connection to close, according to the TCP state machine (refer to RFC793).
Some TCP-based applications need only one TCP connection. However, they may choose to close this TCP connection and create a new one when an error occurs (e.g., a sending failure). This may result in multiple TCP connections existing in the system simultaneously, because it may take a long time for a TCP connection to close, according to the TCP state machine, refer to RFC793.
.. _lwIP lightweight TCP/IP stack: https://savannah.nongnu.org/projects/lwip/

View File

@ -1,2 +1,511 @@
.. include:: ../../en/api-guides/lwip.rst
lwIP
====
:link_to_translation:`en:[English]`
ESP-IDF 使用开源的 `lwIP 轻量级 TCP/IP 协议栈`_,该版 lwIP (`esp-lwip`_) 相对上游项目做了修改和增补。
支持的 API
--------------
ESP-IDF 支持以下 lwIP TCP/IP 协议栈功能:
- `BSD 套接字 API`_
- `Netconn API`_ 已启用,但暂无对 ESP-IDF 应用程序的官方支持
适配的 API
^^^^^^^^^^^^
.. warning::
在使用除 `BSD 套接字 API`_ 外的任意 lwIP API 时,请确保所用 API 为线程安全。请启用 :ref:`CONFIG_LWIP_CHECK_THREAD_SAFETY` 配置选项并运行应用程序,检查所用 API 是否线程安全。此时lwIP 断言 TCP/IP 核心功能可以正确访问。如果未能从正确的 `lwIP FreeRTOS 任务`_ 访问,或没有正确锁定,则执行中止。建议使用 :doc:`/api-reference/network/esp_netif` 组件与 lwIP 交互。
ESP-IDF 间接支持以下常见的 lwIP 应用程序 API
- 动态主机设置协议 (DHCP) 服务器和客户端,由 :doc:`/api-reference/network/esp_netif` 功能间接支持。
- 简单网络时间协议 (SNTP),由 :doc:`/api-reference/network/esp_netif` 功能间接支持,或通过 :component_file:`lwip/include/apps/esp_sntp.h` 中的函数直接支持。该函数还为 :component_file:`lwip/lwip/src/include/lwip/apps/sntp.h` 函数提供了线程安全的 API请参阅 :ref:`system-time-sntp-sync`
- ICMP Ping由 lwIP ping API 的变体支持,请参阅 :doc:`/api-reference/protocols/icmp_echo`
- ICMPv6 Ping由 lwIP 的 ICMPv6 Echo API 支持,用于测试 IPv6 网络连接情况。有关详细信息,请参阅 :example:`protocols/sockets/icmpv6_ping`
- NetBIOS 查找,由标准的 lwIP API 支持,:example:`protocols/http_server/restful_server` 示例中提供了使用 NetBIOS 在局域网中查找主机的选项。
- mDNS 与 lwIP 的默认 mDNS 使用不同实现方式,请参阅 :doc:`/api-reference/protocols/mdns`。但启用 :ref:`CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES` 设置项后lwIP 可以使用 ``gethostbyname()`` 等标准 API 和 ``hostname.local`` 约定查找 mDNS 主机。
- lwIP 中的 PPP 实现可用于在 ESP-IDF 中创建 PPPoS串行 PPP接口。请参阅 :doc:`/api-reference/network/esp_netif` 组件文档,使用 :component_file:`esp_netif/include/esp_netif_defaults.h` 中定义的 ``ESP_NETIF_DEFAULT_PPP()`` 宏创建并配置 PPP 网络接口。:component_file:`esp_netif/include/esp_netif_ppp.h` 中提供了其他的运行时设置。PPPoS 接口通常用于与 NBIoT/GSM/LTE 调制解调器交互。`esp_modem <https://components.espressif.com/component/espressif/esp_modem>`_ 仓库还支持更多应用层友好的 API该仓库内部使用了上述 PPP lwIP 模块。
BSD 套接字 API
-----------------
BSD 套接字 API 是一种常见的跨平台 TCP/IP 套接字 API最初源于 UNIX 操作系统的伯克利标准发行版,现已标准化为 POSIX 规范的一部分。BSD 套接字有时也称 POSIX 套接字,或伯克利套接字。
在 ESP-IDF 中lwIP 支持 BSD 套接字 API 的所有常见用法。
参考
^^^^^^^^^^
BSD 套接字的相关参考资料十分丰富,包括但不限于:
- `单一 UNIX 规范 - BSD 套接字 <https://pubs.opengroup.org/onlinepubs/007908799/xnsix.html>`_
- `伯克利套接字 - 维基百科 <https://en.wikipedia.org/wiki/Berkeley_sockets>`_
示例
^^^^^^^^
以下为 ESP-IDF 中使用 BSD 套接字 API 的部分示例:
- :example:`protocols/sockets/tcp_server`
- :example:`protocols/sockets/tcp_client`
- :example:`protocols/sockets/udp_server`
- :example:`protocols/sockets/udp_client`
- :example:`protocols/sockets/udp_multicast`
- :example:`protocols/http_request`:此简化示例使用 TCP 套接字发送 HTTP 请求,但更推荐使用 :doc:`/api-reference/protocols/esp_http_client` 发送 HTTP 请求
支持的函数
^^^^^^^^^^^^^^^^^^^
在 ESP-IDF 中lwIP 支持以下 BSD 套接字 API 函数,详情请参阅 :component_file:`lwip/lwip/src/include/lwip/sockets.h`
- ``socket()``
- ``bind()``
- ``accept()``
- ``shutdown()``
- ``getpeername()``
- ``getsockopt()````setsockopt()``:请参阅 `套接字选项`_
- ``close()``:通过 :doc:`/api-reference/storage/vfs` 调用
- ``read()````readv()````write()````writev()``:通过 :doc:`/api-reference/storage/vfs` 调用
- ``recv()````recvmsg()````recvfrom()``
- ``send()````sendmsg()````sendto()``
- ``select()``:通过 :doc:`/api-reference/storage/vfs` 调用
- ``poll()``ESP-IDF 通过在内部调用 ``select()`` 实现 ``poll()``,因此,建议直接调用 ``select()``
- ``fcntl()``:请参阅 `fcntl()`_
非标准函数:
- ``ioctl()``:请参阅 `ioctl()`_
.. note::
部分 lwIP 应用程序示例代码使用了带前缀的 BSD API``lwip_socket()``,而非标准 ``socket()``。ESP-IDF 支持使用以上两种形式,但更建议使用标准名称。
套接字错误处理
^^^^^^^^^^^^^^^^^^^^^
要使套接字应用程序保持稳定BSD 套接字错误处理代码至关重要。套接字错误处理通常涉及以下几个方面:
- 错误检测
- 获取错误原因代码
- 根据错误原因代码处理错误
在 lwIP 中,处理套接字错误分以下两种情况:
- 套接字 API 返回错误,请参阅 `套接字 API 错误`_
- ``select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout)`` 包含异常描述符,表示套接字出现错误,详情请参阅 `select() 错误`_
套接字 API 错误
+++++++++++++++++
**错误检测**
- 根据返回值判断套接字 API 是否出错。
**获取错误原因代码**
- 套接字 API 出错时,其返回值不包含失败原因,可以通过应用程序访问 ``errno`` 获取错误原因代码。不同返回值具有不同含义,详情请参阅 `套接字错误原因代码`_
示例:
.. code-block::
int err;
int sockfd;
if (sockfd = socket(AF_INET,SOCK_STREAM,0) < 0) {
// 从 errno 获取错误代码
err = errno;
return err;
}
``select()`` 错误
+++++++++++++++++++++++
**错误检测**
- ``select()`` 包含异常描述符时的套接字错误。
**获取错误原因代码**
- 如果 ``select()`` 报告套接字错误,访问 ``errno`` 无法获取错误原因代码,此时,应调用 ``getsockopt()``。因为当 ``select()`` 包含异常描述符时,错误代码不会直接赋值给 ``errno``
.. note::
``getsockopt()`` 函数具有以下原型:``int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen)``。原型可以获取任意类型、任意状态套接字选项的当前值,并将结果存储在 ``optval`` 中。例如,要在套接字上获取错误代码,可以通过 ``getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &optlen)`` 实现。
示例:
.. code-block::
int err;
if (select(sockfd + 1, NULL, NULL, &exfds, &tval) <= 0) {
err = errno;
return err;
} else {
if (FD_ISSET(sockfd, &exfds)) {
// 使用 getsockopt() 获取 select() 异常集
int optlen = sizeof(int);
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &optlen);
return err;
}
}
套接字错误原因代码
++++++++++++++++++++++++
以下是常见错误代码列表。有关标准 POSIX/C 错误代码的详细列表,请参阅 `newlib errno.h <https://github.com/espressif/newlib-esp32/blob/master/newlib/libc/include/sys/errno.h>`_ 和特定平台扩展 :component_file:`newlib/platform_include/errno.h`
.. list-table::
:header-rows: 1
:widths: 50 50
:align: center
* - 错误代码
- 描述
* - ECONNREFUSED
- 拒绝连接
* - EADDRINUSE
- 地址已在使用中
* - ECONNABORTED
- 软件导致连接中断
* - ENETUNREACH
- 网络不可达
* - ENETDOWN
- 未配置网络接口
* - ETIMEDOUT
- 连接超时
* - EHOSTDOWN
- 主机已关闭
* - EHOSTUNREACH
- 主机不可达
* - EINPROGRESS
- 连接已在进行中
* - EALREADY
- 套接字已连接
* - EDESTADDRREQ
- 需要目标地址
* - EPROTONOSUPPORT
- 未知协议
套接字选项
^^^^^^^^^^^^^^
``getsockopt()`` 支持获取套接字选项,``setsockopt()`` 支持设置套接字选项。
在 ESP-IDF 中lwIP 并不支持所有标准套接字选项。以下套接字选项受 lwIP 支持:
常见选项
++++++++++++++
与级别参数 ``SOL_SOCKET`` 一起使用。
- ``SO_REUSEADDR``:如果 :ref:`CONFIG_LWIP_SO_REUSE` 已启用,则该选项可用,可以设置 :ref:`CONFIG_LWIP_SO_REUSE_RXTOALL` 自定义其行为
- ``SO_KEEPALIVE``
- ``SO_BROADCAST``
- ``SO_ACCEPTCONN``
- ``SO_RCVBUF``:如果 :ref:`CONFIG_LWIP_SO_RCVBUF` 已启用,则该选项可用
- ``SO_SNDTIMEO`` / ``SO_RCVTIMEO``
- ``SO_ERROR``:此选项仅支持与 ``select()`` 一起使用,请参阅 `套接字错误处理`_
- ``SO_TYPE``
- ``SO_NO_CHECK``:仅适用于 UDP 套接字
IP 选项
++++++++++
与级别参数 ``IPPROTO_IP`` 一起使用。
- ``IP_TOS``
- ``IP_TTL``
- ``IP_PKTINFO``:如果 :ref:`CONFIG_LWIP_NETBUF_RECVINFO` 已启用,则该选项可用
对于组播 UDP 套接字:
- ``IP_MULTICAST_IF``
- ``IP_MULTICAST_LOOP``
- ``IP_MULTICAST_TTL``
- ``IP_ADD_MEMBERSHIP``
- ``IP_DROP_MEMBERSHIP``
TCP 选项
+++++++++++
只适用于 TCP 套接字,与级别参数 ``IPPROTO_TCP`` 一起使用。
- ``TCP_NODELAY``
与 TCP 保活探测相关的选项:
- ``TCP_KEEPALIVE``:整数值,以毫秒为单位,设置 TCP 保活探测周期
- ``TCP_KEEPIDLE``:整数值,以秒为单位,与 ``TCP_KEEPALIVE`` 相同
- ``TCP_KEEPINTVL``:整数值,以秒为单位,设置保活探测间隔
- ``TCP_KEEPCNT``:整数值,设置超时前进行的保活探测次数
IPv6 选项
++++++++++++
只适用于 IPv6 套接字,与级别参数 ``IPPROTO_IPV6`` 一起使用。
- ``IPV6_CHECKSUM``
- ``IPV6_V6ONLY``
对于组播 IPv6 UDP 套接字:
- ``IPV6_JOIN_GROUP`` / ``IPV6_ADD_MEMBERSHIP``
- ``IPV6_LEAVE_GROUP`` / ``IPV6_DROP_MEMBERSHIP``
- ``IPV6_MULTICAST_IF``
- ``IPV6_MULTICAST_HOPS``
- ``IPV6_MULTICAST_LOOP``
``fcntl()``
^^^^^^^^^^^
``fcntl()`` 函数是设置与文件描述符相关选项的标准 API。在 ESP-IDF 中,使用 :doc:`/api-reference/storage/vfs` 层实现该函数。
当文件描述符为套接字时,仅支持以下 ``fcntl()`` 值:
- ``O_NONBLOCK`` 用于置位或清除非阻塞 I/O 模式。``O_NDELAY`` 也受支持,与前者功能相同。
- ``O_RDONLY````O_WRONLY````O_RDWR`` 标志用于不同的读或写模式,只能用 ``F_GETFL`` 读取,且无法用 ``F_SETFL`` 设置。根据连接状况即两端开启或任一端关闭TCP 套接字会返回不同模式,而 UDP 套接字始终返回 ``O_RDWR``
``ioctl()``
^^^^^^^^^^^
``ioctl()`` 函数以半标准的方式访问 TCP/IP 协议栈的部分内部功能。ESP-IDF 通过 :doc:`/api-reference/storage/vfs` 层实现此函数。
当文件描述符为套接字时,仅支持以下 ``ioctl()`` 值:
- ``FIONREAD`` 返回套接字网络 buffer 中接收的待处理字节数。
- ``FIONBIO````fcntl(fd, F_SETFL, O_NONBLOCK, ...)`` 相同,也可置位或清除套接字非阻塞 I/O 状态。
Netconn API
-----------
lwIP 支持两种较低级别的 API 和 BSD 套接字 API即 Netconn API 和 Raw API。
lwIP Raw API 适用于单线程设备,无法在 ESP-IDF 中使用。
Netconn API 用于在 lwIP 内部使用 BSD 套接字 API支持直接从 ESP-IDF 的应用程序调用。相较于 BSD 套接字 API该 API 占用资源更少。无需提前将数据复制到内部 lwIP buffer即可使用 Netconn API 发送和接收数据。
.. important::
乐鑫尚未在 ESP-IDF 中测试 Netconn API因此 **此功能已启用,但尚无官方支持**。对于某些功能,可能只有在从 BSD 套接字 API 中使用时才能正常运作。
有关 Netconn API 的更多信息,请参阅 `lwip/lwip/src/include/lwip/api.h <http://www.nongnu.org/lwip/2_0_x/api_8h.html>`_`lwIP 应用程序 **非官方** 开发手册的一部分 <https://lwip.fandom.com/wiki/Netconn_API>`_
lwIP FreeRTOS 任务
------------------
lwIP 创建了专用的 TCP/IP FreeRTOS 任务,处理来自其他任务的套接字 API 请求。
以下配置项可用于修改任务,并调整向 TCP/IP 任务发送数据和从 TCP/IP 任务接收数据的队列(邮箱):
- :ref:`CONFIG_LWIP_TCPIP_RECVMBOX_SIZE`
- :ref:`CONFIG_LWIP_TCPIP_TASK_STACK_SIZE`
- :ref:`CONFIG_LWIP_TCPIP_TASK_AFFINITY`
IPv6 支持
------------
系统支持 IPv4 和 IPv6 的双栈功能,并默认启用这两种协议。如无需要,可将其禁用,请参阅 :ref:`lwip-ram-usage`
在 ESP-IDF 中IPv6 支持仅限 **无状态自动配置**,不支持 **有状态配置**,上游的 lwIP 也不支持 **有状态配置**
IPv6 地址配置通过以下协议或服务定义:
- 支持 **SLAAC** IPv6 无状态地址配置 (RFC-2462)
- 支持 **DHCPv6** IPv6 动态主机配置协议 (RFC-8415)
以上两种地址配置默认处于禁用状态,设备仅使用链路本地地址或静态定义的地址。
.. _lwip-ivp6-autoconfig:
无状态自动配置流程
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
要通过路由器通告协议启用地址自动配置,请启用此配置选项:
- :ref:`CONFIG_LWIP_IPV6_AUTOCONFIG`
该配置选项启用了所有网络接口的 IPv6 自动配置。而在上游 lwIP 中,需要设置 ``netif->ip6_autoconfig_enabled=1``,针对每个 ``netif`` 明确启用自动配置。
.. _lwip-ivp6-dhcp6:
DHCPv6
^^^^^^
lwIP 中的 DHCPv6 非常简单,仅支持无状态配置,可通过以下配置选项启用:
- :ref:`CONFIG_LWIP_IPV6_DHCP6`
由于 DHCPv6 仅在无状态配置下工作,因此还需要通过 :ref:`CONFIG_LWIP_IPV6_AUTOCONFIG` 启用 :ref:`lwip-ivp6-autoconfig`
此外,还需要使用以下语句,在应用程序代码中明确启用 DHCPv6
.. code-block::
dhcp6_enable_stateless(netif);
IPv6 自动配置中的 DNS 服务器
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
要自动配置 DNS 服务器,尤其是在仅使用 IPv6 的网络中配置,可使用以下两种选项:
- 递归域名系统 (DNS),属于邻居发现协议 (NDP) 的一部分,可使用 :ref:`lwip-ivp6-autoconfig`
DNS 服务器的数量必须设置为 :ref:`CONFIG_LWIP_IPV6_RDNSS_MAX_DNS_SERVERS`,该选项默认禁用,即置位为 0。
- DHCPv6 无状态配置,使用 :ref:`lwip-ivp6-dhcp6` 配置 DNS 服务器。注意,此配置假设 IPv6 路由通告标志 (RFC-5175) 进行了如下设置
- 管理地址配置标志 (Managed Address Configuration Flag) = 0
- 其他配置标志 (Other Configuration Flag) = 1
ESP-lwIP 自定义修改
-----------------------------
补充内容
^^^^^^^^^
以下代码均为新增代码,尚未包含至上游 lwIP 版本:
线程安全的套接字
+++++++++++++++++++
调用 ``close()`` 可以从不同于创建套接字的线程中关闭该套接字。该调用持续阻塞,直至其他任务中使用该套接字的函数调用返回。
然而,任务处于主动等待 ``select()````poll()`` API 的状态时,无法删除该任务。销毁任务前,这些 API 必须先退出,否则可能会破坏内部数据结构,并导致后续 lwIP 崩溃。这些 API 在栈上分配了全局引用的回调指针因此在未完全卸载栈的情况下删除任务时lwIP 仍可以持有指向已删除栈的指针。
按需定时器
++++++++++++++++
lwIP 中的 IGMP 和 MLD6 功能都会初始化一个定时器,以便在特定时间触发超时事件。
即便没有活动的超时事件lwIP 也会默认始终启用这些定时器,增加自动 Light-sleep 模式下的 CPU 使用率和功耗。``ESP-lwIP`` 则默认将各定时器设置为 ``按需`` 使用,即只在有待处理事件时启用。
如果要返回默认 lwIP 设置,即始终启用定时器,请禁用 :ref:`CONFIG_LWIP_TIMERS_ONDEMAND`
lwIP 定时器 API
+++++++++++++++
不使用 Wi-Fi 时,可以通过 API 关闭 lwIP 定时器,减少功耗。
以下 API 函数均受支持,详情请参阅 :component_file:`lwip/lwip/src/include/lwip/timeouts.h`
- ``sys_timeouts_init()``
- ``sys_timeouts_deinit()``
附加套接字选项
+++++++++++++++++++++++++
- 目前已实现部分标准 IPV4 和 IPV6 组播套接字选项,详情请参阅 `套接字选项`_
- 使用 ``IPV6_V6ONLY`` 套接字选项,可以设置仅使用 IPV6 的 UDP 和 TCP 套接字,而 lwIP 一般只支持 TCP 套接字。
IP 层特性
+++++++++++++++++
- IPV4 源地址基础路由实现不同
- 支持 IPV4 映射 IPV6 地址
自定义 lwIP 钩子
+++++++++++++++++++++
原始 lwIP 支持通过 ``LWIP_HOOK_FILENAME`` 实现自定义的编译时修改。ESP-IDF 端口层已使用该文件,但仍支持通过由宏 ``ESP_IDF_LWIP_HOOK_FILENAME`` 定义的头文件,在 ESP-IDF 中包含并实现自定义添加。以下示例展示了向构建过程添加自定义钩子文件的过程,其中钩子文件名为 ``my_hook.h``,位于项目的 ``main`` 文件夹中:
.. code-block:: cmake
idf_component_get_property(lwip lwip COMPONENT_LIB)
target_compile_options(${lwip} PRIVATE "-I${PROJECT_DIR}/main")
target_compile_definitions(${lwip} PRIVATE "-DESP_IDF_LWIP_HOOK_FILENAME=\"my_hook.h\"")
限制
^^^^^^^^^^^
在 UDP 套接字上重复调用 ``send()````sendto()`` 最终可能会导致错误。此时 ``errno`` 报错为 ``ENOMEM``,错误原因是底层网络接口驱动程序中的 buffer 大小有限。当所有驱动程序的传输 buffer 已满时UDP 传输事务失败。如果应用程序需要发送大量 UDP 数据报,且不希望发送方丢弃数据报,建议检查错误代码,采用短延迟的重传机制。
.. only:: esp32
:ref:`Wi-Fi <CONFIG_ESP_WIFI_TX_BUFFER>`:ref:`Ethernet <CONFIG_ETH_DMA_TX_BUFFER_NUM>` 项目配置中适当增加传输 buffer 数量,或许可以缓解此情况。
.. only:: not esp32 and SOC_WIFI_SUPPORTED
:ref:`Wi-Fi <CONFIG_ESP_WIFI_TX_BUFFER>` 项目配置中适当增加传输 buffer 数量,或许可以缓解此情况。
.. _lwip-performance:
性能优化
------------------------
影响 TCP/IP 性能因素较多可以从多方面进行优化。经调整ESP-IDF 的默认设置已在 TCP/IP 的吞吐量、响应时间和内存使用间达到平衡。
最大吞吐量
^^^^^^^^^^^^^^^^^^
:example:`wifi/iperf` 示例中,乐鑫测试了在射频密封的封闭环境下 ESP-IDF 的 TCP/IP 吞吐量。
iperf 示例下的 :example_file:`wifi/iperf/sdkconfig.defaults` 文件包含已知可最大化 TCP/IP 吞吐量的设置,但该设置会占用更多 RAM。要牺牲其他性能在应用程序中最大化 TCP/IP 吞吐量,建议将该示例文件中的设置应用到项目的 sdkconfig 文件中。
.. important::
建议逐步应用更改,并在每次更改后,通过特定应用程序的工作负载检查性能。
- 如果系统中有许多任务抢占 CPU 时间,可以考虑调整 lwIP 任务的 CPU 亲和性 (:ref:`CONFIG_LWIP_TCPIP_TASK_AFFINITY`),并以固定优先级 (18) 运行。为优化 CPU 使用,可以考虑将竞争任务分配给不同核心,或将其优先级调整至较低值。有关内置任务优先级的更多详情,请参阅 :ref:`built-in-task-priorities`
- 如果使用仅带有套接字参数的 ``select()`` 函数,禁用 :ref:`CONFIG_VFS_SUPPORT_SELECT` 可以更快地调用 ``select()``
- 如果有足够的空闲 IRAM可以选择 :ref:`CONFIG_LWIP_IRAM_OPTIMIZATION`:ref:`CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION`,提高 TX/RX 吞吐量。
.. only:: SOC_WIFI_SUPPORTED
如果使用 Wi-Fi 网络接口,请参阅 :ref:`wifi-buffer-usage`
最低延迟
^^^^^^^^^^^^^^^
除增加 buffer 大小外,大多数增加吞吐量的设置会减少 lwIP 函数占用 CPU 的时间,进而降低延迟,缩短响应时间。
- 对于 TCP 套接字lwIP 支持设置标准的 ``TCP_NODELAY`` 标记以禁用 Nagle 算法。
.. _lwip-ram-usage:
最小内存使用
^^^^^^^^^^^^^^^^^
由于 RAM 按需从堆中分配,多数 lwIP 的 RAM 使用也按需分配。因此,更改 lwIP 设置减少 RAM 使用时,或许不会改变空闲时的 RAM 使用量,但可以改变高峰期的 RAM 使用量。
- 减少 :ref:`CONFIG_LWIP_MAX_SOCKETS` 可以减少系统中的最大套接字数量。更改此设置,会让处于 ``WAIT_CLOSE`` 状态的 TCP 套接字在需要打开新套接字时更快地关闭和复用,进一步降低峰值 RAM 使用量。
- 减少 :ref:`CONFIG_LWIP_TCPIP_RECVMBOX_SIZE`:ref:`CONFIG_LWIP_TCP_RECVMBOX_SIZE`:ref:`CONFIG_LWIP_UDP_RECVMBOX_SIZE` 可以减少 RAM 使用量,但会影响吞吐量,具体取决于使用情况。
- 减少 :ref:`CONFIG_LWIP_TCP_MSL`:ref:`CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT` 可以减少系统中的最大分段寿命,同时会使处于 ``TIME_WAIT````FIN_WAIT_2`` 状态的 TCP 套接字能更快地关闭和复用。
- 禁用 :ref:`CONFIG_LWIP_IPV6` 可以在系统启动时节省大约 39 KB 的固件大小和 2 KB 的 RAM并在运行 TCP/IP 栈时节省 7 KB 的 RAM。如果无需支持 IPV6可以禁用 IPv6减少 flash 和 RAM 占用。
- 禁用 :ref:`CONFIG_LWIP_IPV4` 可以在系统启动时节省大约 26 KB 的固件大小和 600 B 的 RAM并在运行 TCP/IP 栈时节省 6 KB 的 RAM。如果本地网络仅支持 IPv6 配置,可以禁用 IPv4减少 flash 和 RAM 占用。
.. only:: SOC_WIFI_SUPPORTED
如果使用 Wi-Fi请参阅 :ref:`wifi-buffer-usage`
最大 buffer 使用
+++++++++++++++++
lwIP 消耗的最大堆内存即 lwIP 驱动程序 **理论上可能消耗的最大内存**,通常取决于以下因素:
- 创建 UDP 连接所需的内存:``lwip_udp_conn``
- 创建 TCP 连接所需的内存:``lwip_tcp_conn``
- 应用程序拥有的 UDP 连接数量:``lwip_udp_con_num``
- 应用程序拥有的 TCP 连接数量:``lwip_tcp_con_num``
- TCP 的 TX 窗口大小:``lwip_tcp_tx_win_size``
- TCP 的 RX 窗口大小:``lwip_tcp_rx_win_size``
**因此lwIP 消耗的最大堆内存可以用以下公式计算:**
lwip_dynamic_peek_memory = (lwip_udp_con_num * lwip_udp_conn) + (lwip_tcp_con_num * (lwip_tcp_tx_win_size + lwip_tcp_rx_win_size + lwip_tcp_conn))
某些基于 TCP 的应用程序只需要一个 TCP 连接。然而,当出现错误(如发送失败)时,应用程序可能会关闭此 TCP 连接,并创建一个新的连接。根据 TCP 状态机和 RFC793关闭 TCP 连接可能需要很长时间,这可能导致系统中同时存在多个 TCP 连接。
.. _lwIP 轻量级 TCP/IP 协议栈: https://savannah.nongnu.org/projects/lwip/
.. _esp-lwip: https://github.com/espressif/esp-lwip