More work on sending/receiving avatars. Introduces low priority send queue, so that the server send thread is not stalled when sending loads of avatars. Not functional yet.

This commit is contained in:
lotodore
2007-10-06 12:50:19 +00:00
parent 7dd1013396
commit 3bb28de5ae
7 changed files with 147 additions and 25 deletions
+5 -5
View File
@@ -21,16 +21,16 @@
#ifndef _AVATARMANAGER_H_ #ifndef _AVATARMANAGER_H_
#define _AVATARMANAGER_H_ #define _AVATARMANAGER_H_
#include <playerdata.h>
#include <net/netpacket.h>
#include <core/crypthelper.h> #include <core/crypthelper.h>
#include <map> #include <map>
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
#define MAX_AVATAR_FILE_SIZE 30720 #define MAX_AVATAR_FILE_SIZE 30720
struct AvatarFileState; struct AvatarFileState;
class NetPacket;
class AvatarManager class AvatarManager
{ {
@@ -41,14 +41,14 @@ public:
bool Init(const std::string &dataDir, const std::string &cacheDir); bool Init(const std::string &dataDir, const std::string &cacheDir);
boost::shared_ptr<AvatarFileState> OpenAvatarFileForChunkRead(const std::string &fileName, unsigned &outFileSize); boost::shared_ptr<AvatarFileState> OpenAvatarFileForChunkRead(const std::string &fileName, unsigned &outFileSize, AvatarFileType &outFileType);
unsigned ChunkReadAvatarFile(boost::shared_ptr<AvatarFileState> fileState, unsigned char *data, unsigned chunkSize); unsigned ChunkReadAvatarFile(boost::shared_ptr<AvatarFileState> fileState, unsigned char *data, unsigned chunkSize);
bool SendAvatarFile(const std::string &fileName, unsigned requestId, boost::function<void (boost::shared_ptr<NetPacket>)> sender); bool AvatarFileToNetPackets(const std::string &fileName, unsigned requestId, NetPacketList &packets);
bool GetHashForAvatar(const std::string &fileName, MD5Buf &md5buf); bool GetHashForAvatar(const std::string &fileName, MD5Buf &md5buf);
bool GetAvatarFileName(const MD5Buf &md5buf, std::string &fileName) const; bool GetAvatarFileName(const MD5Buf &md5buf, std::string &fileName) const;
bool StoreAvatarInCache(const MD5Buf &md5buf, const std::string &fileExtension, const unsigned char *data, unsigned size); bool StoreAvatarInCache(const MD5Buf &md5buf, AvatarFileType avatarFileType, const unsigned char *data, unsigned size);
protected: protected:
typedef std::map<MD5Buf, std::string> AvatarMap; typedef std::map<MD5Buf, std::string> AvatarMap;
+41 -11
View File
@@ -18,12 +18,19 @@
***************************************************************************/ ***************************************************************************/
#include "avatarmanager.h" #include "avatarmanager.h"
#include <net/netpacket.h>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <openssl/md5.h> #include <openssl/md5.h>
#include <fstream> #include <fstream>
#include <cstring>
// Not using boost::algorithm here because of STL issues.
#ifdef _MSC_VER
#define STRCASECMP _stricmp
#else
#define STRCASECMP strcasecmp
#endif
using namespace std; using namespace std;
using namespace boost::filesystem; using namespace boost::filesystem;
@@ -59,12 +66,21 @@ AvatarManager::Init(const std::string &dataDir, const std::string &cacheDir)
} }
boost::shared_ptr<AvatarFileState> boost::shared_ptr<AvatarFileState>
AvatarManager::OpenAvatarFileForChunkRead(const std::string &fileName, unsigned &outFileSize) AvatarManager::OpenAvatarFileForChunkRead(const std::string &fileName, unsigned &outFileSize, AvatarFileType &outFileType)
{ {
outFileSize = 0; outFileSize = 0;
outFileType = AVATAR_FILE_TYPE_UNKNOWN;
boost::shared_ptr<AvatarFileState> retVal; boost::shared_ptr<AvatarFileState> retVal;
try try
{ {
path filePath(fileName);
string ext(extension(filePath));
if (STRCASECMP(ext.c_str(), ".png") == 0)
outFileType = AVATAR_FILE_TYPE_PNG;
else if (STRCASECMP(ext.c_str(), ".jpg") == 0 || STRCASECMP(ext.c_str(), ".jpeg") == 0)
outFileType = AVATAR_FILE_TYPE_JPG;
else if (STRCASECMP(ext.c_str(), ".gif") == 0)
outFileType = AVATAR_FILE_TYPE_GIF;
boost::shared_ptr<AvatarFileState> fileState(new AvatarFileState); boost::shared_ptr<AvatarFileState> fileState(new AvatarFileState);
fileState->inputStream.open(fileName.c_str(), ios_base::in | ios_base::binary); fileState->inputStream.open(fileName.c_str(), ios_base::in | ios_base::binary);
if (!fileState->inputStream.fail()) if (!fileState->inputStream.fail())
@@ -108,20 +124,21 @@ AvatarManager::ChunkReadAvatarFile(boost::shared_ptr<AvatarFileState> fileState,
} }
bool bool
AvatarManager::SendAvatarFile(const string &fileName, unsigned requestId, boost::function<void (boost::shared_ptr<NetPacket>)> sender) AvatarManager::AvatarFileToNetPackets(const string &fileName, unsigned requestId, NetPacketList &packets)
{ {
bool retVal = false; bool retVal = false;
unsigned fileSize; unsigned fileSize;
boost::shared_ptr<AvatarFileState> tmpState = OpenAvatarFileForChunkRead(fileName, fileSize); AvatarFileType fileType;
if (tmpState.get() && fileSize) boost::shared_ptr<AvatarFileState> tmpState = OpenAvatarFileForChunkRead(fileName, fileSize, fileType);
if (tmpState.get() && fileSize && fileType != AVATAR_FILE_TYPE_UNKNOWN)
{ {
boost::shared_ptr<NetPacket> avatarHeader(new NetPacketAvatarHeader); boost::shared_ptr<NetPacket> avatarHeader(new NetPacketAvatarHeader);
NetPacketAvatarHeader::Data avatarHeaderData; NetPacketAvatarHeader::Data avatarHeaderData;
avatarHeaderData.requestId = requestId; avatarHeaderData.requestId = requestId;
avatarHeaderData.avatarFileSize = fileSize; avatarHeaderData.avatarFileSize = fileSize;
avatarHeaderData.avatarFileType = AVATAR_TYPE_PNG; // TODO avatarHeaderData.avatarFileType = fileType;
static_cast<NetPacketAvatarHeader *>(avatarHeader.get())->SetData(avatarHeaderData); static_cast<NetPacketAvatarHeader *>(avatarHeader.get())->SetData(avatarHeaderData);
sender(avatarHeader); packets.push_back(avatarHeader);
unsigned numBytes = 0; unsigned numBytes = 0;
unsigned totalBytesRead = 0; unsigned totalBytesRead = 0;
@@ -137,7 +154,7 @@ AvatarManager::SendAvatarFile(const string &fileName, unsigned requestId, boost:
avatarFileData.requestId = requestId; avatarFileData.requestId = requestId;
totalBytesRead += numBytes; totalBytesRead += numBytes;
static_cast<NetPacketAvatarFile *>(avatarFile.get())->SetData(avatarFileData); static_cast<NetPacketAvatarFile *>(avatarFile.get())->SetData(avatarFileData);
sender(avatarFile); packets.push_back(avatarFile);
} }
} while (numBytes); } while (numBytes);
// TODO error handling if numBytes != totalBytesRead // TODO error handling if numBytes != totalBytesRead
@@ -145,7 +162,7 @@ AvatarManager::SendAvatarFile(const string &fileName, unsigned requestId, boost:
NetPacketAvatarEnd::Data avatarEndData; NetPacketAvatarEnd::Data avatarEndData;
avatarEndData.requestId = requestId; avatarEndData.requestId = requestId;
static_cast<NetPacketAvatarEnd *>(avatarEnd.get())->SetData(avatarEndData); static_cast<NetPacketAvatarEnd *>(avatarEnd.get())->SetData(avatarEndData);
sender(avatarEnd); packets.push_back(avatarEnd);
retVal = true; retVal = true;
} }
// else TODO error handling // else TODO error handling
@@ -197,13 +214,26 @@ AvatarManager::GetAvatarFileName(const MD5Buf &md5buf, std::string &fileName) co
} }
bool bool
AvatarManager::StoreAvatarInCache(const MD5Buf &md5buf, const std::string &fileExtension, const unsigned char *data, unsigned size) AvatarManager::StoreAvatarInCache(const MD5Buf &md5buf, AvatarFileType avatarFileType, const unsigned char *data, unsigned size)
{ {
bool retVal = false; bool retVal = false;
try try
{ {
string ext;
switch (avatarFileType)
{
case AVATAR_FILE_TYPE_PNG:
ext = ".png";
break;
case AVATAR_FILE_TYPE_JPG:
ext = ".jpg";
break;
case AVATAR_FILE_TYPE_GIF:
ext = ".gif";
break;
}
path tmpPath(m_cacheDir); path tmpPath(m_cacheDir);
tmpPath /= (md5buf.ToString() + "." + fileExtension); tmpPath /= (md5buf.ToString() + ext);
string fileName(tmpPath.file_string()); string fileName(tmpPath.file_string());
ofstream o(fileName.c_str(), ios_base::out | ios_base::binary); ofstream o(fileName.c_str(), ios_base::out | ios_base::binary);
o.write((const char *)data, size); o.write((const char *)data, size);
+11 -1
View File
@@ -32,6 +32,8 @@
#include <game.h> #include <game.h>
#include <playerinterface.h> #include <playerinterface.h>
#include <boost/bind.hpp>
#include <sstream> #include <sstream>
using namespace std; using namespace std;
@@ -529,10 +531,18 @@ ClientStateWaitSession::InternalProcess(ClientThread &client, boost::shared_ptr<
} }
else if (packet->ToNetPacketRetrieveAvatar()) else if (packet->ToNetPacketRetrieveAvatar())
{ {
// Before letting us join the lobby, the server requests our avatar.
NetPacketRetrieveAvatar::Data retrieveAvatarData; NetPacketRetrieveAvatar::Data retrieveAvatarData;
packet->ToNetPacketRetrieveAvatar()->GetData(retrieveAvatarData); packet->ToNetPacketRetrieveAvatar()->GetData(retrieveAvatarData);
// Before letting us join the lobby, the server requests our avatar. NetPacketList tmpList;
if (client.GetAvatarManager().AvatarFileToNetPackets(
client.GetContext().GetAvatarFile(),
retrieveAvatarData.requestId,
tmpList))
{
client.GetSender().SendLowPrio(client.GetContext().GetSocket(), tmpList);
}
} }
return retVal; return retVal;
+69 -3
View File
@@ -22,6 +22,8 @@
#include <net/socket_msg.h> #include <net/socket_msg.h>
#include <cstring> #include <cstring>
#include <boost/bind.hpp>
using namespace std; using namespace std;
@@ -40,12 +42,64 @@ SenderThread::Send(SOCKET sock, boost::shared_ptr<NetPacket> packet)
if (packet.get() && IS_VALID_SOCKET(sock)) if (packet.get() && IS_VALID_SOCKET(sock))
{ {
boost::mutex::scoped_lock lock(m_outBufMutex); boost::mutex::scoped_lock lock(m_outBufMutex);
if (m_outBuf.size() < SEND_QUEUE_SIZE) // Queue is limited in size. InternalStore(m_outBuf, SEND_QUEUE_SIZE, sock, packet);
m_outBuf.push_back(std::make_pair(packet, sock));
// TODO: Throw exception if failed.
} }
} }
void
SenderThread::Send(SOCKET sock, const NetPacketList &packetList)
{
if (!packetList.empty() && IS_VALID_SOCKET(sock))
{
boost::mutex::scoped_lock lock(m_outBufMutex);
InternalStore(m_outBuf, SEND_QUEUE_SIZE, sock, packetList);
}
}
void
SenderThread::SendLowPrio(SOCKET sock, boost::shared_ptr<NetPacket> packet)
{
if (packet.get() && IS_VALID_SOCKET(sock))
{
boost::mutex::scoped_lock lock(m_lowPrioOutBufMutex);
InternalStore(m_lowPrioOutBuf, SEND_LOW_PRIO_QUEUE_SIZE, sock, packet);
}
}
void
SenderThread::SendLowPrio(SOCKET sock, const NetPacketList &packetList)
{
if (!packetList.empty() && IS_VALID_SOCKET(sock))
{
boost::mutex::scoped_lock lock(m_lowPrioOutBufMutex);
InternalStore(m_lowPrioOutBuf, SEND_LOW_PRIO_QUEUE_SIZE, sock, packetList);
}
}
void
SenderThread::InternalStore(SendDataDeque &sendQueue, unsigned maxQueueSize, SOCKET sock, boost::shared_ptr<NetPacket> packet)
{
if (sendQueue.size() < maxQueueSize) // Queue is limited in size.
sendQueue.push_back(std::make_pair(packet, sock));
// TODO: Throw exception if failed.
}
void
SenderThread::InternalStore(SendDataDeque &sendQueue, unsigned maxQueueSize, SOCKET sock, const NetPacketList &packetList)
{
if (sendQueue.size() + packetList.size() < maxQueueSize)
{
NetPacketList::const_iterator i = packetList.begin();
NetPacketList::const_iterator end = packetList.end();
while (i != end)
{
sendQueue.push_back(std::make_pair(*i, sock));
++i;
}
}
// TODO: Throw exception if failed.
}
void void
SenderThread::Main() SenderThread::Main()
{ {
@@ -57,6 +111,7 @@ SenderThread::Main()
if (!m_tmpOutBufSize) if (!m_tmpOutBufSize)
{ {
SendData tmpData; SendData tmpData;
// Check main queue first.
{ {
boost::mutex::scoped_lock lock(m_outBufMutex); boost::mutex::scoped_lock lock(m_outBufMutex);
if (!m_outBuf.empty()) if (!m_outBuf.empty())
@@ -66,6 +121,17 @@ SenderThread::Main()
} }
} }
// Check low prio queue only if there is nothing in the main queue.
if (!tmpData.first.get())
{
boost::mutex::scoped_lock lock(m_lowPrioOutBufMutex);
if (!m_lowPrioOutBuf.empty())
{
tmpData = m_lowPrioOutBuf.front();
m_lowPrioOutBuf.pop_front();
}
}
if (tmpData.first.get()) if (tmpData.first.get())
{ {
if (IS_VALID_SOCKET(tmpData.second)) if (IS_VALID_SOCKET(tmpData.second))
+3
View File
@@ -28,6 +28,7 @@
#include <core/crypthelper.h> #include <core/crypthelper.h>
#include <vector> #include <vector>
#include <list>
#define NET_VERSION_MAJOR 2 #define NET_VERSION_MAJOR 2
#define NET_VERSION_MINOR 0 #define NET_VERSION_MINOR 0
@@ -158,6 +159,8 @@ private:
const u_int16_t m_maxSize; const u_int16_t m_maxSize;
}; };
typedef std::list<boost::shared_ptr<NetPacket> > NetPacketList;
class NetPacketInit : public NetPacket class NetPacketInit : public NetPacket
{ {
public: public:
+14 -2
View File
@@ -31,7 +31,8 @@
#define SENDER_THREAD_TERMINATE_TIMEOUT 200 #define SENDER_THREAD_TERMINATE_TIMEOUT 200
#define SEND_TIMEOUT_MSEC 10 #define SEND_TIMEOUT_MSEC 10
#define SEND_QUEUE_SIZE 500 #define SEND_QUEUE_SIZE 1000
#define SEND_LOW_PRIO_QUEUE_SIZE 50000
class SenderThread : public Thread class SenderThread : public Thread
{ {
@@ -40,20 +41,31 @@ public:
virtual ~SenderThread(); virtual ~SenderThread();
void Send(SOCKET sock, boost::shared_ptr<NetPacket> packet); void Send(SOCKET sock, boost::shared_ptr<NetPacket> packet);
void Send(SOCKET sock, const NetPacketList &packetList);
void SendLowPrio(SOCKET sock, boost::shared_ptr<NetPacket> packet);
void SendLowPrio(SOCKET sock, const NetPacketList &packetList);
protected: protected:
typedef std::pair<boost::shared_ptr<NetPacket>, SOCKET> SendData;
typedef std::deque<SendData> SendDataDeque;
// Main function of the thread. // Main function of the thread.
virtual void Main(); virtual void Main();
void InternalStore(SendDataDeque &sendQueue, unsigned maxQueueSize, SOCKET sock, boost::shared_ptr<NetPacket> packet);
void InternalStore(SendDataDeque &sendQueue, unsigned maxQueueSize, SOCKET sock, const NetPacketList &packetList);
private: private:
SOCKET m_curSocket; SOCKET m_curSocket;
typedef std::pair<boost::shared_ptr<NetPacket>, SOCKET> SendData;
std::deque<SendData> m_outBuf; std::deque<SendData> m_outBuf;
mutable boost::mutex m_outBufMutex; mutable boost::mutex m_outBufMutex;
std::deque<SendData> m_lowPrioOutBuf;
mutable boost::mutex m_lowPrioOutBufMutex;
char m_tmpOutBuf[MAX_PACKET_SIZE]; char m_tmpOutBuf[MAX_PACKET_SIZE];
unsigned m_tmpOutBufSize; unsigned m_tmpOutBufSize;
+4 -3
View File
@@ -44,9 +44,10 @@ enum PlayerRights
enum AvatarFileType enum AvatarFileType
{ {
AVATAR_TYPE_PNG = 1, AVATAR_FILE_TYPE_UNKNOWN = 0,
AVATAR_TYPE_JPG, AVATAR_FILE_TYPE_PNG,
AVATAR_TYPE_GIF AVATAR_FILE_TYPE_JPG,
AVATAR_FILE_TYPE_GIF
}; };
struct PlayerInfo struct PlayerInfo