Как правильно читать с TCP-сокета в C / C ++?

Я не согласен с «невозможным», обозначенным как правильный ответ. Если кто-то все еще ищет возможность, вот работа, которая работала для меня. Я использую MVC5. Идея заключается в использовании переменной сеанса. Я получил эту идею из ASP.Net Form .

My Model / ViewModel (только соответствующие свойства):

public partial class emp_leaves
    {
        public string fileNameOrig { get; set; }
        public byte[] fileContent { get; set; }

        public HttpPostedFileBase uploadFile { get; set; }
    }

В моем контроллере (HttpPost) : // Check

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(emp_leaves emp_leaves)
{
    if (emp_leaves.uploadFile != null && emp_leaves.uploadFile.ContentLength>0 && !string.IsNullOrEmpty(emp_leaves.uploadFile.FileName))
    {
        emp_leaves.fileNameOrig = Path.GetFileName(emp_leaves.uploadFile.FileName);
        emp_leaves.fileContent = new byte[emp_leaves.uploadFile.ContentLength];
        emp_leaves.uploadFile.InputStream.Read(emp_leaves.fileContent, 0, emp_leaves.uploadFile.ContentLength);
        Session["emp_leaves.uploadFile"] = emp_leaves.uploadFile; //saving the file in session variable here
    }
    else if (Session["emp_leaves.uploadFile"] != null)
    {//if re-submitting after a failed validation you will reach here.
        emp_leaves.uploadFile = (HttpPostedFileBase)Session["emp_leaves.uploadFile"];
        if (emp_leaves.uploadFile != null && emp_leaves.uploadFile.ContentLength>0 && !string.IsNullOrEmpty(emp_leaves.uploadFile.FileName))
        {
            emp_leaves.fileNameOrig = Path.GetFileName(emp_leaves.uploadFile.FileName);
            emp_leaves.uploadFile.InputStream.Position = 0;
            emp_leaves.fileContent = new byte[emp_leaves.uploadFile.ContentLength];
            emp_leaves.uploadFile.InputStream.Read(emp_leaves.fileContent, 0, emp_leaves.uploadFile.ContentLength);    
        }
    }
//code to save follows here...
}

Наконец, в моем представлении редактирования: здесь я условно показываю управление загрузкой файла.

< script type = "text/javascript" >
  $("#removefile").on("click", function(e) {
    if (!confirm('Delete File?')) {
      e.preventDefault();
      return false;
    }
    $('#fileNameOrig').val('');
    //toggle visibility for concerned div
    $('#downloadlrfdiv').hide();
    $('#uploadlrfdiv').show();
    return false;
  }); <
/script>

@model PPMSWEB.Models.emp_leaves @{ HttpPostedFileBase uploadFileSession = Session["emp_leaves.uploadFile"] == null ? null : (HttpPostedFileBase)Session["emp_leaves.uploadFile"]; } @using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data"
})) { @Html.AntiForgeryToken()
@*irrelevant content removed*@
@Model.fileNameOrig @if (isEditable && !Model.readonlyMode) { @Html.Raw(" "); }
@Html.TextBoxFor(model => model.uploadFile, new { @type = "file", @class = "btn btn-default", @title = "Upload file (max 300 KB)" }) @Html.ValidationMessageFor(x => x.uploadFile)
}

23
задан Nick Bolton 22 March 2009 в 21:24
поделиться

7 ответов

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

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

// This assumes buffer is at least x bytes long,
// and that the socket is blocking.
void ReadXBytes(int socket, unsigned int x, void* buffer)
{
    int bytesRead = 0;
    int result;
    while (bytesRead < x)
    {
        result = read(socket, buffer + bytesRead, x - bytesRead);
        if (result < 1 )
        {
            // Throw your error.
        }

        bytesRead += result;
    }
}

Затем в коде

unsigned int length = 0;
char* buffer = 0;
// we assume that sizeof(length) will return 4 here.
ReadXBytes(socketFileDescriptor, sizeof(length), (void*)(&length));
buffer = new char[length];
ReadXBytes(socketFileDescriptor, length, (void*)buffer);

// Then process the data as needed.

delete [] buffer;

делается несколько предположений:

  • интервалы одинакового размера для отправителя и получателя.
  • Инициатива одинакова как для отправителя, так и для получателя.
  • У вас есть контроль над протоколом с обеих сторон
  • Когда вы отправляете сообщение, вы можете рассчитать длину заранее.

Поскольку принято хотеть явно знать размер целого числа, которое вы посылаете по сети, определите их в заголовочном файле и используйте их явно, например:

// These typedefs will vary across different platforms
// such as linux, win32, OS/X etc, but the idea
// is that a Int8 is always 8 bits, and a UInt32 is always
// 32 bits regardless of the platform you are on.
// These vary from compiler to compiler, so you have to 
// look them up in the compiler documentation.
typedef char Int8;
typedef short int Int16;
typedef int Int32;

typedef unsigned char UInt8;
typedef unsigned short int UInt16;
typedef unsigned int UInt32;

Это будет измените вышеприведенное на:

UInt32 length = 0;
char* buffer = 0;

ReadXBytes(socketFileDescriptor, sizeof(length), (void*)(&length));
buffer = new char[length];
ReadXBytes(socketFileDescriptor, length, (void*)buffer);

// process

delete [] buffer;

Надеюсь, это поможет.

32
ответ дан Duncan Jones 22 March 2009 в 21:24
поделиться

Если вы фактически создаете буфер в соответствии с предложением dirks, то:

  int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE);

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

  int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE - 1 );
3
ответ дан 22 March 2009 в 21:24
поделиться

Это статья, на которую я всегда ссылаюсь при работе с сокетами.

МИР SELECT ()

Он покажет вам, как надежно использовать 'select ()', и содержит некоторые другие полезные ссылки внизу для получения дополнительной информации о сокетах.

1
ответ дан Arnold Spence 22 March 2009 в 21:24
поделиться

1) Другие (особенно в срочном порядке) отметили, что для буфера должно быть выделено некоторое пространство памяти. Для небольших значений N (скажем, N < = 4096) вы также можете выделить его в стеке:

#define BUFFER_SIZE 4096
char buffer[BUFFER_SIZE]

Это избавит вас от беспокойства по поводу того, что буфер delete[] должен быть исключением быть брошенным.

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

2) Для кода возврата -1 вы не должны просто немедленно возвращаться (немедленное создание исключения еще более схематично.) Существуют определенные нормальные условия, с которыми вам нужно справиться, если ваш код должен быть чем-то большим, чем короткое домашнее задание. Например, EAGAIN может быть возвращен в errno, если в настоящее время нет данных на неблокирующем сокете. Взгляните на справочную страницу для чтения (2).

3
ответ дан Dan Breslau 22 March 2009 в 21:24
поделиться

Где Вы выделяете память для Вашего buffer? Строка, где Вы вызываете bzero, вызывает неопределенное поведение, так как буфер не указывает ни на какой допустимый регион памяти.

char *buffer = new char[ BUFFER_SIZE ];
// do processing

// don't forget to release
delete[] buffer;
1
ответ дан dirkgently 22 March 2009 в 21:24
поделиться
  • 1
    похож на you' ре, просто делающее набор смайликов. позвольте мне иметь попытку в J:: (: [[[: (: o:pO_O& i-:) $$. Я делал программу? – Alex 4 September 2009 в 11:06

Несколько указателей:

необходимо обработать возвращаемое значение 0, который говорит Вам, что удаленный хост закрыл сокет.

Для неблокирования сокетов, также необходимо проверить ошибочное возвращаемое значение (-1) и удостовериться, что errno не является EINPROGRESS, который ожидается.

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

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

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

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

Что-то вроде этого могло бы работать на Вас:

fd_set read_set;
struct timeval timeout;

timeout.tv_sec = 60; // Time out after a minute
timeout.tv_usec = 0;

FD_ZERO(&read_set);
FD_SET(socketFileDescriptor, &read_set);

int r=select(socketFileDescriptor+1, &read_set, NULL, NULL, &timeout);

if( r<0 ) {
    // Handle the error
}

if( r==0 ) {
    // Timeout - handle that. You could try waiting again, close the socket...
}

if( r>0 ) {
    // The socket is ready for reading - call read() on it.
}

В зависимости от объема данных Вы ожидаете получать, способ, которым Вы неоднократно сканируете все сообщение для "конца"; маркер очень неэффективен. Это лучше сделано с конечным автоматом (состояния, являющиеся 'e '->'n '->'d '->'';) так, чтобы Вы только посмотрели на каждый входящий символ однажды.

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

9
ответ дан Ori Pessach 22 March 2009 в 21:24
поделиться
  • 1
    Папа @P: Я думаю it' s довольно справедливый; I' известные люди ve, которые запомнили достаточно 8 086 инструкций быть в состоянии считать и записать блок непосредственно в шестнадцатеричном числе. OTOH это только сделано возможным из-за DOS+COM; я держал пари, что то же самое в Linux/i386+ELF обязательно составит по крайней мере 300 КБ. Возможно, I' ll дают этому выстрел... – ephemient 5 September 2009 в 08:01

Только добавить к вещам из нескольких из сообщений выше:

чтение () - по крайней мере, в моей системе - возвращает ssize_t. Это похоже на size_t, кроме подписывается. В моей системе это - длинное, не интервал. Вы могли бы получить предупреждения компилятора при использовании интервала, в зависимости от системы, компилятора, и какие предупреждения Вы включили.

0
ответ дан 28 November 2019 в 20:47
поделиться