client: use the GIOChannel for I/O

GIOChannel is more portable than raw read()/write() calls.  We're
using GIOChannel anyway, because we need it for plugging the client
into the GLib main loop.

Configure the GIOChannel to the bare minimum: no character set, no
buffering.
This commit is contained in:
Max Kellermann 2009-01-25 19:54:57 +01:00
parent b0ea975642
commit 8bc6bca555

View File

@ -73,7 +73,6 @@ struct client {
size_t bufferLength; size_t bufferLength;
size_t bufferPos; size_t bufferPos;
int fd; /* file descriptor; -1 if expired */
GIOChannel *channel; GIOChannel *channel;
guint source_id; guint source_id;
@ -114,7 +113,7 @@ static void client_write_output(struct client *client);
bool client_is_expired(const struct client *client) bool client_is_expired(const struct client *client)
{ {
return client->fd < 0; return client->channel == NULL;
} }
int client_get_uid(const struct client *client) int client_get_uid(const struct client *client)
@ -148,7 +147,7 @@ client_manager_expire_event(G_GNUC_UNUSED gpointer data)
static inline void client_set_expired(struct client *client) static inline void client_set_expired(struct client *client)
{ {
if (expire_source_id == 0 && client->fd >= 0) if (expire_source_id == 0 && !client_is_expired(client))
/* delayed deletion */ /* delayed deletion */
expire_source_id = g_idle_add(client_manager_expire_event, expire_source_id = g_idle_add(client_manager_expire_event,
NULL); NULL);
@ -162,11 +161,6 @@ static inline void client_set_expired(struct client *client)
g_io_channel_unref(client->channel); g_io_channel_unref(client->channel);
client->channel = NULL; client->channel = NULL;
} }
if (client->fd >= 0) {
close(client->fd);
client->fd = -1;
}
} }
static gboolean static gboolean
@ -182,13 +176,20 @@ static void client_init(struct client *client, int fd)
client->cmd_list_OK = -1; client->cmd_list_OK = -1;
client->bufferLength = 0; client->bufferLength = 0;
client->bufferPos = 0; client->bufferPos = 0;
client->fd = fd;
#ifndef G_OS_WIN32 #ifndef G_OS_WIN32
client->channel = g_io_channel_unix_new(client->fd); client->channel = g_io_channel_unix_new(fd);
#else #else
client->channel = g_io_channel_win32_new_socket(client->fd); client->channel = g_io_channel_win32_new_socket(fd);
#endif #endif
/* GLib is responsible for closing the file descriptor */
g_io_channel_set_close_on_unref(client->channel, true);
/* NULL encoding means the stream is binary safe; the MPD
protocol is UTF-8 only, but we are doing this call anyway
to prevent GLib from messing around with the stream */
g_io_channel_set_encoding(client->channel, NULL, NULL);
/* we prefer to do buffering */
g_io_channel_set_buffered(client->channel, false);
client->source_id = g_io_add_watch(client->channel, G_IO_IN, client->source_id = g_io_add_watch(client->channel, G_IO_IN,
client_in_event, client); client_in_event, client);
@ -460,23 +461,40 @@ static int client_input_received(struct client *client, size_t bytesRead)
static int client_read(struct client *client) static int client_read(struct client *client)
{ {
ssize_t bytesRead; GError *error = NULL;
GIOStatus status;
gsize bytes_read;
assert(client->bufferPos <= client->bufferLength); assert(client->bufferPos <= client->bufferLength);
assert(client->bufferLength < sizeof(client->buffer)); assert(client->bufferLength < sizeof(client->buffer));
bytesRead = read(client->fd, status = g_io_channel_read_chars
client->buffer + client->bufferLength, (client->channel,
sizeof(client->buffer) - client->bufferLength); client->buffer + client->bufferLength,
sizeof(client->buffer) - client->bufferLength,
&bytes_read, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
return client_input_received(client, bytes_read);
if (bytesRead > 0) case G_IO_STATUS_AGAIN:
return client_input_received(client, bytesRead);
else if (bytesRead < 0 && errno == EINTR)
/* try again later, after select() */ /* try again later, after select() */
return 0; return 0;
else
/* peer disconnected or I/O error */ case G_IO_STATUS_EOF:
/* peer disconnected */
return COMMAND_RETURN_CLOSE; return COMMAND_RETURN_CLOSE;
case G_IO_STATUS_ERROR:
/* I/O error */
g_warning("failed to read from client %d: %s",
client->num, error->message);
g_error_free(error);
return COMMAND_RETURN_CLOSE;
}
/* unreachable */
return COMMAND_RETURN_CLOSE;
} }
static gboolean static gboolean
@ -616,9 +634,47 @@ client_manager_expire(void)
g_list_foreach(clients, client_check_expired_callback, NULL); g_list_foreach(clients, client_check_expired_callback, NULL);
} }
static size_t
client_write_deferred_buffer(struct client *client,
const struct deferred_buffer *buffer)
{
GError *error = NULL;
GIOStatus status;
gsize bytes_written;
status = g_io_channel_write_chars
(client->channel, buffer->data, buffer->size,
&bytes_written, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
return bytes_written;
case G_IO_STATUS_AGAIN:
return 0;
case G_IO_STATUS_EOF:
/* client has disconnected */
client_set_expired(client);
return 0;
case G_IO_STATUS_ERROR:
/* I/O error */
client_set_expired(client);
g_warning("failed to flush buffer for %i: %s",
client->num, error->message);
g_error_free(error);
return 0;
}
/* unreachable */
return 0;
}
static void client_write_deferred(struct client *client) static void client_write_deferred(struct client *client)
{ {
ssize_t ret = 0; size_t ret;
while (!g_queue_is_empty(client->deferred_send)) { while (!g_queue_is_empty(client->deferred_send)) {
struct deferred_buffer *buf = struct deferred_buffer *buf =
@ -627,10 +683,11 @@ static void client_write_deferred(struct client *client)
assert(buf->size > 0); assert(buf->size > 0);
assert(buf->size <= client->deferred_bytes); assert(buf->size <= client->deferred_bytes);
ret = write(client->fd, buf->data, buf->size); ret = client_write_deferred_buffer(client, buf);
if (ret < 0) if (ret == 0)
break; break;
else if ((size_t)ret < buf->size) {
if (ret < buf->size) {
assert(client->deferred_bytes >= (size_t)ret); assert(client->deferred_bytes >= (size_t)ret);
client->deferred_bytes -= ret; client->deferred_bytes -= ret;
buf->size -= ret; buf->size -= ret;
@ -645,6 +702,7 @@ static void client_write_deferred(struct client *client)
g_free(buf); g_free(buf);
g_queue_pop_head(client->deferred_send); g_queue_pop_head(client->deferred_send);
} }
client->lastTime = time(NULL); client->lastTime = time(NULL);
} }
@ -652,11 +710,6 @@ static void client_write_deferred(struct client *client)
g_debug("client %i: buffer empty %lu", client->num, g_debug("client %i: buffer empty %lu", client->num,
(unsigned long)client->deferred_bytes); (unsigned long)client->deferred_bytes);
assert(client->deferred_bytes == 0); assert(client->deferred_bytes == 0);
} else if (ret < 0 && errno != EAGAIN && errno != EINTR) {
/* cause client to close */
g_debug("client %i: problems flushing buffer",
client->num);
client_set_expired(client);
} }
} }
@ -691,23 +744,40 @@ static void client_defer_output(struct client *client,
static void client_write_direct(struct client *client, static void client_write_direct(struct client *client,
const char *data, size_t length) const char *data, size_t length)
{ {
ssize_t ret; GError *error = NULL;
GIOStatus status;
gsize bytes_written;
assert(length > 0); assert(length > 0);
assert(g_queue_is_empty(client->deferred_send)); assert(g_queue_is_empty(client->deferred_send));
if ((ret = write(client->fd, data, length)) < 0) { status = g_io_channel_write_chars(client->channel, data, length,
if (errno == EAGAIN || errno == EINTR) { &bytes_written, &error);
client_defer_output(client, data, length); switch (status) {
} else { case G_IO_STATUS_NORMAL:
g_debug("client %i: problems writing", client->num); case G_IO_STATUS_AGAIN:
client_set_expired(client); break;
return;
} case G_IO_STATUS_EOF:
} else if ((size_t)ret < client->send_buf_used) { /* client has disconnected */
client_defer_output(client, data + ret, length - ret);
client_set_expired(client);
return;
case G_IO_STATUS_ERROR:
/* I/O error */
client_set_expired(client);
g_warning("failed to write to %i: %s",
client->num, error->message);
g_error_free(error);
return;
} }
if (bytes_written < length)
client_defer_output(client, data + bytes_written,
length - bytes_written);
if (!g_queue_is_empty(client->deferred_send)) if (!g_queue_is_empty(client->deferred_send))
g_debug("client %i: buffer created", client->num); g_debug("client %i: buffer created", client->num);
} }