Contents
1
This class is
designed to replace the buggy
MFC
CCeSocket
class for the
WinCE
(Pocket PC)
platform. However, it works even on Win32 systems
(tested on Win2K and WinXP).
It allows to
create both TCP
or UDP socket
types operating in client or server configuration,
integrates a dynamic buffer that stores all incoming
data allowing delayed access, and provides async
events notification.
Asynchronous
notifications are triggered for:
- Incoming data
- Closed
connections
- Accepted
clients (only for TCP)
To read incoming
data, there are four possibilities:
- Direct read:
read data as they arrive. In this way, data is
not stored in the internal buffer.
- Binary read.
- Text read:
it's possible to read single strings.
- Packet read:
you can read single packets, exactly as they
were received.
To send data, you
can choose between:
- Binary mode.
- String mode.
- String mode
with auto EOL termination.
Other functions
let you specify the string EOL format, to query
socket state, to change UDP
or TCP receiving
buffer, and to query internal data buffer state.
2
The first time I
wrote a network application for a PDA, I realized
that the
CCeSocket
,
that comes with MFC,
was completely useless. It not only lacks
asynchronous notifications but it also consumes more
resources than is really necessary, and must be
wrapped in the
CSocketFile
/CArchive
framework to use advanced load/store operations. So
I decided to write a completely new socket wrapper
for the
WinCE
OS that is small, efficient, general purpose, and
with advanced read/send functions. I use it
extensively in a human robot interface for the
RoboCup Robot@Home league.
3
Before explaining
in detail how to use every single function of the
class, I think you should known that the
CCESocket
uses two independent threads, one for receiving
data, and another for accepting connections (if the
socket is
accepting). Both threads use blocking calls to
Winsock API functions to minimize CPU usage. You
should be aware of the existence of these threads
because they call events (virtual functions, see
Notifications), so when you receive an event,
you're on a different thread than the main one.
Creating a
socket
To use
CCESocket
,
you first have to call the
Create
function:
Collapse |
Copy Code
bool Create(int socketType, int bufferSize = 0);
mySocket->Create(SOCK_STREAM);
mySocket->Create(SOCK_DGRAM);
mySocket->Create(SOCK_STREAM, 4096);
socketType
:
can be
SOCK_STREAM
for TCP, or
SOCK_DGRAM
for UDP.
bufferSize
:
you can specify the buffer size for incoming
packets. Otherwise, the default values will be
used: 1024 bytes for
TCP, and 2048 bytes for UDP.
Return value is
TRUE
if the
socket is created successfully,
FALSE
if an error occurs. You can always query the last
error code with the function:
Collapse |
Copy Code
int GetLastError()
Now, you can
decide to make a client or a server socket.
Making a client
socket
To make a client
socket, call:
Collapse |
Copy Code
bool Connect(CString &addr, UINT remotePort);
mySocket->Connect("192.168.0.20", 3000);
mySocket->Connect("www.someServer.com", 5003);
addr
:
is the remote host address. It can be an IP
number or the host name.
remotePort
:
is the remote host port to connect to.
Return value is
TRUE
if the
connection was successfully established,
FALSE
if an error occurs.
-
TCP
socket:
this function really connects the two computers.
In this case, if you want to make a new
connection, you have to
disconnect the socket first.
-
UDP
socket:
CCESocket
doesn't establish a real connection, it only
saves the passed address to the sent data. In
this case, you can reconnect to another address
without first disconnecting.
Now, you can use
your socket to
send and
read data. However, you should first learn how
notifications (events) work, otherwise you won't
know when new data is available for reading or if
your socket is disconnected for some reason.
Making a server
socket
To make a server
socket, call:
Collapse |
Copy Code
bool Accept(UINT localPort, int maxConn = SOMAXCONN);
mySocket->Accept(3000);
mySocket->Accept(5003, 1);
localPort
:
is the local port to bind for incoming requests.
maxConn
:
is the maximum length of the queue for pending
connections.
Return value is
TRUE
if
Accept
is successful,
FALSE
if an error occurs. This function will bind the
socket to a local port waiting for connections.
It provides a
service socket
that can be used to accept the connection.
You'll notice that
this is a virtual function. All events are virtual
functions. In fact, you cannot use
CCESocket
as is, you must subclass it and redefine virtual
functions for the events that you want to catch.
So, when
CCESocket
calls your
OnAccept
function, you can refuse the connection, returning
FALSE
,
or accept the connection, returning
TRUE
.
If you accept the connection, you must create a new
(subclassed)
CCESocket
and
pass the service socket to its
AcceptServiceSocket
function (you do not have to call the
Create
function first):
Collapse |
Copy Code
void AcceptServiceSocket(SOCKET serviceSocket);
Now, you can use
this new socket to
receive and
send messages.
A quick example (for
TCP)
This is a quick
example to explain the
OnAccept
/AcceptServiceSocket
functions.
Collapse |
Copy Code
class CMySocket : public CCESocket
{
public:
CMySocket();
CMySocket(CWnd* parent);
virtual ~CMySocket();
void SetParent(CWnd* parent) {m_parent = parent;}
virtual bool OnAccept(SOCKET serviceSocket);
virtual void OnReceive();
virtual void OnClose(int closeEvent);
protected:
CWnd* m_parent;
};
CMySocket::CMySocket() : CCESocket()
{
m_parent = NULL;
}
CMySocket::CMySocket(CWnd* parent) : CCESocket()
{
m_parent = parent;
}
CMySocket::~CMySocket()
{
}
bool CMySocket::OnAccept(SOCKET serviceSocket)
{
if(m_parent)
{
::PostMessage(m_parent->m_hWnd, ON_ACCEPT,
(WPARAM) serviceSocket, (LPARAM) this);
return TRUE;
}
return FALSE;
}
void CMySocket::OnReceive()
{
if(m_parent)
::PostMessage(m_parent->m_hWnd,
ON_RECEIVE, NULL, (LPARAM) this);
}
void CMySocket::OnClose(int closeEvent)
{
if(m_parent)
::PostMessage(m_parent->m_hWnd, ON_CLOSE,
(WPARAM) closeEvent, (LPARAM) this);
}
ON_ACCEPT
,
ON_RECEIVE
,
and ON_CLOSE
are window messages passed to the main application.
The latter could be something like this (note: the
code is dirty and is intended only to show the
necessary steps to set up a server):
Collapse |
Copy Code
class MyApp : public CDialog
{
public:
...
afx_msg LRESULT OnAccept(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnReceiveData(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnDisconnected(WPARAM wParam, LPARAM lParam);
...
protected:
CMySocket *m_server;
CMySocket *m_serviceSocket;
...
};
BOOL MyApp::OnInitDialog()
{
bool serverStarted;
...
m_server = new CMySocket(this);
if(serverStarted = m_server->Create(SOCK_STREAM))
serverStarted = m_server->Accept(somePortNumber);
if(!serverStarted)
...
}
LRESULT MyApp::OnAccept(WPARAM wParam, LPARAM lParam)
{
m_serviceSocket = new CMySocket(this);
m_serviceSocket->AcceptServiceSocket((SOCKET) wParam);
m_serviceSocket->SendLine("Hello!");
return 0;
}
OnAccept
,
OnReceiveData
,
and OnDisconnect
are triggered by the
ON_ACCEPT
,
ON_RECEIVE
,
and ON_CLOSE
events posted by
CMySocket
.
However, I only defined the
OnAccept
function for this example. I think the code is so
simple that it doesn't need any comment :-)
Disconnecting
To disconnect a
socket, you can call:
Collapse |
Copy Code
void Disconnect();
mySocket->Disconnect();
After a
disconnect, you must call
Create
again if you want to use the socket again.
Notifications
We have already
seen the
OnAccept
event. Let's now analyse
OnReceive
and OnClose
.
To receive these events, you have to subclass
CCESocket
and provide new virtual functions, as already seen
in the
CMySocket
example class.
As soon as you get
new data, the following event is called (if you have
redefined its virtual function):
Collapse |
Copy Code
virtual bool OnReceive(char* buf, int len);
buf
:
is the received data packet.
len
:
it is the packet length.
This notification
is called directly from the receiving thread to
notify that there is new data in the queue. This is
the first notification of this kind, and offers the
possibility to get the data without buffering it. If
you accept the packet, then return
TRUE
.
In this case, you're responsible for deleting the
buffer after its use. If you refuse the data, then
return FALSE
.
In this case, the packet will be stored in the
internal buffer and you'll receive a new
notification:
Collapse |
Copy Code
virtual void OnReceive();
This event only
notifies you that there is data in the buffer. I'll
show you later how to
read data from the buffer.
If a connection
closes, for any reason, you receive:
Collapse |
Copy Code
virtual void OnClose(int closeEvent);
closeEvent
:
it is an enum that can be:
CCESocket::EVN_CONNCLOSED
(=0)
CCESocket::EVN_CONNLOST
(=1)
CCESocket::EVN_SERVERDOWN
(=2)
-
TCP socket:
you receive
EVN_CONNCLOSED
if the other peer disconnects. If you are a
client and wants to make a new connection, call
Create
followed by
Connect
.
If you are a server, then simply release
(delete) the service socket.
-
UDP socket:
you receive
EVN_CONNLOST
if the other peer "disconnects". This happens
only if you try to send data to a disconnected
peer (to my knowledge, this should not happen,
however, this is what I experimented using the
Winsock APIs). If you are a client and wants to
make a new connection, call
Connect
.
If you are a server, you can use or ignore the
event, it doesn't interfere with the server
operation. Note that if you continue to send
data to a disconnected peer,
CCESocket
will ignore your trials, and
send functions will return 0 sent bytes.
If your socket is
a server and, for some reason, the read (UDP)
or accept (TCP)
thread fails, both TCP
or UDP
sockets will
return an
EVN_SERVERDOWN
event. In this case, to respawn the server, you must
call Create
(only if TCP)
followed by
Accept
:
Collapse |
Copy Code
LRESULT MyApp::OnDisconnected(WPARAM wParam, LPARAM lParam)
{
CCESocket *disconnectedSocket = (CCESocket*) lParam;
if(disconnectedSocket == m_server)
{
Sleep(100);
if(m_server->Create(SOCK_STREAM))
if(m_server->Accept(somePortNumber))
return 0;
}
return 0;
}
As stated at the
beginning, keep in mind that these functions are
called from another thread, not from the main
application thread. If you need to execute something
in the window thread, you should send a message to
it with
PostMessage
(as seen in the
CMySocket
example). This is required for MFC objects. They
don't work if passed among threads, you should use
these objects in the same thread where they are
defined.
Reading data
You already know
one of the four ways to read data: direct read
through the first
OnReceive
event. The remaining functions access data directly
from the internal buffer. You'll typically call one
of them after receiving the
OnReceive
event. Data remains available even after a socket
disconnection.
You can read
binary data using:
Collapse |
Copy Code
int Read(char* buf, int len);
char *buf = NULL;
int count;
int len = mySocket->GetDataSize();
if(len > 0)
{
buf = new char[len];
count = mySocket->Read(buf, len);
}
buf
:
the buffer that will receive the data. It must
be already allocated.
len
:
buffer size.
Return value is
the number of bytes actually read.
To read a string,
use the following function:
Collapse |
Copy Code
bool ReadString(CString &str);
CString str;
while(mySocket->GetDataSize() > 0 &&
mySocket->ReadString(str))
doSomethingWithTheString(str);
Return value is
TRUE
if the
string was read,
FALSE
if there
was no string to read. A string is an array of bytes
that begins at the current buffer location and
terminates with an EOL. This value can be set with
the
SetEolFormat
function (see later).
To read the head
packet stored in the buffer, use:
Collapse |
Copy Code
bool GetPacket(char*& buf, int* len);
char *buf = NULL;
int len;
while(mySocket->GetNumPackets() > 0)
{
if(mySocket->GetPacket(buf, &len))
useTheData(buf, len);
if(buf != NULL)
delete[] buf;
}
buf
:
a pointer to the buffer that will contain the
packet data. The buffer will be internally
allocated to match the packet size. You have to
delete it after its use.
len
:
it will contain the buffer size.
Return value is
TRUE
if the
packet was successfully retrieved,
FALSE
if there was no packet to retrieve.
Sending data
You have three
functions to send data.
Binary send:
Collapse |
Copy Code
int Send(const char* buf, int len);
int count;
count = mySocket->Send(myBuffer, myBufferSize);
buf
:
the buffer that contains the data to send.
len
:
buffer size.
Return value is
the number of bytes sent, or
SOCKET_ERROR
if an error occurs. Note: you cannot send data with
a listening TCP socket.
To send a string,
use one of the following functions:
Collapse |
Copy Code
int Send(CString& str);
int SendLine(CString &str);
CString msg = "My beautiful string";
mySocket->Send(msg);
mySocket->Send("Hello!");
mySocket->SendLine(msg);
Both send the
string
str
.
However, the latter adds an EOL to the string. The
EOL is set with the
SetEolFormat
function (see later).
Other
functionalities
Data query
functions
Collapse |
Copy Code
int GetDataSize();
Returns the total
data size queued in the buffer.
Collapse |
Copy Code
int GetNumPackets();
Returns the number
of packets queued in the buffer.
Collapse |
Copy Code
int GetPacketSize();
Returns the size
of the head packet in the queue (next packet to to
be retrieved).
Socket
query functions
Collapse |
Copy Code
int GetLastError();
Returns the error
code of the last error.
Collapse |
Copy Code
int GetSocketType();
Returns
SOCK_STREAM
for
TCP sockets,
SOCK_DGRAM
for UDP sockets.
Collapse |
Copy Code
int GetSocketState();
Returns one of the
following states:
CCESocket::NONE
(=0)
CCESocket::DISCONNECTED
(=1)
CCESocket::CREATED
(=2)
CCESocket::CONNECTED
(=3)
CCESocket::ACCEPTING
(=4)
Other
functions
Collapse |
Copy Code
void SetBufferSize(int bufSize);
bufSize
:
new buffer size.
Call this function
to modify the buffer size. You can call it at any
time, even if the socket is already connected. This
is useful if you want to change the buffer size
after a
AcceptServiceSocket
call.
Collapse |
Copy Code
void SetEolFormat(eolFormat eol);
eolFormat
:
may be one the following:
CCESocket::EOL_NULL
(=0)
CCESocket::EOL_LFCR
(=1)
CCESocket::EOL_CR
(=2)
Sets the new EOL
format for
SendLine
and ReadString
functions.
News:
1
UCanCode Advance E-XD++
CAD Drawing and Printing Solution
Source Code Solution for C/C++, .NET V2023 is released!
2
UCanCode Advance E-XD++
HMI & SCADA Source Code Solution for C/C++, .NET V2023 is released!
3
UCanCode
Advance E-XD++ GIS SVG Drawing and Printing Solution
Source Code Solution for C/C++, .NET V2023 is released!
Contact UCanCode Software
To buy the source code or learn more about with: