Introduction
This article covers the essential concepts and practical implementation of network programming in embedded Linux systems. The content focuses on socket programming, protocol fundamentals, and development techniques.
- Network Fundamentals
1.1 Network Layer Models
Two primary layered models exist in networking:
- OSI Seven-Layer Model: A theoretical framework with seven distinct layers
- TCP/IP Four-Layer Model: The practical implementation used in real-world networking
The layered approach applies the divide-and-conquer principle, splitting complex networking problems into manageable sub-problems across each layer. Each layer provides services to the layer above while consuming services from the layer below.
Data Encapsulation and Decapsulation: When host A transmits data to host B, the process works as follows: the application layer adds its header, the transport layer adds a TCP/UDP header, the network layer adds an IP header, and the data link layer adds appropriate headers and trailers (typically CRC checksums). Routers strip headers at each layer, forward packets based on routing tables, and re-encapsulate before transmission. Host B performs the reverse process, stripping headers layer by layer until the application receives the actual data.
1.2 Socket Concepts
A socket serves as a specialized file descriptor dedicated to network communications. Socket types include:
- Stream Sockets (SOCK_STREAM): Connection-oriented, reliable, used with TCP
- Datagram Sockets (SOCK_DGRAM) connectionless, unreliable, used with UDP
- Raw Sockets (SOCK_RAW): Allow direct access to IP and ICMP protocols, bypassing the transport layer
1.3 IP Address Representation
IP addresses uniquely identify hosts within a network.
IPv4: A 32-bit integer divided into four bytes, each ranging from 0-255, separated by dots (e.g., 192.168.5.11). Address scarcity led to the adoption of private networks.
IPv6: A 128-bit address space divided into eight 16-bit segments (e.g., 2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b), providing virtually unlimited addresses.
Special IP Ranges:
- Private network: 192.168.x.x, 10.x.x.x
- Broadcast addresses: x.x.x.255, 255.255.255.255
- Multicast range: 224.x.x.x to 239.x.x.x
1.4 Port Numbers
Ports are 16-bit integers ranging from 1 to 65535, identifying specific processes on a host:
- Well-known ports: 1-1023 (FTP: 21, SSH: 22, HTTP: 80, HTTPS: 443)
- Reserved ports: 1024-5000 (avoid using)
- Available ports: 5001-65535
Important: TCP and UDP ports operate independently. Network communication requires both IP address (identifying the host) and port number (identifying the specific process).
1.5 Byte Order
Multi-byte data storage in memory involves byte ordering considerations:
- Little Endian: Lower-order bytes stored at lower memory addresses
- Big Endian: Lower-order bytes stored at higher memory addresses
Local systems typically use little endian, while network transmission requires big endian. Conversion between host and network byte order is essential before sending or receiving data.
Byte Order Conversion Functions:
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort); // Host to network (short)
uint32_t htonl(uint32_t hostlong); // Host to network (long)
uint16_t ntohs(uint16_t netshort); // Network to host (short)
uint32_t ntohl(uint32_t netlong); // Network to host (long)
IP Address Conversion Functions:
inet_pton: Converts human-readable IP to network byte order
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
Parameters: af (AF_INET for IPv4, AF_INET6 for IPv6), src (input string), dst (output buffer). Returns 1 on success, -1 on invalid family, 0 on invalid input.
inet_ntop: Converts network byte order IP to human-readable string
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
Parameters: af (address family), src (network byte order input), dst (output string), size (buffer size). Returns pointer to dst on success, NULL on failure.
- Network Programming API Reference
All functions require #include <sys/types.h> and #include <sys/socket.h> except read and write.
2.1 socket Function
Purpose: Creates a network socket file descriptor.
int socket(int domain, int type, int protocol);
Parameters:
Returns: Socket file descriptor on success, -1 on failure.
Example:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket failed");
return -1;
}
2.2 bind Function
Purpose: Associates a socket with a specific IP address and port number.
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Parameters: sockfd (socket descriptor), addr (address structure), addrlen (structure size).
Adress Structures:
// Generic structure (rarely used directly)
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
// IPv4 structure (commonly used)
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
struct in_addr {
in_addr_t s_addr;
};
Returns: 0 on success, -1 on failure.
Example:
#define SERVER_PORT 8080
#define SERVER_ADDR "192.168.1.100"
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_ADDR, &serverAddr.sin_addr) != 1) {
perror("address conversion failed");
return -1;
}
if (bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
perror("bind failed");
return -1;
}
2.3 listen Function
Purpose: Marks a socket as passive, acccepting incoming connections. Must be called after bind.
int listen(int sockfd, int backlog);
Parameters: sockfd (listening socket), backlog (maximum pending connections, typically 128).
Returns: 0 on success, -1 on failure.
Example:
if (listen(sockfd, 128) < 0) {
perror("listen failed");
return -1;
}
2.4 accept Function
Purpose: Blocks until a client connects, returning a new socket for communication.
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
Parameters: sockfd (listening socket), addr (client address output), addrlen (address structure size).
Returns: New communication socket on success, -1 on failure.
Example:
struct sockaddr_in clientAddr;
socklen_t clientLen = sizeof(clientAddr);
int clientSock = accept(sockfd, (struct sockaddr*)&clientAddr, &clientLen);
if (clientSock < 0) {
perror("accept failed");
return -1;
}
2.5 connect Function
Purpose: Initiates a connection from a client to a server. Blocks by default.
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Parameters: sockfd (client socket), addr (server address), addrlen (address size).
Returns: 0 on success, -1 on failure.
Example:
#define SERVER_PORT 8080
#define SERVER_ADDR "192.168.1.100"
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_ADDR, &serverAddr.sin_addr) != 1) {
perror("address conversion failed");
return -1;
}
if (connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
perror("connection failed");
return -1;
}
2.6 read and recv Functions
Purpose: Receives data from a connected socket.
ssize_t read(int sockfd, void *buf, size_t len);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
Parameters: sockfd (communication socket), buf (receive buffer), len (buffer size), flags (special options, typically 0).
Common flags:
- MSG_DONTWAIT: Non-blocking mode
- MSG_OOB: Out-of-band data
- MSG_PEEK: Peek at data without removing from buffer
Returns: Bytes received (positive), 0 (connection closed), -1 (error).
2.7 write and send Functions
Purpose: Sends data over a connected socket.
ssize_t write(int sockfd, const void *buf, size_t len);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
Parameters: sockfd (communication socket), buf (data to send), len (data length), flags (special options, typically 0).
Common flags:
- MSG_DONTWAIT: Non-blocking mode
- MSG_OOB: Out-of-band data
Returns: Bytes sent (positive), -1 (error).
2.8 recvfrom Function
Purpose: Receives datagrams, typically used with UDP. Retrieves sender address information.
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
Parameters: sockfd (socket), buf (receive buffer), len (buffer size), flags (options), src_addr (sender address output), addrlen (address size).
Returns: Bytes received on success, -1 on failure. Blocks by default.
2.9 sendto Function
Purpose: Sends datagrams to a specific destination, typically used with UDP. Does not block by default.
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
Parameters: sockfd (socket), buf (data to send), len (data length), flags (options), dest_addr (destination address), addrlen (address size).
Returns: Bytes sent on success, -1 on failure.