-
Notifications
You must be signed in to change notification settings - Fork 0
04_network_communication_protocol.md
Welcome back, cdfs-ton adventurer! In our previous chapters, we explored how you, as a user, interact with cdfs-ton through the Client Application Interface, how the Metadata Server acts as the system's brain, and how Storage Nodes diligently store your file chunks.
Now, imagine our client, metadata server, and storage nodes are all team members in a big project. They all have important jobs, but how do they talk to each other? What if the client tries to ask for a file, but the metadata server expects a request to store a chunk? It would be pure chaos! They'd be speaking different "languages."
This is where the Network Communication Protocol comes in. It's the standardized "language" and the set of rules that all parts of cdfs-ton (client, metadata server, storage nodes) use to understand each other when they communicate over the network. It's like everyone agrees to use the same dictionary and grammar rules.
Think about how you use a smartphone. When you tap an icon, your phone knows exactly what app to open. When you send a text, your phone knows how to package that message and send it to the right person. This happens because there's an agreed-upon way for apps, your phone's operating system, and the network to communicate.
In cdfs-ton, the Metadata Server and Storage Nodes are separate programs, often running on different computers. When your cdfs_client application wants to, say, put a file:
- It needs to tell the Metadata Server: "I want to register a new file."
- It needs to tell a Storage Node: "I want to store this chunk."
- Later, when retrieving, it needs to tell a Storage Node: "I want to load chunk ID 123."
Without a clear protocol, these components wouldn't understand each other. The protocol defines:
- What kind of request is this? (e.g., "This is a request to store data!")
- What information is included? (e.g., "Here's the chunk ID, its size, and the actual data.")
- How should the information be structured? (e.g., "Always send the request type first, then the size of the following data.")
- How do we know the message arrived correctly? (e.g., "Let's check the data's 'fingerprint'!")
The cdfs-ton protocol defines how messages are built and exchanged. Every message sent between components generally follows a two-part structure: a Header and a Payload.
Every single message in cdfs-ton starts with a small, fixed-size network header. Think of this as the envelope of your message. It tells the recipient two crucial things right away:
-
op_code(Operation Code): What kind of action or request is inside this message? (e.g., "This is a 'register file' message," or "This is a 'store chunk' message.") -
payload_size: How big is the actual data (the "letter" inside the envelope) that follows this header? This is important because message contents can vary in size.
Here's what this header looks like in common/protocol.h:
// File: common/protocol.h
typedef struct {
uint32_t op_code; // What type of operation is this?
uint32_t payload_size; // How many bytes of data follow?
} net_header_t;-
uint32_tmeans an "unsigned 32-bit integer," which is just a number that can store values up to about 4 billion. It's used here to represent different operation codes and sizes.
The op_code tells the receiving component what to do. It's like a list of predefined verbs. When a Metadata Server or Storage Node receives a message, it first reads the op_code from the net_header_t to know how to interpret the rest of the message.
Here are some examples of op_code_t from common/protocol.h:
// File: common/protocol.h
typedef enum {
OP_REGISTER_FILE = 1, // Client -> MD: I've uploaded a file, here's its metadata
OP_GET_METADATA = 2, // Client -> MD: Give me info about this file
OP_STORE_CHUNK = 3, // Client -> SN: Store this chunk for me
OP_LOAD_CHUNK = 4, // Client -> SN: Give me this chunk
OP_HEARTBEAT = 5, // SN -> MD: I'm alive!
// ... many more operations ...
OP_ERROR = 99 // Something went wrong!
} op_code_t;- An
enum(short for enumeration) is a way to assign names to integer values. So,OP_REGISTER_FILEis just a friendly name for the number1,OP_GET_METADATAfor2, and so on. This makes code much easier to read and understand.
After the net_header_t, the actual message content, called the payload, is sent. The structure of this payload depends entirely on the op_code. For example:
- If the
op_codeisOP_STORE_CHUNK, the payload will contain the chunk ID, its size, a checksum, and then the raw chunk data itself. - If the
op_codeisOP_REGISTER_FILE, the payload will contain the filename, the number of chunks, and then information about each chunk (like its ID and where it's stored).
Here's an example of a payload structure for storing a chunk, found in common/protocol.h:
// File: common/protocol.h
// OP_STORE_CHUNK (Client -> Storage Node)
typedef struct {
int32_t chunk_id; // Unique ID of the chunk
size_t size; // How big is this chunk's data?
uint32_t checksum; // A 'fingerprint' to check data integrity
// Followed by exactly `size` bytes of raw chunk data
} req_store_chunk_t;
// Example response from Storage Node after storing (SN -> Client)
typedef struct {
int32_t status; // 0 for success, -1 for error
} resp_store_chunk_t;- Notice the comment: "Followed by exactly
sizebytes of raw chunk data." This means thepayload_sizein thenet_header_twould besizeof(req_store_chunk_t) + size(wheresizeis the chunk's data size). The receiver knows to expect the struct, then the additional raw data.
When sending data over a network, it's not always guaranteed that all bytes will be sent or received in one go. Sometimes, the network might send only part of a message, or receive only part of it. This is unreliable!
To solve this, cdfs-ton uses special helper functions: send_exact and recv_exact. These functions (from common/serialization.h and common/serialization.c) ensure that all the bytes you intend to send actually get sent, and all the bytes you expect to receive actually arrive.
Imagine sending a long letter by mail. send_exact is like a postal worker who makes sure every page of your letter is in the envelope before it leaves. recv_exact is like a diligent recipient who won't say "I got the letter!" until every single page has arrived.
Let's trace how your cdfs_client uses this protocol to tell a Storage Node to store a chunk. This happens after the client has contacted the Metadata Server and found out which storage nodes are available (as we saw in Chapter 1: Client Application Interface).
-
Client Prepares: The client has a chunk of data, its
chunk_id, itssize, and has calculated itschecksum. -
Client Builds
req_store_chunk_t: It fills in areq_store_chunk_tstructure with this information. -
Client Builds
net_header_t: It creates anet_header_twithOP_STORE_CHUNKas theop_codeand calculatessizeof(req_store_chunk_t) + chunk_data_sizefor thepayload_size. -
Client Sends:
- It first uses
send_exactto send thenet_header_t. - Then, it uses
send_exactto send thereq_store_chunk_t. - Finally, it uses
send_exactto send the raw chunk data.
- It first uses
-
Storage Node Receives:
- The Storage Node first uses
recv_exactto read the incomingnet_header_t. - It sees
op_code = OP_STORE_CHUNKand knows to expect areq_store_chunk_tand then raw data. - It uses
recv_exactto read thereq_store_chunk_t. - It then uses
recv_exact(for thepayload_sizeminussizeof(req_store_chunk_t)) to read the actual chunk data.
- The Storage Node first uses
-
Storage Node Processes: It stores the chunk data on its disk, verifies the
checksum, and prepares a response. -
Storage Node Responds: It sends back a
net_header_twith anop_codeindicating success or failure, followed by aresp_store_chunk_tpayload.
Here's a simplified sequence diagram showing this interaction:
sequenceDiagram
participant C as CDFS Client
participant SN as Storage Node
C->>SN: 1. Send net_header_t (OP_STORE_CHUNK, total_payload_size)
C->>SN: 2. Send req_store_chunk_t (chunk_id, size, checksum)
C->>SN: 3. Send raw chunk data (size bytes)
SN->>SN: 4. Storage Node receives and processes
SN->>SN: 5. Saves chunk to disk, verifies checksum
SN-->>C: 6. Send net_header_t (OP_STORE_CHUNK, resp_payload_size)
SN-->>C: 7. Send resp_store_chunk_t (status: 0 for success)
Let's look at the actual C code that makes this happen.
This header file is like the dictionary for cdfs-ton. It defines all the operation codes (op_code_t) and the structures (net_header_t, req_store_chunk_t, etc.) that make up the messages.
// File: common/protocol.h (snippet)
// Operation codes
typedef enum {
OP_REGISTER_FILE = 1,
OP_GET_METADATA = 2,
OP_STORE_CHUNK = 3,
OP_LOAD_CHUNK = 4,
// ...
OP_ERROR = 99
} op_code_t;
// Common request/response header
typedef struct {
uint32_t op_code;
uint32_t payload_size;
} net_header_t;
// OP_STORE_CHUNK request
typedef struct {
int32_t chunk_id;
size_t size;
uint32_t checksum;
// raw chunk data follows this struct
} req_store_chunk_t;
// OP_STORE_CHUNK response
typedef struct {
int32_t status; // 0 for success, -1 for error
} resp_store_chunk_t;This file is included by all cdfs-ton components, ensuring they all "speak" the same dialect and understand the format of every message.
This file contains the crucial send_exact and recv_exact functions, which are used everywhere a message is sent or received.
// File: common/serialization.c (simplified send_exact)
int32_t send_exact(int32_t sockfd, const void *buf, size_t len) {
size_t total_sent = 0;
const uint8_t *p = (const uint8_t *)buf; // Pointer to current position
while (total_sent < len) { // Keep sending until all bytes are sent
ssize_t s = send(sockfd, p + total_sent, len - total_sent, 0);
if (s <= 0) return -1; // Error or connection closed
total_sent += s; // Update count of bytes sent
}
return 0; // All bytes sent successfully
}-
sockfd: This is the "socket file descriptor," an ID number for an open network connection (like a phone line). -
buf: This is a pointer to the data you want to send. -
len: This is the total number of bytes you want to send. - The
whileloop is the key here. It keeps callingsend(which might only send some bytes) untiltotal_sentequalslen.
The recv_exact function works similarly but for receiving:
// File: common/serialization.c (simplified recv_exact)
int32_t recv_exact(int32_t sockfd, void *buf, size_t len) {
size_t total_recv = 0;
uint8_t *p = (uint8_t *)buf; // Pointer to current position
while (total_recv < len) { // Keep receiving until all bytes are received
ssize_t r = recv(sockfd, p + total_recv, len - total_recv, 0);
if (r <= 0) return -1; // Error or connection closed
total_recv += r; // Update count of bytes received
}
return 0; // All bytes received successfully
}- This function ensures that the program waits and keeps trying to receive until the full
lenbytes have arrived, preventing incomplete messages from being processed.
The protocol also includes checksum values (also defined and implemented in common/serialization.h and .c). A checksum is a small value calculated from a larger block of data. If even one byte in the data changes, the checksum will almost certainly change.
- When the client sends a chunk to a Storage Node, it calculates the chunk's checksum and sends it along.
- The Storage Node, upon receiving the chunk, calculates its own checksum from the received data.
- If the two checksums match, the Storage Node knows the data arrived without corruption. If they don't, something went wrong during transmission, and the Storage Node will reject the chunk (as we saw in Chapter 3: Chunk Storage & Management).
This is a vital part of the protocol, ensuring that the precious data you store in cdfs-ton remains accurate and uncorrupted.
The Network Communication Protocol is the invisible but essential glue that holds cdfs-ton together. It provides the standardized "language" and rules for all components (clients, Metadata Server, and Storage Nodes) to talk to each other reliably. By using a clear message structure with headers, operation codes, precise payload definitions, and helper functions like send_exact and recv_exact, cdfs-ton ensures that every request is understood, every piece of data is completely transferred, and every byte arrives correctly and in order. Without this common language, our distributed file system wouldn't be able to function as a cohesive unit.
Next, we'll explore how cdfs-ton deals with problems and ensures your data is always available, even if parts of the system fail, by diving into Fault Tolerance & Replication Monitoring.