tcp_connect: generic library for establishing TCP connections
This commit is contained in:
parent
0c0400b6fc
commit
533a6b0240
11
Makefile.am
11
Makefile.am
@ -299,6 +299,7 @@ src_mpd_SOURCES = \
|
||||
src/client_message.c \
|
||||
src/client_subscribe.h \
|
||||
src/client_subscribe.c \
|
||||
src/tcp_connect.c src/tcp_connect.h \
|
||||
src/tcp_socket.c src/tcp_socket.h \
|
||||
src/udp_server.c src/udp_server.h \
|
||||
src/server_socket.c \
|
||||
@ -904,6 +905,7 @@ noinst_PROGRAMS = \
|
||||
$(C_TESTS) \
|
||||
test/read_conf \
|
||||
test/run_resolver \
|
||||
test/run_tcp_connect \
|
||||
test/run_input \
|
||||
test/dump_playlist \
|
||||
test/run_decoder \
|
||||
@ -932,6 +934,15 @@ test_run_resolver_LDADD = $(MPD_LIBS) \
|
||||
test_run_resolver_SOURCES = test/run_resolver.c \
|
||||
src/resolver.c
|
||||
|
||||
test_run_tcp_connect_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
test_run_tcp_connect_LDADD = $(MPD_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
test_run_tcp_connect_SOURCES = test/run_tcp_connect.c \
|
||||
src/io_thread.c src/io_thread.h \
|
||||
src/fd_util.c \
|
||||
src/resolver.c \
|
||||
src/tcp_connect.c
|
||||
|
||||
test_run_input_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(ARCHIVE_CFLAGS) \
|
||||
$(INPUT_CFLAGS)
|
||||
|
252
src/tcp_connect.c
Normal file
252
src/tcp_connect.c
Normal file
@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "tcp_connect.h"
|
||||
#include "fd_util.h"
|
||||
#include "io_thread.h"
|
||||
#include "glib_compat.h"
|
||||
#include "glib_socket.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#define WINVER 0x0501
|
||||
#include <ws2tcpip.h>
|
||||
#include <winsock.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
struct tcp_connect {
|
||||
const struct tcp_connect_handler *handler;
|
||||
void *handler_ctx;
|
||||
|
||||
int fd;
|
||||
GSource *source;
|
||||
|
||||
unsigned timeout_ms;
|
||||
GSource *timeout_source;
|
||||
};
|
||||
|
||||
static bool
|
||||
is_in_progress_errno(int e)
|
||||
{
|
||||
#ifdef WIN32
|
||||
return e == WSAEINPROGRESS || e == WSAEWOULDBLOCK;
|
||||
#else
|
||||
return e == EINPROGRESS;
|
||||
#endif
|
||||
}
|
||||
|
||||
static gboolean
|
||||
tcp_connect_event(G_GNUC_UNUSED GIOChannel *source,
|
||||
G_GNUC_UNUSED GIOCondition condition,
|
||||
gpointer data)
|
||||
{
|
||||
struct tcp_connect *c = data;
|
||||
|
||||
assert(c->source != NULL);
|
||||
assert(c->timeout_source != NULL);
|
||||
|
||||
/* clear the socket source */
|
||||
g_source_unref(c->source);
|
||||
c->source = NULL;
|
||||
|
||||
/* delete the timeout source */
|
||||
g_source_destroy(c->timeout_source);
|
||||
g_source_unref(c->timeout_source);
|
||||
c->timeout_source = NULL;
|
||||
|
||||
/* obtain the connect result */
|
||||
int s_err = 0;
|
||||
socklen_t s_err_size = sizeof(s_err);
|
||||
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR,
|
||||
(char*)&s_err, &s_err_size) < 0)
|
||||
s_err = errno;
|
||||
|
||||
if (s_err == 0) {
|
||||
/* connection established successfully */
|
||||
|
||||
c->handler->success(c->fd, c->handler_ctx);
|
||||
} else {
|
||||
/* there was an I/O error; close the socket and pass
|
||||
the error to the handler */
|
||||
|
||||
close_socket(c->fd);
|
||||
|
||||
GError *error =
|
||||
g_error_new_literal(g_file_error_quark(), s_err,
|
||||
g_strerror(s_err));
|
||||
c->handler->error(error, c->handler_ctx);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
tcp_connect_timeout(gpointer data)
|
||||
{
|
||||
struct tcp_connect *c = data;
|
||||
|
||||
assert(c->source != NULL);
|
||||
assert(c->timeout_source != NULL);
|
||||
|
||||
/* clear the timeout source */
|
||||
g_source_unref(c->timeout_source);
|
||||
c->timeout_source = NULL;
|
||||
|
||||
/* delete the socket source */
|
||||
g_source_destroy(c->source);
|
||||
g_source_unref(c->source);
|
||||
c->source = NULL;
|
||||
|
||||
/* report timeout to handler */
|
||||
c->handler->timeout(c->handler_ctx);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
tcp_connect_init(gpointer data)
|
||||
{
|
||||
struct tcp_connect *c = data;
|
||||
|
||||
/* create a connect source */
|
||||
GIOChannel *channel = g_io_channel_new_socket(c->fd);
|
||||
c->source = g_io_create_watch(channel, G_IO_OUT);
|
||||
g_io_channel_unref(channel);
|
||||
|
||||
g_source_set_callback(c->source, (GSourceFunc)tcp_connect_event, c,
|
||||
NULL);
|
||||
g_source_attach(c->source, io_thread_context());
|
||||
|
||||
/* create a timeout source */
|
||||
if (c->timeout_ms > 0)
|
||||
c->timeout_source =
|
||||
io_thread_timeout_add(c->timeout_ms,
|
||||
tcp_connect_timeout, c);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
tcp_connect_address(const struct sockaddr *address, size_t address_length,
|
||||
unsigned timeout_ms,
|
||||
const struct tcp_connect_handler *handler, void *ctx,
|
||||
struct tcp_connect **handle_r)
|
||||
{
|
||||
assert(address != NULL);
|
||||
assert(address_length > 0);
|
||||
assert(handler != NULL);
|
||||
assert(handler->success != NULL);
|
||||
assert(handler->error != NULL);
|
||||
assert(handler->canceled != NULL);
|
||||
assert(handler->timeout != NULL || timeout_ms == 0);
|
||||
assert(handle_r != NULL);
|
||||
assert(*handle_r == NULL);
|
||||
|
||||
int fd = socket_cloexec_nonblock(address->sa_family, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
GError *error =
|
||||
g_error_new_literal(g_file_error_quark(), errno,
|
||||
g_strerror(errno));
|
||||
handler->error(error, ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = connect(fd, address, address_length);
|
||||
if (ret >= 0) {
|
||||
/* quick connect, no I/O thread */
|
||||
handler->success(fd, ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_in_progress_errno(errno)) {
|
||||
GError *error =
|
||||
g_error_new_literal(g_file_error_quark(), errno,
|
||||
g_strerror(errno));
|
||||
close_socket(fd);
|
||||
handler->error(error, ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
/* got EINPROGRESS, use the I/O thread to wait for the
|
||||
operation to finish */
|
||||
|
||||
struct tcp_connect *c = g_new(struct tcp_connect, 1);
|
||||
c->handler = handler;
|
||||
c->handler_ctx = ctx;
|
||||
c->fd = fd;
|
||||
c->source = NULL;
|
||||
c->timeout_ms = timeout_ms;
|
||||
c->timeout_source = NULL;
|
||||
|
||||
*handle_r = c;
|
||||
|
||||
io_thread_call(tcp_connect_init, c);
|
||||
}
|
||||
|
||||
static gpointer
|
||||
tcp_connect_cancel_callback(gpointer data)
|
||||
{
|
||||
struct tcp_connect *c = data;
|
||||
|
||||
assert((c->source == NULL) == (c->timeout_source == NULL));
|
||||
|
||||
if (c->source == NULL)
|
||||
return NULL;
|
||||
|
||||
/* delete the socket source */
|
||||
g_source_destroy(c->source);
|
||||
g_source_unref(c->source);
|
||||
c->source = NULL;
|
||||
|
||||
/* delete the timeout source */
|
||||
g_source_destroy(c->timeout_source);
|
||||
g_source_unref(c->timeout_source);
|
||||
c->timeout_source = NULL;
|
||||
|
||||
/* close the socket */
|
||||
close_socket(c->fd);
|
||||
|
||||
/* notify the handler */
|
||||
c->handler->canceled(c->handler_ctx);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
tcp_connect_cancel(struct tcp_connect *c)
|
||||
{
|
||||
if (c->source == NULL)
|
||||
return;
|
||||
|
||||
io_thread_call(tcp_connect_cancel_callback, c);
|
||||
}
|
||||
|
||||
void
|
||||
tcp_connect_free(struct tcp_connect *c)
|
||||
{
|
||||
assert(c->source == NULL);
|
||||
|
||||
g_free(c);
|
||||
}
|
96
src/tcp_connect.h
Normal file
96
src/tcp_connect.h
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_TCP_CONNECT_H
|
||||
#define MPD_TCP_CONNECT_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
struct sockaddr;
|
||||
|
||||
struct tcp_connect_handler {
|
||||
/**
|
||||
* The connection was established successfully.
|
||||
*
|
||||
* @param fd a file descriptor that must be closed with
|
||||
* close_socket() when finished
|
||||
*/
|
||||
void (*success)(int fd, void *ctx);
|
||||
|
||||
/**
|
||||
* An error has occurred. The method is responsible for
|
||||
* freeing the GError.
|
||||
*/
|
||||
void (*error)(GError *error, void *ctx);
|
||||
|
||||
/**
|
||||
* The connection could not be established in the specified
|
||||
* time span.
|
||||
*/
|
||||
void (*timeout)(void *ctx);
|
||||
|
||||
/**
|
||||
* The operation was canceled before a result was available.
|
||||
*/
|
||||
void (*canceled)(void *ctx);
|
||||
};
|
||||
|
||||
struct tcp_connect;
|
||||
|
||||
/**
|
||||
* Establish a TCP connection to the specified address.
|
||||
*
|
||||
* Note that the result may be available before this function returns.
|
||||
*
|
||||
* The caller must free this object with tcp_connect_free().
|
||||
*
|
||||
* @param timeout_ms time out after this number of milliseconds; 0
|
||||
* means no timeout
|
||||
* @param handle_r a handle that can be used to cancel the operation;
|
||||
* the caller must initialize it to NULL
|
||||
*/
|
||||
void
|
||||
tcp_connect_address(const struct sockaddr *address, size_t address_length,
|
||||
unsigned timeout_ms,
|
||||
const struct tcp_connect_handler *handler, void *ctx,
|
||||
struct tcp_connect **handle_r);
|
||||
|
||||
/**
|
||||
* Cancel the operation. It is possible that the result is delivered
|
||||
* before the operation has been canceled; in that case, the
|
||||
* canceled() handler method will not be invoked.
|
||||
*
|
||||
* Even after calling this function, tcp_connect_free() must still be
|
||||
* called to free memory.
|
||||
*/
|
||||
void
|
||||
tcp_connect_cancel(struct tcp_connect *handle);
|
||||
|
||||
/**
|
||||
* Free memory used by this object.
|
||||
*
|
||||
* This function is not thread safe. It must not be called while
|
||||
* other threads are still working with it. If no callback has been
|
||||
* invoked so far, then you must call tcp_connect_cancel() to release
|
||||
* I/O thread resources, before calling this function.
|
||||
*/
|
||||
void
|
||||
tcp_connect_free(struct tcp_connect *handle);
|
||||
|
||||
#endif
|
165
test/run_tcp_connect.c
Normal file
165
test/run_tcp_connect.c
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "resolver.h"
|
||||
#include "io_thread.h"
|
||||
#include "tcp_connect.h"
|
||||
#include "fd_util.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#define WINVER 0x0501
|
||||
#include <ws2tcpip.h>
|
||||
#include <winsock.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
|
||||
static struct tcp_connect *handle;
|
||||
static GMutex *mutex;
|
||||
static GCond *cond;
|
||||
static bool done, success;
|
||||
|
||||
static void
|
||||
my_tcp_connect_success(int fd, G_GNUC_UNUSED void *ctx)
|
||||
{
|
||||
assert(!done);
|
||||
assert(!success);
|
||||
|
||||
close_socket(fd);
|
||||
g_print("success\n");
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
done = success = true;
|
||||
g_cond_signal(cond);
|
||||
g_mutex_unlock(mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
my_tcp_connect_error(GError *error, G_GNUC_UNUSED void *ctx)
|
||||
{
|
||||
assert(!done);
|
||||
assert(!success);
|
||||
|
||||
g_printerr("error: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
done = true;
|
||||
g_cond_signal(cond);
|
||||
g_mutex_unlock(mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
my_tcp_connect_timeout(G_GNUC_UNUSED void *ctx)
|
||||
{
|
||||
assert(!done);
|
||||
assert(!success);
|
||||
|
||||
g_printerr("timeout\n");
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
done = true;
|
||||
g_cond_signal(cond);
|
||||
g_mutex_unlock(mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
my_tcp_connect_canceled(G_GNUC_UNUSED void *ctx)
|
||||
{
|
||||
assert(!done);
|
||||
assert(!success);
|
||||
|
||||
g_printerr("canceled\n");
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
done = true;
|
||||
g_cond_signal(cond);
|
||||
g_mutex_unlock(mutex);
|
||||
}
|
||||
|
||||
static const struct tcp_connect_handler my_tcp_connect_handler = {
|
||||
.success = my_tcp_connect_success,
|
||||
.error = my_tcp_connect_error,
|
||||
.timeout = my_tcp_connect_timeout,
|
||||
.canceled = my_tcp_connect_canceled,
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
g_printerr("Usage: run_tcp_connect IP:PORT\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
GError *error = NULL;
|
||||
struct addrinfo *ai = resolve_host_port(argv[1], 80, 0, SOCK_STREAM,
|
||||
&error);
|
||||
if (ai == NULL) {
|
||||
g_printerr("%s\n", error->message);
|
||||
g_error_free(error);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* initialize GLib */
|
||||
|
||||
g_thread_init(NULL);
|
||||
|
||||
/* initialize MPD */
|
||||
|
||||
io_thread_init();
|
||||
if (!io_thread_start(&error)) {
|
||||
freeaddrinfo(ai);
|
||||
g_printerr("%s", error->message);
|
||||
g_error_free(error);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* open the connection */
|
||||
|
||||
mutex = g_mutex_new();
|
||||
cond = g_cond_new();
|
||||
|
||||
tcp_connect_address(ai->ai_addr, ai->ai_addrlen, 5000,
|
||||
&my_tcp_connect_handler, NULL,
|
||||
&handle);
|
||||
freeaddrinfo(ai);
|
||||
|
||||
if (handle != NULL) {
|
||||
g_mutex_lock(mutex);
|
||||
while (!done)
|
||||
g_cond_wait(cond, mutex);
|
||||
g_mutex_unlock(mutex);
|
||||
|
||||
tcp_connect_free(handle);
|
||||
}
|
||||
|
||||
g_cond_free(cond);
|
||||
g_mutex_free(mutex);
|
||||
|
||||
/* deinitialize everything */
|
||||
|
||||
io_thread_deinit();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
Loading…
Reference in New Issue
Block a user