Coder Social home page Coder Social logo

sockets's Introduction

mbed C++ Socket API

The mbed C++ Socket API provides an interface to sockets which will be familiar to users of BSD sockets, but there are several differences compared to the typical BSD socket implementations. In typical desktop BSD socket applications, the program will block a thread, while waiting for each socket call to complete. This is conceptually simple, but not well-suited to event-based embedded systems. In the C++ Socket API, each socket has a set of callbacks which trigger when events complete, much like the BSD Sockets work in asynchronous mode; instead of having a single SIGIO handler however, the C++ Socket API provides an event handler for each type of event.

Usage Notes

This version is an alpha and there are some known problems.

Event handler context

Event handling currently occurs in the interrupt context. In an upcoming release, there will be provision for event handling in the main loop.

Error handling

Errors are forwarded to the event registered with setOnError(). If this event is not set, errors are ignored. All applications should register an onError event handler.

Getting Started

The best place to start is the mbed-example-network module. There are simple examples there. However, for a simple overview, there are several examples below.

UDP Examples

Creating a UDP Socket

UDPSocket s(SOCKET_STACK_LWIP_IPV4);

Set an error handler

s.setOnError(onError);

Opening a UDP Socket

s.open(SOCKET_AF_INET4);

Send some data

s.send_to(data, dlen, addr, port);

Recieve some data

s.setOnReadable(onRecv);

TCP Examples

Creating a TCP Socket

TCPStream s(SOCKET_STACK_LWIP_IPV4);

Opening a TCP Socket

s.open(SOCKET_AF_INET4);

Connect to a host

s.connect(addr, port, onConnect);

Set the disconnect handler

s.setOnDisconnect(onDisconnect);

Send some data

s.send(data, dlen);

Receive some data

s.setOnReadable(onRecv);

Close the socket

s.close();

DNS Example

This is a complete example of resolving an address with DNS

#include "sockets/v0/UDPSocket.h"
#include "sal-stack-lwip/lwipv4_init.h"
#include "sal-iface-eth/EthernetInterface.h"
using namespace mbed::Sockets::v0;
class Resolver {
private:
    UDPSocket _sock;
public:
    Resolver() : _sock(SOCKET_STACK_LWIP_IPV4) {
        _sock.open(SOCKET_AF_INET4);
    }
    void onDNS(Socket *s, struct socket_addr addr, const char *domain) {
        (void) s;
        SocketAddr sa;
        char buf[16];
        sa.setAddr(&addr);
        sa.fmtIPv4(buf,sizeof(buf));
        printf("Resolved %s to %s\r\n", domain, buf);
    }
    socket_error_t resolve(const char * address) {
        printf("Resolving %s...\r\n", address);
        return _sock.resolve(address,UDPSocket::DNSHandler_t(this, &Resolver::onDNS));
    }
};

EthernetInterface eth;
Resolver *r;
void app_start(int argc, char *argv[]) {
    (void) argc;
    (void) argv;
    static Serial pc(USBTX, USBRX);
    pc.baud(115200);
    printf("Connecting to network...\r\n");
    eth.init();
    eth.connect();
    printf("Connected\r\n");
    lwipv4_socket_init();
    r = new Resolver();
    r->resolve("mbed.org");
}

sockets's People

Contributors

0xc0170 avatar adamgreen avatar adbridge avatar bogdanm avatar bremoran avatar emilmont avatar iriark01 avatar korjaa avatar matthewelse avatar mazgch avatar mbed-test-account avatar neilt6 avatar omdathetkan avatar rgrover avatar sg- avatar sissors avatar tkuyucu-nordicsemi avatar toyowata avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sockets's Issues

UDPSocket issues

  1. The connect method does not check for a NULL api before dereferencing it.
  2. There is an inconsistency with the parameters in the connect method. For UDPSocket, address is passed as a pointer, whereas for TCPStream the same parameter is passed as a reference. They should ideally use the same calling style?

OnSent dependency on lwip timer?

On an otherwise idle system, OnSent() does not seem to appear at a reasonable rate, it seem to come at ~250ms intervals.

If the system is doing something else, like receiving, the OnSent()s seem to appear at a rate related to how quickly the packets were actually sent, ie, much faster.

Is OnSent() somehow processed by the tcp timer or some other timer? Minar gets sleepy if nothing happening? It's a known issue or you need some way to reproduce it?`

Documentation and error checking omissions

  1. send_to()
    a) Implementation returns a specific error code if the socket is not open but this is not documented in the header.
    b) Header specifies that the method is not valid for SOCK_STREAM so should probably state what the error condition returned is if SOCK_STREAM does get used?
    c) Should check that remote_addr is not NULL before dereferencing in the implementation
  2. send()
    a) Implementation returns a specific error code if the socket is not open but this is not documented in the header.
    b) Documentation says that this method is not valid for UDP unless connect() has previously been called. Should document the expected error returned if this is not the case.
  3. recv ()
    a) Implementation returns a specific error code if the socket is not open but this is not documented in the header.
  4. recv_from()
    a) Implementation returns a specific error code if the socket is not open but this is not documented in the header.
    b) Should check that remote_addr is not NULL before dereferencing

5 General
Any pointer dereferenced directly within the method implementation should be checked for being non-NULL before dereferencing. Any pointers passed down to the API do not need to be checked as it can be the responsibility of the API to do so.

V0.1.3 Introduces Breaking Changes

Problem: In v0.1.3, breaking changes were introduced into the API. Specifically, the function getImpl() was removed from SocketAddr.h. Instead of incrementing the revision number, the minor number should have been increased.

This caused an issue when building connector-mbed-client. If you pull the latest version of the project, the build fails here.

Potential Resolution: Push a release with a higher revision number that reverts this change. Then, push a new release with an incremented minor number that adds the changes that had to be reverted.

Redundant error check in Socket class constructor

In the constructor, the following code is redundant:

if (_socket.api == NULL) {
        error_check(SOCKET_ERROR_NULL_PTR);
    }

This is because error_check posts a Minor callback using the function stored in _onError, however at the point of construction this won't have been set and will still be NULL. Thus no callback will be registered anyway.

TCPStream issues

  1. When instantiating the class using the constructor:
    TCPStream(const socket_stack_t stack);

    there is no guarantee of a valid api being set. Thus if the connect method is then used, _socket.api
    could be NULL. Should check this within the connect method.
    If this is added then the method header should be updated accordingly.

  2. Similarly for the setNagle() method. _socket.api could be invoked with a NULL value.
    Also setNagle calls set_option() which returns an error type which is then ignored. Shouldn't this
    error type be forwarded up to the caller of the method?

Repeatable crash in tcp_sent()

After the nagle fix, the libwebsockets test server on mbed3 can get tested intensively.

He sends about 3000 packets/s containing an incrementing number in ASCII, using websockets protocol. All is well until around 1054452, or 1121224, or 722709, etc packets, ie, kinda random depending on the run, after which he dies seemingly always in sal-stack-lwip tcp_out.c, in tcp_output()

...
    /* do not queue empty segments on the unacked list */
    } else {
      tcp_seg_free(seg);
    }
    seg = pcb->unsent;   <<<<====== line 1022
  }
#if TCP_OVERSIZE
  if (pcb->unsent == NULL) {
    /* last unsent has been removed, reset unsent_oversize */
    pcb->unsent_oversize = 0;
  }
#endif /* TCP_OVERSIZE */
...

The backtrace is this

#0  HardFault_Handler () at /home/agreen/projects/mbed/lws-test-server/yotta_modules/mbed-hal-k64f/source/bootstrap_gcc/startup_MK64F12.S:259
#1  <signal handler called>
#2  0x00007236 in tcp_output (pcb=pcb@entry=0x1fffb8c0 <memp_memory+424>)
    at /home/agreen/projects/mbed/lws-test-server/yotta_modules/sal-stack-lwip/source/lwip/core/tcp_out.c:1022
#3  0x000115b6 in lwipv4_socket_send (socket=<optimized out>, buf=<optimized out>, len=<optimized out>)
    at /home/agreen/projects/mbed/lws-test-server/yotta_modules/sal-stack-lwip/source/asynch_socket.c:617
#4  0x00003b1c in lws_ssl_capable_write_no_ssl (wsi=wsi@entry=0x2000b0f0, buf=buf@entry=0x2000b9aa "\201\006\067\062\062\067\060\071", len=len@entry=8)
    at /home/agreen/projects/mbed/lws-test-server/yotta_modules/websockets/lib/lws-plat-mbed3.cpp:123
#5  0x00002878 in lws_issue_raw (wsi=0x2000b0f0, buf=0x2000b9aa "\201\006\067\062\062\067\060\071", len=8)
    at /home/agreen/projects/mbed/lws-test-server/yotta_modules/websockets/lib/output.c:125
#6  0x00002b9c in lws_write (wsi=0x2000b0f0, buf=0x2000b9ac "722709", len=<optimized out>, protocol=<optimized out>)
    at /home/agreen/projects/mbed/lws-test-server/yotta_modules/websockets/lib/output.c:489
#7  0x00001e92 in callback_dumb_increment (context=0x20007a18, wsi=0x2000b0f0, reason=<optimized out>, user=0x2000b998, in=0x0 <__isr_vector>, len=0)
    at /home/agreen/projects/mbed/lws-test-server/source/app.cpp:194
#8  0x0001024c in user_callback_handle_rxflow (callback_function=0x1e51 <callback_dumb_increment(lws_context*, lws*, lws_callback_reasons, void*, void*, size_t)>, 
    context=context@entry=0x20007a18, wsi=wsi@entry=0x2000b0f0, reason=reason@entry=LWS_CALLBACK_SERVER_WRITEABLE, user=0x2000b998, in=in@entry=0x0 <__isr_vector>, 
    len=len@entry=0) at /home/agreen/projects/mbed/lws-test-server/yotta_modules/websockets/lib/libwebsockets.c:657
#9  0x00004b52 in lws_calllback_as_writeable (wsi=0x2000b0f0, context=0x20007a18)
    at /home/agreen/projects/mbed/lws-test-server/yotta_modules/websockets/lib/service.c:41
#10 lws_handle_POLLOUT_event (context=context@entry=0x20007a18, wsi=wsi@entry=0x2000b0f0, pollfd=pollfd@entry=0x1fffa0c8)
    at /home/agreen/projects/mbed/lws-test-server/yotta_modules/websockets/lib/service.c:272
#11 0x00004cd6 in lws_service_fd (context=0x20007a18, pollfd=pollfd@entry=0x1fffa0c8)
    at /home/agreen/projects/mbed/lws-test-server/yotta_modules/websockets/lib/service.c:515
#12 0x000106be in lws_conn::onSent (this=<optimized out>, s=<optimized out>, len=<optimized out>)
    at /home/agreen/projects/mbed/lws-test-server/yotta_modules/websockets/lib/lws-plat-mbed3.cpp:286
#13 0x0000b440 in call (arg=<optimized out>, this=<optimized out>)
    at /home/agreen/projects/mbed/lws-test-server/yotta_modules/core-util/core-util/FunctionPointerBase.h:80
#14 call (this=<optimized out>) at /home/agreen/projects/mbed/lws-test-server/yotta_modules/core-util/core-util/FunctionPointerBind.h:44
#15 operator() (this=<optimized out>) at /home/agreen/projects/mbed/lws-test-server/yotta_modules/core-util/core-util/FunctionPointerBind.h:104
#16 minar::SchedulerData::start (this=0x20007088) at /home/agreen/projects/mbed/lws-test-server/yotta_modules/minar/source/minar.cpp:471
#17 0x0000b468 in minar::Scheduler::start () at /home/agreen/projects/mbed/lws-test-server/yotta_modules/minar/source/minar.cpp:295
#18 0x00005766 in main () at /home/agreen/projects/mbed/lws-test-server/yotta_modules/mbed-drivers/source/retarget.cpp:458

It's certainly possible my side is doing something bad, but my part of this stack:

a) really tries to avoid malloc or new in his activities, there is a new per connection, but no new connections are coming after this test starts

b) regulates his packet send by only ever having one in flight at a time on a connection, he does not send another until onSent() is coming

if it's not related to OOM or packets piling up, then it seems like it might be a probability / stability related issue in the networking stack?

Pausing accept processing

If you have a listening socket, is there an mbed3 api way to delay it processing accepts?

The server may choose to restrict some resource necessary for accepted connection processing, so that the number of simultaneous pending connects cannot affect total allocation of this resource. For example accepted connects may perform mallocs, if there is no management of the dynamic number of accepted connects the device can be DoS'd by firing a bunch of connect requests at it simultaneously.

In that case the best way to control it is when the resource pool is busy, listen socket accepts are stopped from happening, they are not rejected just on hold in the request queue. Normally in Posix this is explicitly done in the server and you modulate accepts by, eg, temporarily disabling the listen socket POLLIN on poll().

How can the same thing be achieved cleanly in mbed3?

Listening socket problems

I can make the TCP echo listening app serve some http, using the current stuff from git on K64F. So that's encouraging.

However when this code is elaborated a bit, it acts differently depending on whether a browser is the connecting client (data is sent immediately on accept) or if I telnet to the port (no data is sent until I type something).

With the browser, ReadableHandler_t never gets called on the accepted TCP Stream socket. If I anyway try a recv(), there is no data waiting. The data packet from the browser is lost.

With telnet, no problem.

Since this service happens in interrupt context atm is it possible there is some kind of race between the accept() and setting the handlers? Any related "known problem" that can lead to losing the incoming packet?

resolve should not require an open socket

In order for resolve to execute, a stack must be known. This stack could be either a) the only resolved stack, b) the stack of an open socket, c) the stack of a specific interface, or d) each registered stack, in order.

Implementations of get addr and get port are not testing for a socket being open

Currently the implementations for:

socket_error_t Socket::getLocalAddr(SocketAddr *addr) const
socket_error_t Socket::getLocalPort(uint16_t *port) const
socket_error_t Socket::getRemoteAddr(SocketAddr *addr) const
socket_error_t Socket::getRemotePort(uint16_t *port) const

only test for a NULL api , they should also test for a NULL impl (as this is an equivalent test for whether the socket is open or not)

TCPListener issues

  1. In TCPListener.h: documentation for methods accept() and reject() is incomplete. _onIncoming is also not documented.
  2. In TCPListener.cpp: In the implementations for start_listening() and stop_listening() should check that _socket.api is not NULL before invoking.

Descriptions of the get addr and port in socket.h need to be updated

Currently there are a list of failing error conditions for the following methods:

    virtual socket_error_t getLocalAddr(SocketAddr *addr) const;
    virtual socket_error_t getRemoteAddr(SocketAddr *addr) const;
    virtual socket_error_t getLocalPort(uint16_t *port) const;
    virtual socket_error_t getRemotePort(uint16_t *port) const;

Only some of these error conditions are directly returned by the socket method implementations, all others are returned by the underlying calls to the specific api functions. As they are currently not guaranteed to honour those error conditions, this documentation should be updated.

This may not be trivial however, as ideally this higher level api would have all possible error conditions without having to refer to the api implementation documentation.

Resolve should track queries and handlers

Currently, when a DNS lookup is initiated from a particular socket, an onDNS handler is installed in the socket. However, if a second query is initiated before the first finishes, that can overwrite the original onDNS handler. This creates a race condition on a per-socket basis.

There are three steps to a solution for this problem:

  1. Document the race condition and provide a workaround (create a socket for each DNS lookup)
  2. Provide a mechanism for escalating function pointer/void* pairs to full C++11 functors (including lambdas, etc.)
  3. Provide a DNS lookup API that does not require a socket.

C Function escalation mechanism

It is common practice to provide the DNS callback with a void* user-supplied callback.

This could be used to supply a object/function pair, though this does not account for virtual inheritance. The Function API could provide a convenient abstraction for this problem, allowing a C function pointer with a void * context object to escalate to a full C++11 call, including lambdas, etc.

Providing this abstraction requires a three class-static API functions provided by the Function class:

  • get_reference() obtains a void * pointer to the target functor and increment a the reference count
  • call_from_void() Translates a void * back to a FunctionInterface * and calls it
  • call_from_void_dec() Exactly the same as call_from_void(), except that it also decrements the reference count.

DNS lookup API

The DNS lookup API currently uses the Socket instance as a proxy for discovering the stack in use. This is unnecessary and makes the API more cumbersome. In single-stack environments, resolve() can be a free function. In multi-stack environments, it can be:

  1. a free/static function that iterates through stacks until a match is found
  2. a free/static function that takes either a stack index or a stack ID
  3. a function bound to each network interface
  4. a function bound to the socket, that requires an open socket (the current status-quo)

Option 1) seems to be the least cumbersome option.

cc @bogdanm @adbridge @autopulated @0xc0170

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.