Только один вызов write () отправляет данные через сокет - PullRequest
2 голосов
/ 12 апреля 2011

Первый вопрос stackoverflow!Я искал ... обещаю.Я не нашел никаких ответов на мое затруднительное положение.У меня ... серьезно обостряющаяся проблема, если не сказать больше.Короче говоря, я разрабатываю инфраструктуру для игры, в которой мобильные приложения (приложение для Android и приложение для iOS) взаимодействуют с сервером с помощью сокетов для отправки данных в базу данных.Скрипт внутреннего сервера (который я называю BES или Back End Server) имеет длину в несколько тысяч строк кода.По сути, у него есть основной метод, который принимает входящие подключения к сокету и разветвляет их, а также метод, который считывает входные данные из сокета и определяет, что с ним делать.Большая часть кода заключается в методах, которые отправляют и получают данные из базы данных и отправляют их обратно в мобильные приложения.Все они работают нормально, за исключением нового метода, который я добавил.Этот метод получает большой объем данных из базы данных, кодирует их как объект JSON и отправляет обратно в мобильное приложение, которое также декодирует его из объекта JSON и выполняет то, что ему нужно.Моя проблема в том, что эти данные очень велики, и большую часть времени не проходят через сокет за одну запись данных.Таким образом, я добавил одну дополнительную запись данных в сокет, который сообщает приложению о размере объекта JSON, который он собирается получить.Однако после этой записи при следующей записи в мобильное приложение отправляются пустые данные.

Странно то, что при удалении первой записи, которая отправляет размер объекта JSON, происходит фактическая отправка JSON.Объект работает нормально.Это просто очень ненадежно, и я должен надеяться, что это отправит все в одном чтении.Чтобы добавить еще больше странности в ситуацию, когда я делаю размер данных, которые вторая запись отправляет огромное число, приложение iOS будет читать его правильно, но оно будет иметь данные в середине в противном случае пустого массива.

Что в мире происходит?Любое понимание очень ценится!Ниже приведен лишь базовый фрагмент моих двух команд записи на стороне сервера.

Имейте в виду, что ВСЕГДА в этом сценарии нормально работают операции чтения и записи, но это единственное место, где я выполняю 2 операции записи.спина к спине.

Серверный скрипт находится на сервере Ubuntu в нативном C с использованием сокетов Беркли, а iOS использует класс-оболочку AsyncSocket.

int n;
//outputMessage contains a string that tells the mobile app how long the next message
//(returnData) will be
n = write(sock, outputMessage, sizeof(outputMessage));
if(n < 0)
   //error handling is here
//returnData is a JSON encoded string (well, char[] to be exact, this is native-C)
n = write(sock, returnData, sizeof(returnData));
if(n < 0)
   //error handling is here

Мобильное приложение делает двачитает вызовы и получает outputMessage просто отлично, но returnData - это всегда просто набор пустых данных, если я не перезаписываю sizeof(returnData) на какое-то чрезвычайно большое число, и в этом случае iOS получит данные в серединев противном случае пустой объект данных (точнее, объект NSData).Также может быть важно отметить, что метод, который я использую на стороне iOS в своем классе AsyncSocket, считывает данные до той длины, которую они получают от первого вызова write.Поэтому, если я скажу ему прочитать, скажем, 10000 байт, он создаст объект NSData такого размера и будет использовать его в качестве буфера при чтении из сокета.

Любая помощь очень, С благодарностью приветствуется.Спасибо всем заранее!

Ответы [ 4 ]

6 голосов
/ 12 апреля 2011

Это просто очень ненадежно, и я должен надеяться, что он отправит все это в одном чтении.

Ключ к успешному программированию с использованием TCP заключается в том, что на уровне приложения не существует понятия «пакет» или «блок» данных TCP. Приложение видит только поток байтов без границ. Когда вы вызываете write() на отправляющей стороне с некоторыми данными, уровень TCP может выбрать нарезку и нарезку ваших данных любым удобным для них способом, включая объединение нескольких блоков вместе.

Вы можете написать 10 байтов два раза и прочитать 5, а затем 15 байтов. Или, может быть, ваш приемник будет видеть 20 байтов одновременно. То, что вы не можете сделать, это просто "надеяться", что некоторые отправленные вами куски байтов попадут на другой конец в тех же кусках.

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

1 голос
/ 14 апреля 2011

Спасибо за все отзывы!Я включил ответы каждого в решение.Я создал метод, который записывает в сокет структуру iovec, используя writev вместо write.Класс-обертка, который я использую на iOS, AsyncSocket (кстати, это просто фантастика ... посмотрите здесь -> AsyncSocket Google Code Repo ) прекрасно обрабатывает получение iovec, ипо-видимому, за кулисами, поскольку это не требует дополнительных усилий с моей стороны, чтобы правильно прочитать все данные.Класс AsyncSocket не вызывает мой метод делегата didReadData сейчас, пока он не получит все данные, указанные в структуре iovec.

Опять же, спасибо всем!Это очень помогло.Буквально в одночасье я получил ответы на вопрос, с которым я столкнулся уже неделю.Я с нетерпением жду, чтобы стать более вовлеченным в сообщество stackoverflow!

Пример кода для решения:

//returnData is the JSON encoded string I am returning
//sock is my predefined socket descriptor
struct iovec iov[1];
int iovcnt = 0;
iov[0].iov_base = returnData;
iov[0].iov_len = strlen(returnData);
iovcnt = sizeof(iov) / sizeof(struct iovec);
n = writev(sock, iov, iovcnt)
if(n < 0)
   //error handling here
while(n < iovcnt)
   //rebuild iovec struct with remaining data from returnData (from position n to the end of the string)
0 голосов
/ 12 апреля 2011

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

writev() также запишет все данные перед возвратом (если вы используете блокирующий ввод / вывод).

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

0 голосов
/ 12 апреля 2011

Вы действительно должны определить функцию write_complete, которая полностью записывает буфер в сокет. Проверьте возвращаемое значение write, оно также может быть положительным числом, но меньше размера буфера. В этом случае вам нужно write снова оставить оставшуюся часть буфера.

О, и использование sizeof также подвержено ошибкам. Поэтому в приведенной выше функции write_complete вы должны напечатать заданный размер и сравнить его с ожидаемым.

...