609 lines
16 KiB
C++
609 lines
16 KiB
C++
/**
|
|
* @file kTcpClient.cpp
|
|
*
|
|
* @internal
|
|
* Copyright (C) 2008-2021 by LMI Technologies Inc.
|
|
* Licensed under the MIT License.
|
|
* Redistributed files must retain the above copyright notice.
|
|
*/
|
|
#define K_PLATFORM
|
|
#include <kApi/Io/kTcpClient.h>
|
|
#include <kApi/Io/kSocket.h>
|
|
#include <kApi/Threads/kTimer.h>
|
|
|
|
kBeginClassEx(k, kTcpClient)
|
|
kAddPrivateVMethod(kTcpClient, kObject, VRelease)
|
|
kAddPrivateVMethod(kTcpClient, kStream, VReadSomeImpl)
|
|
kAddPrivateVMethod(kTcpClient, kStream, VWriteImpl)
|
|
kAddPrivateVMethod(kTcpClient, kStream, VFlush)
|
|
kAddPrivateVMethod(kTcpClient, kStream, VFill)
|
|
kAddPrivateVMethod(kTcpClient, kStream, VSeek)
|
|
kEndClassEx()
|
|
|
|
kFx(kStatus) kTcpClient_Construct(kTcpClient* client, kIpVersion ipVersion, kAlloc allocator)
|
|
{
|
|
kAlloc alloc = kAlloc_Fallback(allocator);
|
|
kStatus status;
|
|
|
|
kCheck(kAlloc_GetObject(alloc, kTypeOf(kTcpClient), client));
|
|
|
|
if (!kSuccess(status = xkTcpClient_Init(*client, kTypeOf(kTcpClient), ipVersion, alloc)))
|
|
{
|
|
kAlloc_FreeRef(alloc, client);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
kFx(kStatus) xkTcpClient_ConstructFromSocket(kTcpClient* client, kSocket socket, kAlloc allocator)
|
|
{
|
|
kAlloc alloc = kAlloc_Fallback(allocator);
|
|
kType type = kTypeOf(kTcpClient);
|
|
kStatus status;
|
|
|
|
kCheck(kAlloc_GetObject(alloc, type, client));
|
|
|
|
if (!kSuccess(status = xkTcpClient_InitFromSocket(*client, type, socket, alloc)))
|
|
{
|
|
kAlloc_FreeRef(alloc, client);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
kFx(kStatus) xkTcpClient_Init(kTcpClient client, kType type, kIpVersion ipVersion, kAlloc allocator)
|
|
{
|
|
kSocket socket = kNULL;
|
|
kStatus status;
|
|
|
|
kTry
|
|
{
|
|
kTest(kSocket_Construct(&socket, kIP_VERSION_4, kSOCKET_TYPE_TCP, allocator));
|
|
kTest(kSocket_Bind(socket, kIpAddress_Any(ipVersion), kIP_PORT_ANY));
|
|
|
|
kTest(xkTcpClient_InitFromSocket(client, type, socket, allocator));
|
|
}
|
|
kCatch(&status)
|
|
{
|
|
kDestroyRef(&socket);
|
|
kEndCatch(status);
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkTcpClient_InitFromSocket(kTcpClient client, kType type, kSocket socket, kAlloc allocator)
|
|
{
|
|
kObjR(kTcpClient, client);
|
|
kStatus status;
|
|
|
|
kCheckArgs(!kIsNull(socket));
|
|
|
|
kCheck(kStream_Init(client, type, allocator));
|
|
|
|
obj->socket = kNULL;
|
|
obj->writeTimeout = 0;
|
|
obj->readTimeout = 0;
|
|
obj->socketReadBufferSize = 0;
|
|
obj->cancelQuery.function = kNULL;
|
|
obj->cancelQuery.receiver = kNULL;
|
|
obj->isCancelled = 0;
|
|
obj->timedOut = kFALSE;
|
|
obj->isSeekEnabled = kFALSE;
|
|
|
|
kTry
|
|
{
|
|
kTest(kSocket_SetBlocking(socket, kFALSE));
|
|
|
|
obj->socket = socket;
|
|
obj->socketReadBufferSize = xkSocket_ReadBuffer(socket);
|
|
}
|
|
kCatch(&status)
|
|
{
|
|
xkTcpClient_VRelease(client);
|
|
kEndCatch(status);
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkTcpClient_VRelease(kTcpClient client)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
kCheck(kObject_Destroy(obj->socket));
|
|
|
|
kCheck(kObject_FreeMem(client, obj->base.readBuffer));
|
|
kCheck(kObject_FreeMem(client, obj->base.writeBuffer));
|
|
|
|
kCheck(kStream_VRelease(client));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_SetWriteBuffers(kTcpClient client, kSSize socketSize, kSSize clientSize)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
kCheck(kStream_Flush(client));
|
|
|
|
if (socketSize >= 0)
|
|
{
|
|
kCheck(kSocket_SetWriteBuffer(obj->socket, (kSize)socketSize));
|
|
}
|
|
|
|
if (clientSize >= 0)
|
|
{
|
|
kCheck(kObject_FreeMemRef(client, &obj->base.writeBuffer));
|
|
|
|
obj->base.writeCapacity = 0;
|
|
obj->base.writeBegin = 0;
|
|
obj->base.writeEnd = 0;
|
|
|
|
if (clientSize > 0)
|
|
{
|
|
kCheck(kObject_GetMem(client, (kSize)clientSize, &obj->base.writeBuffer));
|
|
obj->base.writeCapacity = (kSize)clientSize;
|
|
obj->base.writeEnd = (kSize)clientSize;
|
|
}
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_SetReadBuffers(kTcpClient client, kSSize socketSize, kSSize clientSize)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
kCheckState((obj->base.readEnd - obj->base.readBegin) == 0);
|
|
|
|
if (socketSize >= 0)
|
|
{
|
|
kCheck(kSocket_SetReadBuffer(obj->socket, (kSize)socketSize));
|
|
obj->socketReadBufferSize = xkSocket_ReadBuffer(obj->socket);
|
|
}
|
|
|
|
if (clientSize >= 0)
|
|
{
|
|
kCheck(kObject_FreeMemRef(client, &obj->base.readBuffer));
|
|
|
|
obj->base.readCapacity = 0;
|
|
obj->base.readBegin = 0;
|
|
obj->base.readEnd = 0;
|
|
|
|
if (clientSize > 0)
|
|
{
|
|
kCheck(kObject_GetMem(client, (kSize)clientSize, &obj->base.readBuffer));
|
|
obj->base.readCapacity = (kSize)clientSize;
|
|
}
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_SetWriteTimeout(kTcpClient client, k64u timeout)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
obj->writeTimeout = timeout;
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_SetReadTimeout(kTcpClient client, k64u timeout)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
obj->readTimeout = timeout;
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_SetNoDelay(kTcpClient client, kBool noDelay)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
return kSocket_SetNoDelay(obj->socket, noDelay);
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_EnableSeek(kTcpClient client, kBool enabled)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
obj->isSeekEnabled = enabled;
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_SetCancelHandler(kTcpClient client, kCallbackFx function, kPointer receiver)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
obj->cancelQuery.function = function;
|
|
obj->cancelQuery.receiver = receiver;
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_Cancel(kTcpClient client)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
kAtomic32s_Exchange(&obj->isCancelled, kTRUE);
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_Connect(kTcpClient client, kIpAddress address, k32u port, k64u timeout)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
return kSocket_Connect(obj->socket, address, port, timeout);
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_BeginConnect(kTcpClient client, kIpAddress address, k32u port)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
return kSocket_BeginConnect(obj->socket, address, port);
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_EndConnect(kTcpClient client, k64u timeout)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
return kSocket_EndConnect(obj->socket, timeout);
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_Shutdown(kTcpClient client)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
return kSocket_Shutdown(obj->socket);
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_Wait(kTcpClient client, k64u timeout)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
if (kTcpClient_Available(client) > 0)
|
|
{
|
|
return kOK;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* FSS-288: Wind River APPNOTE_000531 suggests to use low water marks with Vx Works.
|
|
*/
|
|
#if defined(K_VX_KERNEL)
|
|
xkSocket_SetReadLowWater(obj->socket, 1);
|
|
#endif
|
|
|
|
return xkSocket_WaitEx(obj->socket, kSOCKET_EVENT_READ, timeout);
|
|
}
|
|
}
|
|
|
|
kFx(kStatus) xkTcpClient_VFlush(kTcpClient client)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
if (obj->base.writeBegin > 0)
|
|
{
|
|
kCheck(xkTcpClient_WriteAll(client, obj->base.writeBuffer, obj->base.writeBegin));
|
|
obj->base.writeBegin = 0;
|
|
}
|
|
|
|
if (obj->isSeekEnabled && (obj->base.readBegin > 0))
|
|
{
|
|
kMemMove(obj->base.readBuffer, &obj->base.readBuffer[obj->base.readBegin], obj->base.readEnd - obj->base.readBegin);
|
|
obj->base.readEnd = (obj->base.readEnd - obj->base.readBegin);
|
|
obj->base.readBegin = 0;
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkTcpClient_VFill(kTcpClient client)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
kSize mediumCount;
|
|
|
|
kCheck(xkTcpClient_ReadAtLeast(client, &obj->base.readBuffer[0], 1, obj->base.readCapacity, &mediumCount));
|
|
|
|
obj->base.readBegin = 0;
|
|
obj->base.readEnd = mediumCount;
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkTcpClient_VSeek(kTcpClient client, k64s offset, kSeekOrigin origin)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
switch (origin)
|
|
{
|
|
case kSEEK_ORIGIN_BEGIN:
|
|
offset -= (k64s)kStream_BytesRead(client);
|
|
break;
|
|
case kSEEK_ORIGIN_CURRENT:
|
|
break;
|
|
case kSEEK_ORIGIN_END:
|
|
return kERROR_PARAMETER;
|
|
}
|
|
|
|
if (offset >= 0)
|
|
{
|
|
kByte buffer[256];
|
|
|
|
//support forward read seek by reading and discarding
|
|
while (offset > 0)
|
|
{
|
|
k64u chunkSize = kMin_((k64u)offset, sizeof(buffer));
|
|
|
|
kCheck(kStream_Read(client, buffer, (kSize)chunkSize));
|
|
|
|
offset -= chunkSize;
|
|
}
|
|
}
|
|
else if (((k64s)obj->base.readBegin + offset) >= 0)
|
|
{
|
|
//support reverse read seek by adjusting buffer pointers...
|
|
obj->base.readBegin += (kSSize) offset;
|
|
}
|
|
else
|
|
{
|
|
//...unless the seek destination is already out of buffer
|
|
return kERROR_PARAMETER;
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkTcpClient_VReadSomeImpl(kTcpClient client, void* buffer, kSize minCount, kSize maxCount, kSize* bytesRead)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
kByte* dest = (kByte*) buffer;
|
|
kSize readCount = 0;
|
|
kSize copyCount, mediumCount;
|
|
|
|
kCheckArgs(minCount <= maxCount);
|
|
|
|
//consume any bytes in the read buffer first
|
|
if ((obj->base.readEnd - obj->base.readBegin) > 0)
|
|
{
|
|
copyCount = kMin_(maxCount, obj->base.readEnd - obj->base.readBegin);
|
|
|
|
kMemCopy(dest, &obj->base.readBuffer[obj->base.readBegin], copyCount);
|
|
|
|
obj->base.readBegin += copyCount;
|
|
readCount += copyCount;
|
|
}
|
|
|
|
//if the request is not yet satisfied
|
|
if (readCount < minCount)
|
|
{
|
|
//if the request is larger than the internal read buffer, read directly into the caller's buffer; else read into the internal buffer
|
|
if ((maxCount - readCount) > obj->base.readCapacity)
|
|
{
|
|
kCheck(xkTcpClient_ReadAtLeast(client, &dest[readCount], minCount-readCount, maxCount-readCount, &mediumCount));
|
|
|
|
obj->base.readBegin = obj->base.readEnd = 0;
|
|
readCount += mediumCount;
|
|
}
|
|
else
|
|
{
|
|
//if seek is not enabled, prefer to write data at the beginning of the buffer
|
|
if (!obj->isSeekEnabled || ((maxCount - readCount) > (obj->base.readCapacity - obj->base.readEnd)))
|
|
{
|
|
obj->base.readBegin = obj->base.readEnd = 0;
|
|
}
|
|
|
|
kCheck(xkTcpClient_ReadAtLeast(client, &obj->base.readBuffer[obj->base.readEnd], minCount - readCount, obj->base.readCapacity - obj->base.readEnd, &mediumCount));
|
|
|
|
copyCount = kMin_(mediumCount, maxCount - readCount);
|
|
|
|
kCheck(kMemCopy(&dest[readCount], &obj->base.readBuffer[obj->base.readEnd], copyCount));
|
|
|
|
obj->base.readBegin += copyCount;
|
|
obj->base.readEnd += mediumCount;
|
|
readCount += copyCount;
|
|
}
|
|
}
|
|
|
|
if (!kIsNull(bytesRead))
|
|
{
|
|
*bytesRead = readCount;
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkTcpClient_VWriteImpl(kTcpClient client, const void* buffer, kSize size)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
//if the internal write buffer already has content, but not enough free space, flush
|
|
if ((obj->base.writeBegin > 0) && ((obj->base.writeEnd - obj->base.writeBegin) < size))
|
|
{
|
|
kCheck(kStream_Flush(client));
|
|
}
|
|
|
|
//if the write is larger than the internal write buffer, write directly to the medium; else write to the internal buffer
|
|
if ((obj->base.writeEnd - obj->base.writeBegin) < size)
|
|
{
|
|
kCheck(xkTcpClient_WriteAll(client, (const kByte*) buffer, size));
|
|
}
|
|
else
|
|
{
|
|
kCheck(kMemCopy(&obj->base.writeBuffer[obj->base.writeBegin], buffer, size));
|
|
obj->base.writeBegin += size;
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkTcpClient_ReadAtLeast(kTcpClient client, kByte* buffer, kSize minCount, kSize maxCount, kSize* bytesRead)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
k64u startTime = (obj->readTimeout == 0) ? 0 : kTimer_Now();
|
|
kBool hasTimedOut = kFALSE;
|
|
kStatus result;
|
|
kSize totalRead = 0;
|
|
kSize read;
|
|
|
|
kCheck(kSocket_SetEvents(obj->socket, kSOCKET_EVENT_READ));
|
|
|
|
do
|
|
{
|
|
kCheck(kTcpClient_Status(client));
|
|
|
|
hasTimedOut = (obj->readTimeout == 0) ? kFALSE : ((kTimer_Now() - startTime) >= obj->readTimeout);
|
|
|
|
/*
|
|
* FSS-288: Wind River APPNOTE_000531 suggests to use low water marks with Vx Works. This decreases
|
|
* CPU utilization when receiving data.
|
|
*/
|
|
#if defined(K_VX_KERNEL)
|
|
{
|
|
k32s lowWater = (k32s)(minCount - totalRead);
|
|
lowWater = kMin_(lowWater, (k32s)obj->socketReadBufferSize/2);
|
|
xkSocket_SetReadLowWater(obj->socket, lowWater);
|
|
}
|
|
#endif
|
|
|
|
result = kSocket_Wait(obj->socket, xkTCP_CLIENT_CANCEL_QUERY_INTERVAL);
|
|
|
|
if (result == kOK)
|
|
{
|
|
kCheck(kSocket_Read(obj->socket, &buffer[totalRead], maxCount-totalRead, &read));
|
|
obj->base.bytesRead += (k64u) read;
|
|
totalRead += read;
|
|
}
|
|
else if ((result == kERROR_TIMEOUT) && !kIsNull(obj->cancelQuery.function))
|
|
{
|
|
if (!kSuccess(obj->cancelQuery.function(obj->cancelQuery.receiver, client, kNULL)))
|
|
{
|
|
kAtomic32s_Exchange(&obj->isCancelled, kTRUE);
|
|
return kERROR_ABORT;
|
|
}
|
|
}
|
|
else if (result != kERROR_TIMEOUT)
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
while ((totalRead < minCount) && !hasTimedOut);
|
|
|
|
if (totalRead < minCount)
|
|
{
|
|
obj->timedOut = kTRUE;
|
|
return kERROR_TIMEOUT;
|
|
}
|
|
|
|
*bytesRead = totalRead;
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkTcpClient_WriteAll(kTcpClient client, const kByte* buffer, kSize count)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
k64u startTime = (obj->writeTimeout == 0) ? 0 : kTimer_Now();
|
|
kBool hasTimedOut = kFALSE;
|
|
kStatus result;
|
|
kSize totalWritten = 0;
|
|
kSize written;
|
|
kChar peekByte;
|
|
|
|
do
|
|
{
|
|
kCheck(kTcpClient_Status(client));
|
|
|
|
hasTimedOut = (obj->writeTimeout == 0) ? kFALSE : ((kTimer_Now() - startTime) >= obj->writeTimeout);
|
|
|
|
result = xkSocket_WaitEx(obj->socket, kSOCKET_EVENT_WRITE, xkTCP_CLIENT_CANCEL_QUERY_INTERVAL);
|
|
|
|
if (result == kOK)
|
|
{
|
|
kCheck(kSocket_Write(obj->socket, &buffer[totalWritten], count-totalWritten, &written));
|
|
obj->base.bytesWritten += (k64u) written;
|
|
totalWritten += written;
|
|
}
|
|
else if (result == kERROR_TIMEOUT)
|
|
{
|
|
if (kSuccess(xkSocket_WaitEx(obj->socket, kSOCKET_EVENT_READ, 0)))
|
|
{
|
|
kCheck(xkSocket_Peek(obj->socket, &peekByte, sizeof(peekByte), kNULL));
|
|
}
|
|
|
|
if (!kIsNull(obj->cancelQuery.function))
|
|
{
|
|
if (!kSuccess(obj->cancelQuery.function(obj->cancelQuery.receiver, client, kNULL)))
|
|
{
|
|
kAtomic32s_Exchange(&obj->isCancelled, kTRUE);
|
|
return kERROR_ABORT;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
while ((totalWritten < count) && !hasTimedOut);
|
|
|
|
if (totalWritten < count)
|
|
{
|
|
obj->timedOut = kTRUE;
|
|
return kERROR_TIMEOUT;
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kSocket) kTcpClient_Socket(kTcpClient client)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
return obj->socket;
|
|
}
|
|
|
|
kFx(kSize) kTcpClient_Available(kTcpClient client)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
return obj->base.readEnd - obj->base.readBegin;
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_LocalEndPoint(kTcpClient client, kIpEndPoint* endPoint)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
return kSocket_LocalEndPoint(obj->socket, endPoint);
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_RemoteEndPoint(kTcpClient client, kIpEndPoint* endPoint)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
return kSocket_RemoteEndPoint(obj->socket, endPoint);
|
|
}
|
|
|
|
kFx(kStatus) kTcpClient_Status(kTcpClient client)
|
|
{
|
|
kObj(kTcpClient, client);
|
|
|
|
if (kAtomic32s_Get(&obj->isCancelled))
|
|
{
|
|
return kERROR_ABORT;
|
|
}
|
|
else if (obj->timedOut)
|
|
{
|
|
return kERROR_TIMEOUT;
|
|
}
|
|
else
|
|
{
|
|
return xkSocket_Status(obj->socket);
|
|
}
|
|
}
|