I explain how things work in my
https://github.com/cpq/embedded-network-programming-guide - go read it.
Long story short:
1. LWIP has 3 APIs: raw, netconn, and socket
2. raw API is a callback based API: TCP/IP stack receives data, and calls your function. Works in RTOS and non-RTOS environment, cause LWIP just calls your function, so it is simple as a pancake.
3. netconn API is just a small layer on top of raw API that implements connection data buffering for a given connection, that's all, so you can consider it as a raw API, too
4. socket API implements BSD interface, designed like files - cause that's what those UNIX people used to use. They thought, hey, we represent things in UNIX as files - kernel objects with numeric IDs in userland (file descriptors), and API like read/write/open/close. So why don't we do the same for network connections? And they did.
5. network connections they called sockets, and assigned a similar numeric ID (socket number), and did the same open/close/read/write API as for files.
6. And here goes a problem - whilst you can just read from a file and expect data synchronously, you can't from a socket. you can wait for data forever. So they thought - oh shit, we can block forever, so let's introduce O_NONBLOCK for sockets. And since we can have multiple sockets, we can read them all in a loop using O_NONBLOCK, fine! But wait, that's going to be a busy loop.... What we gonna do? Ah, let's introduce a multiplexing syscall, select/poll, which will just block until any of the sockets is ready
7. That's convoluted API, but that's how it was designed. And it is a standard now. All because read/write/poll/select can block.
8. A proper API for network connections of course is event based.
9. But everyone uses BSD API - just because, and so to do so, they MUST use RTOS, because read/write/poll/select can block, so they run a TCP/IP task in a separate task, and user network tasks in a separate task, and use RTOS queues for passing data between them - like, recv() from a socket uses RTOS queue to send data from a socket buffer in LWIP task, to your user task. And then yeah, your task is likely going to buffer the data again, just like the socket buffer did one layer below.
10. But that's exactly how socket API works.
So I think that gives you a clue about the API difference.