Understanding POSIX: Concepts, Network APIs, and Semaphore Programming in C
This article explains the POSIX standards, their role in providing portable Unix-like system interfaces, demonstrates network client‑server programming with POSIX sockets, analyzes key socket functions and the TCP three‑way handshake, and introduces POSIX named and unnamed semaphores with example code for synchronization and producer‑consumer patterns.
POSIX (Portable Operating System Interface) is a family of IEEE/ISO standards that define a portable API for Unix‑like operating systems, allowing source‑level code to run on many different systems.
POSIX Concept
POSIX specifies the application programming interface (API) and related utilities such as command‑line shells. Programs written to the POSIX API can be easily ported to various Unix derivatives, including Linux.
The standard defines the system‑call interface that operating systems must expose to applications. Most Unix‑like OSes provide these interfaces through the C standard library.
POSIX : Portable Operating System Interface of UNIX.
It defines the OS interfaces that applications should use.
Applications using POSIX‑conformant APIs are portable across systems.
POSIX specifies the system‑call interface for applications.
The most common OS API in the Unix world is based on the POSIX standard and is usually delivered as a C library (glibc on Linux), which implements the POSIX system calls.
POSIX Network API
Common client‑server network programming APIs are shown below.
Server (server.cpp)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
if (argc != 2) {
printf("Using:./server port\nExample:./server 5005\n\n");
return -1;
}
int listenfd;
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
return -1;
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(atoi(argv[1]));
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) {
perror("bind");
close(listenfd);
return -1;
}
if (listen(listenfd, 5) != 0) {
perror("listen");
close(listenfd);
return -1;
}
int clientfd;
int socklen = sizeof(struct sockaddr_in);
struct sockaddr_in clientaddr;
clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, (socklen_t *)&socklen);
printf("client (%s) connect server success...\n", inet_ntoa(clientaddr.sin_addr));
char buffer[1024];
while (1) {
int ret;
memset(buffer, 0, sizeof(buffer));
if ((ret = recv(clientfd, buffer, sizeof(buffer), 0)) <= 0) {
printf("ret = %d , client disconnected!!!\n", ret);
break;
}
printf("recv msg: %s\n", buffer);
if ((ret = send(clientfd, buffer, strlen(buffer), 0)) <= 0) {
perror("send");
break;
}
printf("response client: %s success...\n", buffer);
}
close(listenfd);
close(clientfd);
return 0;
}Client (client.cpp)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
if (argc != 3) {
printf("Using:./client ip port\nExample:./client 127.0.0.1 5005\n\n");
return -1;
}
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
return -1;
}
struct hostent *h;
if ((h = gethostbyname(argv[1])) == 0) {
printf("gethostbyname failed.\n");
close(sockfd);
return -1;
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2]));
memcpy(&servaddr.sin_addr, h->h_addr, h->h_length);
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) {
perror("connect");
close(sockfd);
return -1;
}
char buffer[1024];
for (int i = 0; i < 3; i++) {
int ret;
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "This is message [%d]", i+1);
if ((ret = send(sockfd, buffer, strlen(buffer), 0)) <= 0) {
perror("send");
break;
}
printf("Sent: %s\n", buffer);
memset(buffer, 0, sizeof(buffer));
if ((ret = recv(sockfd, buffer, sizeof(buffer), 0)) <= 0) {
printf("ret = %d error\n", ret);
break;
}
printf("Received from server: %s\n", buffer);
sleep(1);
}
close(sockfd);
}The program output demonstrates a successful three‑way handshake, data exchange, and graceful termination.
Key Socket Functions
socket()
int socket(int domain, int type, int protocol);Creates a socket descriptor and a TCP control block (TCB) that holds the five‑tuple (source IP, source port, destination IP, destination port, protocol).
listen()
int listen(int listenfd, int backlog);Marks the socket as passive and sets the length of the pending‑connection queue (backlog).
connect()
Initiates a connection from the client to the server, triggering the TCP three‑way handshake.
accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);Retrieves a pending connection from the queue, allocates a new descriptor for the established connection, and returns it.
TCP Three‑Way Handshake
The client sends a SYN packet, the server replies with SYN‑ACK (after moving the request to the half‑open queue), and the client sends an ACK, completing the connection. The server then moves the connection to the fully‑established queue, where accept() can retrieve it.
Data Transfer Functions
send() copies data from user space to the kernel’s send buffer; the call returning does not guarantee that the data has been transmitted on the wire.
recv() copies data from the kernel’s receive buffer to user space.
Connection Termination (close)
Calling close() on a socket initiates the TCP four‑way termination handshake, moving the socket through FIN_WAIT1, FIN_WAIT2, TIME_WAIT, and finally CLOSED states.
POSIX Semaphores (Inter‑process Communication)
POSIX provides named and unnamed semaphores. Both are counters with an associated PCB wait queue.
Named Semaphore
Created with sem_open() , closed with sem_close() , and removed with sem_unlink() . Named semaphores can be shared between unrelated processes via a common name.
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);Unnamed Semaphore
Created with sem_init() and destroyed with sem_destroy() . Useful for threads sharing memory or for processes that share a memory segment.
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);Semaphore Operations
sem_wait() decrements the counter; if the result is negative, the calling thread blocks.
#include <semaphore.h>
int sem_wait(sem_t *sem);sem_trywait() attempts a non‑blocking decrement, returning -1 with EAGAIN if the semaphore is zero.
sem_timedwait() waits with a timeout, returning -1 with ETIMEOUT on expiry.
sem_post() increments the counter and wakes a waiting thread if any.
#include <semaphore.h>
int sem_post(sem_t *sem);Producer‑Consumer Example Using Unnamed Semaphores
The following C++ program implements a bounded buffer with a mutex semaphore, a producer semaphore, and a consumer semaphore.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
#include <sys/syscall.h>
#include <iostream>
#define RESOURCECOUNT 4
#define PTHREADCOUNT 2
class ModelOfProdConsByPosix {
public:
ModelOfProdConsByPosix() : _queue(RESOURCECOUNT) {
_capacity = RESOURCECOUNT;
sem_init(&_lock, 0, 1);
sem_init(&_prod, 0, RESOURCECOUNT);
sem_init(&_cons, 0, 0);
_write_pos = _read_pos = 0;
}
~ModelOfProdConsByPosix() {
sem_destroy(&_lock);
sem_destroy(&_prod);
sem_destroy(&_cons);
}
void push(int &data) {
sem_wait(&_prod);
sem_wait(&_lock);
_queue[_write_pos] = data;
_write_pos = (_write_pos + 1) % _capacity;
sem_post(&_lock);
sem_post(&_cons);
}
void pop(int &data) {
sem_wait(&_cons);
sem_wait(&_lock);
data = _queue[_read_pos];
_read_pos = (_read_pos + 1) % _capacity;
sem_post(&_lock);
sem_post(&_prod);
}
private:
std::vector<int> _queue;
int _capacity;
sem_t _lock, _prod, _cons;
int _write_pos, _read_pos;
};
void *ConsumerStart(void *arg) {
ModelOfProdConsByPosix *cp = (ModelOfProdConsByPosix*)arg;
int data = 0;
while (1) {
cp->pop(data);
printf("Consumer thread %d got %d\n", (int)syscall(SYS_gettid), data);
}
}
void *ProducerStart(void *arg) {
ModelOfProdConsByPosix *cp = (ModelOfProdConsByPosix*)arg;
int data = 0;
while (1) {
cp->push(data);
printf("Producer thread %d produced %d\n", (int)syscall(SYS_gettid), data);
++data;
}
}
int main() {
ModelOfProdConsByPosix *cp = new ModelOfProdConsByPosix;
pthread_t cons[PTHREADCOUNT], prod[PTHREADCOUNT];
for (int i = 0; i < PTHREADCOUNT; ++i) {
pthread_create(&cons[i], NULL, ConsumerStart, (void*)cp);
pthread_create(&prod[i], NULL, ProducerStart, (void*)cp);
}
for (int i = 0; i < PTHREADCOUNT; ++i) {
pthread_join(cons[i], NULL);
pthread_join(prod[i], NULL);
}
return 0;
}The program prints interleaved producer and consumer messages, demonstrating correct synchronization using POSIX semaphores.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.