OOSMOS Sockets Example
1. Introduction
2. Sockets on Windows and Linux

1. Introduction

Combining OOSMOS's set of Thread functions and the reusable sock class, this example demonstrates how to connect and communicate over many connections at the same time using only OOSMOS objects and without the use of RTOS threads.
In this example, we create client objects that connect to a waiting listener object which then creates a corresponding server object. Thus the client object and the new server object have a peer-to-peer connection. The client object then repeatedly sends data to the server object which then echoes the data back to the client. This continues until the programs are forcibly terminated.
You'll find the full source code for all the objects of the example below. The code depends on the reusable sock class, so you will want to familiarize yourself with that class before you can effectively read the example code.
Refer to the Object Diagram below for an overview of how the objects interact:
  • clienttest.c - Is the client side main program. Its job is simple: call clientNew for each desired client connection.
  • client - Each client attempts to connect to the port that a listener is listening on. Once the connection is established, it sends some data to the server and then waits for a response. It does this endlessly until the program is forcibly terminated. E.g. the user presses CNTL-C or closes the window.
  • servertest.c - Is the server side main program. It creates a single listener object to which clients will attempt to connect. This is done by calling listenerNew. The arguments to listenerNew are the TCP port to listen on as well as the address of a routine to call when a new client connection request is made.
  • listener - The listener object waits for incoming connection request and then calls a callback routine (called SpawnServer) which is implemented in servertest.c.
  • server - Server objects are created in servertest.c by function SpawnServer as new connection requests come in from clients. Once created, each server is now paired with a client. Because the client sends data and then waits for a response, the server does the complement of that and waits for data and then send it back to the client.

1.1 Object Diagram

Sockets Example Object Diagram

1.2 client.h

1
22
23
24
25
26
27
28
29
30
31
[GPLv2]
 
#ifndef client_h
#define client_h
 
typedef struct clientTag client;
 
extern client * clientNew(const char * pHost, unsigned Port);
extern void clientDelete(client * pClient);
 
#endif
oosmos/Classes/Tests/Sockets/client.h

1.3 client.c

client.c implements the client class. The object has only one state, Running, that uses an oosmos_POLL event to make thread calls.
On line 52, we call sockConnect inside an oosmos_ThreadWaitCond_TimeoutMS_Event thread call to connect to the server.
Once the connection is established, we drop down into the for loop and immediately send the string 123456 to the server. OOSMOS waits on lines line 65 until the data is delivered.
Then, we wait on lines line 69 as we receive the echoed data back from the server.
We do this forever inside the for loop beginning on line 61.
1
22
23
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
[GPLv2]
 
#includes...
 
enum
{
ConnectionTimeoutEvent = 1,
ClosedEvent
};
 
struct clientTag
{
oosmos_sStateMachine(StateMachine, oosmos_sEvent, 1);
oosmos_sLeaf Running_State;
 
const char * m_pHost;
unsigned m_Port;
sock * m_pSock;
char m_Buffer[100];
};
 
static void ClientThread(client * pClient, oosmos_sState * pState, uint32_t IP_HostByteOrder)
{
bool TimedOut;
 
oosmos_ThreadBegin();
oosmos_ThreadWaitCond_TimeoutMS(sockConnect(pClient->m_pSock, IP_HostByteOrder, pClient->m_Port), 2000, &TimedOut);
 
if (TimedOut) {
printf("%p: Unable to connect to server: Timed out. Terminating.\n", (void *) pClient->m_pSock);
exit(1);
}
 
printf("%p: CONNECTED\n", (void *) pClient->m_pSock);
 
for (;;) {
size_t BytesReceived;
 
printf("%p: Sending...\n", (void *) pClient->m_pSock);
oosmos_ThreadWaitCond(sockSend(pClient->m_pSock, "123456", sizeof("123456")));
 
printf("%p: Waiting for incoming data...\n", (void *) pClient->m_pSock);
 
oosmos_ThreadWaitCond(sockReceive(pClient->m_pSock, pClient->m_Buffer, sizeof(pClient->m_Buffer), &BytesReceived));
printf("%p: Client side Received '%s', BytesReceived: %u\n", (void *) pClient->m_pSock, pClient->m_Buffer, (unsigned) BytesReceived);
}
oosmos_ThreadEnd();
}
 
static bool Running_State_Code(void * pObject, oosmos_sState * pState, const oosmos_sEvent * pEvent)
{
client * pClient = (client *) pObject;
 
switch (oosmos_EventCode(pEvent)) {
case oosmos_POLL: {
const uint32_t IP_HostByteOrder = sockDotToIP_HostByteOrder(pClient->m_pHost);
ClientThread(pClient, pState, IP_HostByteOrder);
return true;
}
 
case ClosedEvent: {
printf("Server closed. Terminating...\n");
exit(1);
}
}
 
return false;
}
 
extern client * clientNew(const char * pHost, unsigned Port)
{
client * pClient = (client *) malloc(sizeof(client));
 
// StateName Parent
// ===========================================================
oosmos_StateMachineInit(pClient, StateMachine, NULL, Running_State );
oosmos_LeafInit (pClient, Running_State, StateMachine, Running_State_Code);
 
pClient->m_pSock = sockNew();
pClient->m_pHost = pHost;
pClient->m_Port = Port;
 
sockSubscribeClosedEvent(pClient->m_pSock, oosmos_EventQueue(pClient), ClosedEvent, NULL);
 
return pClient;
}
 
extern void clientDelete(client * pClient)
{
printf("In clientDelete\n");
oosmos_StateMachineDetach(pClient, StateMachine);
free(pClient);
}
oosmos/Classes/Tests/Sockets/client.c

1.4 server.h

1
22
23
24
25
26
27
28
29
30
31
32
33
[GPLv2]
 
#ifndef server_h
#define server_h
 
#include "sock.h"
 
typedef struct serverTag server;
 
extern server * serverNew(sock * pSock);
extern void serverDelete(server * pServer);
 
#endif
oosmos/Classes/Tests/Sockets/server.h

1.5 server.c

server.c implements the server class. The object has only one state, Running, that uses an oosmos_POLL event to make thread calls.
On line 50, we wait at a sockReceive call inside an oosmos_ThreadWaitCond thread call for data to be delivered by a client.
On line line 53, we send the data back to the client.
All of this is done forever inside the for loop starting on line 48.
1
22
23
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
[GPLv2]
 
#includes...
 
enum
{
ClosedEvent = 1
};
 
struct serverTag
{
oosmos_sStateMachine(StateMachine, oosmos_sEvent, 1);
oosmos_sLeaf Running_State;
 
sock * m_pSock;
size_t m_BytesReceived;
char m_Buffer[100];
};
 
static void IncomingThread(server * pServer, oosmos_sState * pState)
{
oosmos_ThreadBegin();
for (;;) {
printf("Waiting for incoming data...\n");
oosmos_ThreadWaitCond(sockReceive(pServer->m_pSock, pServer->m_Buffer, sizeof(pServer->m_Buffer), &pServer->m_BytesReceived));
printf("Server side, String: '%s', BytesReceived: %u\n", pServer->m_Buffer, (unsigned) pServer->m_BytesReceived);
 
oosmos_ThreadWaitCond(sockSend(pServer->m_pSock, pServer->m_Buffer, pServer->m_BytesReceived));
}
oosmos_ThreadEnd();
}
 
static bool Running_State_Code(void * pObject, oosmos_sState * pState, const oosmos_sEvent * pEvent)
{
server * pServer = (server *) pObject;
 
switch (oosmos_EventCode(pEvent)) {
case oosmos_POLL: {
IncomingThread(pServer, pState);
return true;
}
case ClosedEvent: {
serverDelete(pServer);
return true;
}
}
 
return false;
}
 
extern server * serverNew(sock * pSock)
{
server * pServer = (server *) malloc(sizeof(server));
 
printf("New Server %p.\n", (void *) pServer);
 
// StateName Parent
// ===========================================================
oosmos_StateMachineInit(pServer, StateMachine, NULL, Running_State );
oosmos_LeafInit (pServer, Running_State, StateMachine, Running_State_Code);
 
pServer->m_pSock = pSock;
sockSubscribeClosedEvent(pSock, oosmos_EventQueue(pServer), ClosedEvent, NULL);
 
return pServer;
}
 
extern void serverDelete(server * pServer)
{
oosmos_StateMachineDetach(pServer, StateMachine);
 
printf("Delete Server %p.\n", (void *) pServer);
sockDelete(pServer->m_pSock);
 
// Note: Once an oosmos object is created and registered on the object list, it can not be deleted.
}
oosmos/Classes/Tests/Sockets/server.c

1.6 listener.h

1
22
23
24
25
26
27
28
29
30
31
32
[GPLv2]
 
#ifndef listener_h
#define listener_h
 
#include "sock.h"
 
typedef struct listenerTag listener;
 
extern listener * listenerNew(int Port, void (*pNewConnection)(sock *));
 
#endif
oosmos/Classes/Tests/Sockets/listener.h

1.7 listener.c

listener.c implements the listener class. The object has only one state, Running. As we enter the Running state, we issue the sockListen function (line 59) to establish the TCP port to which clients will connect.
On line 47, we wait for connection requests from clients. When a connection is established, variable pNewSock will hold a pointer to a new sock object.
With this sock object, we call a user function that will create a new server object (line 47). See servertest.c.
1
22
23
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
[GPLv2]
 
#includes...
 
struct listenerTag
{
oosmos_sStateMachineNoQueue(StateMachine);
oosmos_sLeaf Running_State;
 
sock * m_pSock;
void (*m_pAcceptedFunc)(sock *);
};
 
static void ListeningThread(listener * pListener, oosmos_sState * pState)
{
oosmos_ThreadBegin();
for (;;) {
sock * pNewSock;
 
printf("Waiting for incoming connections...\n");
 
oosmos_ThreadWaitCond(sockAccepted(pListener->m_pSock, &pNewSock));
pListener->m_pAcceptedFunc(pNewSock);
}
oosmos_ThreadEnd();
}
 
static bool Running_State_Code(void * pObject, oosmos_sState * pState, const oosmos_sEvent * pEvent)
{
listener * pListener = (listener *) pObject;
 
switch (oosmos_EventCode(pEvent)) {
case oosmos_ENTER: {
sockListen(pListener->m_pSock, 60009, 50);
return true;
}
case oosmos_POLL: {
ListeningThread(pListener, pState);
return true;
}
}
 
return false;
}
 
extern listener * listenerNew(int Port, void (*pAcceptedFunc)(sock *))
{
oosmos_Allocate(pListener, listener, 1, NULL);
 
// StateName Parent
// =============================================================
oosmos_StateMachineInitNoQueue(pListener, StateMachine, NULL, Running_State );
oosmos_LeafInit (pListener, Running_State, StateMachine, Running_State_Code);
 
pListener->m_pSock = sockNew();
pListener->m_pAcceptedFunc = pAcceptedFunc;
 
return pListener;
}
oosmos/Classes/Tests/Sockets/listener.c

2. Sockets on Windows and Linux

This example has been tested on Windows (Microsoft's Visual C++ 2008 Express Edition command line compiler), Linux (Raspberry Pi, Red Hat and Mac OS X).

2.1 clienttest.c

clienttest.c contains the client-side main program. It simply creates a number of client objects passing the IP and port number of a listener object to which each client will connect (line 31).
Once the client objects are created, we then drop into the endless loop to call oosmos_RunStateMachines. Because we are running on either Windows or Linux, we don't own the processor, so we relinquish control for a number of milliseconds. The net effect is that this example program takes a very small amount of CPU time.
1
22
23
27
28
29
30
31
32
33
34
35
36
37
38
[GPLv2]
 
#includes...
 
extern int main(int argc, char *argv[])
{
for (unsigned Count = 1; Count <= 5; Count += 1) {
clientNew("127.0.0.1", 60009);
}
 
for (;;) {
oosmos_RunStateMachines();
oosmos_DelayMS(1);
}
}
oosmos/Classes/Tests/Sockets/clienttest.c

2.2 servertest.c

servertest.c contains the server-side main program. Initially it creates a listener object with two arguments: 1) the TCP port number on which to listen for connection requests and 2) the address of a subroutine to call when new connection requests come in (line 37).
We supply the address of a local function, SpawnServer, as the second argument (lines 30-33), which calls serverNew to create a new server object.
1
22
23
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[GPLv2]
 
#includes...
 
static void SpawnServer(sock * pSock)
{
serverNew(pSock);
}
 
extern int main(int argc, char *argv[])
{
listenerNew(60009, SpawnServer);
 
for (;;) {
oosmos_RunStateMachines();
oosmos_DelayMS(1);
}
}
oosmos/Classes/Tests/Sockets/servertest.c
Copyright © 2014-2024  OOSMOS, LLC