2015-03-04 3 views
0

Мне нужно написать небольшое клиент-серверное приложение в C на Linux.безопасно ли вызывать запись последовательно при использовании sock_stream?

Я построил короткий пример, чтобы изучить немного больше, так как я новичок в сетевом программировании.

Я в основном пытаюсь отправить массив double, динамически выделяемый клиентом.

я нашел следующий способ сделать это (на стороне клиента):

write(sd,&datas.size,sizeof(int)); /* send size */ 
write(sd,datas.yi,datas.size*sizeof(double));/* send array */ 

и на стороне сервера:

read(sd_cli,&datas.size,sizeof(int)); /* receive size */ 
datas.yi=(double *)malloc(datas.size*sizeof(double)); 
read(sd_cli,datas.yi,datas.size*sizeof(double)); /* receiving datas */ 

На первый взгляд мой код, кажется, работает хорошо.

Но так как звонки write не блокируются, я спрашиваю себя, может ли последовательность read получить, например массив double до его размера?

Есть ли какие-либо гарантии, что это никогда не произойдет?

Спасибо.

+1

Это нормально, но будьте осторожны с энтузиазмом. И [Не бросать 'malloc()'] (http://stackoverflow.com/a/605858/1983495) –

+1

TCP ориентирован на поток, вещи никогда не выходят из строя. –

+4

TCP гарантирует правильный заказ при доставке.Однако не забудьте проверить возвращаемое значение 'read()' не только на ошибку, но и на том, сколько байтов на самом деле там, где они читаются, поскольку 'read()' может читать меньше от сокета, как было сказано. Это возможно редко происходит для небольших фрагментов данных, но просто попробуйте отправить несколько миллионов удвоений, и вы видите, о чем я говорю. Внимательно прочитайте документацию 'read()'. – alk

ответ

1

Розетки типа SOCK_STREAM обеспечивают надежную передачу данных в порядке, хотя детали зависят от характера лежащего в основе транспорта. Читатель получит все успешно записанные данные (если он действительно хочет их прочитать), байт по-байтам в том порядке, в котором они были написаны, но не обязательно в одинаковых размерах.

Блокировка vs. неблокирование не имеет к этому никакого отношения, хотя я фактически не вижу, что заставляет вас говорить, что ваши записи не блокируются. Возможно, вы замечаете тот факт, что ни write(), ни read() обещают передать полное количество байтов, запрошенных на любой данный вызов. Это само по себе не гарантирует защиты от блокировки, но вам абсолютно необходимо учитывать его правильно, особенно с сокетами, и даже тем более, если вы действительно поставили один или оба конца сокета в неблокирующий режим. Первоначальная версия вашего вопроса, казалось, утверждала, что вы его учитываете.

В любом случае, запрещая какую-либо ошибку ядра, ваш клиент никогда не будет считывать размер массива после любой части массива и не получает байтов в другом относительном порядке, чем они были написаны.

Чтобы быть совершенно ясными, здесь представлены разумные реализации для чтения и записи массивов переменной длины double через сокет потока. Они предполагают, что отправитель и получатель имеют идентичные представления типа double, что, безусловно, будет иметь место для сокетов UNIX-домена. Они не совсем тривиальны, хотя вспомогательные функции, содержащие около половины коды пригодны для повторного использования:

/****** 
* helper functions 
*/ 

/* 
* Returns the number of bytes written, which may be zero, or a number 
* less than zero on failure. 
*/ 
ssize_t write_fully(int fd, const void *buf, size_t count) { 
    const unsigned char *next = buf; 
    size_t remaining = count; 

    while (remaining) { 
     ssize_t n_written = write(fd, next, remaining); 

     if (n_written < 0) { 
      /* error */ 
      return n_written; 
     } else { 
      assert(n_written <= remaining); 
      next += n_written; 
      remaining -= n_written; 
     } 
    } 

    /* all bytes successfully written */ 
    return count; 
} 

/* 
* Returns the number of bytes read on success, or a number less 
* than zero on error. It is accounted "success" if the end of the stream 
* is reached before the requested number of bytes is read; that case 
* can be distinguished by the return value, but no recovery is 
* possible. 
*/ 
ssize_t read_fully(int fd, void *buf, size_t count) { 
    unsigned char *next = buf; 
    size_t remaining = count; 

    while (remaining) { 
     ssize_t n_read = read(fd, next, remaining); 

     if (n_read < 0) { 
      /* error */ 
      return n_read; 
     } else if (n_read) { 
      assert(n_read <= remaining); 
      next += n_read; 
      remaining -= n_read; 
     } else { 
      /* premature end of file */ 
      return count - remaining; 
     } 
    } 

    /* all bytes successfully read */ 
    return count; 
} 

/****** 
* Array-transfer functions 
*/ 

/* returns 0 on success, else nonzero */ 
int write_double_array(int fd, unsigned n, double d[n]) { 
    ssize_t bytes_written; 

    bytes_written = write_fully(fd, &n, sizeof(n)); 
    if (bytes_written < 0) return bytes_written; 

    bytes_written = write_fully(fd, d, n * sizeof(double)); 
    return (bytes_written < 0) ? bytes_written : 0; 
} 

/* 
* returns 0 on success, else nonzero. 
* On success, the caller takes responsibility for freeing the 
* dynamically-allocated result array. 
*/ 
int read_double_array(int fd, unsigned *n, double **d) { 
    unsigned temp_n; 
    ssize_t bytes_read = read_fully(fd, &temp_n, sizeof(temp_n)); 

    if (bytes_read < 0) { 
     return -1; 
    } else if (bytes_read != sizeof(temp_n)) { 
     return 1; 
    } else if (temp_n) { 
     size_t n_bytes = temp_n * sizeof(double); 
     double *temp = malloc(n_bytes); 

     if (!temp) return -1; /* allocation failure */ 
     if (read_fully(fd, temp, n_bytes) < n_bytes) { 
      free(temp); 
      return -1; 
     } 

     /* success */ 
     *d = temp; 
    } 

    *n = temp_n; 
    return 0; 
} 

Вы могли бы реализовать протокол массива передачи другим, но этот подход посылает данные в той же форме, что вам претензии не принимаются. Вы не можете смело сделать это более просто, чем это.

+0

Мой комментарий относится к любому сокету потока. Однако я наблюдаю, что, если вы специально хотите сокет потока со вкусом TCP/IP, то он должен быть настроен с доменом 'AF_INET' или, возможно,' AF_INET6', * not * 'PF_UNIX'. –

+0

Да, вы правы, я не использую TCP, вопрос был фактически связан с 'SOCK_STREAM', так как я настроил свой сокет с помощью' socket (PF_UNIX, SOCK_STREAM, 0) '. Я только что исправил этот пост. Благодарю. – IbliSS

+0

Стыковые сокеты надежны в отношении доставки по заказу, но * только *, если API используется правильно, что не является чем-то настоящим кодом, который игнорирует значения write() и read() return. В настоящем коде запись теоретического размера массива теоретически может быть неудачной, а затем запись данных будет успешной, если к тому времени будет доступно буферное пространство. Или наоборот, от чтения слишком рано. ** Сокет надежный, но неблокирующие вызовы не являются ** –

Смежные вопросы