Linux网络编程---IO超时控制

Publish: July 28, 2016 Category: C/C++ No Comments

   对于网络IO,我们一般情况下都需要超时机制来避免进行操作的进程被handle住,经典的做法就是采用select+非阻塞IO进行判断,select在超时时间内判断是否可以读写操作,然后采用非堵塞读写,不过一般实现的时候读操作不需要设置为非堵塞,读操作只有在没有数据的 时候才会阻塞,select的判断成功说明存在数据,所以即使是阻塞读在这种情况下也是可以做到非阻塞的效果,就没有必要设置成非阻塞的情况了.


下面是针对网络中FD的读、写超时的做法:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>


#ifndef O_NONBLOCK
#define O_NONBLOCK O_NDELAY
#endif

// 设为非阻塞
int ndelay_on(int fd)
{
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
}
// 设为阻塞
int ndelay_off(int fd)
{
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);
}


// return:
// -1: system fail
// -2: timeout
// >0: read length
int read_nb_timeout(int fd, char *buf, int len, int timeout)
{
    fd_set rfds;
    struct timeval tv;
    tv.tv_sec = timeout;
    tv.tv_usec = 0;
    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);
    if (select(fd + 1, &rfds, (fd_set *) 0, (fd_set *) 0, &tv) == -1) {
        //  select fail
        printf("select fail:%s\n", strerror(errno));
        return -1;
    }
    if (FD_ISSET(fd, &rfds)) {
        return read(fd, buf, len);
    }
    printf("read data from remote host timeout[%d]\n", timeout);
    return -2;
}


// return:
// -1: system fail
// -2: timeout
// >0: written length
int write_nb_timeout(int fd, char *buf, int len, int timeout)
{
    if (ndelay_on(fd) == -1) {
        printf("set nonblock fail:%s\n", strerror(errno));
        return -1;
    }
    int wrote_len = 0;
    char *pbuf = buf;
    int nwrite = 0;
    int n = len;
    fd_set wfds;
    struct timeval tv;
    while (n > 0) {
        tv.tv_sec = timeout;
        tv.tv_usec = 0;
        FD_ZERO(&wfds);
        FD_SET(fd, &wfds);
        int ret = select(fd + 1, (fd_set *) 0, &wfds, (fd_set *) 0, &tv);
        if ( ret == -1 ) {
            //  select fail
            printf("select fail:%s\n", strerror(errno));
            ndelay_off(fd);
            return -1;
        } else if ( ret == 0 ) {
            //  timeout
            printf("write data to remote host timeout[%d]:%s\n", timeout, strerror(errno));
            ndelay_off(fd);
            return -2;
        } else {
            if (FD_ISSET(fd, &wfds)) {
                nwrite = write(fd, pbuf + wrote_len, n);
                printf("write len:%d/%d [%s]\n", nwrite, n, pbuf + wrote_len);
                if ( (nwrite == -1) && ((errno != EAGAIN) || (errno != EINTR)) ) {
                    printf("write to remote host fail:[%d]%s\n", errno, strerror(errno));
                    ndelay_off(fd);
                    return -1;
                } else if ( nwrite == 0 ) {
                    printf("remote host closed, but i don't send data [%d] to finished\n", n);
                    ndelay_off(fd);
                    return -1;
                } else {
                    wrote_len += nwrite;
                    n -= nwrite;
                }
            }
        }
    }
    ndelay_off(fd);
    return wrote_len;
}


// 测试从标准输入里读
int main(int argc, char **argv)
{
    char buf[1024] = {0};
    int r = 0;
    while (1) {
        r = read_nb_timeout(1, buf, sizeof(buf), 5);
        if (r <= 0) {
            time_t now;
            time(&now);
            struct tm *p = localtime(&now);
            printf("%d-%d-%d %d:%d:%d Error: read timeout\n", (1900+p->tm_year), (1+p->tm_mon), p->tm_mday, p->tm_hour, p->tm_min, p->tm_s
ec);
        } else {
            printf(">>> %s\n", buf);
            memset(buf, 0, sizeof(buf));
        }
    }
    return 0;
}


执行后,如果5秒内不写数据到标准输入则报错,写入后就再计算下个5秒

linux 系统服务启动脚本

Publish: July 26, 2016 Category: Shell No Comments

一个dt程序的服务启动脚本例子:

编辑脚本: /etc/init.d/dt

############################## dt SCRIPT BEGIN
#!/bin/sh
# dt
#
# processname: /usr/home/kyosold/dt
# pidfile:     /var/run/dt.pid

[ -f /etc/rc.d/init.d/functions ] && . /etc/rc.d/init.d/functions

PIDFILE=/var/run/dt.pid

RETVAL=0    #使用变量作为判断和关联上下文的载体

start() {
    echo -n $"Starting dt: "
    if [ ! -f $PIDFILE]; then
        /usr/home/songjian/dt >/dev/null 2>&1
        RETVAL=$?
        echo
        if [ $RETVAL -eq 0 ]; then
            action "启动 dt:" /bin/true
    
            # 从ps -ef进程列表中查询 dt 进程号,并写入 /var/run/dt.pid 文件中
            pid=$(ps -ef | grep dt | grep -v grep | awk '{print $2}')
            echo $pid > $PIDFILE
        else
            action "启动 dt:" /bin/false
        fi
    
        return $RETVAL
    else
        echo
        action "启动 dt:" /bin/false
        
        return 1
    fi
}

stop() {
    echo -n $"Stopping dt:"
    if [ -f $PIDFILE]; then
        kill `cat /var/run/dt.pid`
        RETVAL=$?
        echo
        if [ $RETVAL -eq 0 ]; then
            action "停止 dt:" /bin/true
    
            # 删除/var/run/dt.pid文件
            rm -f $PIDFILE
        else
            action "启动 dt:" /bin/false
        fi
    
        return $RETVAL
    else
        echo
        action "启动 dt:" /bin/false
        
        return 1
    fi
}

case "$1" in
    start)
        start
        ;;  
    stop)
        stop
        ;;  
    restart)
        sh $0 stop
        sleep 2
        sh $0 start
        ;;
    *)
        echo "Format error!"
        echo $"Usage: $0 {start|stop|restart|"
        exit 1
        ;;
esac
exit $RETVAL
############################## SCRIPT END

修改文件的权限:

chmod 755 /etc/init.d/dt


执行:

service dt start
service dt stop
service dt restart


注意

        由于使用该脚本后,程序默认路径为根(/)路径了,所以程序里有用到相对路径的话,需要做chdir,可以采用以下方法:

        由于service脚本启动命令是全路径的,所以使用dirname获取到目录,并chdir:

    char *dname = dirname(argv[0]);
    log_debug("get current path[%s]", dname);
    if (chdir(dname) == -1) {
        log_error("chdir path[%s] fail:[%d]%s", dname, errno, strerror(errno));
        exit(1);
    }

        

Linux下检查和删除BOM头

Publish: July 22, 2016 Category: Shell No Comments

    当源程序是gb格式时,转换为 utf8 的时候,很多情况是头部会出现BOM,当是php程序的时候,这样会出现很多意想不到的事情,那怎么办?可以用linxu命令查找,然后对文件的BOM进行删除就OK了。

grep -r $'\xEF\xBB\xBF' * |grep .php

BOM:UTF-8签名 (UTF-8 signature) 也叫做BOM (Byte Order Mark)


用 vim 去除utf-8的BOM:

1、去掉 utf-8 BOM

:set nobomb

2、保留 utf-8 BOM

:set bomb


shadowrocket 使用配置文件方法

Publish: July 5, 2016 Category: 个人 No Comments

shadowrocket 可以使用surge的配置文件,使用方法如下: http://blog.vmeila.com/ssconfig/ShadowRocket 使用配置文件.docx


简单使用方法:

系统调优

Publish: July 4, 2016 Category: Shell,C/C++ No Comments

TCP 调优:

   我们知道TCP链接是有很多开销的,一个是会占用文件描述符,另一个是会开缓存,一般来说一个系统可以支持的TCP链接数是有限的,我们需要清楚认识到TCP链接对系统的开销是很大的。正是因为TCP是耗资源的,所以,很多攻击都是让你系统上出现大量的TCP链接,把你的系统资源耗尽。比如SYNC Flood攻击。


    所以,我们要注意配置KeepAlive参数,这个参数的意思是定义一个时间,如果链接上没有数据传输,系统会在这个时间内发一个包,如果没有收件回应,那么TCP就认为该链接断了,然后会关闭链接,这个可以回收系统资源开销。(注: HTTP层上也有KeepAlive参数) 对于像HTTP这个的短链接,设置一个1-2分钟的KeepAlive非常重要。这可以在一定程序上防止DoS攻击。有下面几个参数 (下面这些参数值仅供参考):

net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_intvl = 30

意思是某个TCP连接在idle1800秒(30分钟)后,内核才发起probe,如果probe 3次(30秒/次)不成功,内核才彻底放弃,认为该连接已经失效。

net.ipv4.tcp_fin_timeout = 30

表示如果套接字由本端发起关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。


    对于TCP的TIME_WAIT这个状态,主动关闭的一方进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),默认为4分钟,TIME_WAIT状态下的资源不能回收。有大量的TIME_WAIT链接的情况一般是在HTTP服务器上。对此,有两个参数需要注意:

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1

前者表示允许将TIME_WAIT sockets重新用于新的TCP连接 (重用TIME_WAIT)。

后者表示开启TCP连接中TIME_WAIT sockets的快速回收。