IPC研究(3) -- pipe续

续上篇IPC-Pipes

上篇的example3中,我用named pipe(FIFO)来实现client和server的通信。
由于client每次发完一个包后,都会sleep(2),所以几乎没有资源冲突和同步的问题。
但是,如果将sleep(2)去掉,会出现什么结果呢?

我们加入以下这个函数,在每次接受到包之后,比较接收到的包和原始发送的包。
static int is_pkt_ok(struct cs_packet_t *pkt_recv, struct cs_packet_t *pkt_sent)
{
        if (pkt_recv->cli_pid != pkt_sent->cli_pid)
                return 0;
        if ( strcmp(pkt_recv->action, pkt_sent->action) )
                return 0;
        char *p = pkt_recv->data;
        char *q = pkt_sent->data;
        while (*p != '\0')
     {
                if ( toupper(*p) != toupper(*q) )
                        return 0;
                p++;
                q++;
        }
        return 1;
}
结果会发现,如果没有sleep(2),即,client发送包的速度很快,server处的处理就不对了。
原因是:
1. 如果procA打开一个fifo来read, procB打开同一个fifo来写,并且直接关掉,此时,procA将读到0个字节。也就是说,writer端关闭proc,reader端会读到0个字节(其实,这是个比较方便的特性,因为reader就有办法知道远方的writer是否关闭了pipe)。以下将有个示例程序证明这一点。

2. 在server端,对于每个packet新建一个线程来处理,这就有可能出现以下情况:一个线程处理完包,write回结果,但还没有关闭pipe时,另外一个线程启动,这个线程处理的是同一个client的包,于是它将与上一个线程共用一个FIFO来发送结果。于是就出现了冲突。client正等着接受第二个线程处理的结果,此时第一个线程关闭pipe的writer端的这个操作,也会使得client中的read不再block而返回0字节。

其实,当多个线程访问同一个资源而互相又不知道时,你基本上已经有一个bug了。
此处,FIFO是共有资源,两个线程同时访问,但又不加控制,结果不言而喻。

解决方案1:
client如果读到0字节,说明这是一个无效的read,重新read。
结果:出现奇怪的现象。要么server和client卡住,要么client一直在读pipe,但是每次读的结果都是0个字节。此时,server程序的VSZ高的出奇,如下:
22728  0.1  0.3 3124528 3684 pts/1    S+   22:49   0:00 ./server
这个方案应该是解决了以上问题,出现奇怪现象的原因应该是虚拟内存使用过高了。虚拟内存使用过高的原因是,不断的创建线程,在线程结束后,主线程没有pthread_join它,导致该线程相关的资源不能被操作系统回收,一旦这样的线程多了之后,虚拟内存就使用过高,导致程序无法继续运行了。

解决方案2:
server中添加pthread_join,等待该处理线程结束,回首资源。这样肯定没有问题,但是,所有的处理就串行化了。可见这个server设计的有烂!
不过这个比解决方案1好点,因为它至少解决了问题。

解决方案3:

利用thread attibutes,将处理packet的线程设置为detached thread。这样,当线程结束时,系统会自动回收线程的资源。这个方案比2要好一点。 

pthread_attr_t attr;

pthread_t thread;

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

pthread_create(&thread, &attr, &thread_function, NULL);

pthread_attr_destroy(&attr);

解决方案4:
对每个packet进行线程的创建和销毁,简直有点蠢!
server可以将packet放在一个FIFO中,而处理packet的线程不断从FIFO中取出packet进行处理。
对FIFO的操作应该由某个函数进行操作,这个函数应该是re-entrant的,所以要注意资源的同步。
处理packet的线程数量,如果是一个,那么在client较少的情况下应该没有问题。因为一个线程来处理几个client的请求还是说的过去的;如果是多个,那么就要注意了,因为几个处理线程拥有共同的资源,即,命名管道,需要进行仔细的处理,防止出现资源竞争。

以下代码实现server中的packet queue。client代码无需修改。
另外,目前代码中有以下几个地方需要改进。
1. client增加uer command interface,由此可以写脚本或者c程序来控制client的数量和行为。
2. server中的packet要增加同步机制,避免竞争状态出现。
3. server中增加线程池机制。
4. server中增加对client的管理线程,主要用于管理server->client的pipe,避免处理packet的线程反复开启和关闭同一个client的FIFO。
5. packet queue的长度要可以根据目前负载动态改变。
(下一步先实现client中的user command interface)

/**
 * server_with_queue.c
 *
 * client/server using named pipes (FIFO)
 * server implementation with a packet queue
 **/

#include "common.h"
#include <pthread.h>

#define DEBUG_SERVER 1

#if DEBUG_SERVER
static void print_pkt(struct cs_packet_t *pkt)
{
    printf("==========packet=================\n");
    printf("client pid = %d \n", pkt->cli_pid);
    printf("client action = %s \n", pkt->action);
    printf("client data = %s \n", pkt->data);
    printf("==========end pkt================\n");
}

#else
static void print_pkt(struct cs_packet_t *pkt)
{
    return;
}

#endif

/**packet queue implementation; all operations on packet queue should be synchronized to avoid race condition*/
#define PKT_QUEUE_SIZE 32

/**
 * struct: pkt_queue_t -- FIFO queue of (struct cs_packet_t*)
 * @size: size of the queue, we use power of 2 size because it make modulus operation easier and faster
 * @head : head of this circular queue
 * @tail: tail of this circular queue
 * @pkt_queue: stores PKT_QUEUE_SIZE elements of type (struct cs_packet_t*)
 *
 **/
struct pkt_queue_t
{
    int size;
    int head;
    int tail;
    struct cs_packet_t **queue;
};

static struct pkt_queue_t pkt_queue; /* global variable for this server */

/**
 * function: init_pkt_queue
 *
 * the return value must be checked
 **/
static int init_pkt_queue(struct pkt_queue_t *pkt_queue)
{
    pkt_queue->size = PKT_QUEUE_SIZE;
    pkt_queue->head = 0;
    pkt_queue->tail = 0;
    pkt_queue->queue = (struct cs_packet_t **)malloc( pkt_queue->size*sizeof(struct cs_packet_t*) );
    if (pkt_queue == NULL)
    {
        perror("allocating packet queue failed");
        return -1;
    }
    for (int i=0; i<pkt_queue->size; i++)
    {
        pkt_queue->queue[i] = (struct cs_packet_t*)malloc( sizeof(struct cs_packet_t) );
        if (pkt_queue->queue[i] == NULL)
        {
            perror("allocating packet queue failed");
            return -1;
        }
    }
    return 0;
}

static int is_empty(struct pkt_queue_t *pkt_queue)
{
    return (pkt_queue->head == pkt_queue->tail);
}

static int is_full(struct pkt_queue_t *pkt_queue)
{
    int tmp = (pkt_queue->tail+1) & (pkt_queue->size - 1);
    return (pkt_queue->head == tmp);
}

static void clone_packet(struct cs_packet_t *pkt_dst, const struct cs_packet_t *pkt_src)
{
    pkt_dst->cli_pid = pkt_src->cli_pid;
    strcpy(pkt_dst->action, pkt_src->action);
    strcpy(pkt_dst->data, pkt_src->data);
}

/**
 * function: in_pkt_queue -- add an element into this FIFO
 * @return: 0 for success, -1 for fail
 *
 * we don't simply put the pointer into the queue, but copies the value of every field
 * the return value must be checked
 **/
static int in_pkt_queue(struct pkt_queue_t *pkt_queue, const struct cs_packet_t *pkt_to_add)
{
    if (is_full(pkt_queue))
    {
        return -1;
    }
    clone_packet(pkt_queue->queue[pkt_queue->tail], pkt_to_add);
    pkt_queue->tail = (pkt_queue->tail+1) & (pkt_queue->size - 1);
    return 0;
}

/**
 * function: out_pkt_queue  --- remove an element out of the FIFO
 * @pkt_dst: the target packet to copy the info to
 * @return: 0 for success, -1 for fail(this happens when the FIFO is empty)
 *
 * the return value must be checked
 **/
static int out_pkt_queue(struct pkt_queue_t *pkt_queue, struct cs_packet_t *pkt_dst)
{
    if (is_empty(pkt_queue))
    {
        return -1;
    }
    clone_packet(pkt_dst, pkt_queue->queue[pkt_queue->head]);
    pkt_queue->head = (pkt_queue->head+1)&(pkt_queue->size-1);
    return 0;
}

static int destroy_pkt_queue(struct pkt_queue_t *pkt_queue)
{
    for (int i=0; i<pkt_queue->size; i++)
    {
        free(pkt_queue->queue[i]);
    }
    free(pkt_queue->queue);
    return 0;
}

/**
 * function: thread_handle_requests
 *
 * thread which handles client requests, sends back the result to client through cli_pipe_%d pipe and then exits
 * client1 --(pkt1)--> |                      | --> pkt1 (thread1)
 * client2 --(pkt2)--> | --> packet queue --> | --> pkt2 (thread2)
 * client3 --(pkt3)--> |                      | --> pkt3 (thread3)
 *
 * N threads may loop to take the requests out of the queue and then handles it.
 * This avoids the overhead of thead creation and destruction.
 **/
void *thread_handle_requests(void *arg) /* arg is NULL */
{
    struct cs_packet_t *pkt = (struct cs_packet_t*)malloc(sizeof(struct cs_packet_t));
    if (pkt == NULL)
    {
        perror("Not Enough Memory In Heap!");
        exit(-1);
    }

    /* main loop in this thread, never exits voluntarily */
    for (;;)
    {
        /* loop to get a packet from packet queue */
        for (;;)
        {
            if ( out_pkt_queue(&pkt_queue, pkt) < 0 ) /* the queue is empty */
            {
                sleep(1);
            }
            else
            {
                break;
            }
        }
        print_pkt(pkt);

        char client_fifo[32];    /* name of this client's fifo, cli_fifo_%d */
        int client_fifo_fd;
        memset(client_fifo, 0, sizeof(client_fifo));
        sprintf(client_fifo, CLIENT_FIFO_NAME, pkt->cli_pid);
        client_fifo_fd = open(client_fifo, O_WRONLY);

        if (client_fifo_fd == -1)
        {
            perror("open client pipe failed");
            exit(-1);
        }

        if (!strcmp(pkt->action, "upcase"))
        {
            char *tmp_char_ptr = pkt->data;
            while (*tmp_char_ptr)
            {
                *tmp_char_ptr = toupper(*tmp_char_ptr);
                tmp_char_ptr++;
            }
            int ret = write(client_fifo_fd, pkt, sizeof(struct cs_packet_t));
            if (ret < 0)
                perror("write failed \n");
            else
                printf("<<server>> ---- write %d bytes \n", ret);
        }
        else if (!strcmp(pkt->action, "downcase"))
        {
            char *tmp_char_ptr = pkt->data;
            while (*tmp_char_ptr)
            {
                *tmp_char_ptr = tolower(*tmp_char_ptr);
                tmp_char_ptr++;
            }
            int ret = write(client_fifo_fd, pkt, sizeof(struct cs_packet_t));
            if (ret < 0)
                perror("write failed \n");
            else
                printf("<<server>> ---- write %d bytes \n", ret);
        }
        else
        {
            sprintf(pkt->data, "Action %s not supported", pkt->action);
            int ret = write(client_fifo_fd, pkt, sizeof(struct cs_packet_t));
            if (ret < 0)
                perror("write failed \n");
            else
                printf("<<server>> ---- write %d bytes \n", ret);
        }
        close(client_fifo_fd);
   
    }
    free(pkt);
    return NULL;
}


void main()
{
    struct cs_packet_t pkt;
    int server_fifo_fd;
    pthread_t th;
    void *result;
    /* make server fifo */
    mkfifo(SERVER_FIFO_NAME, 0777);
    server_fifo_fd = open(SERVER_FIFO_NAME, O_RDONLY);
    if (server_fifo_fd == -1)
    {
        perror("create server fifo failed");
        exit(-1);
    }

    /* init packet queue */
    int ret = init_pkt_queue(&pkt_queue);
    if (ret < 0)
    {
        perror("allocating pkt_queue failed");
        exit(-1);
    }
    /* create a thread to handle requests */
    ret = pthread_create(&th, NULL, thread_handle_requests, (void*)&pkt);
    if (ret < 0)
    {
        perror("create thread failed");
        exit(-1);
    }
    /* read from server fifo */
    for (;;)
    {
        int read_res = read(server_fifo_fd, &pkt, sizeof(pkt));
        if (read_res > 0)
        {
            /* add the packet into the packet queue [pkt_queue] */
            for (;;)
            {
                ret = in_pkt_queue(&pkt_queue, &pkt);
                if (ret < 0) /* the queue is full */
                {
                    sleep(1);
                }
                else
                {
                    break;
                }
            }
        } /* if (read_res > 0) */
    }      /* loop to listen to cs fifo */
    destroy_pkt_queue(&pkt_queue);
}



你可能感兴趣的:(IPC研究(3) -- pipe续)