简体   繁体   中英

A smart way to send unknown length string in a struct over TCP? (C, Linux)

I'm implementing a simple server/client in C on Linux. Lets say I want, as a client, to ask the server if it has a specific file on it. I want to be able to send my query to the server inside a struct (I am familier with the problems of this action, but its for homework).

My problem is that when using a struct, I can't allocate memory for the file name in advanced. So a struct of the form:

struct{
int operation;
char *fileName;
long fileSize;
};

is not helping me, because even if I allocate the memory for fileName, sizeof(struct) stays the same (sizeof(char*)+sizeof(fileSize)) so I can't send it to the server.

Hope I'm being clear here... my question is how can I send a string, of an unknown-size to the server, using struct. (One option would be sending the fileSize in advanced, and then sending a char* of that size, but I don't want to).

Thanks!

Not sure I understood your problem. But here's a way to still use your struct and pack the filename at the end. What is important here is that you need to malloc enough bytes for the struct + the filename data that is hidden after the struct.

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

typedef struct
{
    // put any data you want here
    // ...
    size_t fileNameLength;
    char fileName[0]; // fake array, used to access extra bytes allocated after the struct
} FileInfo;

int main (int argc, char **argv)
{
    FileInfo *fi = NULL;

    // The fake array has size 0 !
    printf ("sizeof FileInfo is %d\n", sizeof (FileInfo));

    // let's suppose you've guessed your filename
    char filename[] = "homework.txt";
    size_t filename_length = strlen (filename); // this is the length with no NULL byte
    printf ("filename length (without NULL byte) is %d\n", filename_length);

    fi = malloc (sizeof(FileInfo) + filename_length);
    fi->fileNameLength = filename_length;
    memcpy (&fi->fileName, filename, filename_length);

    printf ("filename is %.*s\n", fi->fileNameLength, fi->fileName);

    // To send your struct + filename, just use the same address (fi),
    // but don't use as length sizeof (FileInfo), but this length:
    size_t struct_total_length = sizeof (FileInfo) + fi->fileNameLength;
    return 0;
}

You could split the send into 3 commands - operation (int), filename length (int), filename (0..n chars).

typedef struct Cmd{
    int operation;
    char *fileName;
    long fileSize; /* not required here but was in the question */
} Cmd;

Cmd cmd;
...
send(sockfd, &cmd.operation, sizeof(cmd.operation), 0);
size_t len = strlen(cmd.fileName);
send(sockfd, &len, sizeof(len), 0);
send(sockfd, fileName, len, 0);

If your homework requires that you use a single send command, allocate a buffer big enough for all three then copy into it.

You don't want to be sending a structure across the wire from one machine to another as well as potential endian issues you can also face the problem that a structure layout on one machine may not be the same as a structure layout on a different platform.

What you want is:

  Client side                                     Server side

convert struct to data stream ---(1)----> convert data stream to struct

Then you just define the contract (1) for the syntax of the data stream Defining that filenames are preceded by their length seems reasonable or alternatively sending the filename null terminated.

As codewarrior says hard-wiring the maximum filename is rarely a good idea.

I think that there is no better solution in your case than sending at first the length of file and then its name.

You can also use struct as below (without pre-sending size)

#define LONG_ENOUGH_FOR_FILE_NAME 128 //this defined to what you need
struct{
  int operation;
  char fileName[LONG_ENOUGH_FOR_FILE_NAME];
  long fileSize;
};

but this is quite vulnerable solution. You could have file name longer than

LONG_ENOUGH_FOR_FILE_NAME

buffer size and this will lead to some serious problems.

You need to "serialize" the data. Encode the length, write the length, encode the string (this is typically a no-op), write the string.

Sender:

uint32_t net_length = htonl(strlen(filename)+1);  // include trailing NUL
send(socket, &net_length, 4);
send(socket, filename, strlen(filename));

Receiver (pseudo-code):

char buffer[whatever]
until have at least 4 bytes in buffer {
  got = recv(socket, buffer+have, sizeof(buffer)-have);
  have += got
}
uint32_t length = ntohl(*(uint32_t*)buffer)
until have at least length+4 bytes in buffer {
  got = recv(socket, buffer+have, sizeof(buffer)-have);
  have += got
}
char* filename = &buffer[4]

This uses a fixed-size receive buffer which isn't ideal but it illustrates the idea. You'd be better to just read until you have the length (4 bytes) and then use malloc() to get a buffer big enough to hold the filename and read the rest directly into that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM