Microcontroller Development – Socket Programming

Click to listen to music while watching☝ (Today’s recommendation: “Zhong Wu Yan”)

Socket programming is a commonly used programming in networking. We achieve communication between networks by creating socket keywords in the network. Through collecting a lot of information, this chapter aims to provide a comprehensive understanding of socket programming. The article references a lot of analysis from experts, combined with personal understanding, to create a summary article.

1: Brief Introduction to Socket

Socket programming is a technology that is often used in network communication.

Since it is a technology, and now it is object-oriented programming, some experts in the computer industry have proposed abstract communication protocols based on the TCP/IP protocol through repeated theoretical or practical derivations. Some generic programming experts have interface-based these abstract concepts and created specialized interfaces corresponding to each concept proposed by the protocol, forming the current socket standard specifications. These interfaces are encapsulated into callable interfaces for developers to use.

Currently, developers have created many encapsulated classes to improve socket programming, making it more convenient to implement various aspects of initial socket communication. Therefore, we must first understand the communication principles of sockets; only by fundamentally understanding socket communication can we quickly and conveniently understand all aspects of sockets and truly grasp them from the ground up.

A socket is an abstraction of an endpoint for bi-directional communication between application processes on different hosts in a network. A socket is one end of communication between processes on the network, providing a mechanism for application layer processes to exchange data using network protocols. From its position, the socket connects application processes above and the network protocol stack below, serving as the interface for applications to communicate via network protocols and interact with the network protocol stack.

Socket was originally a communication model applied in UNIX systems. All operations in UNIX systems are file-oriented, and the socket communication model is also based on file operations. Both client and server complete the operations of ‘open – read/write – close’ through this file to transmit information and complete communication.

Sockets are the cornerstone of communication and the basic operational unit supporting TCP/IP protocol network communication. A socket can be seen as an endpoint for inter-process communication between different hosts, forming the programming interface within a single host and across the entire network. Sockets exist within a communication domain, which is an abstract concept introduced to handle general threads communicating through sockets. Sockets typically exchange data with sockets in the same domain (data exchange may also cross domain boundaries, but in such cases, some interpreter must be executed). Various processes use the same domain to communicate with each other using the Internet protocol suite.

A socket can be seen as an endpoint in the communication connection between two network applications. It is a logical concept in the network environment. It serves as an API (Application Programming Interface) for inter-process communication and can be named and addressed. Each socket in use has its type and a connected process. During communication, one network application writes a segment of information into its host’s socket, and this socket sends the information to another host’s socket through the transmission medium connected to the network interface card (NIC), allowing the recipient to receive this information. A socket is defined by the combination of an IP address and a port, providing a mechanism for delivering data packets to application layer processes.

Sockets are designed for a client/server model. One side acts as the client, while the other acts as the server. The server continuously listens on the transport layer port specified by the socket and waits for connection requests from the client. When the server receives a connection request from the client, it accepts the connection, and once the connection is established, data can be transmitted.

A socket consists of two parts: an IP address and a port number. Each pair of processes communicating over the network needs to use a pair of sockets. Sockets used for communication between different processes are different. Sockets can be used to distinguish data transmissions between different processes. The three important parameters of a socket are the target IP, the transport protocol used at the transport layer, and the port number used at the transport layer.

A socket is represented as (IP address: port number), and the representation of a socket is to write the dotted-decimal IP address followed by the port number, separated by a colon or comma. Each transport layer connection is uniquely determined by the two endpoints (i.e., two sockets) at both ends of the communication. For example: if the IP address is 210.37.145.1 and the port number is 23, then the socket becomes (210.37.145.1:23).

Sockets are at the transport layer of the network protocol and can be classified into three different types: stream sockets, datagram sockets, and raw sockets.

Socket Communication Principles

You are probably not unfamiliar with the terms TCP/IP, UDP, and socket programming, right? With the development of network technology, these terms are everywhere. So I want to ask:

1. What are TCP/IP and UDP?

2. Where is the socket?

3. What is a socket?

4. Will you use them?

What are TCP/IP and UDP?

TCP/IP (Transmission Control Protocol/Internet Protocol) is an industrial standard protocol suite designed for wide area networks (WANs). UDP (User Datagram Protocol) is the protocol that corresponds to TCP. It is one of the protocols in the TCP/IP protocol suite.

Here is a diagram showing the relationship between these protocols.

Microcontroller Development - Socket ProgrammingFigure 1

The TCP/IP protocol suite includes the transport layer, network layer, and link layer. Now you know the relationship between TCP/IP and UDP, right?

Where is the socket?

In Figure 1, we do not see the shadow of the socket, so where is it? Let’s use a diagram to clarify.

Microcontroller Development - Socket ProgrammingFigure 2

So the socket is here.

What is a socket?

A socket is an intermediate software abstraction layer for communication between the application layer and the TCP/IP protocol suite. It is a set of interfaces. In design patterns, a socket is actually a facade pattern that hides the complexity of the TCP/IP protocol suite behind the socket interface. For users, a simple set of interfaces is all you need, allowing the socket to organize data to comply with the specified protocol.

Will you use them?

Predecessors have already done a lot of work for us, making communication between networks much simpler, but there is still a lot of work to be done. In the past, when I heard about socket programming, I thought it was quite advanced programming knowledge, but as long as you understand the working principles of socket programming, the mysterious veil will be lifted.

A real-life scenario: You want to call a friend. First, you dial, and when your friend hears the phone ring, they pick up the phone. At this point, you and your friend establish a connection and can talk. Once the conversation ends, you hang up the phone, concluding the interaction. This scenario explains the working principle. Perhaps the TCP/IP protocol suite was born out of real life, who knows?

Microcontroller Development - Socket Programming

Figure 3

First, let’s talk about the server side. The server initializes the socket, then binds it to a port (bind), listens on the port (listen), and calls accept to block, waiting for a client connection. At this point, if a client initializes a socket and connects to the server (connect), if the connection is successful, the connection between the client and server is established. The client sends a data request, the server receives the request and processes it, then sends the response data back to the client, which reads the data and finally closes the connection, completing the interaction.

============================================

We deeply understand the value of information exchange. How do processes communicate between networks? For example, when we open a browser every day to browse the web, how does the browser process communicate with the web server? When you chat on QQ, how does the QQ process communicate with the server or the QQ process of your friends? All of these rely on sockets. So what is a socket? What types of sockets are there? What are the basic functions of sockets? These are what this article aims to introduce. The main content of this article is as follows:

  • 1. How do processes communicate between networks?

  • 2. What is a socket?

  • 3. Basic operations of sockets

    • 3.1. socket() function

    • 3.2. bind() function

    • 3.3. listen(), connect() functions

    • 3.4. accept() function

    • 3.5. read(), write() functions, etc.

    • 3.6. close() function

  • 4. Detailed explanation of the three-way handshake in TCP to establish a connection

  • 5. Detailed explanation of the four-way handshake in TCP to release a connection

  • 6. An example

1. How do processes communicate between networks?

There are many ways for local inter-process communication (IPC), but they can be summarized into four categories:

  • Message passing (pipes, FIFO, message queues)

  • Synchronization (mutexes, condition variables, read-write locks, file and write locks, semaphores)

  • Shared memory (anonymous and named)

  • Remote procedure calls (Solaris doors and Sun RPC)

But these are not the theme of this article! We want to discuss how processes communicate between networks. The primary issue to solve is how to uniquely identify a process; otherwise, communication would be impossible! Locally, a process can be uniquely identified by its PID, but this is not feasible in a network. In fact, the TCP/IP protocol suite has already solved this problem. The network layer’s ‘IP address’ can uniquely identify hosts in the network, while the transport layer’s ‘protocol + port’ can uniquely identify applications (processes) on hosts. Thus, using a triplet (IP address, protocol, port) can identify network processes, allowing inter-process communication over the network using this identifier.

Applications using the TCP/IP protocol typically use application programming interfaces: UNIX BSD sockets and UNIX System V’s TLI (which has been deprecated) to achieve communication between network processes. Currently, almost all applications use sockets, and now is the era of networks; inter-process communication is ubiquitous. This is why I say, “Everything is a socket.”

2. What is a socket?

Now that we know that processes in the network communicate through sockets, what is a socket? The socket originated in Unix, and one of the basic philosophies of Unix/Linux is “Everything is a file.” Everything can be operated using the “open -> read/write -> close” model. My understanding is that a socket is an implementation of this model; a socket is a special file, and some socket functions operate on it (read/write IO, open, close), which we will introduce later.

The Origin of the Term Socket

The first use of the term in the networking field was found in a document published on February 12, 1970, IETF RFC33, authored by Stephen Carr, Steve Crocker, and Vint Cerf. According to the Computer History Museum, Crocker stated, “Elements of the naming space can be called socket interfaces. A socket interface constitutes one end of a connection, and a connection can be fully specified by a pair of socket interfaces.” The Computer History Museum added, “This is about 12 years earlier than the BSD socket interface definition.”

3. Basic operations of sockets

Since a socket is an implementation of the “open—write/read—close” model, it provides function interfaces corresponding to these operations. Below, we will introduce several basic socket interface functions, taking TCP as an example.

3.1. socket() function

int socket(int domain, int type, int protocol);

The socket function corresponds to the open operation of a regular file. The open operation of a regular file returns a file descriptor, while socket() creates a socket descriptor (socket descriptor) that uniquely identifies a socket. This socket descriptor is used in subsequent operations as a parameter for read/write operations.

Just as different parameters can be passed to fopen to open different files, different parameters can also be specified to create different socket descriptors when creating a socket. The three parameters of the socket function are:

  • domain: This is the protocol domain, also known as the protocol family. Common protocol families include AF_INET, AF_INET6, AF_LOCAL (or AF_UNIX, Unix domain socket), AF_ROUTE, etc. The protocol family determines the address type of the socket, and the corresponding address must be used in communication. For example, AF_INET determines that an IPv4 address (32-bit) and port number (16-bit) combination must be used, while AF_UNIX determines that an absolute pathname should be used as the address.

  • type: Specifies the type of socket. Common socket types include SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_PACKET, SOCK_SEQPACKET, etc. (What types of sockets are there?).

  • protocol: As the name implies, this specifies the protocol. Common protocols include IPPROTO_TCP, IPPROTO_UDP, IPPROTO_SCTP, IPPROTO_TIPC, etc., which correspond to TCP transport protocol, UDP transport protocol, STCP transport protocol, TIPC transport protocol (which I will discuss in a separate article!).

Note: The type and protocol above cannot be arbitrarily combined; for example, SOCK_STREAM cannot be combined with IPPROTO_UDP. When the protocol is 0, the default protocol corresponding to the type will be automatically selected.

When we call socket to create a socket, the returned socket descriptor exists in the protocol family (address family, AF_XXX) space but does not have a specific address. If we want to assign an address to it, we must call the bind() function; otherwise, when calling connect() or listen(), the system will automatically assign a random port.

Microcontroller Development - Socket Programming

1. Stream Socket (SOCK_STREAM)

Stream sockets provide connection-oriented, reliable data transmission services. This service guarantees that data can be delivered without errors, without duplication, and in order. The reason stream sockets can achieve reliable data services is that they use the Transmission Control Protocol, i.e., TCP (The Transmission Control Protocol).

2. Datagram Socket (SOCK_DGRAM)

Datagram sockets provide a connectionless, unreliable service. This service cannot guarantee the reliability of data transmission; data may be lost or duplicated during transmission, and there is no guarantee that data will be received in order. Datagram sockets use the User Datagram Protocol (UDP) for data transmission. Because datagram sockets cannot guarantee the reliability of data transmission, appropriate handling must be done in the program for possible data loss.

3. Raw Socket (SOCK_RAW)

Raw sockets differ from standard sockets (which refer to the stream and datagram sockets introduced earlier) in that raw sockets can read and write IP packets that have not been processed by the kernel, while stream sockets can only read TCP protocol data, and datagram sockets can only read UDP protocol data. Therefore, if you want to access data sent by other protocols, you must use raw sockets. Raw sockets are mainly used for developing some protocols and can perform lower-level operations.

socket() functionint socket(int domain, int type, int protocol);The socket function corresponds to the open operation of a regular file. The open operation of a regular file returns a file descriptor, while socket() creates a socket descriptor (socket descriptor) that uniquely identifies a socket. This socket descriptor is used in subsequent operations as a parameter for read/write operations.

Just as different parameters can be passed to fopen to open different files, different parameters can also be specified to create different socket descriptors when creating a socket. The three parameters of the socket function are:

domain: This is the protocol domain, also known as the protocol family. Common protocol families include AF_INET, AF_INET6, AF_LOCAL (or AF_UNIX, Unix domain socket), AF_ROUTE, etc. The protocol family determines the address type of the socket, and the corresponding address must be used in communication. For example, AF_INET determines that an IPv4 address (32-bit) and port number (16-bit) combination must be used, while AF_UNIX determines that an absolute pathname should be used as the address.type: Specifies the type of socket. Common socket types include SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_PACKET, SOCK_SEQPACKET, etc. (What types of sockets are there?).protocol: As the name implies, this specifies the protocol. Common protocols include IPPROTO_TCP, IPPROTO_UDP, IPPROTO_SCTP, IPPROTO_TIPC, etc., which correspond to TCP transport protocol, UDP transport protocol, STCP transport protocol, TIPC transport protocol (which I will discuss in a separate article!).Note: The type and protocol above cannot be arbitrarily combined; for example, SOCK_STREAM cannot be combined with IPPROTO_UDP. When the protocol is 0, the default protocol corresponding to the type will be automatically selected.

When we call socket to create a socket, the returned socket descriptor exists in the protocol family (address family, AF_XXX) space but does not have a specific address. If we want to assign an address to it, we must call the bind() function; otherwise, when calling connect() or listen(), the system will automatically assign a random port.

3.2. bind() functionAs mentioned above, the bind() function assigns a specific address from an address family to the socket. For example, AF_INET and AF_INET6 assign an IPv4 or IPv6 address and port number combination to the socket.

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);The three parameters of the function are:

sockfd: This is the socket descriptor created by the socket() function, which uniquely identifies a socket. The bind() function binds a name to this descriptor.addr: A pointer to a const struct sockaddr *, pointing to the protocol address to be bound to sockfd. This address structure varies depending on the address family used when creating the socket, such as for IPv4:struct sockaddr_in {sa_family_t sin_family;in_port_t sin_port;struct in_addr sin_addr;};

struct in_addr {uint32_t s_addr;};For IPv6:struct sockaddr_in6 {sa_family_t sin6_family;in_port_t sin6_port;uint32_t sin6_flowinfo;struct in6_addr sin6_addr;uint32_t sin6_scope_id;};

struct in6_addr {unsigned char s6_addr[16];};For Unix domain:#define UNIX_PATH_MAX 108

struct sockaddr_un {sa_family_t sun_family;char sun_path[UNIX_PATH_MAX];};

addrlen: This corresponds to the length of the address.Typically, the server binds a well-known address (such as IP address + port number) during startup to provide services, allowing clients to connect to the server. The client does not need to specify this, as the system automatically assigns a port number and its own IP address combination. This is why servers usually call bind() before listen, while clients do not call it and let the system generate one randomly during connect.

Network Byte Order and Host Byte OrderHost byte order refers to the big-endian and little-endian modes we usually talk about: different CPUs have different byte order types, which refers to the order in which integers are stored in memory, known as host order. The definitions of standard Big-Endian and Little-Endian are as follows:

a) Little-Endian means that the low-order byte is stored at the lower memory address, and the high-order byte is stored at the higher memory address.

b) Big-Endian means that the high-order byte is stored at the lower memory address, and the low-order byte is stored at the higher memory address.

Network byte order: A 32-bit value of 4 bytes is transmitted in the following order: first 0-7 bits, then 8-15 bits, then 16-23 bits, and finally 24-31 bits. This transmission order is called big-endian byte order. Since all binary integers in the TCP/IP header must be transmitted in this order, it is also known as network byte order. Byte order, as the name suggests, refers to the order of bytes, which does not pose a problem for data of one byte.

Therefore, when binding an address to a socket, please convert the host byte order to network byte order and do not assume that the host byte order is the same as the network byte order using Big-Endian. This issue has caused significant problems! In company project code, this issue led to many inexplicable problems, so please remember not to make any assumptions about the host byte order; always convert it to network byte order before assigning it to the socket.

3.3. listen(), connect() functionsIf you are a server, after calling socket() and bind(), you will call listen() to listen on this socket. If the client then calls connect() to send a connection request, the server will receive this request.

int listen(int sockfd, int backlog);int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);The first parameter of the listen function is the socket descriptor to be listened on, and the second parameter is the maximum number of connections that can be queued for the socket. The socket() function creates a socket of the active type by default. The listen function turns the socket into a passive type, waiting for client connection requests.

The first parameter of the connect function is the socket descriptor of the client, and the second parameter is the server’s socket address, with the third parameter being the length of the socket address. The client establishes a connection with the TCP server by calling the connect function.

3.4. accept() functionAfter the TCP server calls socket(), bind(), and listen(), it will listen on the specified socket address. After the TCP client calls socket() and connect(), it sends a connection request to the TCP server. Once the TCP server detects this request, it will call the accept() function to accept the request, thus establishing the connection. After that, network I/O operations can begin, similar to ordinary file read/write I/O operations.

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);The first parameter of the accept function is the server’s socket descriptor, and the second parameter is a pointer to struct sockaddr *, used to return the client’s protocol address. The third parameter is the length of the protocol address. If accept is successful, the return value is a new descriptor automatically generated by the kernel, representing the TCP connection with the returning client.

Note: The first parameter of accept is the server’s socket descriptor, which is generated by the server calling the socket() function and is called the listening socket descriptor. The descriptor returned by the accept function is the connected socket descriptor. A server usually creates only one listening socket descriptor, which exists throughout the server’s lifecycle. The kernel creates a connected socket descriptor for each client connection accepted by the server process, and when the server completes its service for a client, the corresponding connected socket descriptor is closed.

Creating a Socket – socket()

Before using a socket, an application must have a socket. The system call socket() provides the means to create a socket for applications, with the following call format:

  1. SOCKET PASCAL FAR socket(int af, int type, int protocol)

This call accepts three parameters: af, type, and protocol. The parameter af specifies the area where communication occurs: AF_UNIX, AF_INET, AF_NS, etc., while DOS and WINDOWS only support AF_INET, which is the internet area. Therefore, the address family and protocol family are the same. The parameter type describes the type of socket to be established. There are three types:

(1) TCP stream socket (SOCK_STREAM) provides a connection-oriented, reliable data transmission service that sends data without errors or duplication, and receives it in the order sent. Flow control is built in to prevent data flow from exceeding limits; data is treated as a byte stream with no length limit. The File Transfer Protocol (FTP) uses stream sockets.

(2) Datagram socket (SOCK_DGRAM) provides a connectionless service. Data packets are sent independently, without providing error guarantees; data may be lost or duplicated, and the order of reception may be mixed up. The Network File System (NFS) uses datagram sockets.

(3) Raw socket (SOCK_RAW) allows direct access to lower-layer protocols such as IP and ICMP. It is commonly used to test new protocol implementations or access new devices configured in existing services.

The protocol parameter specifies the specific protocol used by the socket. If the caller does not wish to specify a particular protocol, it should be set to 0 to use the default connection mode. A socket is established based on these three parameters, and the corresponding resources are allocated to it, returning an integer socket number. Therefore, the socket() system call specifies the “protocol” element in the relevant five-tuple.

Assigning a Local Address – bind()

Once a socket is created with socket(), it exists in a namespace (address family), but it has not been named. bind() associates the socket address (including the local host address and local port address) with the created socket number, assigning a name to the socket to specify its local half. Its call format is as follows:

  1. int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen);

The parameter s is the socket descriptor returned by the socket() call and is not yet connected. The parameter name is the local address (name) assigned to the socket s, which has a variable length and the structure varies according to the communication domain. namelen indicates the length of name. If no error occurs, bind() returns 0; otherwise, it returns SOCKET_ERROR.

Listening for Connections – listen()

This call is used for connection-oriented servers to indicate that they are willing to accept connections. listen() must be called before accept(), with the following call format:

  1. int PASCAL FAR listen(SOCKET s, int backlog);

The parameter s identifies a local established socket that is not yet connected, and the server is willing to accept requests from it. backlog indicates the maximum length of the request connection queue, limiting the number of queued requests. The currently allowed maximum value is 5. If no error occurs, listen() returns 0; otherwise, it returns SOCKET_ERROR.

Data Transmission – send() and recv()

Once a connection is established, data can be transmitted. Common system calls are send() and recv().

The send() call is used to send output data on the connected data or stream socket specified by s, with the following format:

  1. int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int flags);

The parameter s is the descriptor of the connected local socket. buf points to a buffer containing the data to be sent, with its length specified by len. flags specify the transmission control method, such as whether to send out-of-band data, etc. If no error occurs, send() returns the total number of bytes sent; otherwise, it returns SOCKET_ERROR.

The recv() call is used to receive input data on the connected data or stream socket specified by s, with the following format:

  1. int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int flags);

The parameter s is the descriptor of the connected socket. buf points to the buffer for receiving input data, with its length specified by len. flags specify the transmission control method, such as whether to receive out-of-band data, etc. If no error occurs, recv() returns the total number of bytes received. If the connection is closed, it returns 0; otherwise, it returns SOCKET_ERROR.

Input/Output Multiplexing – select()

The select() call is used to detect the status of one or more sockets. For each socket, this call can request information on read, write, or error status. The set of sockets for which a given status is requested is indicated by an fd_set structure. When returning, this structure is updated to reflect the subset of sockets that meet specific conditions, and the select() call returns the number of sockets that meet the conditions, with the following call format:

  1. int PASCAL FAR select(int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);

The parameter nfds specifies the range of values for the socket descriptors being checked; this variable is generally ignored.

The parameter readfds points to a pointer to the set of socket descriptors for which read detection is requested. The caller wants to read data from them. The parameter writefds points to a pointer to the set of socket descriptors for which write detection is requested. The exceptfds points to a pointer to the set of socket descriptors for which error detection is requested. The timeout points to the maximum time that the select() function waits; if set to NULL, it will be a blocking operation. select() returns the total number of socket descriptors that are ready in the fd_set structure, or returns SOCKET_ERROR if an error occurs.

Closing a Socket – closesocket()

closesocket() closes the socket s and releases the resources allocated to that socket; if s involves an open TCP connection, that connection is released. The calling format of closesocket() is as follows:

  1. BOOL PASCAL FAR closesocket(SOCKET s);

The parameter s is the socket descriptor to be closed. If no error occurs, closesocket() returns 0; otherwise, it returns SOCKET_ERROR.

Disclaimer

Note: The views expressed in this article are solely those of the author and do not guarantee or commit to the content. Readers should reference and verify its authenticity and legality. If you find that the source of any image, text, or video content is incorrectly marked or infringes on your rights, please inform this public account, and we will promptly modify or delete it.

Short drama recommendation: “Enemies on the Road”

Leave a Comment

×