Skip to content

Commit 72345bd

Browse files
obiltschnigmatejk
authored andcommitted
Merge pull request #4863 from pocoproject/fix-sendFile
Fix StreamSocket::sendFile()
1 parent 0ec643a commit 72345bd

File tree

10 files changed

+525
-67
lines changed

10 files changed

+525
-67
lines changed

Net/CMakeLists.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,24 @@ POCO_HEADERS_AUTO(SRCS ${HDRS_G})
1010
POCO_SOURCES_AUTO_PLAT(SRCS WIN32 src/wepoll.c)
1111
POCO_HEADERS_AUTO(SRCS src/wepoll.h)
1212

13-
if (MSVC)
13+
if (MSVC OR MINGW)
1414
set(HAVE_SENDFILE ON)
1515
else()
1616
include(CheckIncludeFiles)
1717
include(CheckSymbolExists)
1818
check_include_files(sys/sendfile.h HAVE_SYS_SENDFILE_H)
1919
if(HAVE_SYS_SENDFILE_H)
2020
check_symbol_exists(sendfile sys/sendfile.h HAVE_SENDFILE)
21-
if (NOT DEFINED HAVE_SENDFILE)
22-
check_symbol_exists(sendfile64 sys/sendfile.h HAVE_SENDFILE)
21+
if (NOT HAVE_SENDFILE)
22+
check_symbol_exists(sendfile64 sys/sendfile.h HAVE_SENDFILE64)
2323
endif()
2424
else()
2525
# BSD version
2626
check_symbol_exists(sendfile "sys/types.h;sys/socket.h;sys/uio.h" HAVE_SENDFILE)
2727
endif()
2828
endif()
2929

30-
if (DEFINED HAVE_SENDFILE)
30+
if (HAVE_SENDFILE OR HAVE_SENDFILE64)
3131
message(STATUS "OS has native sendfile function")
3232
add_compile_definitions(POCO_HAVE_SENDFILE)
3333
endif()

Net/include/Poco/Net/SocketImpl.h

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,25 @@ class Net_API SocketImpl: public Poco::RefCountedObject
287287
/// The preferred way for a socket to receive urgent data
288288
/// is by enabling the SO_OOBINLINE option.
289289

290+
virtual std::streamsize sendFile(Poco::FileInputStream& FileInputStream, std::streamoff offset = 0, std::streamsize count = 0);
291+
/// Sends the contents of a file in an optimized way, if possible.
292+
///
293+
/// If count is != 0, sends the given number of bytes, otherwise
294+
/// sends all bytes, starting from the given offset.
295+
///
296+
/// On POSIX systems, this means using sendfile() or sendfile64().
297+
/// On Windows, this means using TransmitFile().
298+
///
299+
/// If neither is available, or the socket is a SecureSocketImpl()
300+
/// (secure() returns true), falls back to reading the file
301+
/// block by block and callind sendBytes().
302+
///
303+
/// Returns the number of bytes sent, which may be
304+
/// less than the number of bytes specified.
305+
///
306+
/// Throws NetException (or a subclass) in case of any errors.
307+
/// Also throws if the socket is non-blocking.
308+
290309
virtual int available();
291310
/// Returns the number of bytes available that can be read
292311
/// without causing the socket to block.
@@ -487,17 +506,7 @@ class Net_API SocketImpl: public Poco::RefCountedObject
487506

488507
bool initialized() const;
489508
/// Returns true iff the underlying socket is initialized.
490-
#ifdef POCO_HAVE_SENDFILE
491-
Int64 sendFile(FileInputStream &FileInputStream, UInt64 offset = 0);
492-
/// Sends file using system function
493-
/// for posix systems - with sendfile[64](...)
494-
/// for windows - with TransmitFile(...)
495-
///
496-
/// Returns the number of bytes sent, which may be
497-
/// less than the number of bytes specified.
498-
///
499-
/// Throws NetException (or a subclass) in case of any errors.
500-
#endif
509+
501510
protected:
502511
SocketImpl();
503512
/// Creates a SocketImpl.
@@ -538,6 +547,14 @@ class Net_API SocketImpl: public Poco::RefCountedObject
538547

539548
void checkBrokenTimeout(SelectMode mode);
540549

550+
std::streamsize sendFileNative(Poco::FileInputStream& FileInputStream, std::streamoff offset, std::streamsize count);
551+
/// Implements sendFile() using an OS-specific API like
552+
/// sendfile() or TransmitFile().
553+
554+
std::streamsize sendFileBlockwise(Poco::FileInputStream& FileInputStream, std::streamoff offset, std::streamsize count);
555+
/// Implements sendFile() by reading the file blockwise and
556+
/// calling sendBytes() for each block.
557+
541558
static int lastError();
542559
/// Returns the last error code.
543560

Net/include/Poco/Net/StreamSocket.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,17 +267,26 @@ class Net_API StreamSocket: public Socket
267267
///
268268
/// The preferred way for a socket to receive urgent data
269269
/// is by enabling the SO_OOBINLINE option.
270-
#ifdef POCO_HAVE_SENDFILE
271-
IntPtr sendFile(FileInputStream &FileInputStream, UIntPtr offset = 0);
272-
/// Sends file using system function
273-
/// for posix systems - with sendfile[64](...)
274-
/// for windows - with TransmitFile(...)
270+
271+
std::streamsize sendFile(Poco::FileInputStream& FileInputStream, std::streamoff offset = 0, std::streamsize count = 0);
272+
/// Sends the contents of a file in an optimized way, if possible.
273+
///
274+
/// If count is != 0, sends the given number of bytes, otherwise
275+
/// sends all bytes, starting from the given offset.
276+
///
277+
/// On POSIX systems, this means using sendfile() or sendfile64().
278+
/// On Windows, this means using TransmitFile().
279+
///
280+
/// If neither is available, or the socket is a SecureSocketImpl()
281+
/// (secure() returns true), falls back to reading the file
282+
/// block by block and callind sendBytes().
275283
///
276284
/// Returns the number of bytes sent, which may be
277285
/// less than the number of bytes specified.
278286
///
279287
/// Throws NetException (or a subclass) in case of any errors.
280-
#endif
288+
/// Also throws if the socket is non-blocking.
289+
281290
StreamSocket(SocketImpl* pImpl);
282291
/// Creates the Socket and attaches the given SocketImpl.
283292
/// The socket takes ownership of the SocketImpl.

Net/src/HTTPServerResponseImpl.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ void HTTPServerResponseImpl::sendFile(const std::string& path, const std::string
135135
#ifdef POCO_HAVE_SENDFILE
136136
_pStream->flush(); // flush the HTTP headers to the socket, required by HTTP 1.0 and above
137137

138-
Poco::IntPtr sent = 0;
139-
Poco::IntPtr offset = 0;
138+
std::streamsize sent = 0;
139+
std::streamoff offset = 0;
140140
while (sent < length)
141141
{
142142
offset = sent;

Net/src/SocketImpl.cpp

Lines changed: 115 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,27 @@ void SocketImpl::sendUrgent(unsigned char data)
696696
}
697697

698698

699+
700+
701+
std::streamsize SocketImpl::sendFile(FileInputStream& fileInputStream, std::streamoff offset, std::streamsize count)
702+
{
703+
if (!getBlocking()) throw NetException("sendFile() not supported for non-blocking sockets");
704+
705+
#ifdef POCO_HAVE_SENDFILE
706+
if (secure())
707+
{
708+
return sendFileBlockwise(fileInputStream, offset, count);
709+
}
710+
else
711+
{
712+
return sendFileNative(fileInputStream, offset, count);
713+
}
714+
#else
715+
return sendFileBlockwise(fileInputStream, offset, count);
716+
#endif
717+
}
718+
719+
699720
int SocketImpl::available()
700721
{
701722
int result = 0;
@@ -1424,13 +1445,16 @@ void SocketImpl::error(int code, const std::string& arg)
14241445
throw IOException(NumberFormatter::format(code), arg, code);
14251446
}
14261447
}
1448+
1449+
14271450
#ifdef POCO_HAVE_SENDFILE
14281451
#ifdef POCO_OS_FAMILY_WINDOWS
1429-
Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64 offset)
1452+
1453+
1454+
std::streamsize SocketImpl::sendFileNative(FileInputStream& fileInputStream, std::streamoff offset, std::streamsize count)
14301455
{
14311456
FileIOS::NativeHandle fd = fileInputStream.nativeHandle();
1432-
UInt64 fileSize = fileInputStream.size();
1433-
std::streamoff sentSize = fileSize - offset;
1457+
if (count == 0) count = fileInputStream.size() - offset;
14341458
LARGE_INTEGER offsetHelper;
14351459
offsetHelper.QuadPart = offset;
14361460
OVERLAPPED overlapped;
@@ -1441,57 +1465,75 @@ Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64
14411465
if (overlapped.hEvent == nullptr)
14421466
{
14431467
int err = GetLastError();
1444-
error(err, std::string("[sendfile error]") + Error::getMessage(err));
1468+
error(err);
14451469
}
1446-
bool result = TransmitFile(_sockfd, fd, sentSize, 0, &overlapped, nullptr, 0);
1470+
bool result = TransmitFile(_sockfd, fd, count, 0, &overlapped, nullptr, 0);
14471471
if (!result)
14481472
{
14491473
int err = WSAGetLastError();
1450-
if ((err != ERROR_IO_PENDING) && (WSAGetLastError() != WSA_IO_PENDING)) {
1474+
if ((err != ERROR_IO_PENDING) && (WSAGetLastError() != WSA_IO_PENDING))
1475+
{
14511476
CloseHandle(overlapped.hEvent);
1452-
error(err, std::string("[sendfile error]") + Error::getMessage(err));
1477+
error(err);
14531478
}
14541479
WaitForSingleObject(overlapped.hEvent, INFINITE);
14551480
}
14561481
CloseHandle(overlapped.hEvent);
1457-
return sentSize;
1482+
return count;
14581483
}
1484+
1485+
14591486
#else
1460-
Int64 _sendfile(poco_socket_t sd, FileIOS::NativeHandle fd, UInt64 offset, std::streamoff sentSize)
1487+
1488+
1489+
namespace
14611490
{
1462-
Int64 sent = 0;
1463-
#ifdef __USE_LARGEFILE64
1464-
sent = sendfile64(sd, fd, (off64_t *)&offset, sentSize);
1465-
#else
1466-
#if POCO_OS == POCO_OS_LINUX && !defined(POCO_EMSCRIPTEN)
1467-
sent = sendfile(sd, fd, (off_t *)&offset, sentSize);
1468-
#elif POCO_OS == POCO_OS_MAC_OS_X
1469-
int result = sendfile(fd, sd, offset, &sentSize, nullptr, 0);
1470-
if (result < 0)
1471-
{
1472-
sent = -1;
1473-
}
1474-
else
1491+
std::streamsize sendFileUnix(poco_socket_t sd, FileIOS::NativeHandle fd, std::streamsize offset, std::streamoff count)
14751492
{
1476-
sent = sentSize;
1477-
}
1478-
#else
1479-
throw Poco::NotImplementedException("sendfile not implemented for this platform");
1480-
#endif
1481-
#endif
1482-
if (errno == EAGAIN || errno == EWOULDBLOCK)
1483-
{
1484-
sent = 0;
1485-
}
1486-
return sent;
1493+
Int64 sent = 0;
1494+
#ifdef __USE_LARGEFILE64
1495+
sent = sendfile64(sd, fd, (off64_t*) &offset, count);
1496+
#else
1497+
#if POCO_OS == POCO_OS_LINUX && !defined(POCO_EMSCRIPTEN)
1498+
sent = sendfile(sd, fd, (off_t*) &offset, count);
1499+
#elif POCO_OS == POCO_OS_MAC_OS_X
1500+
int result = sendfile(fd, sd, offset, &count, NULL, 0);
1501+
if (result < 0)
1502+
{
1503+
sent = -1;
1504+
}
1505+
else
1506+
{
1507+
sent = count;
1508+
}
1509+
#elif POCO_OS == POCO_OS_FREE_BSD
1510+
int result = sendfile(fd, sd, offset, &count, NULL, NULL, 0);
1511+
if (result < 0)
1512+
{
1513+
sent = -1;
1514+
}
1515+
else
1516+
{
1517+
sent = count;
1518+
}
1519+
#else
1520+
throw Poco::NotImplementedException("sendfile not implemented for this platform");
1521+
#endif
1522+
#endif
1523+
if (errno == EAGAIN || errno == EWOULDBLOCK)
1524+
{
1525+
sent = 0;
1526+
}
1527+
return sent;
1528+
}
14871529
}
14881530

1489-
Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, UInt64 offset)
1531+
1532+
std::streamsize SocketImpl::sendFileNative(FileInputStream& fileInputStream, std::streamoff offset, std::streamsize count)
14901533
{
14911534
FileIOS::NativeHandle fd = fileInputStream.nativeHandle();
1492-
UInt64 fileSize = fileInputStream.size();
1493-
std::streamoff sentSize = fileSize - offset;
1494-
Int64 sent = 0;
1535+
if (count == 0) count = fileInputStream.size() - offset;
1536+
std::streamsize sent = 0;
14951537
struct sigaction sa, old_sa;
14961538
sa.sa_handler = SIG_IGN;
14971539
sigemptyset(&sa.sa_mask);
@@ -1500,20 +1542,53 @@ Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, UInt64 offset)
15001542
while (sent == 0)
15011543
{
15021544
errno = 0;
1503-
sent = _sendfile(_sockfd, fd, offset, sentSize);
1545+
sent = sendFileUnix(_sockfd, fd, offset, count);
15041546
if (sent < 0)
15051547
{
1506-
error(errno, std::string("[sendfile error]") + Error::getMessage(errno));
1548+
error(errno);
15071549
}
15081550
}
1509-
if(old_sa.sa_handler == SIG_ERR)
1551+
if (old_sa.sa_handler == SIG_ERR)
15101552
{
15111553
old_sa.sa_handler = SIG_DFL;
15121554
}
15131555
sigaction(SIGPIPE, &old_sa, nullptr);
15141556
return sent;
15151557
}
1558+
1559+
15161560
#endif // POCO_OS_FAMILY_WINDOWS
15171561
#endif // POCO_HAVE_SENDFILE
15181562

1563+
1564+
std::streamsize SocketImpl::sendFileBlockwise(FileInputStream& fileInputStream, std::streamoff offset, std::streamsize count)
1565+
{
1566+
fileInputStream.seekg(offset, std::ios_base::beg);
1567+
Poco::Buffer<char> buffer(8192);
1568+
std::size_t bufferSize = buffer.size();
1569+
if (count > 0 && bufferSize > count) bufferSize = count;
1570+
1571+
std::streamsize len = 0;
1572+
fileInputStream.read(buffer.begin(), bufferSize);
1573+
std::streamsize n = fileInputStream.gcount();
1574+
while (n > 0 && (count == 0 || len < count))
1575+
{
1576+
len += n;
1577+
sendBytes(buffer.begin(), n);
1578+
if (count > 0 && len < count)
1579+
{
1580+
const std::size_t remaining = count - len;
1581+
if (bufferSize > remaining) bufferSize = remaining;
1582+
}
1583+
if (fileInputStream)
1584+
{
1585+
fileInputStream.read(buffer.begin(), bufferSize);
1586+
n = fileInputStream.gcount();
1587+
}
1588+
else n = 0;
1589+
}
1590+
return len;
1591+
}
1592+
1593+
15191594
} } // namespace Poco::Net

Net/src/StreamSocket.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,12 @@ void StreamSocket::sendUrgent(unsigned char data)
212212
{
213213
impl()->sendUrgent(data);
214214
}
215-
#ifdef POCO_HAVE_SENDFILE
216-
IntPtr StreamSocket::sendFile(FileInputStream &fileInputStream, UIntPtr offset)
215+
216+
217+
std::streamsize StreamSocket::sendFile(Poco::FileInputStream& fileInputStream, std::streamoff offset, std::streamsize count)
217218
{
218-
return impl()->sendFile(fileInputStream, offset);
219+
return impl()->sendFile(fileInputStream, offset, count);
219220
}
220-
#endif
221+
222+
221223
} } // namespace Poco::Net

0 commit comments

Comments
 (0)