Files
handleaiuinew/env/LMI/include/kApi/Io/kTcpClient.cpp
T
2024-05-18 23:53:26 +08:00

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);
}
}