Hello everyone! I want to share with the public the framework on the basis of which there are currently many servers serving thousands of clients in various server systems. exsc (EXtensible Server Core) is a framework written in C and allows you to have one or more server threads within one application. Each server thread is capable of serving a large number of clients. Although the framework can be used in a request-response model, it was primarily designed to maintain a constant connection with a large number of clients and exchange messages in real time. Since I myself like to take a ready-made HelloWorld project, compile it and see how everything works, at the end of the article I will post a link to such a project.
Many operations are done for a specific connection. Within the framework of this framework, the exsc_excon structure is responsible for the connection. This structure has the following fields:
ix - connection index. This is the serial number of the connection that was free at the time the client connected.
id - connection identifier. This is a unique connection number. Unlike an index, it does not repeat itself.
addr - client IP address
name - connection name. Multiple connections can be named with the same name and then some message can be sent to all connections with the same name (see exsc_sendbyname function).
Kernel initialization
In order to work with the kernel, we need to initialize it using the function
void exsc_init(int maxsrvcnt);
The maxsrvcnt parameter tells the kernel how many server threads we will use within our application.
Starting the server stream
Next, we need to start the server stream using the function
int exsc_start(uint16_t port, int timeout, int timeframe, int recvbufsize, int concnt, void newcon(struct exsc_excon excon), void closecon(struct exsc_excon excon), void recv(struct exsc_excon excon, char *buf, int bufsize), void ext());
port - the port that will listen to the server stream.
timeout - indicates how long the server thread will wait for any activity from the client. If during this time the client has not sent any messages, then the server thread closes such a connection. Therefore, if we want to keep a constant connection and set this parameter for example 30 seconds, then it is necessary to send any ping message every 10-15 seconds.
timeframe- the time frame for which we allow the request to be executed. For example, if this value is set to 100 milliseconds and the server thread has processed all current requests from users in 10 seconds, then it will leave the remaining 90 milliseconds to the processor to perform other tasks. Thus, the smaller this value, the faster the server thread will process requests, but the more it will load the processor.
recvbufsize - the size of the buffer that the server thread will read at a time.
concnt - the maximum number of connections with which the server thread works simultaneously.
newcon- callback function that will work every time a new client connects. The parameters of this function will be passed the connection of the client that connected.
closecon is a callback function that will run every time an inactive connection is closed. The parameters of this function will be passed the connection of the client that disconnected.
recv is a callback function that will be called when the client sends packets. The parameters of this function will be passed the connection of the client from which the data came, a pointer to the data and the size of the buffer with data.
ext- callback function that will be called each pass of the server thread loop. This function is made to extend the functionality of the kernel. For example, you can link the processing of timers here.
The exsc_start function returns a handle to the server thread, which will be needed to call some of the framework's functions.
Sending messages
The function is responsible for sending messages.
void exsc_send(int des, struct exsc_excon *excon, char *buf, int bufsize);
This function is thread safe (you can call it from any thread). As parameters, you need to pass it the handle of the server stream (which we received as the return value of the exsc_start function ), the connection to which we want to send a message, a pointer to the buffer with the message and the size of the buffer.
We also have the opportunity to send a message to a group of clients. There is a function for this
void exsc_sendbyname(int des, char *conname, char *buf, int bufsize);
It is similar to the exsc_send function, except for the second parameter, which is the name of the connections to which the message will be sent.
Setting the connection name
In order to further identify the connection in the future, or store some information about it with the connection, or send messages to a group of clients, use the function
void exsc_setconname(int des, struct exsc_excon *excon, char *name);
This function is thread safe. The first parameter is the handle of the server stream, the second parameter is the connection itself, and the third parameter is the name of this connection.
Connecting a server stream to another server
Sometimes, server-side logic requires you to connect to another server in order to request or transmit any data. For such tasks, a function was introduced that creates such a connection.
void exsc_connect(int des, const char *addr, uint16_t port, struct exsc_excon *excon);
This function is thread safe. As parameters, we need to pass the handle of the server stream, the address of the server to which we need to connect, the server consumption to which we need to connect, and as the last parameter we pass the connection pointer with which we can later call other functions of the framework. It is worth noting that we do not need to wait for the connection to take place. We can call the exsc_connect and exsc_send functions one after the other and the system will make sure that the message is sent as soon as it can connect to the remote server.
Example server with comments
#include <stdio.h>
#include <string.h>
#include "../exnetwork/exsc.h"
int g_des; //
//
void exsc_newcon(struct exsc_excon con)
{
printf("the connection was open %s\n", con.addr);
}
//
void exsc_closecon(struct exsc_excon con)
{
printf("the connection was closed %s\n", con.addr);
}
//
void exsc_recv(struct exsc_excon con, char *buf, int bufsize)
{
char msg[512] = { 0 };
memcpy(msg, buf, bufsize);
printf("receive data from %s\n%s\n", con.addr, msg);
if (strcmp(msg, "say hello") == 0)
{
strcpy(msg, "hello");
exsc_send(g_des, &con, msg, strlen(msg));
}
}
//
void exsc_ext()
{
}
int main()
{
printf("server_test_0 is started\n");
exsc_init(2); //
// 7777
// 30
// 10
// 1024
// 10000
g_des = exsc_start(7777, 30, 10, 1024, 10000, exsc_newcon, exsc_closecon, exsc_recv, exsc_ext);
// ,
// exit ENTER
while (1)
{
const int cmdmaxlen = 256;
char cmd[cmdmaxlen];
fgets(cmd, cmdmaxlen, stdin);
if (strcmp(cmd, "exit\n") == 0)
{
break;
}
}
return 0;
}
Conclusion
The exsc kernel does only a low level of interaction with clients. Although this is the most important element of the server system, the foundation on which everything is built, in addition to it, you need to build higher levels that will be responsible for managing connections, generating messages, assembling messages (which will probably come in several stages). If this article has a positive response, a second part will be written, which will develop the topic of developing a top-level server program based on this kernel.
PS
If someone decides to understand the internal logic of this framework or applies it in their projects and finds bugs or bottlenecks in it, please let us know. The community is for that, so that everyone who can contribute to open source projects.
Link to the library The
example is located in the archive exsc_test_0.zip