握瑾怀瑜 发布的文章

vmware-tool安装及设置共享目录

宿主计算机为windows系统,虚拟机为Cent OS 6.10
用VMware Workstation安装的时候会报错,这里是从VMware官网下载的包安装的

  1. 下载vmtool安装包
  2. 解压
unzip VMware-Tools-10.0.9-3917699.zip
  1. 挂载linux iso文件
cd VMware-Tools-10.0.9-3917699/vmtools/
mkdir /tmp/vmware
mount -o loop linux.iso /tmp/vmware
  1. 解压出安装包文件
cd /tmp/vmware/
cp VMwareTools-10.0.9-3917699.tar.gz /tmp/
cd /tmp/
chmod 777 VMwareTools-10.0.9-3917699.tar.gz
tar -zxvf VMwareTools-10.0.9-3917699.tar.gz
  1. 安装,碰到选项一直回车就行
cd vmware-tools-distrib/
  1. 在 VMware 的虚拟机设置->选项中添加共享目录,我这里添加的名称为 share-dir(这个名称在第7步有用),windows路径为 E:\share-dir
  2. 在linux虚拟机中把共享的目录挂载在虚拟机的目录上
mount -t vmhgfs .host:/share-dir /tmp/target-share-dir

ok了,如果需要开机就挂载的话,可以把第7步的命令放到开机脚本中

redis 学习-11 lua脚本及php中的lua脚本

简介

用于高效地处理 CAS (check-and-set)命令,整个脚本会以原子性的方式执行

关于lua语言相关的就不说了,这里记录下再php中怎么使用lua,注意这里使用的是predis

/**
 * 定义命令类,需继承 ScriptCommand 类
 * Class MyMultipleSetNx
 */
class MyMultipleSetNx extends Predis\Command\ScriptCommand
{
    /**
     * key的数量
     * @return int
     */
    public function getKeysCount()
    {
        // 参数个数,等于-1则代表最后一个参数,其他的全部是key
        return 3;
    }

    /**
     * 脚本代码
     * @return string
     */
    public function getScript()
    {
        return <<<LUA
local result = {}
for key,value in pairs(KEYS) do
    if redis.call('exists', value) == 1 then
        result[key] = 0
    else
        result[key] = 1
        redis.call('set', value, ARGV[key])
    end
end
return result
LUA;
    }
}

$redisClient = new \Predis\Client('tcp://127.0.0.1:6379');
// 定义命令
$redisClient->getProfile()->defineCommand('myMsetNx', 'MyMultipleSetNx');
// 调用
$result = call_user_func_array([$redisClient, 'myMsetNx'], ['myMultipleNxKey1', 'myMultipleNxKey2', 'myMultipleNxKey3', 'myMultipleNxValue1', 'myMultipleNxValue2', 'myMultipleNxValue3']);

var_dump($result);

输出

[[email protected] redis-test]# php-5.6.13 tes-redis.php 
/www_new/redis-test/tes-redis.php:42:
array(3) {
  [0] =>
  int(0)
  [1] =>
  int(0)
  [2] =>
  int(0)
}

redis 学习-10 pipeline及php中的pipeline

概念

redis执行一条命令分为四个过程

  1. 发送命令
  2. 命令排队
  3. 命令执行
  4. 返回结果

其中1和4称为Round Trip Time(RTT,往返时间)
pipeline的原理就是减少往返时间来提高速度,但是需要注意一点,如果pipeline中的命令过多,可能会导致阻塞redis,所以在使用pipeline时,需要把握好度,不然就得不偿失了

php中pipeline的用法

注意:以下实例全部是以Predis为基础来写的
需要注意下,不同命令的返回值是不一样的,需要区别对待

# 代码
$redisClient = new \Predis\Client('tcp://127.0.0.1:6379');

// 回调方式使用
$result = $redisClient->pipeline(function ($client){
    /* @var \Predis\Client $client */
    $client->set('commandA', 'aaa');
    $client->sadd('commandC', 'bbb');
});

// 接口形式调用
$result = $redisClient->pipeline()->set('commandA', 'aaa')
    ->sadd('commandC', 'bbb')
    ->execute();

var_dump($result);

# 输出
array(2) {
  [0] =>
  class Predis\Response\Status#12 (1) {
    private $payload =>
    string(2) "OK"
  }
  [1] =>
  int(0)
}

linux编程学习 05-02 网络编程

上一篇:http://jinblog.com/archives/866.html

网络高级编程

  • 前面介绍的函数如recv,send,read和write等函数都是阻塞性函数,若资源没有准备好,则调用该函数的进程将进入阻塞状态,下面介绍两种I/O复用的解决方案

    • fcntl函数实现(非阻塞)
    • select函数

I/O多路转换-select函数

int select(int maxfdp1,fd_set readfds,fd_set writefds,fd_set exceptfds,struct timeval timeout);

  • 头文件
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
  • 返回准备就绪的描述符数,若超时则为0,若出错则为-1
  • timval结构体
struct timeval
{
        long tv_sec;//seconds
        long tv_usec;//and microseconds
};
  • 参数

    • maxfdp1:最大fd加1(max fd plus 1),在三个描述符集中找出最高描述符编号值,然后加1,就是第一个参数的值
    • readfds,writefds,exceptfds:是指向描述符集的指针。这三个描述符集说明了我们关小的可读,可写或处于异常条件的各个描述符。每个描述符集存放在一个fd_set数据类型中
    • timeout:指定愿意等待的时间

      • NULL:永远等待,知道捕捉到信号或文件描述符已准备好为止
      • 具体值:struct timeval类型的指针,若等待为timeout时间,还没有文件描述符准备好,就立即返回
      • 0:从不等待,测试所有指定的描述符立即返回
  • 传向select的参数告诉内核

    • 我们所关心的描述符
    • 对于每个描述符我们所关心的条件(是否可读一个给定的描述符,是否可写一个指定的描述符,是否关心一个描述符的异常条件)
    • 希望等待多长时间(可以永远等待,等待一个固定量的时间,或完全不等待)
  • 从select返回时内核告诉我们

    • 已准备好的描述符的数量
    • 哪一个描述符已准备好读,写或异常条件
    • 使用这种返回值,就可调用相应的I/O函数(一般是write或read),并且确知该函数不会阻塞
  • select函数根据希望进行的文件操作对文件描述符进行分类处理,这里,对文件描述符的处理主要设计4个宏函数

    • FD_ZERO(fd_set* set) 清除一个文件描述符集
    • FD_SET(int fd,fd_set* set) 将一个文件描述符加入文件描述符集中
    • FD_CLR(int fd,fd_set* set) 将一个文件描述符从文件描述符集中清除
    • FD_ISSET(int fd,fd_set* set) 测试该集中的一个给定位是否有变化
  • 在使用select函数之前,首先使用FD_ZERO和FD_SET来初始化文件描述符集,并使用select函数时,可循环使用FD_ISSET测试描述符集,在执行完成对相关的文件描述符后,可用FD_CLR来清除描述符集

fcntl函数实现示例

#include <stdio.h>
// atoi函数
#include <stdlib.h>
// wait函数
#include <sys/wait.h>
// I/O函数
#include <unistd.h>
#include <time.h>
// socket 系列函数
#include <sys/socket.h>
// sockaddr_in 结构体
#include <netinet/in.h>
// hton 函数
#include <arpa/inet.h>
#include <signal.h>
// memset函数
#include <string.h>
// pthread库
#include <pthread.h>
// errno
#include <errno.h>
// fcntl函数
#include <fcntl.h>
// ctrl + c时关掉服务器socket
void siginit_handle(int);
// 初始化指定数目的线程处理连接
void child_thread_init(int);
// 客户端链表节点结构体
struct client_fd
{
        int fd;
        struct client_fd* pre;
        struct client_fd* next;
};
typedef struct client_fd client_fd;
// 链表
client_fd* client_fd_link = NULL;
// 链表互斥锁
pthread_mutex_t link_mutex;
// 服务器socket
int server_fd = 0;
// 添加链表元素
int add_client(int);
// 删除链表元素,通过fd删除元素
int del_client_by_fd(int);
// 通过节点地址删除元素
int del_client_by_client(client_fd*);
// 服务器连接日志
void server_log(struct sockaddr_in*);
// ctrl + c关闭服务器socket
void sigint_handle(int sig);

int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        // 初始化互斥锁
        pthread_mutex_init(&link_mutex,NULL);
        struct sockaddr_in server_attr;
        memset(&server_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }

        /*
         * 2. 设置SO_REUSEADDR,避免重启导致bind报错
         */
        int opt = 1;
        setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(int));
        /*
         * 3. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 4. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }

        // 忽略SIGPIPE信号处理函数,避免由于客户端断开连接,并由于read发送SIGPIPE信号导致进程结>束
        signal(SIGPIPE,SIG_IGN);
        int client_fd = 0;
        struct sockaddr_in client_attr;
        socklen_t addrlen = sizeof(client_attr);
        int client_flag = 0;
        // 开启子线程处理连接·
        child_thread_init(8);
        while(1)
        {
                /*
                * 5. 调用accept获得客户端连接,并返回一个新的socket文件描述符,新的文件描述符放>进client_fd链表中
                * 没有客户端连接,此函数会阻塞,直到获得一个客户端连接
                */
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                if(client_fd <= 0)
                {
                        continue;
                }
                server_log(&client_attr);
                // 修改socket为非阻塞读写
                client_flag = fcntl(client_fd,F_GETFL);
                client_flag |= O_NONBLOCK;
                fcntl(client_fd,F_SETFL,client_flag);
                add_client(client_fd);
                // 线程是共享的进程资源,这里不要关闭,不然线程里面的也没了
                // close(client_fd);
        }
        // close(server_fd);
        return 0;
}
void sigint_handle(int sig)
{
        close(server_fd);
        exit(0);
}
void server_log(struct sockaddr_in* client_attr)
{
        char ip[16] = {'\0'};
        // 结构体的的数据不能直接显示,需要先转换成本机字节序
        inet_ntop(AF_INET,&client_attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connected by %s:%d\n",ip,ntohs(client_attr->sin_port));
}
void* thread_handle(void* data)
{
        /*
        * 5. 遍历客户端socket_fd,对有输入的客户端进行回应
        */
        client_fd* temp;
        char res[512] = {'\0'},buf[512] = {'\0'};
        int read_len = 0,res_len = sizeof(res);
        while(1)
        {
                temp = client_fd_link;
                while(temp != NULL)
                {
                        read_len = read(temp->fd,res,res_len);
                        // 客户端关闭,则关闭客户端连接
                        if(read_len == 0)
                        {
                                perror("read");
                                del_client_by_client(temp);
                                break;
                        }
                        else if(read_len > 0)
                        {
                                // buf 与 res不能一样
                                sprintf(buf,"thread : %lu\nmessage : %s\n",pthread_self(),res);

                                sprintf(res,"%s\n",buf);

                                if(write(temp->fd,res,res_len) != res_len)
                                {
                                        // 客户端关闭,则关闭客户端连接
                                        if(errno == EPIPE)
                                        {
                                                perror("write");
                                                del_client_by_client(temp);
                                                break;
                                        }
                                }
                        }
                        temp = temp->next;
                }
        }
        return (void*)NULL;
}
void child_thread_init(int max)
{
        pthread_t tid = 0;
        // 初始化线程属性,用来创建分离的线程,使子线程结束后自动回收
        pthread_attr_t thread_attr;
        pthread_attr_init(&thread_attr);
        pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
        // 子线程处理I/O,达到并发的效果
        while(max > 0)
        {
                pthread_create(&tid,&thread_attr,thread_handle,(void*)NULL);
                max--;
        }
}
// 添加链表元素
int add_client(int fd)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                client_fd_link = malloc(sizeof(client_fd));
                client_fd_link->pre = NULL;
                client_fd_link->next = NULL;
                client_fd_link->fd = fd;
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        client_fd* temp = client_fd_link;
        while(temp->next != NULL)
        {
                temp = temp->next;
        }
        temp->next = malloc(sizeof(client_fd));
        temp->next->pre = temp;
        temp->next->next = NULL;
        temp->next->fd = fd;
        pthread_mutex_unlock(&link_mutex);
        return 0;
}
// 删除链表元素,通过fd删除元素
int del_client_by_fd(int fd)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                pthread_mutex_unlock(&link_mutex);
                return -1;
        }
        client_fd* temp = client_fd_link->next;
        // 链表第一个元素比较特殊,做特殊处理
        if(fd == client_fd_link->fd)
        {
                free(client_fd_link);
                client_fd_link = temp;
                if(client_fd_link != NULL)
                {
                        client_fd_link->pre = NULL;
                }
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        while(temp != NULL)
        {
                if(temp->fd == fd)
                {
                        temp->pre->next = temp->next;
                        if(temp->next != NULL)
                        {
                                temp->next->pre = temp->pre;
                        }
                        close(temp->fd);
                        free(temp);
                        pthread_mutex_unlock(&link_mutex);
                        return 0;
                }
                temp = temp->next;
        }
        pthread_mutex_unlock(&link_mutex);
        return -1;
}
// 通过节点地址删除元素
int del_client_by_client(client_fd* client)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                pthread_mutex_unlock(&link_mutex);
                return -1;
        }
        client_fd* temp = client_fd_link->next;
        // 链表第一个元素比较特殊,做特殊处理
        if(client == client_fd_link)
        {
                free(client_fd_link);
                client_fd_link = temp;
                if(client_fd_link != NULL)
                {
                        client_fd_link->pre = NULL;
                }
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        while(temp != NULL)
        {
                if(temp == client)
                {
                        temp->pre->next = temp->next;
                        if(temp->next != NULL)
                        {
                                temp->next->pre = temp->pre;
                        }
                        close(temp->fd);
                        free(temp);
                        pthread_mutex_unlock(&link_mutex);
                        return 0;
                }
                temp = temp->next;
        }
        pthread_mutex_unlock(&link_mutex);
        return -1;
}

select函数实现示例

#include <stdio.h>
// atoi函数
#include <stdlib.h>
// wait函数
#include <sys/wait.h>
// I/O函数
#include <unistd.h>
#include <time.h>
// socket 系列函数
#include <sys/socket.h>
// sockaddr_in 结构体
#include <netinet/in.h>
// hton 函数
#include <arpa/inet.h>
#include <signal.h>
// memset函数
#include <string.h>
// pthread库
#include <pthread.h>
// errno
#include <errno.h>
// fcntl函数
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
// ctrl + c时关掉服务器socket
void siginit_handle(int);
// 初始化指定数目的线程处理连接
void child_thread_init(int);
// 客户端链表节点结构体
struct client_fd
{
        int fd;
        struct client_fd* pre;
        struct client_fd* next;
};
typedef struct client_fd client_fd;
// 链表
client_fd* client_fd_link = NULL;
// 链表互斥锁
pthread_mutex_t link_mutex;
// 服务器socket
int server_fd = 0;
// 添加链表元素
int add_client(int);
// 删除链表元素,通过fd删除元素
int del_client_by_fd(int);
// 通过节点地址删除元素
int del_client_by_client(client_fd*);
// 服务器连接日志
void server_log(struct sockaddr_in*);
// ctrl + c关闭服务器socket
void sigint_handle(int sig);
// 把链表中的fd加入到set中,并返回socket描述符最大的一个
int init_set(fd_set*);
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        // 初始化互斥锁
        pthread_mutex_init(&link_mutex,NULL);
        struct sockaddr_in server_attr;
        memset(&server_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }

        /*
         * 2. 设置SO_REUSEADDR,避免重启导致bind报错
         */
        int opt = 1;
        setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(int));
        /*
         * 3. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 4. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }

        // 忽略SIGPIPE信号处理函数,避免由于客户端断开连接,并由于read发送SIGPIPE信号导致进程结>束
        signal(SIGPIPE,SIG_IGN);
        int client_fd = 0;
        struct sockaddr_in client_attr;
        socklen_t addrlen = sizeof(client_attr);
        // 开启子线程处理连接·
        child_thread_init(8);
        while(1)
        {
                /*
                * 5. 调用accept获得客户端连接,并返回一个新的socket文件描述符,新的文件描述符放>进client_fd链表中
                * 没有客户端连接,此函数会阻塞,直到获得一个客户端连接
                */
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                if(client_fd <= 0)
                {
                        continue;
                }
                server_log(&client_attr);
                add_client(client_fd);
                // 线程是共享的进程资源,这里不要关闭,不然线程里面的也没了
        }
        return 0;
}
void sigint_handle(int sig)
{
        close(server_fd);
        exit(0);
}
void server_log(struct sockaddr_in* client_attr)
{
        char ip[16] = {'\0'};
        // 结构体的的数据不能直接显示,需要先转换成本机字节序
        inet_ntop(AF_INET,&client_attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connected by %s:%d\n",ip,ntohs(client_attr->sin_port));
}
void response(client_fd* temp)
{
        char res[512] = {'\0'},buf[512] = {'\0'};
        int read_len = 0,res_len = sizeof(res);
        read_len = read(temp->fd,res,res_len);
        // 客户端关闭,则关闭客户端连接
        if(read_len == 0)
        {
                printf("close connection\n");
                del_client_by_client(temp);
                return;
        }
        else if(read_len > 0)
        {
                // buf 与 res不能一样
                sprintf(buf,"thread : %lu\nmessage : %s\n",pthread_self(),res);

                sprintf(res,"%s\n",buf);

                if(write(temp->fd,res,res_len) != res_len)
                {
                        // 客户端关闭,则关闭客户端连接
                        if(errno == EPIPE)
                        {
                                printf("close connection\n");
                                del_client_by_client(temp);
                                return;
                        }
                }
        }
}
void* thread_handle(void* data)
{
        /*
        * 5. 遍历客户端socket_fd,对有输入的客户端进行回应
        */
        client_fd* temp;
        fd_set set;
        int max = init_set(&set);
        struct timeval time = {2,0};
        int n = 0;
        while(1)
        {
                // printf("n : %d;max : %d\n",n,max);
                // 注意:是文件描述符最大的加1,
                n = select(max + 1,&set,NULL,NULL,&time);
                temp = client_fd_link;
                while(temp != NULL && n > 0)
                {
                        if(FD_ISSET(temp->fd,&set) == 0)
                        {
                                continue;
                        }
                        response(temp);
                        temp = temp->next;
                        n--;
                }
                // 时间需要重新设置
                time.tv_sec = 2;
                time.tv_usec = 0;
                // 用最新的连接信息设置set,重设最大值
                max = init_set(&set);
        }
        return (void*)NULL;
}
void child_thread_init(int max)
{
        pthread_t tid = 0;
        // 初始化线程属性,用来创建分离的线程,使子线程结束后自动回收
        pthread_attr_t thread_attr;
        pthread_attr_init(&thread_attr);
        pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
        // 子线程处理I/O,达到并发的效果
        while(max > 0)
        {
                pthread_create(&tid,&thread_attr,thread_handle,(void*)NULL);
                max--;
        }
}
// 添加链表元素
int add_client(int fd)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                client_fd_link = malloc(sizeof(client_fd));
                client_fd_link->pre = NULL;
                client_fd_link->next = NULL;
                client_fd_link->fd = fd;
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        client_fd* temp = client_fd_link;
        while(temp->next != NULL)
        {
                temp = temp->next;
        }
        temp->next = malloc(sizeof(client_fd));
        temp->next->pre = temp;
        temp->next->next = NULL;
        temp->next->fd = fd;
        pthread_mutex_unlock(&link_mutex);
        return 0;
}
// 删除链表元素,通过fd删除元素
int del_client_by_fd(int fd)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                pthread_mutex_unlock(&link_mutex);
                return -1;
        }
        client_fd* temp = client_fd_link->next;
        // 链表第一个元素比较特殊,做特殊处理
        if(fd == client_fd_link->fd)
        {
                free(client_fd_link);
                client_fd_link = temp;
                if(client_fd_link != NULL)
                {
                        client_fd_link->pre = NULL;
                }
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        while(temp != NULL)
        {
                if(temp->fd == fd)
                {
                        temp->pre->next = temp->next;
                        if(temp->next != NULL)
                        {
                                temp->next->pre = temp->pre;
                        }
                        close(temp->fd);
                        free(temp);
                        pthread_mutex_unlock(&link_mutex);
                        return 0;
                }
                temp = temp->next;
        }
        pthread_mutex_unlock(&link_mutex);
        return -1;
}
// 通过节点地址删除元素
int del_client_by_client(client_fd* client)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                pthread_mutex_unlock(&link_mutex);
                return -1;
        }
        client_fd* temp = client_fd_link->next;
        // 链表第一个元素比较特殊,做特殊处理
        if(client == client_fd_link)
        {
                free(client_fd_link);
                client_fd_link = temp;
                if(client_fd_link != NULL)
                {
                        client_fd_link->pre = NULL;
                }
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        while(temp != NULL)
        {
                if(temp == client)
                {
                        temp->pre->next = temp->next;
                        if(temp->next != NULL)
                        {
                                temp->next->pre = temp->pre;
                        }
                        close(temp->fd);
                        free(temp);
                        pthread_mutex_unlock(&link_mutex);
                        return 0;
                }
                temp = temp->next;
        }
        pthread_mutex_unlock(&link_mutex);
        return -1;
}
int init_set(fd_set* set)
{
        client_fd* temp = client_fd_link;
        // 清空set
        FD_ZERO(set);
        int max = 0;
        while(temp != NULL)
        {
                // 获取最大的set
                if(max < temp->fd)
                {
                        max = temp->fd;
                }
                // 把client_fd加入set中
                FD_SET(temp->fd,set);
                temp = temp->next;
        }
        return max;
}

守护进程

  • 守护进程(deamon)是生存期长的一种进程。他们常常在系统装入时启动,在系统关闭时终止。
  • 所有守护进程都以超级用户(用户ID为0)的优先权运行
  • 守护进程没有控制终端
  • 守护进程的父进程都是init进程

守护进程编程步骤

  1. 使用umask将文件模式创建创建屏蔽字设置为0
  2. 调用fork,然后让父进程退出(exit)
  3. 调用setsid创建一个新会话
  4. 将当前工作目录更改为根目录
  5. 关闭不需要的文件描述符

守护进程出错处理

  • 由于守护进程完全脱离了控制终端,因此,不能像其他程序一样通过输出错误信息到控制台的方式来通知程序员
  • 通常的方式是使用syslog服务,将出错信息输入到“/var/log/syslog”系统日志文件中
  • syslog是linux中的系统日志管理服务,通过守护进程syslog来维护

syslog服务说明

  • openlog函数用于打开系统日志服务的一个连接
  • syslog函数用于向日志文件中写入消息,在这里可以规定消息的优先级,消息的输出格式等
  • closelog函数用于关闭系统日志服务的连接
openlog函数

void openlog(char* ident,int option,int facility);

  • 头文件
#include <syslog.h>
  • 参数

    • ident:要向每个消息加入的字符串,通常为程序名称
    • option

      • LOG_CONS 若日志消息不能通过发送至syslog,则将该消息写至控制台
      • LOG_NDELAY 立即打开linux域数据报套接口至syslog守护进程。通常,在记录第一条消息之前,该套接口不打开
      • LOG_PERROR 除将日志发送给syslog外,还将他写至stderr
      • LOG_PID 每条消息都包含进程id,此选择项可供对每个请求都fork一个子进程的守护进程使用
    • facility

      • LOG_AUTH 授权程序,如login,su,getty等
      • LOG_CRON cron和at
      • LOG_DAEMON 系统守护进程,如ftpd,routed等
      • LOG_KERN 内核产生的消息
      • LOG_LOCAL0~7 保留由本地使用
      • LOG_LPR 行打系统,如lpd,lpc等
      • LOG_MAIL 邮件系统
      • LOG_NEWSU senet网络新闻系统
      • LOG_SYSLOG syslog守护进程本身(用这个就ok)
      • LOG_USER 来自其他用户进程的消息
      • LOG_UUCP UUCP系统
syslog和closelog函数

void syslog(int priority,char* format,...);
void closelog(void);

  • 头文件
#include <syslog.h>
  • 参数

    • priority:消息优先级

      • LOG_EMERG 紧急(系统不可使用,最高优先级)
      • LOG_ALERT 必须立即修复的条件
      • LOG_CRIT 临界条件(例如,硬设备出错)
      • LOG_ERR 出错条件
      • LOG_WARNING 警告条件
      • LOG_NOTICE 正常,但重要的条件
      • LOG_INFO 信息性消息
      • LOG_DEBUG 调试排错消息(最低优先级)

示例

示例为select函数例子的main函数部分,注意要把头文件加上去和其他部分补齐才能运行

int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        /* 守护进程变成步骤  */
        // 1. 使用umask将文件模式创建屏蔽字设置为0
        umask(0);
        // 2. 调用fork,然后让父进程退出
        int pid = fork();
        if(pid > 0)
        {
                exit(0);
        }
        // 3. 调用setsid创建一个新会话
        setsid();
        // 4. 将当前工作目录更改为根目录
        chroot("/");
        // 5. 关闭不需要的文件描述符
        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);

        /* 使用syslog */
        // 1. 打开
        openlog("jin",LOG_PID,LOG_SYSLOG);
        // 2. 记录
        syslog(LOG_INFO,"jin deamon start");
        // 3. 关闭
        closelog();

        // 初始化互斥锁
        pthread_mutex_init(&link_mutex,NULL);
        struct sockaddr_in server_attr;
        memset(&server_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }

        /*
         * 2. 设置SO_REUSEADDR,避免重启导致bind报错
         */
        int opt = 1;
        setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(int));
        /*
         * 3. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 4. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }

        // 忽略SIGPIPE信号处理函数,避免由于客户端断开连接,并由于read发送SIGPIPE信号导致进程结>束
        signal(SIGPIPE,SIG_IGN);
        int client_fd = 0;
        struct sockaddr_in client_attr;
        socklen_t addrlen = sizeof(client_attr);
        // 开启子线程处理连接·
        child_thread_init(8);
        while(1)
        {
                /*
                * 5. 调用accept获得客户端连接,并返回一个新的socket文件描述符,新的文件描述符放>进client_fd链表中
                * 没有客户端连接,此函数会阻塞,直到获得一个客户端连接
                */
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                if(client_fd <= 0)
                {
                        continue;
                }
                server_log(&client_attr);
                add_client(client_fd);
                // 线程是共享的进程资源,这里不要关闭,不然线程里面的也没了
        }
        return 0;
}