Stockyard Code Walkthrough

Both the client and server programs are written and compiled in Visual C++.  I've also written a database management utility (Stockyard Manager) in C#/.NET.  The two main classes in the program are syUser for the client and syServer on the server: syUser serves as the main window for the client and syServer is the controlling thread on the server side.  All communications between the client and server are handled by these two classes; therefore, these classes are both derived from CAsyncSocket.  

class syUser :
    public CDialog, public CAsyncSocket
{
    ...
    bool OpenSocket();

    void OnConnect(int nErrorCode);
    void OnReceive(int nErrorCode);
    void OnClose(int nErrorCode);

    ...
}

 

class syServer :
    public CWinThread, public CAsyncSocket
{
    ...
    InitInstance();

    void OnReceive(int nErrorCode);
    void OnAccept(int nErrorCode);
    void OnClose(int nErrorCode);

    ...
}

 
On server startup, a socket is opened from InitInstance() to listen for a client request.
 

 
 

BOOL syServer::InitInstance()
{
    if (!OpenDataStore())
        return FALSE;

    ...

    if (!AfxSocketInit())
        return FALSE;

    if (!CAsyncSocket::Create(SY_PORTNUM, SOCK_STREAM,
        FD_READ | FD_WRITE | FD_ACCEPT | FD_CLOSE, 0))
    {
        Log("Server: Socket Creation Error %d", GetLastError());
        return FALSE;
    }

    if (!Listen(1))
    {
        Log("Server: Socket Connect Error %d", GetLastError());
        return FALSE;
    }

    ...
}

 


When a client is detected, the MFC framework calls the OnAccept() automatically.  The class keeps track of two socket handles: one to listen exclusively and the other to receive client messages.  I only allow one client to connect, so the listening socket is detached from the class and replaced with the other.
 

 

void syServer::OnAccept
(
    int nErrorCode
)
{
    CAsyncSocket acceptSock;
    if (!nErrorCode)
    {
        if (Accept(acceptSock,0,0) == SOCKET_ERROR)
            TRACE("Send Error %d\n", GetLastError());

        listenSock = CAsyncSocket::Detach();
        CAsyncSocket::Attach(acceptSock.Detach());
        clientFlag = true;

        Log("Server: Client connection accepted.");
    }
}

 


When the client starts, it too tries to open a connection.  When the server is detected, OnConnect() gets called then the both sides can begin communicating with each other.
 

 

bool syUser::OpenSocket()
{
    if (!CAsyncSocket::Create())
    {
        TRACE("syUser: Socket Create Error %d\n", GetLastError());
        return false;
    }

    AsyncSelect(FD_READ | FD_WRITE | FD_CLOSE | FD_CONNECT);
    if (!Connect("127.0.0.1", SY_PORTNUM))
    {
        int ret = GetLastError();
        if (ret != WSAEWOULDBLOCK) // there could be msgs in the socket
        {
            TRACE("syUser: Socket Connect Error %d\n", GetLastError());
            return false;
        }
    }

    return true;
}
 

 


Shared Memory

OpenDataStore() initiates a memory file which acts as shared memory between client and server.  The number of stocks helps determine the size of the memory region.  A pointer to the region is casted onto a global object whose class definition layouts the entire memory region.  This simplifies accessing the memory, which basically amounts to referencing a member variable of the object, although offsets must be used rather than direct memory addressing.  Also the use of the new and delete operators as well as constructors cannot be used.
 

 

class dtBase
{
public:
    int         storeSize;
    short       svrState;
    short       clock;
    int         clockDay;
    struct tm   clockInfo;
    bool        speedFlg;
    dtOptions   Options;
    dtAccount   Account[2];
    dtConsole   Log;
    dtConsole   News;
    dtConsole   Analyzer;
    char        dbMode;
    int         stockCount;
    int         maxStock;
    int         scFlagCount;
    int         posCount;
    int         posPoolSize;
    int         positions;
    dtStock     stocks;

    void Init();
} *DT;

bool syServer::OpenDataStore
(
    int storeSize
)
{
    svrRegistry sReg;

    if (!DBS.Login(sReg.dbUser, sReg.dbPwd))
        return false;

    if (!storeSize)
        storeSize = sizeof(dtBase) + (DBS.GetStockCount() * sizeof(dtStock)) +
            (sizeof(dtPosition) * DTSTORE_POSPOOL);

    hDataStore = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
        0, storeSize, DTSTORE_NAME);

    if (hDataStore == NULL || hDataStore == INVALID_HANDLE_VALUE)
    {
        TRACE("Could not create file mapping object (%d).\n",
            GetLastError());
        return false;
    }

    DT = (dtBase *)MapViewOfFile(hDataStore, FILE_MAP_ALL_ACCESS,
        0, 0, storeSize);

    if (DT == NULL)
    {
        TRACE("Could not map view of file (%d).\n",
            GetLastError());
 
        return false;
    }

    DT->storeSize = storeSize;
    DT->Init();
    return true;
}

 


Each stock and position is identified with a unique numeric key which also serves as a primary key in the relational database.  When the shared memory is fully loaded, both client and server save keys and their references into their own CMap objects.  Whenever an action is needed on a specific stock or position, one program communicates the key and the type of action to the other program.  For example, the server usually sends update messages to the client notifying it that data has changed for a stock.  The server sends the command along with the stock key to the client, which in turn uses the key to access shared memory for that particular stock.
 

 

void uiDatabase::Load()
{
    int i;
    dtStock *srec;

    for(i=0;i<DT->stockCount;i++)
    {
        srec = &((&DT->stocks)[i]);
        stockBase.SetAt(srec->sid, srec);
    }
}

dtStock * uiDatabase::ReadStock( STOCKID id)
{
    dtStock *rec;

    if (!id || !stockBase.Lookup(id,rec))
        return NULL;

    return rec;
}

uiDatabase DBU;

void winStock::OnUpdate(STOCKID sid)
{
    dtStock *rec;
    UI.UpdateDetail(sid);
    rec = DBU.ReadStock(sid);
    short flg;

    if (rec->change > 0)
        flg = BCT_GREEN;
    else if (rec->change < 0)
        flg = BCT_RED;
    else
        flg = 0;

    if (m_List.IsColumnStatic())
        m_List.UpdateFlag(sid,flg);
    else
        m_List.UpdateRecord(sid,flg);
}

 


...And the Rest

There is so much code to go through, so I'll just list them here with a brief description.  I apologize for not commenting the code since I'm just a one-man development team.  Feel free to ask me questions by email to reymano@yahoo.com.

Files

Description

RwPropCtrl.h
RwPropCtrl.cpp

Displays a list of attributes and their corresponding values.  The control uses references to original data, and therefore does not need to make a copy for itself.

svrBroker.h
svrBroker.cpp

A wrapper class for the MyTrack API.

svrQueues.h
svrQueues.cpp

The server's queuing system.  There are four queues: three market and one for special purpose.  Three worker threads wait on the market queues by using MFC's CEvent and CCriticalSection mechanisms.

trdRouter.h
trdRouter.cpp

The worker thread responsible for accepting messages from the brokerage system and directing them to the appropriate message queue for other threads to process.

uiChartCtrl.h
uiChartCrtl.cpp

Displays stock data in chart form.  The program offers four zoom levels (hour, day, 7-day, and 12-week) and can display hi/low/avg prices, volume, rating, and trend movements.  You can also select a particular time on the chart to see the price and volume for that moment.

uiTickerCtrl.h
uiTickerCtrl.cpp

Displays recent ticker prices.  Trades on the bid price are shown in red; trades on the ask are green.  I removed the hour and dollar values from the display so I can cram as many ticker updates on there.

 

1