#c #sockets #tcp #network-programming
#c #сокеты #tcp #сетевое программирование
Вопрос:
Я пишу протокол TCP-клиента и сервера для школьного проекта. Клиент отправляет сообщение «GET r n», и сервер должен передать » OKrn», размер файла и сам файл, если он существует в каталоге сервера. Я заблокирован при передаче файлов
Я пытался решить это небольшими шагами за раз. Я настроил соединение, отправил запрос от клиента и получил сообщение «OK» от сервера.
Теперь я открыл файл на сервере и попытался отправить его клиенту по 128 байт за раз. Чтение файла работает и, по-видимому, также отправка буферов, но клиент ничего не получает…
Вот мой сервер.c
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "../errlib.h"
#include "../sockwrap.h"
#define BUFLEN 128 /* Buffer length */
#define TIMEOUT 15 /* TIMEOUT */
/* FUNCTION PROTOTYPES */
void service(int s);
/* GLOBAL VARIABLES */
char *prog_name;
int main(int argc, char *argv[])
{
int conn_request_skt; /* passive socket */
uint16_t lport_n, lport_h; /* port used by server (net/host ord.) */
int bklog = 2; /* listen backlog */
int s; /* connected socket */
fd_set cset; // waiting for connection
struct timeval tval; // timeout
size_t n;
socklen_t addrlen;
struct sockaddr_in saddr, caddr; /* server and client addresses */
prog_name = argv[0];
if (argc != 2) {
printf("Usage: %s <port number>n", prog_name);
exit(1);
}
/* get server port number */
if (sscanf(argv[1], "%" SCNu16, amp;lport_h)!=1)
err_sys("Invalid port number");
lport_n = htons(lport_h);
/* create the socket */
printf("creating socket...n");
s = Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
printf("done, socket number %un",s);
/* bind the socket to any local IP address */
bzero(amp;saddr, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = lport_n;
saddr.sin_addr.s_addr = INADDR_ANY;
showAddr("Binding to address", amp;saddr);
Bind(s, (struct sockaddr *) amp;saddr, sizeof(saddr));
printf("done.n");
/* listen */
printf ("Listening at socket %d with backlog = %d n",s,bklog);
Listen(s, bklog);
printf("done.n");
conn_request_skt = s;
/* main server loop */
for ( ; ; )
{
printf("waiting for connection...n");
/* accept next connection */
FD_ZERO(amp;cset);
FD_SET(conn_request_skt, amp;cset);
tval.tv_sec = TIMEOUT;
tval.tv_usec = 0;
n = Select(FD_SETSIZE, amp;cset, NULL, NULL, amp;tval);
if ( n > 0 ){
addrlen = sizeof(struct sockaddr_in);
s = Accept(conn_request_skt, (struct sockaddr *) amp;caddr, amp;addrlen);
showAddr("Accepted connection from", amp;caddr);
printf("new socket: %un",s);
/* serve the client on socket s */
service(s);
} else {
printf("No connection request after %d secondsn",TIMEOUT);
}
}
}
void service(int s) {
char buf[BUFLEN]; /* reception buffer */
char filename[BUFLEN];
int n;
long filesize;
uint32_t fsize;
FILE *fp;
for ( ; ; )
{
n = recv(s, buf, BUFLEN, 0);
if (n < 0) {
printf("Read errorn");
close(s);
printf("Socket %d closedn", s);
break;
} else if (n == 0) {
printf("Connection closed by party on socket %dn",s);
close(s);
break;
} else {
printf("Received request from socket d :n", s);
sscanf(buf, "GET %srn", filename);
strcpy(buf, " OKrn");
printf("%s",buf);
if(writen(s, buf, strlen(buf)) != strlen(buf))
printf("Write error while sending OKn");
// open file
fp = fopen(filename, "r");
if( fp == NULL){
//TODO close connection
}
// calculating dim of file
fseek(fp, 0L, SEEK_END);
filesize = ftell(fp);
rewind(fp); // go back at beginning of file
fsize = htonl(filesize); // size file in network byte order
// sending file size
if(writen(s, amp;fsize, 4) != 4)
printf("Write error while sending file sizen");
while(fread(buf, 1, BUFLEN - 1, fp) == BUFLEN - 1){
printf("%s", buf);
if(writen(s, buf, strlen(buf)) != strlen(buf))
printf("Write error while bufn");
}
printf("%s", buf);
printf("I am heren");
}
}
}
Пока здесь мой клиент.c
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "../errlib.h"
#include "../sockwrap.h"
#define BUFLEN 128 /* BUFFER LENGTH */
#define TIMEOUT 15 /* TIMEOUT*/
/* GLOBAL VARIABLES */
char *prog_name;
int main(int argc, char *argv[])
{
char request[BUFLEN]; /* request buffer */
char rbuf[BUFLEN]; /* reception buffer */
uint32_t taddr_n; /* server IP addr. (net/host ord) */
uint16_t tport_n, tport_h; /* server port number (net/host ord) */
int s, len;
int resu<
struct sockaddr_in saddr; /* server address structure */
struct in_addr sIPaddr; /* server IP addr. structure */
fd_set cset; // variables for timeout
struct timeval tval;
size_t n;
prog_name = argv[0];
if(argc < 4)
err_sys("Wrong number of parameters!n");
// read address from first argument
taddr_n = inet_addr(argv[1]);
if (taddr_n == INADDR_NONE)
err_sys("Invalid address");
// read port number from second argument
if (sscanf(argv[2], "%" SCNu16, amp;tport_h)!=1)
err_sys("Invalid port number");
tport_n = htons(tport_h);
/* create the socket */
printf("Creating socketn");
s = Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
printf("done. Socket fd number: %dn",s);
/* prepare address structure */
bzero(amp;saddr, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = tport_n;
saddr.sin_addr = sIPaddr;
printf("trying to connect to the server...n");
/* connect */
showAddr("Connecting to target address", amp;saddr);
Connect(s, (struct sockaddr *) amp;saddr, sizeof(saddr));
printf("done.n");
// loop to request files
for (int i = 3 ; i < argc ; i ){ // i = 4 because the first file is the fourth argument
// check if file name is too big
if(strlen(argv[i]) >= BUFLEN - 6)
err_sys("The file name is too big for the buffer request!n");
// create the string of bytes for the request
strcpy(request, "GET ");
strcat(request, argv[i]);
strcat(request, "rn");
len = strlen(request);
if(writen(s, request, len) != len){
printf("Write errorn");
break;
}
printf("waiting for response...n");
// receive file from server
n = recv(s, rbuf, BUFLEN, 0);
if (n < 0) {
printf("Read errorn");
close(s);
printf("Socket %d closedn", s);
break;
} else if (n == 0) {
printf("Connection closed by party on socket %dn",s);
close(s);
break;
} else {
printf("Received reply from servern");
uint32_t fsize;
printf("%s",rbuf);
if(strcmp(rbuf, " OKrn") == 0){
n = recv(s, amp;fsize, 4, 0);
if (n < 0) {
printf("Read errorn");
close(s);
printf("Socket %d closedn", s);
break;
} else if (n == 0) {
printf("Connection closed by party on socket %dn",s);
close(s);
break;
} else {
// received file dimension
fsize = ntohl(fsize);
}
while(fsize > 0){
printf("I am here1nn");
// receive file
n = recv(s, rbuf, BUFLEN-1, 0);
if (n < 0) {
printf("Read errorn");
close(s);
printf("Socket %d closedn", s);
break;
} else if (n == 0) {
printf("Connection closed by party on socket %dn",s);
close(s);
break;
} else {
printf("I am here");
fsize -= n;
}
}
}
}
}
printf("===========================================================n");
close(s);
exit(0);
}
Recv в клиенте, где я должен получать файл, просто блокируется, ничего не получая. Я не понимаю, чего мне не хватает…
Комментарии:
1. Насколько велик файл, который вы пытаетесь отправить?
2. Также можете ли вы опубликовать вывод после запуска кода?
3. Файл имеет размер 79 байт, и вывод с сервера: « создание сокета … выполнено, привязка сокета номер 3 к адресу 0.0.0.0!2048 выполнено. Прослушивание в сокете 3 с невыполненной работой = 2 выполнено. ожидание соединения… Принято соединение от 127.0.0.1!50646 новый сокет: 4 Получен запрос от сокета 004: OK привет всем, это пример текстового файла, чтобы посмотреть, работают ли клиент и сервер??? Я здесь «
4. Клиент: « Создание сокета завершено. Номер fd сокета: 3 пытается подключиться к серверу… Подключение к целевому адресу 0.0.0.0!2048 выполнено. ожидание ответа… Получен ответ от сервера ОК, я здесь 1. Соединение закрыто стороной на сокете 3 «
5.
while (fread(buf, 1, BUFLEN - 1, fp) == BUFLEN - 1)
недопустимо. Файл короче, чем ваш буфер, и даже если это не последнее чтение, почти наверняка будет. Итак, ничего не было написано. Вы должны использоватьread()
для файла; вы должны использовать положительное число, возвращаемоеread()
в качестве длины для записи, как на сервере, так и на клиенте; вы не должны предполагать, что конечные нули сstrlen()
; и вы не должны предполагать, чтоread()
заполняет буфер.
Ответ №1:
Проблема здесь общая: вы не соблюдаете границы сообщений.
В вашем клиенте вы выполняете recv
и проверяете, больше ли количество байтов, чем 0. Но тогда вы не выполняете дополнительную проверку длины. Затем вы выполняете a strcmp
для конкретной строки, которую вы ожидаете получить ( OKrn
). Но вы могли получить 3 байта ( OK
) или вы могли получить 10: ( OKrnXXXXX
) или более [в сторону: также, recv
не гарантирует, что ваша строка байтов заканчивается нулем]. Ничто не мешает ядру на обратной стороне от пакетной обработки преамбулы плюс последующие байты в один TCP-пакет. Аналогично, ничто не мешает локальной стороне объединять несколько пакетов TCP в один буфер.
Вы должны указать границы сообщений. Если вы ожидаете, что ваше следующее сообщение будет размером 5 байт, то вы должны получить ровно 5 байт (и повторить попытку, если вы получите меньше — будьте осторожны, чтобы проверить EOF также на случай, если другая сторона прервала работу раньше). Или, в качестве альтернативы, вставьте слой буферизации перед вашей логикой приема, чтобы он получал до некоторого большого количества, возвращал вам нужное количество байтов, а затем сохранял все, что есть в избытке, для последующего вызова «receive».
Чтобы повторить это по-другому: ваш сервер отправляет OKrn
, затем он отправляет длину в четыре байта, затем он начинает отправлять файл. Но это означает, что ваш первый recv
на стороне клиента мог получать преамбулу, плюс длину, плюс первые N байтов файла за один системный вызов.
TCP не соблюдает, не предоставляет и не применяет границы сообщений.
Комментарии:
1. Или, если выразиться как можно проще, вы реализуете протокол обмена сообщениями, но никогда не писали функцию для получения сообщения.
2. Большое спасибо, проблема была там