Chapter 12: Advanced Topics in C

Introduction

As you become more proficient in C programming, you’ll encounter advanced concepts that push the boundaries of what you can achieve. This chapter delves into several advanced topics in C, including multithreading, network programming, and interfacing with hardware. These topics require a solid understanding of C fundamentals, but mastering them will allow you to create highly efficient, scalable, and versatile applications.

Multithreading in C

Multithreading is the ability of a CPU to execute multiple threads concurrently, which can significantly enhance the performance of applications by leveraging multi-core processors. In C, the POSIX Threads (pthreads) library is commonly used for creating and managing threads.

Creating and Managing Threads

The pthreads library provides a set of functions for working with threads. To use it, you must include the <pthread.h> header file.

Example:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

// Function to be executed by each thread
void* threadFunction(void* arg) {
int* threadId = (int*)arg;
printf("Thread %d is running\n", *threadId);
pthread_exit(NULL);
}

int main() {
pthread_t threads[5];
int threadIds[5];
int result;

// Create threads
for (int i = 0; i < 5; i++) {
threadIds[i] = i + 1;
result = pthread_create(&threads[i], NULL, threadFunction, (void*)&threadIds[i]);
if (result) {
printf("Error creating thread %d\n", result);
exit(-1);
}
}

// Wait for all threads to complete
for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}

return 0;
}

Synchronization

When multiple threads access shared resources, synchronization mechanisms are necessary to prevent data races and ensure consistency. Mutexes (mutual exclusions) are commonly used for this purpose.

Example:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_mutex_t lock;
int counter = 0;

void* threadFunction(void* arg) {
pthread_mutex_lock(&lock);
counter++;
printf("Counter value: %d\n", counter);
pthread_mutex_unlock(&lock);
pthread_exit(NULL);
}

int main() {
pthread_t threads[5];
int result;

// Initialize the mutex
if (pthread_mutex_init(&lock, NULL)) {
printf("Error initializing mutex\n");
return 1;
}

// Create threads
for (int i = 0; i < 5; i++) {
result = pthread_create(&threads[i], NULL, threadFunction, NULL);
if (result) {
printf("Error creating thread %d\n", result);
exit(-1);
}
}

// Wait for all threads to complete
for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}

// Destroy the mutex
pthread_mutex_destroy(&lock);

return 0;
}

Network Programming in C

Network programming allows you to create applications that communicate over a network. The sockets API is a popular interface for network programming in C.

Creating a Simple Client-Server Application

A typical client-server application involves a server that listens for incoming connections and a client that initiates a connection to the server.

Server Example:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
char* hello = "Hello from server";

// Create socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}

// Forcefully attaching socket to the port 8080
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);

// Bind the socket to the network address and port
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}

// Listen for incoming connections
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}

// Accept an incoming connection
if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}

// Read and send data
read(new_socket, buffer, 1024);
printf("Message from client: %s\n", buffer);
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent\n");

close(new_socket);
close(server_fd);

return 0;
}

Client Example:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
int sock = 0, valread;
struct sockaddr_in serv_addr;
char* hello = "Hello from client";
char buffer[1024] = {0};

// Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("Socket creation error\n");
return -1;
}

serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);

// Convert IPv4 and IPv6 addresses from text to binary form
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
printf("Invalid address/Address not supported\n");
return -1;
}

// Connect to the server
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
printf("Connection failed\n");
return -1;
}

send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");
valread = read(sock, buffer, 1024);
printf("Message from server: %s\n", buffer);

close(sock);

return 0;
}

Interfacing with Hardware

Interfacing with hardware directly through C can be powerful for developing applications that need to control or interact with hardware devices. This includes working with GPIO (General Purpose Input/Output) pins, serial ports, and other peripherals.

GPIO Access on Embedded Systems

On embedded systems like the Raspberry Pi, you can control GPIO pins using the wiringPi library.

Example:

#include <wiringPi.h>
#include <stdio.h>

int main() {
// Setup the library
if (wiringPiSetup() == -1) {
printf("Setup wiringPi failed!\n");
return 1;
}

// Set pin 0 as output
pinMode(0, OUTPUT);

// Blink LED connected to pin 0
while (1) {
digitalWrite(0, HIGH); // LED on
delay(500); // wait 500 ms
digitalWrite(0, LOW); // LED off
delay(500); // wait 500 ms
}

return 0;
}

Conclusion

Advanced topics in C programming, such as multithreading, network programming, and hardware interfacing, open up a world of possibilities for creating high-performance and versatile applications. By mastering these topics, you can develop applications that are efficient, responsive, and capable of leveraging the full potential of modern hardware and networking capabilities. This chapter has provided detailed examples and explanations to help you get started with these advanced topics, equipping you with the knowledge to tackle complex programming challenges.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *