VxWorks 7, a powerful real-time operating system (RTOS), offers a robust environment for developing embedded systems with networking capabilities. Its reliable and deterministic nature makes it ideal for applications ranging from industrial control to aerospace. This article will guide you through the fundamentals of network programming in VxWorks 7, complete with a practical code example and a detailed explanation.
Unleashing Network Power with the BSD Socket API #
At the heart of VxWorks 7’s networking stack lies the widely adopted Berkeley Sockets (BSD) API. This familiar interface provides a standardized way for applications to communicate over various network protocols, primarily TCP/IP. Whether you’re building a device that needs to send sensor data, receive commands, or interact with cloud services, understanding the BSD socket API is your first step.
Key concepts within the BSD socket API include:
Sockets
: Think of a socket as an endpoint for network communication. It’s a combination of an IP address and a port number.Protocols
: These are the rules governing data exchange. TCP (Transmission Control Protocol) offers reliable, connection-oriented communication, while UDP (User Datagram Protocol) provides a faster, connectionless alternative.Addresses
: Network addresses, typically IPv4 or IPv6, uniquely identify devices on a network.Ports
: Ports are virtual channels within a device, allowing multiple applications to share the same network interface.
A Practical Example: A Simple UDP Echo Server #
Let’s illustrate network programming in VxWorks 7 with a basic UDP echo server. This server will listen for incoming UDP packets on a specific port and send the received data back to the sender.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_PORT 12345
#define MAX_BUFFER_SIZE 1024
void udpEchoServer() {
int sockfd;
struct sockaddr_in serverAddr, clientAddr;
socklen_t clientAddrLen = sizeof(clientAddr);
char buffer[MAX_BUFFER_SIZE];
ssize_t bytesReceived;
// 1. Create a UDP socket
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
return;
}
// 2. Configure the server address
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY; // Listen on all available interfaces
serverAddr.sin_port = htons(SERVER_PORT); // Convert port to network byte order
// 3. Bind the socket to the server address
if (bind(sockfd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
perror("bind failed");
close(sockfd);
return;
}
printf("UDP Echo Server listening on port %d...\n", SERVER_PORT);
while (1) {
// 4. Receive data from a client
if ((bytesReceived = recvfrom(sockfd, buffer, MAX_BUFFER_SIZE, 0,
(struct sockaddr *)&clientAddr, &clientAddrLen)) < 0) {
perror("recvfrom failed");
continue;
}
printf("Received %zd bytes from %s:%d\n", bytesReceived,
inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
// 5. Send the received data back to the client
if (sendto(sockfd, buffer, bytesReceived, 0,
(const struct sockaddr *)&clientAddr, clientAddrLen) != bytesReceived) {
perror("sendto failed");
} else {
printf("Echoed %zd bytes back to the client.\n", bytesReceived);
}
}
// 6. Close the socket (this part will likely not be reached in this simple server)
close(sockfd);
}
Detailed Explanation: #
- Include Headers:
We include necessary header files for socket programming (sys/socket.h
, netinet/in.h
, arpa/inet.h
), standard input/output (stdio.h
), string manipulation (string.h
), and POSIX operating system API (unistd.h
).
- Define Constants:
SERVER_PORT
specifies the port number the server will listen on (12345
in this case), and MAX_BUFFER_SIZE
defines the maximum size of the data buffer.
- udpEchoServer() Function:
This function encapsulates the logic of our UDP echo server.
- Create a Socket:
- socket(AF_INET, SOCK_DGRAM, 0) creates a socket.
- AF_INET specifies the Internet Protocol version 4 (IPv4) address family.
- SOCK_DGRAM indicates that we are using UDP (datagram) sockets.
- The third argument is the protocol, which is 0 for the default protocol associated with SOCK_DGRAM (which is UDP).
- The function returns a file descriptor (sockfd) representing the socket. A negative value indicates an error.
- Configure Server Address:
- struct sockaddr_in serverAddr; declares a structure to hold the server’s address information.
- memset(&serverAddr, 0, sizeof(serverAddr)); initializes the structure to zero.
- serverAddr.sin_family = AF_INET; sets the address family to IPv4.
- serverAddr.sin_addr.s_addr = INADDR_ANY; tells the server to listen on all available network interfaces of the system.
- serverAddr.sin_port = htons(SERVER_PORT); sets the server’s port number. htons() converts the port number from host byte order to network byte order, which is crucial for network communication.
- Bind the Socket:
- bind(sockfd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) associates the created socket (sockfd) with the configured server address (serverAddr). This step is essential for the server to receive incoming connections or data on the specified address and port.
- A negative return value indicates an error during the binding process.
- Receive Data:
-
The
while (1)
loop makes the server continuously listen for incoming data. -
recvfrom(sockfd, buffer, MAX_BUFFER_SIZE, 0, (struct sockaddr *)&clientAddr, &clientAddrLen)
waits for a UDP packet to arrive on the socket.- buffer is the memory area where the received data will be stored.
- MAX_BUFFER_SIZE is the maximum number of bytes to receive.
- The flags argument is 0 for basic receiving.
- (struct sockaddr *)&clientAddr and &clientAddrLen are used to store the address information (IP address and port) of the client that sent the data.
- recvfrom() returns the number of bytes received or a negative value if an error occurred.
- Send Data Back (Echo):
sendto(sockfd, buffer, bytesReceived, 0, (const struct sockaddr *)&clientAddr, clientAddrLen)
sends the received data back to the client from whom it was received.- The arguments are similar to recvfrom(), but here clientAddr and clientAddrLen specify the destination address.
- sendto() returns the number of bytes sent or a negative value on error.
- Close Socket:
- close(sockfd) closes the socket, releasing the resources associated with it. In this simple server, this line might not be reached as the while(1) loop runs indefinitely. In a more sophisticated application, you would have mechanisms to gracefully shut down the server.
Building and Running on VxWorks 7 #
To build and run this code on a VxWorks 7 target, you would typically:
-
Develop on a Host Machine: Write and compile the code on a development host using the appropriate VxWorks 7 development tools and SDK. Ensure your project is configured to include the necessary networking libraries.
-
Transfer to Target: Transfer the compiled executable to your VxWorks 7 target system (e.g., via FTP, TFTP, or a debugging interface).
-
Execute on Target: Run the executable on the VxWorks 7 target, likely through the VxWorks shell or a custom application launcher.
You would then need a separate UDP client application running on another machine or even on the same target (in a different task) to send data to the server’s IP address and port (e.g., using tools like netcat or writing a simple client program).
TCP Communication: A Server and Client Example #
TCP (Transmission Control Protocol) provides reliable, ordered, and connection-oriented communication. This makes it suitable for applications where data integrity is paramount, such as file transfer, web services, and remote control.
TCP Server Code: #
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_PORT 5000
#define MAX_BUFFER_SIZE 1024
void tcpServer() {
int serverFd, clientFd;
struct sockaddr_in serverAddr, clientAddr;
socklen_t clientAddrLen = sizeof(clientAddr);
char buffer[MAX_BUFFER_SIZE];
ssize_t bytesReceived, bytesSent;
// 1. Create a TCP socket
if ((serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket creation failed");
return;
}
// 2. Configure the server address
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(SERVER_PORT);
// 3. Bind the socket to the server address
if (bind(serverFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
perror("bind failed");
close(serverFd);
return;
}
// 4. Listen for incoming connections
if (listen(serverFd, 5) < 0) { // Listen queue size of 5
perror("listen failed");
close(serverFd);
return;
}
printf("TCP Server listening on port %d...\n", SERVER_PORT);
while (1) {
printf("Waiting for a client connection...\n");
// 5. Accept a client connection
if ((clientFd = accept(serverFd, (struct sockaddr *)&clientAddr, &clientAddrLen)) < 0) {
perror("accept failed");
continue;
}
printf("Client connected from %s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
// 6. Communication loop with the client
while ((bytesReceived = recv(clientFd, buffer, MAX_BUFFER_SIZE, 0)) > 0) {
printf("Received %zd bytes: %.*s", bytesReceived, (int)bytesReceived, buffer);
// Echo back the received data
bytesSent = send(clientFd, buffer, bytesReceived, 0);
if (bytesSent < 0) {
perror("send failed");
break; // Exit inner loop on send error
}
printf("Echoed %zd bytes back to the client.\n", bytesSent);
}
if (bytesReceived == 0) {
printf("Client disconnected.\n");
} else if (bytesReceived < 0) {
perror("recv failed");
}
// 7. Close the client socket
close(clientFd);
}
// 8. Close the server socket (this part might not be reached in this simple server)
close(serverFd);
}
TCP Client Code: #
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_IP "127.0.0.1" // Replace with the actual server IP address
#define SERVER_PORT 5000
#define MAX_BUFFER_SIZE 1024
void tcpClient() {
int clientFd;
struct sockaddr_in serverAddr;
char buffer[MAX_BUFFER_SIZE];
ssize_t bytesRead, bytesSent;
// 1. Create a TCP socket
if ((clientFd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket creation failed");
return;
}
// 2. Configure the server address
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) <= 0) {
perror("inet_pton failed for IP address");
close(clientFd);
return;
}
// 3. Connect to the server
if (connect(clientFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
perror("connect failed");
close(clientFd);
return;
}
printf("Connected to server %s:%d\n", SERVER_IP, SERVER_PORT);
// 4. Communication loop with the server
while (1) {
printf("Enter message to send (or 'quit' to exit): ");
fgets(buffer, MAX_BUFFER_SIZE, stdin);
// Remove trailing newline from fgets
size_t len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
if (strcmp(buffer, "quit") == 0) {
break;
}
// Send data to the server
bytesSent = send(clientFd, buffer, strlen(buffer), 0);
if (bytesSent < 0) {
perror("send failed");
break;
}
printf("Sent %zd bytes to server: %s\n", bytesSent, buffer);
// Receive response from the server
bytesRead = recv(clientFd, buffer, MAX_BUFFER_SIZE - 1, 0);
if (bytesRead > 0) {
buffer[bytesRead] = '\0'; // Null-terminate the received data
printf("Received %zd bytes from server: %s\n", bytesRead, buffer);
} else if (bytesRead == 0) {
printf("Server closed the connection.\n");
break;
} else {
perror("recv failed");
break;
}
}
// 5. Close the client socket
close(clientFd);
printf("Connection closed.\n");
}
Detailed Explanation: #
TCP Server (tcpServer()): #
-
Create a TCP Socket: Similar to UDP, we use socket(), but this time with SOCK_STREAM to indicate a TCP socket.
-
Configure Server Address: The process of setting up the server address structure (serverAddr) is the same as in the UDP example.
-
Bind the Socket: We associate the socket with the server’s IP address and port using bind().
-
Listen for Connections:
- listen(serverFd, 5) puts the server socket into a passive listening mode. It tells the operating system to listen for incoming connection requests on the bound address and port.
- The second argument (5 in this case) specifies the maximum number of pending connection requests that can be queued.
- Accept a Client Connection:
- accept(serverFd, (struct sockaddr *)&clientAddr, &clientAddrLen) blocks until a client attempts to connect to the server.
- When a connection request arrives, accept() creates a new socket (clientFd) specifically for communication with that client. The original serverFd remains listening for further connections.
- clientAddr and clientAddrLen are populated with the address information of the connecting client.
- Communication Loop:
- The while ((bytesReceived = recv(clientFd, buffer, MAX_BUFFER_SIZE, 0)) > 0) loop handles data exchange with the connected client.
- recv(clientFd, buffer, MAX_BUFFER_SIZE, 0) reads data from the connected client socket. It will block until data is available.
- If recv() returns a positive value, data was received. If it returns 0, the client has closed the connection gracefully. If it returns a negative value, an error occurred.
- send(clientFd, buffer, bytesReceived, 0) sends the received data back to the client (echo).
-
Close Client Socket: Once the communication with a client is finished (either the client closed the connection or an error occurred), close(clientFd) closes the socket associated with that specific client.
-
Close Server Socket: The outer close(serverFd) would typically be called if the server needs to shut down entirely. In this simple infinite loop server, it might not be reached.
TCP Client (tcpClient()): #
-
Create a TCP Socket: Similar to the server, the client creates a TCP socket using socket(AF_INET, SOCK_STREAM, 0).
-
Configure Server Address:
- We set up the serverAddr structure with the server’s IP address (SERVER_IP) and port (SERVER_PORT).
- inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) converts the human-readable IP address string into a binary form suitable for the sockaddr_in structure.
- Connect to the Server:
- connect(clientFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) attempts to establish a connection with the server at the specified address and port.
- connect() will block until the connection is established or an error occurs (e.g., the server is not listening).
- Communication Loop:
- The while (1) loop allows the client to send messages to the server and receive responses.
- fgets(buffer, MAX_BUFFER_SIZE, stdin) reads input from the user.
- The trailing newline character from fgets is removed.
- If the user enters “quit”, the loop breaks, and the client closes the connection.
- send(clientFd, buffer, strlen(buffer), 0) sends the user’s message to the connected server.
- recv(clientFd, buffer, MAX_BUFFER_SIZE - 1, 0) waits for and receives a response from the server. The received data is null-terminated to be treated as a C string.
- The loop checks for different return values of recv() to handle data received, server disconnection, or errors.
- Close Client Socket:
close(clientFd) closes the client’s socket, terminating the connection with the server.
Building and Running: #
You would compile these two code snippets separately for your VxWorks 7 environment. Ensure that the server executable is running on your target system before you run the client executable (which might be on the same target in a different task or on a separate machine with network connectivity to the target). Remember to replace “127.0.0.1” in the client code with the actual IP address of your VxWorks 7 target if the client is running on a different machine.
This TCP example provides a fundamental building block for more complex networked applications in VxWorks 7. You can expand upon this by implementing different communication protocols, handling multiple client connections on the server side using threads, and incorporating more sophisticated data processing.
Further Exploration #
This example provides a basic introduction. Network programming in VxWorks 7 offers a wealth of possibilities:
- TCP Communication: For reliable, connection-oriented communication, explore the socket() with SOCK_STREAM, listen(), accept(), connect(), send(), and recv() functions.
- Multithreading: For handling multiple client connections concurrently, you’ll likely need to use VxWorks threads (taskSpawn()) to manage each connection.
- Network Protocols: VxWorks 7 supports various network protocols beyond UDP and TCP, such as IPsec, SNMP, and more.
- Network Interfaces: Understanding how to configure and manage network interfaces on your VxWorks target is crucial.
- Error Handling: Robust network applications require careful error handling to gracefully manage network issues.
Conclusion #
Network programming in VxWorks 7 empowers embedded systems to interact with the wider world. By leveraging the familiar BSD socket API and understanding the fundamental concepts, developers can build sophisticated and reliable networked applications on this powerful RTOS. This simple UDP echo server serves as a stepping stone for exploring the exciting possibilities of connecting your VxWorks 7 devices.