Basic protocol structure on the server side
This commit is contained in:
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
LibreFront --- an OpenFront clone
|
||||||
|
Copyright (C) 2025 Adrien Jaguenet
|
||||||
|
Copyright (C) 2025 Emil Kosz
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <common/PlayerConnection.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char name[32];
|
||||||
|
//TODO: clan tag, flag, territory pattern under a PlayerInfo struct
|
||||||
|
int gameID;
|
||||||
|
} JoinRequest;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int playerID;
|
||||||
|
union {
|
||||||
|
JoinRequest join;
|
||||||
|
} req;
|
||||||
|
} ClientRequest;
|
||||||
|
|
||||||
|
/* These prototypes are shared between client and server, but their implementation will be different
|
||||||
|
* and will be found in client/ClientRequest.c and server/ClientRequest.c */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param playerConn: player connection info
|
||||||
|
* @param req: the request to be sent (client) or received (server)
|
||||||
|
* @return: on success, the playerID, -1 on failure
|
||||||
|
*/
|
||||||
|
int requestJoinGame(PlayerConnection* playerConn, JoinRequest* req);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
LibreFront --- an OpenFront clone
|
||||||
|
Copyright (C) 2025 Adrien Jaguenet
|
||||||
|
Copyright (C) 2025 Emil Kosz
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
GSocket* socket;
|
||||||
|
GSocketConnection* connection;
|
||||||
|
|
||||||
|
/* Obviously, the client's istream is the server's ostream, and vice versa*/
|
||||||
|
GInputStream* istream;
|
||||||
|
GOutputStream* ostream;
|
||||||
|
} PlayerConnection;
|
||||||
|
|
||||||
|
PlayerConnection* PlayerConnection_accept(GSocket* socket);
|
||||||
|
void PlayerConnection_close(PlayerConnection** self);
|
||||||
|
|
||||||
|
void PlayerConnection_sendErrorResponse(PlayerConnection* self, const char fmt[], ...);
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
LibreFront --- an OpenFront clone
|
||||||
|
Copyright (C) 2025 Adrien Jaguenet
|
||||||
|
Copyright (C) 2025 Emil Kosz
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void* data;
|
||||||
|
} ServerResponse;
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#include "messaging.h"
|
||||||
|
|
||||||
|
int Message_read(GInputStream* stream, Message* message) {
|
||||||
|
GError *error;
|
||||||
|
gssize n =
|
||||||
|
g_input_stream_read (stream, &(message->header), sizeof(message->header), NULL,
|
||||||
|
&error);
|
||||||
|
if (n < 0) {
|
||||||
|
g_error("Could not g_input_stream_read() %s", error->message);
|
||||||
|
g_clear_error(&error);
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
g_info("Received message with version=%d, data_size=%d, type=%x", message->header.version, message->header.data_size, message->header.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message->header.version != PROTOCOL_VERSION) {
|
||||||
|
g_error("Wrong protocol (expects %d, received %d)", PROTOCOL_VERSION, message->header.version);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
LibreFront --- an OpenFront clone
|
||||||
|
Copyright (C) 2025 Adrien Jaguenet
|
||||||
|
Copyright (C) 2025 Emil Kosz
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include "ClientRequest.h"
|
||||||
|
#include "ServerResponse.h"
|
||||||
|
|
||||||
|
#define PROTOCOL_VERSION 0x1
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
RESPONSE_ERROR = 0xffffffff,
|
||||||
|
} ResponseType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
REQUEST_HELLO = 0x00000001,
|
||||||
|
REQUEST_JOINGAME = 0x00000002,
|
||||||
|
} RequestType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
guint32 version;
|
||||||
|
guint32 type;
|
||||||
|
guint32 data_size;
|
||||||
|
} MessageHeader;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
MessageHeader header;
|
||||||
|
void* data;
|
||||||
|
} Message;
|
||||||
|
|
||||||
|
/* @param stream: input stream (from a socket ideally)
|
||||||
|
* @param message: message structure that will be read into
|
||||||
|
* @return -1 on error
|
||||||
|
* */
|
||||||
|
int Message_read(GInputStream* stream, Message* message);
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
LibreFront --- an OpenFront clone
|
||||||
|
Copyright (C) 2025 Adrien Jaguenet
|
||||||
|
Copyright (C) 2025 Emil Kosz
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} PlayerInfo;
|
||||||
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
LibreFront --- an OpenFront clone
|
||||||
|
Copyright (C) 2025 Adrien Jaguenet
|
||||||
|
Copyright (C) 2025 Emil Kosz
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <common/ClientRequest.h>
|
||||||
|
|
||||||
|
int requestJoinGame(PlayerConnection* playerConn, JoinRequest* req) {
|
||||||
|
if (1 /*TODO game ID is not correct */) {
|
||||||
|
PlayerConnection_sendErrorResponse(playerConn, "Incorrect game ID %d", req->gameID);
|
||||||
|
}
|
||||||
|
if (1 /*TODO: username is not valid */ ) {
|
||||||
|
//WARNING: zero-terminated string?
|
||||||
|
PlayerConnection_sendErrorResponse(playerConn, "Invalid username '%s'", req->name);
|
||||||
|
}
|
||||||
|
if (1 /*TODO: Check that the game is not full*/) {
|
||||||
|
PlayerConnection_sendErrorResponse(playerConn, "Game with ID %d is full", req->gameID);
|
||||||
|
}
|
||||||
|
int playerID = -1 /*TODO: Select a pseudo-random unique positive integer to serve as the player's "secret" ID*/;
|
||||||
|
return playerID;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
LibreFront --- an OpenFront clone
|
||||||
|
Copyright (C) 2025 Adrien Jaguenet
|
||||||
|
Copyright (C) 2025 Emil Kosz
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <common/PlayerConnection.h>
|
||||||
|
#include <common/messaging.h>
|
||||||
|
|
||||||
|
PlayerConnection* PlayerConnection_accept(GSocket* socket) {
|
||||||
|
GError* error = NULL;
|
||||||
|
PlayerConnection *pconn = g_new0(PlayerConnection, 1);
|
||||||
|
|
||||||
|
pconn->socket = g_socket_accept (socket, NULL, &error);
|
||||||
|
pconn->connection =
|
||||||
|
g_socket_connection_factory_create_connection (pconn->socket);
|
||||||
|
|
||||||
|
|
||||||
|
if (!pconn->connection)
|
||||||
|
{
|
||||||
|
g_printerr ("accept() failed: %s\n", error->message);
|
||||||
|
goto error_flow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_info ("New connection accepted");
|
||||||
|
}
|
||||||
|
|
||||||
|
pconn->istream = g_io_stream_get_input_stream (G_IO_STREAM (pconn->connection));
|
||||||
|
if (!pconn->istream) {
|
||||||
|
g_printerr("Could not get input stream: %s", error->message);
|
||||||
|
goto error_flow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_info ("Got input stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
pconn->ostream = g_io_stream_get_output_stream (G_IO_STREAM (pconn->connection));
|
||||||
|
if (!pconn->ostream) {
|
||||||
|
g_printerr("Could not get output stream: %s", error->message);
|
||||||
|
goto error_flow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_info ("Got output stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return pconn;
|
||||||
|
|
||||||
|
error_flow:
|
||||||
|
g_clear_error (&error);
|
||||||
|
PlayerConnection_close(&pconn);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerConnection_close(PlayerConnection** self) {
|
||||||
|
g_io_stream_close (G_IO_STREAM (*self), NULL, NULL);
|
||||||
|
g_object_unref ((*self)->connection);
|
||||||
|
g_object_unref ((*self)->socket);
|
||||||
|
g_free(*self);
|
||||||
|
*self = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerConnection_sendErrorResponse(PlayerConnection* self, const char fmt[], ...) {
|
||||||
|
//WARNING: is this safe against non-null-terminated strings?
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args);
|
||||||
|
|
||||||
|
gsize bytes_written = 0;
|
||||||
|
Message msg;
|
||||||
|
//TODO: create a proper ErrorResponse object
|
||||||
|
msg.header.version = PROTOCOL_VERSION;
|
||||||
|
msg.header.type = RESPONSE_ERROR;
|
||||||
|
msg.header.data_size = 0;
|
||||||
|
g_output_stream_write(self->ostream, &msg, sizeof(msg.header), NULL, &error);
|
||||||
|
g_output_stream_vprintf (self->ostream, &bytes_written, NULL, &error, fmt, args);
|
||||||
|
g_output_stream_flush (self->ostream, NULL, &error);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
+57
-75
@@ -3,6 +3,8 @@
|
|||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include <common/PlayerConnection.h>
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char *argv[])
|
main (int argc, char *argv[])
|
||||||
{
|
{
|
||||||
@@ -13,100 +15,80 @@ main (int argc, char *argv[])
|
|||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
int PORT = SERVER_DEFAULT_PORT;
|
int PORT = SERVER_DEFAULT_PORT;
|
||||||
if (argc > 1)
|
if (argc > 1)
|
||||||
{
|
{
|
||||||
PORT = atoi (argv[1]);
|
PORT = atoi (argv[1]);
|
||||||
g_info ("Changed the listening port from default=%d to custom=%d",
|
g_info ("Changed the listening port from default=%d to custom=%d",
|
||||||
SERVER_DEFAULT_PORT, PORT);
|
SERVER_DEFAULT_PORT, PORT);
|
||||||
}
|
}
|
||||||
GSocket *socket = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM,
|
GSocket *socket = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM,
|
||||||
G_SOCKET_PROTOCOL_TCP, &error);
|
G_SOCKET_PROTOCOL_TCP, &error);
|
||||||
if (!socket)
|
if (!socket)
|
||||||
{
|
{
|
||||||
g_printerr ("Socket creation failed: %s\n", error->message);
|
g_printerr ("Socket creation failed: %s\n", error->message);
|
||||||
g_clear_error (&error);
|
g_clear_error (&error);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
g_info ("Created TCP socket.");
|
g_info ("Created TCP socket.");
|
||||||
}
|
}
|
||||||
|
|
||||||
GInetAddress *addr = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
|
GInetAddress *addr = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
|
||||||
GSocketAddress *sockaddr = g_inet_socket_address_new (addr, PORT);
|
GSocketAddress *sockaddr = g_inet_socket_address_new (addr, PORT);
|
||||||
|
|
||||||
if (!g_socket_bind (socket, sockaddr, TRUE, &error))
|
if (!g_socket_bind (socket, sockaddr, TRUE, &error))
|
||||||
{
|
{
|
||||||
g_printerr ("Bind failed: %s\n", error->message);
|
g_printerr ("Bind failed: %s\n", error->message);
|
||||||
g_clear_error (&error);
|
g_clear_error (&error);
|
||||||
g_object_unref (socket);
|
g_object_unref (socket);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
g_info ("Bound TCP socket to port %d", PORT);
|
g_info ("Bound TCP socket to port %d", PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_object_unref (addr);
|
g_object_unref (addr);
|
||||||
g_object_unref (sockaddr);
|
g_object_unref (sockaddr);
|
||||||
|
|
||||||
if (!g_socket_listen (socket, &error))
|
if (!g_socket_listen (socket, &error))
|
||||||
{
|
{
|
||||||
g_printerr ("Listen failed: %s\n", error->message);
|
g_printerr ("Listen failed: %s\n", error->message);
|
||||||
g_clear_error (&error);
|
g_clear_error (&error);
|
||||||
g_object_unref (socket);
|
g_object_unref (socket);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
g_info ("TCP socket is now listening");
|
g_info ("TCP socket is now listening");
|
||||||
}
|
}
|
||||||
|
|
||||||
g_info ("Listening...");
|
g_info ("Listening...");
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
GSocket *socket2 = g_socket_accept (socket, NULL, &error);
|
PlayerConnection *playerConnection = PlayerConnection_accept(socket);
|
||||||
GSocketConnection *connection =
|
if (! playerConnection) {
|
||||||
g_socket_connection_factory_create_connection (socket2);
|
g_printerr("Error creating a player connection");
|
||||||
if (!connection)
|
|
||||||
{
|
|
||||||
g_printerr ("accept() failed: %s\n", error->message);
|
|
||||||
g_clear_error (&error);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g_info ("New connection accepted");
|
|
||||||
}
|
|
||||||
|
|
||||||
GInputStream *istream =
|
|
||||||
g_io_stream_get_input_stream (G_IO_STREAM (connection));
|
|
||||||
gchar buffer[1024];
|
|
||||||
gssize n =
|
|
||||||
g_input_stream_read (istream, buffer, sizeof (buffer) - 1, NULL,
|
|
||||||
&error);
|
|
||||||
|
|
||||||
if (n > 0)
|
|
||||||
{
|
|
||||||
buffer[n] = '\0';
|
|
||||||
g_print ("Received from client: %s\n", buffer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g_printerr ("Read error: %s\n", error->message);
|
|
||||||
g_clear_error (&error);
|
|
||||||
}
|
|
||||||
|
|
||||||
GOutputStream *ostream =
|
|
||||||
g_io_stream_get_output_stream (G_IO_STREAM (connection));
|
|
||||||
const gchar *response =
|
|
||||||
"Hello from the librefront server. Not implemented!!";
|
|
||||||
g_output_stream_write (ostream, response, strlen (response), NULL,
|
|
||||||
&error);
|
|
||||||
g_output_stream_flush (ostream, NULL, &error);
|
|
||||||
|
|
||||||
g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
|
|
||||||
g_object_unref (connection);
|
|
||||||
g_object_unref (socket2);
|
|
||||||
}
|
}
|
||||||
|
gchar buffer[1024];
|
||||||
|
gssize n =
|
||||||
|
g_input_stream_read (playerConnection->istream, buffer, sizeof (buffer) - 1, NULL,
|
||||||
|
&error);
|
||||||
|
|
||||||
|
if (n > 0)
|
||||||
|
{
|
||||||
|
buffer[n] = '\0';
|
||||||
|
g_print ("Received from client: %s\n", buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_printerr ("Read error: %s\n", error->message);
|
||||||
|
g_clear_error (&error);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerConnection_sendErrorResponse(playerConnection, "This is an error response");
|
||||||
|
|
||||||
|
PlayerConnection_close(&playerConnection);
|
||||||
|
}
|
||||||
|
|
||||||
g_object_unref (socket);
|
g_object_unref (socket);
|
||||||
fprintf (stderr, "Server not implemented!\n");
|
fprintf (stderr, "Server not implemented!\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user