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