2021SC@SDUSC
目录
Implementatin of a searpc transport based on named pipe(2)
Implementatin of a searpc transport based on named pipe(2)
基于named-pipe的searpc-client
经过前一篇分析,基本分析完成了与searpc客户端相关的方法.在之前的分析中,我们已经知道了,searpc-client的作用手势存放传输函数和socket fd
,接下来从这两个方面在总结基于named-pipe
的searpc-client的用法.
首先是传输函数,这里的searpc客户端与服务器传输数据的方式是通过named-pipe的,而作用是将收发数据的传输函数自然也应该基于named-pipe
.首先通过searpc_create_named_pipe_client
方法构造named-pipe客户端,在传输函数searpc_named_pipe_send
,中指定该客户端,并通过它收发数据.其余部分与基于socket
的searpc-client相同.
其次是socket fd
,基于named-pipe的searpc-client显示是不需要socket fd的,但是需要一个用于标识并获取到所使用的named-pipe的标识作为代替,即是ClientTransportData,他是一个包含了named-pipe客户端与服务名的结构体,传输函数通过它获取到named-pipe,并传输数据.
searpc-named-pipe-transport-c
接下来继续分析searpc-named-pipe-transport-c
创建named-pipe服务器
SearpcNamedPipeServer* searpc_create_named_pipe_server(const char *path)
{
SearpcNamedPipeServer *server = g_malloc0(sizeof(SearpcNamedPipeServer));
memcpy(server->path, path, strlen(path) + 1);
return server;
}
SearpcNamedPipeServer* searpc_create_named_pipe_server_with_threadpool (const char *path, int named_pipe_server_thread_pool_size)
{
GError *error = NULL;
SearpcNamedPipeServer *server = g_malloc0(sizeof(SearpcNamedPipeServer));
memcpy(server->path, path, strlen(path) + 1);
server->named_pipe_server_thread_pool = g_thread_pool_new (handle_named_pipe_client_with_threadpool,
NULL,
named_pipe_server_thread_pool_size,
FALSE,
&error);
if (!server->named_pipe_server_thread_pool) {
if (error) {
g_warning ("Falied to create named pipe server thread pool : %s\n", error->message);
g_clear_error (&error);
} else {
g_warning ("Falied to create named pipe server thread pool.\n");
}
g_free (server);
return NULL;
}
return server;
}
上述两个方法都创建了一个NamedPipeServer
,并指定了路径,区别在于是否创建了线程池;其中线程池中的线程都指定了handle_named_pipe_client_with_threadpool
方法
但是并没有指定pipe_fd
启动named-pipe服务器
int searpc_named_pipe_server_start(SearpcNamedPipeServer *server)
{
#if !defined(WIN32)
int pipe_fd = socket (AF_UNIX, SOCK_STREAM, 0);
const char *un_path = server->path;
if (pipe_fd < 0) {
g_warning ("Failed to create unix socket fd : %s\n",
strerror(errno));
return -1;
}
struct sockaddr_un saddr;
saddr.sun_family = AF_UNIX;
if (strlen(server->path) > sizeof(saddr.sun_path)-1) {
g_warning ("Unix socket path %s is too long."
"Please set or modify UNIX_SOCKET option in ccnet.conf.\n",
un_path);
goto failed;
}
if (g_file_test (un_path, G_FILE_TEST_EXISTS)) {
g_message ("socket file exists, delete it anyway\n");
if (g_unlink (un_path) < 0) {
g_warning ("delete socket file failed : %s\n", strerror(errno));
goto failed;
}
}
g_strlcpy (saddr.sun_path, un_path, sizeof(saddr.sun_path));
if (bind(pipe_fd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {
g_warning ("failed to bind unix socket fd to %s : %s\n",
un_path, strerror(errno));
goto failed;
}
if (listen(pipe_fd, 10) < 0) {
g_warning ("failed to listen to unix socket: %s\n", strerror(errno));
goto failed;
}
if (chmod(un_path, 0700) < 0) {
g_warning ("failed to set permisson for unix socket %s: %s\n",
un_path, strerror(errno));
goto failed;
}
server->pipe_fd = pipe_fd;
#endif // !defined(WIN32)
/* TODO: use glib thread pool */
pthread_create(&server->listener_thread, NULL, named_pipe_listen, server);
return 0;
#if !defined(WIN32)
failed:
close(pipe_fd);
return -1;
#endif
}
该方法的主要作用是创建监听线程listener_thread
,并传入方法named_pipe_listen
除此之外,若当前环境不是WIN32
(named-pipe仅在win32API下支持),那么是需要用socket fd
代替named-pipe fd
的
named-pipe监听方法
typedef struct {
SearpcNamedPipe connfd;
} ServerHandlerData;
static void* named_pipe_listen(void *arg)
{
SearpcNamedPipeServer *server = arg;
#if !defined(WIN32)
while (1) {
int connfd = accept (server->pipe_fd, NULL, 0);
ServerHandlerData *data = g_malloc(sizeof(ServerHandlerData));
data->connfd = connfd;
if (server->named_pipe_server_thread_pool)
g_thread_pool_push (server->named_pipe_server_thread_pool, data, NULL);
else {
pthread_t handler;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&handler, &attr, handle_named_pipe_client_with_thread, data);
}
}
#else // !defined(WIN32)
while (1) {
HANDLE connfd = INVALID_HANDLE_VALUE;
BOOL connected = FALSE;
connfd = CreateNamedPipe(
server->path, // pipe name
PIPE_ACCESS_DUPLEX, // read/write access
PIPE_TYPE_MESSAGE | // message type pipe
PIPE_READMODE_MESSAGE | // message-read mode
PIPE_WAIT, // blocking mode
PIPE_UNLIMITED_INSTANCES, // max. instances
kPipeBufSize, // output buffer size
kPipeBufSize, // input buffer size
0, // client time-out
NULL); // default security attribute
if (connfd == INVALID_HANDLE_VALUE) {
G_WARNING_WITH_LAST_ERROR ("Failed to create named pipe");
break;
}
/* listening on this pipe */
connected = ConnectNamedPipe(connfd, NULL) ?
TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if (!connected) {
G_WARNING_WITH_LAST_ERROR ("failed to ConnectNamedPipe()");
CloseHandle(connfd);
break;
}
/* g_debug ("Accepted a named pipe client\n"); */
ServerHandlerData *data = g_malloc(sizeof(ServerHandlerData));
data->connfd = connfd;
if (server->named_pipe_server_thread_pool)
g_thread_pool_push (server->named_pipe_server_thread_pool, data, NULL);
else {
pthread_t handler;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&handler, &attr, handle_named_pipe_client_with_thread, data);
}
}
#endif // !defined(WIN32)
return NULL;
}
首先定义结构体ServerHandlerData
,用来存储连接成功后获取的通信描述符
与上面相同,若处于非WIN32
环境下,需要使用监听socket
的方法监听,即accept
方法.
-
accept()
:接收客户端发起的tcp连接请求,返回一个通信描述符,专门用于与连接成功的客户端进行通信
若可以使用named-pipe
,那么创建一个NamedPipe
对象,传入named-pipe-server
中存放的path
然后通过ConnectNamedPipe()
方法开始监听.连接成功后将通信描述符放进ServerHandlerData
.
然后开始进行通信.若存在线程池,则调用g_thread_pool_push
,将通信描述符作为一个任务插入到线程池;否则创建一个新线程,处理通信任务,并指定handle_named_pipe_client_with_thread
方法
named-pipe监听到连接后处理通信过程
static void* handle_named_pipe_client_with_thread(void *arg)
{
named_pipe_client_handler(arg);
return NULL;
}
static void handle_named_pipe_client_with_threadpool(void *data, void *user_data)
{
named_pipe_client_handler(data);
}
static void named_pipe_client_handler(void *data)
{
ServerHandlerData *handler_data = data;
SearpcNamedPipe connfd = handler_data->connfd;
guint32 len;
guint32 bufsize = 4096;
char *buf = g_malloc(bufsize);
g_message ("start to serve on pipe client\n");
while (1) {
len = 0;
if (pipe_read_n(connfd, &len, sizeof(guint32)) < 0) {
g_warning("failed to read rpc request size: %s\n", strerror(errno));
break;
}
if (len == 0) {
/* g_debug("EOF reached, pipe connection lost"); */
break;
}
while (bufsize < len) {
bufsize *= 2;
buf = realloc(buf, bufsize);
}
if (pipe_read_n(connfd, buf, len) < 0 || len == 0) {
g_warning("failed to read rpc request: %s\n", strerror(errno));
break;
}
char *service, *body;
if (request_from_json (buf, len, &service, &body) < 0) {
break;
}
gsize ret_len;
char *ret_str = searpc_server_call_function (service, body, strlen(body), &ret_len);
g_free (service);
g_free (body);
len = (guint32)ret_len;
if (pipe_write_n(connfd, &len, sizeof(guint32)) < 0) {
g_warning("failed to send rpc response(%s): %s\n", ret_str, strerror(errno));
g_free (ret_str);
break;
}
if (pipe_write_n(connfd, ret_str, ret_len) < 0) {
g_warning("failed to send rpc response: %s\n", strerror(errno));
g_free (ret_str);
break;
}
g_free (ret_str);
}
#if !defined(WIN32)
close(connfd);
#else // !defined(WIN32)
DisconnectNamedPipe(connfd);
CloseHandle(connfd);
#endif // !defined(WIN32)
g_free (data);
g_free (buf);
}
线程池和单个线程都使用了named_pipe_client_handler
方法处理通信描述符
首先获取到named-pipe
的通信描述符connfd
,然后创建缓冲区开始循环收发信息:
-
通过
pipe_read_n
读取到json请求并存放到缓冲区
-
使用
request_from_json
,将json串转化为service
,函数名与参数 -
调用
searpc_server_call_function
调用rpc函数 -
使用
pipe_write_n
方法写回返回值
在所有过程处理结束后,应该关闭socket
或者named-pipe
named-pipe客户端的连接
int searpc_named_pipe_client_connect(SearpcNamedPipeClient *client)
{
#if !defined(WIN32)
client->pipe_fd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un servaddr;
servaddr.sun_family = AF_UNIX;
g_strlcpy (servaddr.sun_path, client->path, sizeof(servaddr.sun_path));
if (connect(client->pipe_fd, (struct sockaddr *)&servaddr, (socklen_t)sizeof(servaddr)) < 0) {
g_warning ("pipe client failed to connect to server: %s\n", strerror(errno));
return -1;
}
#else // !defined(WIN32)
SearpcNamedPipe pipe_fd;
pipe_fd = CreateFile(
client->path, // pipe name
GENERIC_READ | // read and write access
GENERIC_WRITE,
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
0, // default attributes
NULL); // no template file
if (pipe_fd == INVALID_HANDLE_VALUE) {
G_WARNING_WITH_LAST_ERROR("Failed to connect to named pipe");
return -1;
}
DWORD mode = PIPE_READMODE_MESSAGE;
if (!SetNamedPipeHandleState(pipe_fd, &mode, NULL, NULL)) {
G_WARNING_WITH_LAST_ERROR("Failed to set named pipe mode");
return -1;
}
client->pipe_fd = pipe_fd;
#endif // !defined(WIN32)
/* g_debug ("pipe client connected to server\n"); */
return 0;
}
对于不支持named-pipe
的环境,同样使用socket
进行连接
而对于named-pipe
,则是创建一个文件用于存放管道相关的信息,并设置到named-pipe
客户端的pipe_fd
基于named-pipe的searpc-client
不同于searpc-client,server的核心工作与数据传输过程关系不大,因此与基于socket的searpc-server仅在创建与监听时有所不同
总结
基于命名管道的searpc主要的不同之处在于与数据传输相关的一些方法上使用的named-pipe
而非socket
而在使用中,底层的传输方法对用户是透明的,可以根据使用环境选择