Насколько я понимаю, данные через TCP передаются как сплошной поток, до тех пор пока соединение не будет разорвано. Если посмотреть на структуру сегмента TCP,там даже нет информации о длинне данных (в отличии от UDP, к примеру). Таким образом, если мы читаем что-то с TCP сокета в буфер, чтение будет происходить до тех пор, пока соединение не закроется, либо буффер не переполниться.
Однако если посмотреть на реальный код, это не так - recv() на сервере читает ровно столько байт, сколько отправлено с клиента с помощью send()
Каким образом recv() понимает, что все данные получены, и управление нужно вернуть в вызывающий код?
Полный и минимальный пример на голых сокетах:
Сервер:
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <memory>
#include <arpa/inet.h>
const int BUFFER_SIZE = 1024;
const int PORT = 12345;
int main() {
//create server socket
int socketFd = ::socket(AF_INET, SOCK_STREAM, 0);
if (socketFd < 0) {
return -1;
}
int opt_val = 1;
setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof opt_val);
//bind to address
sockaddr_in socketAddress;
socketAddress.sin_family = AF_INET;
socketAddress.sin_port = htons(PORT);
socketAddress.sin_addr.s_addr = htons(INADDR_ANY);
int rc = ::bind(socketFd,
reinterpret_cast<sockaddr*>(&socketAddress),
sizeof(socketAddress));
if (rc < 0) {
return -2;
}
//listen
rc = ::listen(socketFd, SOMAXCONN);
if (rc < 0) {
return -3;
}
//accept new connection
sockaddr_in socketAdress;
unsigned int sizeOfSocketAdress = sizeof(socketAdress);
int clientSocket = ::accept(socketFd, (struct sockaddr *)&socketAdress, &sizeOfSocketAdress);
if (clientSocket < 0) {
return -4;
}
//receive
char buffer[BUFFER_SIZE];
int receivedBytes = ::recv(clientSocket, buffer, BUFFER_SIZE, MSG_NOSIGNAL);
std::cout << "Received " << receivedBytes << " bytes : " << buffer << std::endl; // Прочитано 5 байт "hello", хотя буфер не заполнен и соединение не прервано
return 0;
}
Клиент:
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main() {
struct sockaddr_in sa;
int res;
int socketFd;
socketFd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketFd == -1) {
perror("cannot create socket");
exit(EXIT_FAILURE);
}
memset(&sa, 0, sizeof sa);
sa.sin_family = AF_INET;
sa.sin_port = htons(12345);
res = inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr);
if (connect(socketFd, (struct sockaddr *)&sa, sizeof sa) == -1) {
perror("connect failed");
close(socketFd);
exit(EXIT_FAILURE);
}
auto buf = "hello";
auto len = 5;
int sentBytes = ::send(socketFd, buf, len, 0);
std::cout << "sent " << sentBytes << "bytes: " << buf << std::endl;
std::string tmp;
std::getline(std::cin, tmp); //приостановка выполнения, соединение все еще не закрыто
return EXIT_SUCCESS;
}
Нет, recv
читает количество байт не больше указанного размера буфера, совершенно не волнуясь по поводу получения всех данных. В лучшем случае с клиента приходит пакет с флагом PSH, намекающий, что имеет смысл отдать данные читателю сейчас.
Каким образом recv() понимает, что все данные получены, и управление нужно вернуть в вызывающий код?
Никаким. Это забота приложения. С точки зрения приложения, TCP канал - это обычный файл. Как приложение узнаёт, что некая порция данных принята полностью? Существует только два способа:
Оба эти пункта 100% применимы к ТСР соединениям. Например, в HTTP протоколе применяется вариант пункта 1: каждое сообщение НАЧИНАЕТСЯ специальной строкой заголовка и ЗАВЕРШАЕТСЯ пустой строкой. Данные передаются несколько иным способом, но идея та же.
Поведение TCP непредсказуемо :) нет никакой 100% гарантии что 'пакет пользовательских данных' доставлен частично или полностью. Это вам решать в своей программе.
Имеет смысл иногда проводить доп проверки на наличие данных, покажу на примере функции вычитывающей все данные для асинхронного сокета
:
// функция работает как 'умный' TCP.socket.data.flush
void tcp_recv_empty(int sock, ssize_t sz)
{
ssize_t rsz;
unsigned char rbuf[65536]; // 1500 более бережно :)
# if !defined(MSG_WAITALL)
# define MSG_WAITALL 0x40
# endif
if (!sz)
{
if ((ioctl(sock, FIONREAD, &sz) != 0) || (!sz)) { return; }
}
while (sz > 0)
{
errno = 0;
switch ((rsz = recv(sock, rbuf, sizeof(rbuf), MSG_WAITALL)))
{
case (ssize_t)-1:
{
# if defined(EAGAIN)
if ((errno == EAGAIN) || (errno == EINTR))
# elif defined(EWOULDBLOCK)
if ((errno == EWOULDBLOCK) || (errno == EINTR))
# endif
{
continue;
}
return;
}
case 0:
{
return;
}
default:
{
sz -= rsz;
}
}
}
}
Всегда надо полагаться на код возврата recv*()
и состояние получаемое например такой функцией:
int net_socket_iserror(int sock)
{
int se;
socklen_t sl = sizeof(int);
# if defined(SO_ERROR)
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &se, &sl) < 0)
{
return errno;
}
return se;
# else
return 0;
# endif
}
Необходимо вызвать метод объекта ob2 из метода exe объекта ob1, используя ссылку, находящуюся в векторе объекта ob1
Передо мной стоит задача: Мне необходимо максимально быстро найти количество целых квадратных корней из нескольких триллионов целых чисел,...