GUACAMOLE-171: Merge move of broadcast socket to public API.
This commit is contained in:
commit
a1886f51bd
@ -70,26 +70,27 @@ noinst_HEADERS = \
|
|||||||
user-handlers.h \
|
user-handlers.h \
|
||||||
raw_encoder.h
|
raw_encoder.h
|
||||||
|
|
||||||
libguac_la_SOURCES = \
|
libguac_la_SOURCES = \
|
||||||
audio.c \
|
audio.c \
|
||||||
client.c \
|
client.c \
|
||||||
encode-jpeg.c \
|
encode-jpeg.c \
|
||||||
encode-png.c \
|
encode-png.c \
|
||||||
error.c \
|
error.c \
|
||||||
hash.c \
|
hash.c \
|
||||||
id.c \
|
id.c \
|
||||||
palette.c \
|
palette.c \
|
||||||
parser.c \
|
parser.c \
|
||||||
pool.c \
|
pool.c \
|
||||||
protocol.c \
|
protocol.c \
|
||||||
raw_encoder.c \
|
raw_encoder.c \
|
||||||
socket.c \
|
socket.c \
|
||||||
socket-fd.c \
|
socket-broadcast.c \
|
||||||
socket-nest.c \
|
socket-fd.c \
|
||||||
socket-tee.c \
|
socket-nest.c \
|
||||||
timestamp.c \
|
socket-tee.c \
|
||||||
unicode.c \
|
timestamp.c \
|
||||||
user.c \
|
unicode.c \
|
||||||
|
user.c \
|
||||||
user-handlers.c
|
user-handlers.c
|
||||||
|
|
||||||
# Compile WebP support if available
|
# Compile WebP support if available
|
||||||
|
@ -52,23 +52,6 @@ guac_layer __GUAC_DEFAULT_LAYER = {
|
|||||||
|
|
||||||
const guac_layer* GUAC_DEFAULT_LAYER = &__GUAC_DEFAULT_LAYER;
|
const guac_layer* GUAC_DEFAULT_LAYER = &__GUAC_DEFAULT_LAYER;
|
||||||
|
|
||||||
/**
|
|
||||||
* Single chunk of data, to be broadcast to all users.
|
|
||||||
*/
|
|
||||||
typedef struct __write_chunk {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The buffer to write.
|
|
||||||
*/
|
|
||||||
const void* buffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of bytes in the buffer.
|
|
||||||
*/
|
|
||||||
size_t length;
|
|
||||||
|
|
||||||
} __write_chunk;
|
|
||||||
|
|
||||||
guac_layer* guac_client_alloc_layer(guac_client* client) {
|
guac_layer* guac_client_alloc_layer(guac_client* client) {
|
||||||
|
|
||||||
/* Init new layer */
|
/* Init new layer */
|
||||||
@ -143,251 +126,6 @@ void guac_client_free_stream(guac_client* client, guac_stream* stream) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback which handles read requests on the broadcast socket. This callback
|
|
||||||
* always fails, as the broadcast socket is write-only; it cannot be read.
|
|
||||||
*
|
|
||||||
* @param socket
|
|
||||||
* The broadcast socket to read from.
|
|
||||||
*
|
|
||||||
* @param buf
|
|
||||||
* The buffer into which data should be read.
|
|
||||||
*
|
|
||||||
* @param count
|
|
||||||
* The number of bytes to attempt to read.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The number of bytes read, or -1 if an error occurs. This implementation
|
|
||||||
* always returns -1, as the broadcast socket is write-only and cannot be
|
|
||||||
* read.
|
|
||||||
*/
|
|
||||||
static ssize_t __guac_socket_broadcast_read_handler(guac_socket* socket,
|
|
||||||
void* buf, size_t count) {
|
|
||||||
|
|
||||||
/* Broadcast socket reads are not allowed */
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback invoked by guac_client_foreach_user() which write a given chunk of
|
|
||||||
* data to that user's socket. If the write attempt fails, the user is
|
|
||||||
* signalled to stop with guac_user_stop().
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The user that the chunk of data should be written to.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* A pointer to a __write_chunk which describes the data to be written.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Always NULL.
|
|
||||||
*/
|
|
||||||
static void* __write_chunk_callback(guac_user* user, void* data) {
|
|
||||||
|
|
||||||
__write_chunk* chunk = (__write_chunk*) data;
|
|
||||||
|
|
||||||
/* Attempt write, disconnect on failure */
|
|
||||||
if (guac_socket_write(user->socket, chunk->buffer, chunk->length))
|
|
||||||
guac_user_stop(user);
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Socket write handler which operates on each of the sockets of all connected
|
|
||||||
* users. This write handler will always succeed, but any failing user-specific
|
|
||||||
* writes will invoke guac_user_stop() on the failing user.
|
|
||||||
*
|
|
||||||
* @param socket
|
|
||||||
* The socket to which the given data must be written.
|
|
||||||
*
|
|
||||||
* @param buf
|
|
||||||
* The buffer containing the data to write.
|
|
||||||
*
|
|
||||||
* @param count
|
|
||||||
* The number of bytes to attempt to write from the given buffer.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The number of bytes written, or -1 if an error occurs. This handler will
|
|
||||||
* always succeed, and thus will always return the exact number of bytes
|
|
||||||
* specified by count.
|
|
||||||
*/
|
|
||||||
static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket,
|
|
||||||
const void* buf, size_t count) {
|
|
||||||
|
|
||||||
guac_client* client = (guac_client*) socket->data;
|
|
||||||
|
|
||||||
/* Build chunk */
|
|
||||||
__write_chunk chunk;
|
|
||||||
chunk.buffer = buf;
|
|
||||||
chunk.length = count;
|
|
||||||
|
|
||||||
/* Broadcast chunk to all users */
|
|
||||||
guac_client_foreach_user(client, __write_chunk_callback, &chunk);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback which is invoked by guac_client_foreach_user() to flush all
|
|
||||||
* pending data on the given user's socket. If an error occurs while flushing
|
|
||||||
* a user's socket, that user is signalled to stop with guac_user_stop().
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The user whose socket should be flushed.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
|
|
||||||
* by this callback, and should be left as NULL.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Always NULL.
|
|
||||||
*/
|
|
||||||
static void* __flush_callback(guac_user* user, void* data) {
|
|
||||||
|
|
||||||
/* Attempt flush, disconnect on failure */
|
|
||||||
if (guac_socket_flush(user->socket))
|
|
||||||
guac_user_stop(user);
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Socket flush handler which operates on each of the sockets of all connected
|
|
||||||
* users. This flush handler will always succeed, but any failing user-specific
|
|
||||||
* flush will invoke guac_user_stop() on the failing user.
|
|
||||||
*
|
|
||||||
* @param socket
|
|
||||||
* The broadcast socket to flush.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero if the flush operation succeeds, non-zero if the operation fails.
|
|
||||||
* This handler will always succeed, and thus will always return zero.
|
|
||||||
*/
|
|
||||||
static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) {
|
|
||||||
|
|
||||||
guac_client* client = (guac_client*) socket->data;
|
|
||||||
|
|
||||||
/* Flush all users */
|
|
||||||
guac_client_foreach_user(client, __flush_callback, NULL);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback which is invoked by guac_client_foreach_user() to lock the given
|
|
||||||
* user's socket in preparation for the beginning of a Guacamole protocol
|
|
||||||
* instruction.
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The user whose socket should be locked.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
|
|
||||||
* by this callback, and should be left as NULL.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Always NULL.
|
|
||||||
*/
|
|
||||||
static void* __lock_callback(guac_user* user, void* data) {
|
|
||||||
|
|
||||||
/* Lock socket */
|
|
||||||
guac_socket_instruction_begin(user->socket);
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Socket lock handler which acquires the socket locks of all connected users.
|
|
||||||
* Socket-level locks are acquired in preparation for the beginning of a new
|
|
||||||
* Guacamole instruction to ensure that parallel writes are only interleaved at
|
|
||||||
* instruction boundaries.
|
|
||||||
*
|
|
||||||
* @param socket
|
|
||||||
* The broadcast socket to lock.
|
|
||||||
*/
|
|
||||||
static void __guac_socket_broadcast_lock_handler(guac_socket* socket) {
|
|
||||||
|
|
||||||
guac_client* client = (guac_client*) socket->data;
|
|
||||||
|
|
||||||
/* Lock sockets of all users */
|
|
||||||
guac_client_foreach_user(client, __lock_callback, NULL);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback which is invoked by guac_client_foreach_user() to unlock the given
|
|
||||||
* user's socket at the end of a Guacamole protocol instruction.
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The user whose socket should be unlocked.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
|
|
||||||
* by this callback, and should be left as NULL.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Always NULL.
|
|
||||||
*/
|
|
||||||
static void* __unlock_callback(guac_user* user, void* data) {
|
|
||||||
|
|
||||||
/* Unlock socket */
|
|
||||||
guac_socket_instruction_end(user->socket);
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Socket unlock handler which releases the socket locks of all connected users.
|
|
||||||
* Socket-level locks are released after a Guacamole instruction has finished
|
|
||||||
* being written.
|
|
||||||
*
|
|
||||||
* @param socket
|
|
||||||
* The broadcast socket to unlock.
|
|
||||||
*/
|
|
||||||
static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) {
|
|
||||||
|
|
||||||
guac_client* client = (guac_client*) socket->data;
|
|
||||||
|
|
||||||
/* Unlock sockets of all users */
|
|
||||||
guac_client_foreach_user(client, __unlock_callback, NULL);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback which handles select operations on the broadcast socket, waiting
|
|
||||||
* for data to become available such that the next read operation will not
|
|
||||||
* block. This callback always fails, as the broadcast socket is write-only; it
|
|
||||||
* cannot be read.
|
|
||||||
*
|
|
||||||
* @param socket
|
|
||||||
* The broadcast socket to wait for.
|
|
||||||
*
|
|
||||||
* @param usec_timeout
|
|
||||||
* The maximum amount of time to wait for data, in microseconds, or -1 to
|
|
||||||
* potentially wait forever.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A positive value on success, zero if the timeout elapsed and no data is
|
|
||||||
* available, or a negative value if an error occurs. This implementation
|
|
||||||
* always returns -1, as the broadcast socket is write-only and cannot be
|
|
||||||
* read.
|
|
||||||
*/
|
|
||||||
static int __guac_socket_broadcast_select_handler(guac_socket* socket,
|
|
||||||
int usec_timeout) {
|
|
||||||
|
|
||||||
/* Selecting the broadcast socket is not possible */
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
guac_client* guac_client_alloc() {
|
guac_client* guac_client_alloc() {
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
@ -437,16 +175,7 @@ guac_client* guac_client_alloc() {
|
|||||||
pthread_rwlock_init(&(client->__users_lock), &lock_attributes);
|
pthread_rwlock_init(&(client->__users_lock), &lock_attributes);
|
||||||
|
|
||||||
/* Set up socket to broadcast to all users */
|
/* Set up socket to broadcast to all users */
|
||||||
guac_socket* socket = guac_socket_alloc();
|
client->socket = guac_socket_broadcast(client);
|
||||||
client->socket = socket;
|
|
||||||
socket->data = client;
|
|
||||||
|
|
||||||
socket->read_handler = __guac_socket_broadcast_read_handler;
|
|
||||||
socket->write_handler = __guac_socket_broadcast_write_handler;
|
|
||||||
socket->select_handler = __guac_socket_broadcast_select_handler;
|
|
||||||
socket->flush_handler = __guac_socket_broadcast_flush_handler;
|
|
||||||
socket->lock_handler = __guac_socket_broadcast_lock_handler;
|
|
||||||
socket->unlock_handler = __guac_socket_broadcast_unlock_handler;
|
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
|
|
||||||
@ -550,12 +279,12 @@ int guac_client_add_user(guac_client* client, guac_user* user, int argc, char**
|
|||||||
|
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
|
||||||
pthread_rwlock_wrlock(&(client->__users_lock));
|
|
||||||
|
|
||||||
/* Call handler, if defined */
|
/* Call handler, if defined */
|
||||||
if (client->join_handler)
|
if (client->join_handler)
|
||||||
retval = client->join_handler(user, argc, argv);
|
retval = client->join_handler(user, argc, argv);
|
||||||
|
|
||||||
|
pthread_rwlock_wrlock(&(client->__users_lock));
|
||||||
|
|
||||||
/* Add to list if join was successful */
|
/* Add to list if join was successful */
|
||||||
if (retval == 0) {
|
if (retval == 0) {
|
||||||
|
|
||||||
@ -584,12 +313,6 @@ void guac_client_remove_user(guac_client* client, guac_user* user) {
|
|||||||
|
|
||||||
pthread_rwlock_wrlock(&(client->__users_lock));
|
pthread_rwlock_wrlock(&(client->__users_lock));
|
||||||
|
|
||||||
/* Call handler, if defined */
|
|
||||||
if (user->leave_handler)
|
|
||||||
user->leave_handler(user);
|
|
||||||
else if (client->leave_handler)
|
|
||||||
client->leave_handler(user);
|
|
||||||
|
|
||||||
/* Update prev / head */
|
/* Update prev / head */
|
||||||
if (user->__prev != NULL)
|
if (user->__prev != NULL)
|
||||||
user->__prev->__next = user->__next;
|
user->__prev->__next = user->__next;
|
||||||
@ -608,6 +331,12 @@ void guac_client_remove_user(guac_client* client, guac_user* user) {
|
|||||||
|
|
||||||
pthread_rwlock_unlock(&(client->__users_lock));
|
pthread_rwlock_unlock(&(client->__users_lock));
|
||||||
|
|
||||||
|
/* Call handler, if defined */
|
||||||
|
if (user->leave_handler)
|
||||||
|
user->leave_handler(user);
|
||||||
|
else if (client->leave_handler)
|
||||||
|
client->leave_handler(user);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_client_foreach_user(guac_client* client, guac_user_callback* callback, void* data) {
|
void guac_client_foreach_user(guac_client* client, guac_user_callback* callback, void* data) {
|
||||||
|
@ -53,10 +53,6 @@ struct guac_client {
|
|||||||
* provide their own mechanism of I/O for their protocol. The guac_socket
|
* provide their own mechanism of I/O for their protocol. The guac_socket
|
||||||
* structure is used only to communicate conveniently with the Guacamole
|
* structure is used only to communicate conveniently with the Guacamole
|
||||||
* web-client.
|
* web-client.
|
||||||
*
|
|
||||||
* Because this socket broadcasts to all connected users, this socket MUST
|
|
||||||
* NOT be used within the same thread as a "leave" or "join" handler. Doing
|
|
||||||
* so results in undefined behavior, including possible segfaults.
|
|
||||||
*/
|
*/
|
||||||
guac_socket* socket;
|
guac_socket* socket;
|
||||||
|
|
||||||
@ -437,10 +433,6 @@ void guac_client_remove_user(guac_client* client, guac_user* user);
|
|||||||
* MAY invoke guac_client_foreach_user(), doing so should not be necessary, and
|
* MAY invoke guac_client_foreach_user(), doing so should not be necessary, and
|
||||||
* may indicate poor design choices.
|
* may indicate poor design choices.
|
||||||
*
|
*
|
||||||
* Because this function loops through all connected users, this function MUST
|
|
||||||
* NOT be invoked within the same thread as a "leave" or "join" handler. Doing
|
|
||||||
* so results in undefined behavior, including possible segfaults.
|
|
||||||
*
|
|
||||||
* @param client
|
* @param client
|
||||||
* The client whose users should be iterated.
|
* The client whose users should be iterated.
|
||||||
*
|
*
|
||||||
@ -465,11 +457,6 @@ void guac_client_foreach_user(guac_client* client,
|
|||||||
* This function is reentrant, but the user list MUST NOT be manipulated
|
* This function is reentrant, but the user list MUST NOT be manipulated
|
||||||
* within the same thread as a callback to this function.
|
* within the same thread as a callback to this function.
|
||||||
*
|
*
|
||||||
* Because this function depends on a consistent list of connected users, this
|
|
||||||
* function MUST NOT be invoked within the same thread as a "leave" or "join"
|
|
||||||
* handler. Doing so results in undefined behavior, including possible
|
|
||||||
* segfaults.
|
|
||||||
*
|
|
||||||
* @param client
|
* @param client
|
||||||
* The client to retrieve the owner from.
|
* The client to retrieve the owner from.
|
||||||
*
|
*
|
||||||
@ -498,11 +485,6 @@ void* guac_client_for_owner(guac_client* client, guac_user_callback* callback,
|
|||||||
* This function is reentrant, but the user list MUST NOT be manipulated
|
* This function is reentrant, but the user list MUST NOT be manipulated
|
||||||
* within the same thread as a callback to this function.
|
* within the same thread as a callback to this function.
|
||||||
*
|
*
|
||||||
* Because this function depends on a consistent list of connected users, this
|
|
||||||
* function MUST NOT be invoked within the same thread as a "leave" or "join"
|
|
||||||
* handler. Doing so results in undefined behavior, including possible
|
|
||||||
* segfaults.
|
|
||||||
*
|
|
||||||
* @param client
|
* @param client
|
||||||
* The client that the given user is expected to be associated with.
|
* The client that the given user is expected to be associated with.
|
||||||
*
|
*
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
* @file socket.h
|
* @file socket.h
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "client-types.h"
|
||||||
#include "socket-constants.h"
|
#include "socket-constants.h"
|
||||||
#include "socket-fntypes.h"
|
#include "socket-fntypes.h"
|
||||||
#include "socket-types.h"
|
#include "socket-types.h"
|
||||||
@ -220,6 +221,32 @@ guac_socket* guac_socket_nest(guac_socket* parent, int index);
|
|||||||
*/
|
*/
|
||||||
guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary);
|
guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates and initializes a new guac_socket which duplicates all
|
||||||
|
* instructions written across the sockets of each connected user of the given
|
||||||
|
* guac_client. The returned socket is a write-only socket. Attempts to read
|
||||||
|
* from the socket will fail. If a write occurs while no users are connected,
|
||||||
|
* that write will simply be dropped.
|
||||||
|
*
|
||||||
|
* Return values (error codes) from each user's socket will not affect the
|
||||||
|
* in-progress write, but each failing user will be forcibly stopped with
|
||||||
|
* guac_user_stop().
|
||||||
|
*
|
||||||
|
* If an error occurs while allocating the guac_socket object, NULL is returned,
|
||||||
|
* and guac_error is set appropriately.
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* The client associated with the group of connected users across which
|
||||||
|
* duplicates of all instructions should be written.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A write-only guac_socket object which broadcasts copies of all
|
||||||
|
* instructions written across all connected users of the given
|
||||||
|
* guac_client, or NULL if an error occurs while allocating the guac_socket
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
guac_socket* guac_socket_broadcast(guac_client* client);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given unsigned int to the given guac_socket object. The data
|
* Writes the given unsigned int to the given guac_socket object. The data
|
||||||
* written may be buffered until the buffer is flushed automatically or
|
* written may be buffered until the buffer is flushed automatically or
|
||||||
|
377
src/libguac/socket-broadcast.c
Normal file
377
src/libguac/socket-broadcast.c
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "client.h"
|
||||||
|
#include "error.h"
|
||||||
|
#include "socket.h"
|
||||||
|
#include "user.h"
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data associated with an open socket which writes to all connected users of
|
||||||
|
* a particular guac_client.
|
||||||
|
*/
|
||||||
|
typedef struct guac_socket_broadcast_data {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The guac_client whose connected users should receive all instructions
|
||||||
|
* written to this socket.
|
||||||
|
*/
|
||||||
|
guac_client* client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock which is acquired when an instruction is being written, and
|
||||||
|
* released when the instruction is finished being written.
|
||||||
|
*/
|
||||||
|
pthread_mutex_t socket_lock;
|
||||||
|
|
||||||
|
} guac_socket_broadcast_data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single chunk of data, to be broadcast to all users.
|
||||||
|
*/
|
||||||
|
typedef struct __write_chunk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The buffer to write.
|
||||||
|
*/
|
||||||
|
const void* buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of bytes in the buffer.
|
||||||
|
*/
|
||||||
|
size_t length;
|
||||||
|
|
||||||
|
} __write_chunk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback which handles read requests on the broadcast socket. This callback
|
||||||
|
* always fails, as the broadcast socket is write-only; it cannot be read.
|
||||||
|
*
|
||||||
|
* @param socket
|
||||||
|
* The broadcast socket to read from.
|
||||||
|
*
|
||||||
|
* @param buf
|
||||||
|
* The buffer into which data should be read.
|
||||||
|
*
|
||||||
|
* @param count
|
||||||
|
* The number of bytes to attempt to read.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The number of bytes read, or -1 if an error occurs. This implementation
|
||||||
|
* always returns -1, as the broadcast socket is write-only and cannot be
|
||||||
|
* read.
|
||||||
|
*/
|
||||||
|
static ssize_t __guac_socket_broadcast_read_handler(guac_socket* socket,
|
||||||
|
void* buf, size_t count) {
|
||||||
|
|
||||||
|
/* Broadcast socket reads are not allowed */
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked by guac_client_foreach_user() which write a given chunk of
|
||||||
|
* data to that user's socket. If the write attempt fails, the user is
|
||||||
|
* signalled to stop with guac_user_stop().
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The user that the chunk of data should be written to.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* A pointer to a __write_chunk which describes the data to be written.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Always NULL.
|
||||||
|
*/
|
||||||
|
static void* __write_chunk_callback(guac_user* user, void* data) {
|
||||||
|
|
||||||
|
__write_chunk* chunk = (__write_chunk*) data;
|
||||||
|
|
||||||
|
/* Attempt write, disconnect on failure */
|
||||||
|
if (guac_socket_write(user->socket, chunk->buffer, chunk->length))
|
||||||
|
guac_user_stop(user);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket write handler which operates on each of the sockets of all connected
|
||||||
|
* users. This write handler will always succeed, but any failing user-specific
|
||||||
|
* writes will invoke guac_user_stop() on the failing user.
|
||||||
|
*
|
||||||
|
* @param socket
|
||||||
|
* The socket to which the given data must be written.
|
||||||
|
*
|
||||||
|
* @param buf
|
||||||
|
* The buffer containing the data to write.
|
||||||
|
*
|
||||||
|
* @param count
|
||||||
|
* The number of bytes to attempt to write from the given buffer.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The number of bytes written, or -1 if an error occurs. This handler will
|
||||||
|
* always succeed, and thus will always return the exact number of bytes
|
||||||
|
* specified by count.
|
||||||
|
*/
|
||||||
|
static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket,
|
||||||
|
const void* buf, size_t count) {
|
||||||
|
|
||||||
|
guac_socket_broadcast_data* data =
|
||||||
|
(guac_socket_broadcast_data*) socket->data;
|
||||||
|
|
||||||
|
/* Build chunk */
|
||||||
|
__write_chunk chunk;
|
||||||
|
chunk.buffer = buf;
|
||||||
|
chunk.length = count;
|
||||||
|
|
||||||
|
/* Broadcast chunk to all users */
|
||||||
|
guac_client_foreach_user(data->client, __write_chunk_callback, &chunk);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback which is invoked by guac_client_foreach_user() to flush all
|
||||||
|
* pending data on the given user's socket. If an error occurs while flushing
|
||||||
|
* a user's socket, that user is signalled to stop with guac_user_stop().
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The user whose socket should be flushed.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
|
||||||
|
* by this callback, and should be left as NULL.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Always NULL.
|
||||||
|
*/
|
||||||
|
static void* __flush_callback(guac_user* user, void* data) {
|
||||||
|
|
||||||
|
/* Attempt flush, disconnect on failure */
|
||||||
|
if (guac_socket_flush(user->socket))
|
||||||
|
guac_user_stop(user);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket flush handler which operates on each of the sockets of all connected
|
||||||
|
* users. This flush handler will always succeed, but any failing user-specific
|
||||||
|
* flush will invoke guac_user_stop() on the failing user.
|
||||||
|
*
|
||||||
|
* @param socket
|
||||||
|
* The broadcast socket to flush.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Zero if the flush operation succeeds, non-zero if the operation fails.
|
||||||
|
* This handler will always succeed, and thus will always return zero.
|
||||||
|
*/
|
||||||
|
static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) {
|
||||||
|
|
||||||
|
guac_socket_broadcast_data* data =
|
||||||
|
(guac_socket_broadcast_data*) socket->data;
|
||||||
|
|
||||||
|
/* Flush all users */
|
||||||
|
guac_client_foreach_user(data->client, __flush_callback, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback which is invoked by guac_client_foreach_user() to lock the given
|
||||||
|
* user's socket in preparation for the beginning of a Guacamole protocol
|
||||||
|
* instruction.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The user whose socket should be locked.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
|
||||||
|
* by this callback, and should be left as NULL.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Always NULL.
|
||||||
|
*/
|
||||||
|
static void* __lock_callback(guac_user* user, void* data) {
|
||||||
|
|
||||||
|
/* Lock socket */
|
||||||
|
guac_socket_instruction_begin(user->socket);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket lock handler which acquires the socket locks of all connected users.
|
||||||
|
* Socket-level locks are acquired in preparation for the beginning of a new
|
||||||
|
* Guacamole instruction to ensure that parallel writes are only interleaved at
|
||||||
|
* instruction boundaries.
|
||||||
|
*
|
||||||
|
* @param socket
|
||||||
|
* The broadcast socket to lock.
|
||||||
|
*/
|
||||||
|
static void __guac_socket_broadcast_lock_handler(guac_socket* socket) {
|
||||||
|
|
||||||
|
guac_socket_broadcast_data* data =
|
||||||
|
(guac_socket_broadcast_data*) socket->data;
|
||||||
|
|
||||||
|
/* Acquire exclusive access to socket */
|
||||||
|
pthread_mutex_lock(&(data->socket_lock));
|
||||||
|
|
||||||
|
/* Lock sockets of all users */
|
||||||
|
guac_client_foreach_user(data->client, __lock_callback, NULL);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback which is invoked by guac_client_foreach_user() to unlock the given
|
||||||
|
* user's socket at the end of a Guacamole protocol instruction.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The user whose socket should be unlocked.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
|
||||||
|
* by this callback, and should be left as NULL.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Always NULL.
|
||||||
|
*/
|
||||||
|
static void* __unlock_callback(guac_user* user, void* data) {
|
||||||
|
|
||||||
|
/* Unlock socket */
|
||||||
|
guac_socket_instruction_end(user->socket);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket unlock handler which releases the socket locks of all connected users.
|
||||||
|
* Socket-level locks are released after a Guacamole instruction has finished
|
||||||
|
* being written.
|
||||||
|
*
|
||||||
|
* @param socket
|
||||||
|
* The broadcast socket to unlock.
|
||||||
|
*/
|
||||||
|
static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) {
|
||||||
|
|
||||||
|
guac_socket_broadcast_data* data =
|
||||||
|
(guac_socket_broadcast_data*) socket->data;
|
||||||
|
|
||||||
|
/* Unlock sockets of all users */
|
||||||
|
guac_client_foreach_user(data->client, __unlock_callback, NULL);
|
||||||
|
|
||||||
|
/* Relinquish exclusive access to socket */
|
||||||
|
pthread_mutex_unlock(&(data->socket_lock));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback which handles select operations on the broadcast socket, waiting
|
||||||
|
* for data to become available such that the next read operation will not
|
||||||
|
* block. This callback always fails, as the broadcast socket is write-only; it
|
||||||
|
* cannot be read.
|
||||||
|
*
|
||||||
|
* @param socket
|
||||||
|
* The broadcast socket to wait for.
|
||||||
|
*
|
||||||
|
* @param usec_timeout
|
||||||
|
* The maximum amount of time to wait for data, in microseconds, or -1 to
|
||||||
|
* potentially wait forever.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A positive value on success, zero if the timeout elapsed and no data is
|
||||||
|
* available, or a negative value if an error occurs. This implementation
|
||||||
|
* always returns -1, as the broadcast socket is write-only and cannot be
|
||||||
|
* read.
|
||||||
|
*/
|
||||||
|
static int __guac_socket_broadcast_select_handler(guac_socket* socket,
|
||||||
|
int usec_timeout) {
|
||||||
|
|
||||||
|
/* Selecting the broadcast socket is not possible */
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees all implementation-specific data associated with the given socket, but
|
||||||
|
* not the socket object itself.
|
||||||
|
*
|
||||||
|
* @param socket
|
||||||
|
* The guac_socket whose associated data should be freed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Zero if the data was successfully freed, non-zero otherwise. This
|
||||||
|
* implementation always succeeds, and will always return zero.
|
||||||
|
*/
|
||||||
|
static int __guac_socket_broadcast_free_handler(guac_socket* socket) {
|
||||||
|
|
||||||
|
guac_socket_broadcast_data* data =
|
||||||
|
(guac_socket_broadcast_data*) socket->data;
|
||||||
|
|
||||||
|
/* Destroy locks */
|
||||||
|
pthread_mutex_destroy(&(data->socket_lock));
|
||||||
|
|
||||||
|
free(data);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_socket* guac_socket_broadcast(guac_client* client) {
|
||||||
|
|
||||||
|
pthread_mutexattr_t lock_attributes;
|
||||||
|
|
||||||
|
/* Allocate socket and associated data */
|
||||||
|
guac_socket* socket = guac_socket_alloc();
|
||||||
|
guac_socket_broadcast_data* data =
|
||||||
|
malloc(sizeof(guac_socket_broadcast_data));
|
||||||
|
|
||||||
|
/* Store client as socket data */
|
||||||
|
data->client = client;
|
||||||
|
socket->data = data;
|
||||||
|
|
||||||
|
pthread_mutexattr_init(&lock_attributes);
|
||||||
|
pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED);
|
||||||
|
|
||||||
|
/* Init lock */
|
||||||
|
pthread_mutex_init(&(data->socket_lock), &lock_attributes);
|
||||||
|
|
||||||
|
/* Set read/write handlers */
|
||||||
|
socket->read_handler = __guac_socket_broadcast_read_handler;
|
||||||
|
socket->write_handler = __guac_socket_broadcast_write_handler;
|
||||||
|
socket->select_handler = __guac_socket_broadcast_select_handler;
|
||||||
|
socket->flush_handler = __guac_socket_broadcast_flush_handler;
|
||||||
|
socket->lock_handler = __guac_socket_broadcast_lock_handler;
|
||||||
|
socket->unlock_handler = __guac_socket_broadcast_unlock_handler;
|
||||||
|
socket->free_handler = __guac_socket_broadcast_free_handler;
|
||||||
|
|
||||||
|
return socket;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user