927 ℉

15.04.17

Docker基础技术:Linux Namespace(下)

转自 coolshell.cn 原作者 陈皓

Docker基础技术:Linux Namespace(上)中我们了解了,UTD、IPC、PID、Mount 四个namespace,我们模仿Docker做了一个相当相当山寨的镜像。在这一篇中,主要想向大家介绍Linux的User和Network的Namespace。

好,下面我们就介绍一下还剩下的这两个Namespace。

User Namespace

User Namespace主要是用了CLONE_NEWUSER的参数。使用了这个参数后,内部看到的UID和GID已经与外部不同了,默认显示为65534。那是因为容器找不到其真正的UID所以,设置上了最大的UID(其设置定义在/proc/sys/kernel/overflowuid)。

要把容器中的uid和真实系统的uid给映射在一起,需要修改 /proc/<pid>/uid_map/proc/<pid>/gid_map 这两个文件。这两个文件的格式为:

ID-inside-ns ID-outside-ns length

其中:

  • 第一个字段ID-inside-ns表示在容器显示的UID或GID,
  • 第二个字段ID-outside-ns表示容器外映射的真实的UID或GID。
  • 第三个字段表示映射的范围,一般填1,表示一一对应。

比如,把真实的uid=1000映射成容器内的uid=0

$ cat /proc/2465/uid_map
         0       1000          1

再比如下面的示例:表示把namespace内部从0开始的uid映射到外部从0开始的uid,其最大范围是无符号32位整形

$ cat /proc/$$/uid_map
         0          0          4294967295

另外,需要注意的是:

  • 写这两个文件的进程需要这个namespace中的CAP_SETUID (CAP_SETGID)权限(可参看Capabilities)
  • 写入的进程必须是此user namespace的父或子的user namespace进程。
  • 另外需要满如下条件之一:1)父进程将effective uid/gid映射到子进程的user namespace中,2)父进程如果有CAP_SETUID/CAP_SETGID权限,那么它将可以映射到父进程中的任一uid/gid。

这些规则看着都烦,我们来看程序吧(下面的程序有点长,但是非常简单,如果你读过《Unix网络编程》上卷,你应该可以看懂):

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/capability.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char container_stack[STACK_SIZE];
char* const container_args[] = {
    "/bin/bash",
    NULL
};

int pipefd[2];

void set_map(char* file, int inside_id, int outside_id, int len) {
    FILE* mapfd = fopen(file, "w");
    if (NULL == mapfd) {
        perror("open file error");
        return;
    }
    fprintf(mapfd, "%d %d %d", inside_id, outside_id, len);
    fclose(mapfd);
}

void set_uid_map(pid_t pid, int inside_id, int outside_id, int len) {
    char file[256];
    sprintf(file, "/proc/%d/uid_map", pid);
    set_map(file, inside_id, outside_id, len);
}

void set_gid_map(pid_t pid, int inside_id, int outside_id, int len) {
    char file[256];
    sprintf(file, "/proc/%d/gid_map", pid);
    set_map(file, inside_id, outside_id, len);
}

int container_main(void* arg)
{

    printf("Container [%5d] - inside the container!\n", getpid());

    printf("Container: eUID = %ld;  eGID = %ld, UID=%ld, GID=%ld\n",
            (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());

    /* 等待父进程通知后再往下执行(进程间的同步) */
    char ch;
    close(pipefd[1]);
    read(pipefd[0], &ch, 1);

    printf("Container [%5d] - setup hostname!\n", getpid());
    //set hostname
    sethostname("container",10);

    //remount "/proc" to make sure the "top" and "ps" show container's information
    mount("proc", "/proc", "proc", 0, NULL);

    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    const int gid=getgid(), uid=getuid();

    printf("Parent: eUID = %ld;  eGID = %ld, UID=%ld, GID=%ld\n",
            (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());

    pipe(pipefd);

    printf("Parent [%5d] - start a container!\n", getpid());

    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | SIGCHLD, NULL);


    printf("Parent [%5d] - Container [%5d]!\n", getpid(), container_pid);

    //To map the uid/gid, 
    //   we need edit the /proc/PID/uid_map (or /proc/PID/gid_map) in parent
    //The file format is
    //   ID-inside-ns   ID-outside-ns   length
    //if no mapping, 
    //   the uid will be taken from /proc/sys/kernel/overflowuid
    //   the gid will be taken from /proc/sys/kernel/overflowgid
    set_uid_map(container_pid, 0, uid, 1);
    set_gid_map(container_pid, 0, gid, 1);

    printf("Parent [%5d] - user/group mapping done!\n", getpid());

    /* 通知子进程 */
    close(pipefd[1]);

    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

上面的程序,我们用了一个pipe来对父子进程进行同步,为什么要这样做?因为子进程中有一个execv的系统调用,这个系统调用会把当前子进程的进程空间给全部覆盖掉,我们希望在execv之前就做好user namespace的uid/gid的映射,这样,execv运行的/bin/bash就会因为我们设置了uid为0的inside-uid而变成#号的提示符。

整个程序的运行效果如下:

hchen@ubuntu:~$ id
uid=1000(hchen) gid=1000(hchen) groups=1000(hchen)

hchen@ubuntu:~$ ./user #<--以hchen用户运行
Parent: eUID = 1000;  eGID = 1000, UID=1000, GID=1000 
Parent [ 3262] - start a container!
Parent [ 3262] - Container [ 3263]!
Parent [ 3262] - user/group mapping done!
Container [    1] - inside the container!
Container: eUID = 0;  eGID = 0, UID=0, GID=0 #<---Container里的UID/GID都为0了
Container [    1] - setup hostname!

root@container:~# id #<----我们可以看到容器里的用户和命令行提示符是root用户了
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)

虽然容器里是root,但其实这个容器的/bin/bash进程是以一个普通用户hchen来运行的。这样一来,我们容器的安全性会得到提高。

我们注意到,User Namespace是以普通用户运行,但是别的Namespace需要root权限,那么,如果我要同时使用多个Namespace,该怎么办呢?一般来说,我们先用一般用户创建User Namespace,然后把这个一般用户映射成root,在容器内用root来创建其它的Namesapce。

Network Namespace

Network的Namespace比较啰嗦。在Linux下,我们一般用ip命令创建Network Namespace(Docker的源码中,它没有用ip命令,而是自己实现了ip命令内的一些功能——是用了Raw Socket发些“奇怪”的数据,呵呵)。这里,我还是用ip命令讲解一下。

首先,我们先看个图,下面这个图基本上就是Docker在宿主机上的网络示意图(其中的物理网卡并不准确,因为docker可能会运行在一个VM中,所以,这里所谓的“物理网卡”其实也就是一个有可以路由的IP的网卡)

上图中,Docker使用了一个私有网段,172.40.1.0,docker还可能会使用10.0.0.0和192.168.0.0这两个私有网段,关键看你的路由表中是否配置了,如果没有配置,就会使用,如果你的路由表配置了所有私有网段,那么docker启动时就会出错了。

当你启动一个Docker容器后,你可以使用ip link show或ip addr show来查看当前宿主机的网络情况(我们可以看到有一个docker0,还有一个veth22a38e6的虚拟网卡——给容器用的):

hchen@ubuntu:~$ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state ... 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc ...
    link/ether 00:0c:29:b7:67:7d brd ff:ff:ff:ff:ff:ff
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
    link/ether 56:84:7a:fe:97:99 brd ff:ff:ff:ff:ff:ff
5: veth22a38e6: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc ...
    link/ether 8e:30:2a:ac:8c:d1 brd ff:ff:ff:ff:ff:ff

那么,要做成这个样子应该怎么办呢?我们来看一组命令:

## 首先,我们先增加一个网桥lxcbr0,模仿docker0
brctl addbr lxcbr0
brctl stp lxcbr0 off
ifconfig lxcbr0 192.168.10.1/24 up #为网桥设置IP地址

## 接下来,我们要创建一个network namespace - ns1

# 增加一个namesapce 命令为 ns1 (使用ip netns add命令)
ip netns add ns1 

# 激活namespace中的loopback,即127.0.0.1(使用ip netns exec ns1来操作ns1中的命令)
ip netns exec ns1   ip link set dev lo up 

## 然后,我们需要增加一对虚拟网卡

# 增加一个pair虚拟网卡,注意其中的veth类型,其中一个网卡要按进容器中
ip link add veth-ns1 type veth peer name lxcbr0.1

# 把 veth-ns1 按到namespace ns1中,这样容器中就会有一个新的网卡了
ip link set veth-ns1 netns ns1

# 把容器里的 veth-ns1改名为 eth0 (容器外会冲突,容器内就不会了)
ip netns exec ns1  ip link set dev veth-ns1 name eth0 

# 为容器中的网卡分配一个IP地址,并激活它
ip netns exec ns1 ifconfig eth0 192.168.10.11/24 up


# 上面我们把veth-ns1这个网卡按到了容器中,然后我们要把lxcbr0.1添加上网桥上
brctl addif lxcbr0 lxcbr0.1

# 为容器增加一个路由规则,让容器可以访问外面的网络
ip netns exec ns1     ip route add default via 192.168.10.1

# 在/etc/netns下创建network namespce名称为ns1的目录,
# 然后为这个namespace设置resolv.conf,这样,容器内就可以访问域名了
mkdir -p /etc/netns/ns1
echo "nameserver 8.8.8.8" > /etc/netns/ns1/resolv.conf

上面基本上就是docker网络的原理了,只不过,

  • Docker的resolv.conf没有用这样的方式,而是用了上篇中的Mount Namesapce的那种方式
    • 另外,docker是用进程的PID来做Network Namespace的名称的。

了解了这些后,你甚至可以为正在运行的docker容器增加一个新的网卡:

ip link add peerA type veth peer name peerB 
brctl addif docker0 peerA 
ip link set peerA up 
ip link set peerB netns ${container-pid} 
ip netns exec ${container-pid} ip link set dev peerB name eth1 
ip netns exec ${container-pid} ip link set eth1 up ; 
ip netns exec ${container-pid} ip addr add ${ROUTEABLE_IP} dev eth1 ;

上面的示例是我们为正在运行的docker容器,增加一个eth1的网卡,并给了一个静态的可被外部访问到的IP地址。

这个需要把外部的“物理网卡”配置成混杂模式,这样这个eth1网卡就会向外通过ARP协议发送自己的Mac地址,然后外部的交换机就会把到这个IP地址的包转到“物理网卡”上,因为是混杂模式,所以eth1就能收到相关的数据,一看,是自己的,那么就收到。这样,Docker容器的网络就和外部通了。

当然,无论是Docker的NAT方式,还是混杂模式都会有性能上的问题,NAT不用说了,存在一个转发的开销,混杂模式呢,网卡上收到的负载都会完全交给所有的虚拟网卡上,于是就算一个网卡上没有数据,但也会被其它网卡上的数据所影响。

这两种方式都不够完美,我们知道,真正解决这种网络问题需要使用VLAN技术,于是Google的同学们为Linux内核实现了一个IPVLAN的驱动,这基本上就是为Docker量身定制的。

Namespace文件

上面就是目前Linux Namespace的玩法。 现在,我来看一下其它的相关东西。

让我们运行一下上篇中的那个pid.mnt的程序(也就是PID Namespace中那个mount proc的程序),然后不要退出。

$ sudo ./pid.mnt 
[sudo] password for hchen: 
Parent [ 4599] - start a container!
Container [    1] - inside the container!

我们到另一个shell中查看一下父子进程的PID:

hchen@ubuntu:~$ pstree -p 4599
pid.mnt(4599)───bash(4600)

我们可以到proc下(/proc//ns)查看进程的各个namespace的id(内核版本需要3.8以上)。

下面是父进程的:

hchen@ubuntu:~$ sudo ls -l /proc/4599/ns
total 0
lrwxrwxrwx 1 root root 0  4月  7 22:01 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0  4月  7 22:01 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0  4月  7 22:01 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0  4月  7 22:01 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0  4月  7 22:01 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0  4月  7 22:01 uts -> uts:[4026531838]

下面是子进程的:

hchen@ubuntu:~$ sudo ls -l /proc/4600/ns
total 0
lrwxrwxrwx 1 root root 0  4月  7 22:01 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0  4月  7 22:01 mnt -> mnt:[4026532520]
lrwxrwxrwx 1 root root 0  4月  7 22:01 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0  4月  7 22:01 pid -> pid:[4026532522]
lrwxrwxrwx 1 root root 0  4月  7 22:01 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0  4月  7 22:01 uts -> uts:[4026532521]

我们可以看到,其中的ipc,net,user是同一个ID,而mnt,pid,uts都是不一样的。如果两个进程指向的namespace编号相同,就说明他们在同一个namespace下,否则则在不同namespace里面。

这些文件还有另一个作用,那就是,一旦这些文件被打开,只要其fd被占用着,那么就算PID所属的所有进程都已经结束,创建的namespace也会一直存在。比如:我们可以通过:mount –bind /proc/4600/ns/uts ~/uts 来hold这个namespace。

另外,我们在上篇中讲过一个setns的系统调用,其函数声明如下:

int setns(int fd, int nstype);

其中第一个参数就是一个fd,也就是一个open()系统调用打开了上述文件后返回的fd,比如:

fd = open("/proc/4600/ns/nts", O_RDONLY);  // 获取namespace文件描述符
setns(fd, 0); // 加入新的namespace

参考文档

jack.zh 标签:docker 继续阅读

849 ℉

15.04.17

Docker基础技术:Linux Namespace(上)

转自 coolshell.cn 原作者 陈皓

时下最热的技术莫过于Docker了,很多人都觉得Docker是个新技术,其实不然,Docker除了其编程语言用go比较新外,其实它还真不是个新东西,也就是个新瓶装旧酒的东西,所谓的The New “Old Stuff”。Docker和Docker衍生的东西用到了很多很酷的技术,我会用几篇 文章来把这些技术给大家做个介绍,希望通过这些文章大家可以自己打造一个山寨版的docker。

当然,文章的风格一定会尊重时下的“流行”——我们再也没有整块整块的时间去看书去专研,而我们只有看微博微信那样的碎片时间(那怕我们有整块的时间,也被那些在手机上的APP碎片化了)。所以,这些文章的风格必然坚持“马桶风格”(希望简单到占用你拉一泡屎就时间,而且你还不用动脑子,并能学到些东西)

废话少说,我们开始。先从Linux Namespace开始。

简介

Linux Namespace是Linux提供的一种内核级别环境隔离的方法。不知道你是否还记得很早以前的Unix有一个叫chroot的系统调用(通过修改根目录把用户jail到一个特定目录下),chroot提供了一种简单的隔离模式:chroot内部的文件系统无法访问外部的内容。Linux Namespace在此基础上,提供了对UTS、IPC、mount、PID、network、User等的隔离机制。

举个例子,我们都知道,Linux下的超级父亲进程的PID是1,所以,同chroot一样,如果我们可以把用户的进程空间jail到某个进程分支下,并像chroot那样让其下面的进程 看到的那个超级父进程的PID为1,于是就可以达到资源隔离的效果了(不同的PID namespace中的进程无法看到彼此)

Linux Namespace 有如下种类,官方文档在这里《Namespace in Operation

分类 系统调用参数 相关内核版本
Mount namespaces CLONE_NEWNS Linux 2.4.19
UTS namespaces CLONE_NEWUTS Linux 2.6.19
IPC namespaces CLONE_NEWIPC Linux 2.6.19
PID namespaces CLONE_NEWPID Linux 2.6.24
Network namespaces CLONE_NEWNET 始于Linux 2.6.24 完成于 Linux 2.6.29
User namespaces CLONE_NEWUSER 始于 Linux 2.6.23 完成于 Linux 3.8

主要是š三个系统调用

  • clone() - 实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述参数达到隔离。
  • šunshare() - 使某进程脱离某个namespace
  • šsetns() - 把某进程加入到某个namespace

unshare() 和 setns() 都比较简单,大家可以自己man,我这里不说了。

下面还是让我们来看一些示例(以下的测试程序最好在Linux 内核为3.8以上的版本中运行,我用的是ubuntu 14.04)。

clone()系统调用

首先,我们来看一下一个最简单的clone()系统调用的示例,(后面,我们的程序都会基于这个程序做修改):

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];

char* const container_args[] = {
    "/bin/bash",
    NULL
};

int container_main(void* arg)
{
    printf("Container - inside the container!\n");
    /* 直接执行一个shell,以便我们观察这个进程空间里的资源是否被隔离了 */
    execv(container_args[0], container_args); 
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent - start a container!\n");
    /* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, SIGCHLD, NULL);
    /* 等待子进程结束 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

从上面的程序,我们可以看到,这和pthread基本上是一样的玩法。但是,对于上面的程序,父子进程的进程空间是没有什么差别的,父进程能访问到的子进程也能。

下面, 让我们来看几个例子看看,Linux的Namespace是什么样的。

UTS Namespace

下面的代码,我略去了上面那些头文件和数据结构的定义,只有最重要的部分。

int container_main(void* arg)
{
    printf("Container - inside the container!\n");
    sethostname("container",10); /* 设置hostname */
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent - start a container!\n");
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | SIGCHLD, NULL); /*启用CLONE_NEWUTS Namespace隔离 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

运行上面的程序你会发现(需要root权限),子进程的hostname变成了 container。

hchen@ubuntu:~$ sudo ./uts
Parent - start a container!
Container - inside the container!
root@container:~# hostname
container
root@container:~# uname -n
container

IPC Namespace

IPC全称 Inter-Process Communication,是Unix/Linux下进程间通信的一种方式,IPC有共享内存、信号量、消息队列等方法。所以,为了隔离,我们也需要把IPC给隔离开来,这样,只有在同一个Namespace下的进程才能相互通信。如果你熟悉IPC的原理的话,你会知道,IPC需要有一个全局的ID,即然是全局的,那么就意味着我们的Namespace需要对这个ID隔离,不能让别的Namespace的进程看到。

要启动IPC隔离,我们只需要在调用clone时加上CLONE_NEWIPC参数就可以了。

int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWIPC | SIGCHLD, NULL); 

首先,我们先创建一个IPC的Queue(如下所示,全局的Queue ID是0)

hchen@ubuntu:~$ ipcmk -Q 
Message queue id: 0

hchen@ubuntu:~$ ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0xd0d56eb2 0          hchen      644        0            0

如果我们运行没有CLONE_NEWIPC的程序,我们会看到,在子进程中还是能看到这个全启的IPC Queue。

hchen@ubuntu:~$ sudo ./uts
Parent - start a container!
Container - inside the container!

root@container:~# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0xd0d56eb2 0          hchen      644        0            0

但是,如果我们运行加上了CLONE_NEWIPC的程序,我们就会下面的结果:

root@ubuntu:~$ sudo./ipc
Parent - start a container!
Container - inside the container!

root@container:~/linux_namespace# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

我们可以看到IPC已经被隔离了。

PID Namespace

我们继续修改上面的程序:

int container_main(void* arg)
{
    /* 查看子进程的PID,我们可以看到其输出子进程的 pid 为 1 */
    printf("Container [%5d] - inside the container!\n", getpid());
    sethostname("container",10);
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent [%5d] - start a container!\n", getpid());
    /*启用PID namespace - CLONE_NEWPID*/
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL); 
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;

运行结果如下(我们可以看到,子进程的pid是1了):

hchen@ubuntu:~$ sudo ./pid
Parent [ 3474] - start a container!
Container [    1] - inside the container!
root@container:~# echo $$
1

你可能会问,PID为1有个毛用啊?我们知道,在传统的UNIX系统中,PID为1的进程是init,地位非常特殊。他作为所有进程的父进程,有很多特权(比如:屏蔽信号等),另外,其还会为检查所有进程的状态,我们知道,如果某个子进程脱离了父进程(父进程没有wait它),那么init就会负责回收资源并结束这个子进程。所以,要做到进程空间的隔离,首先要创建出PID为1的进程,最好就像chroot那样,把子进程的PID在容器内变成1。

但是,我们会发现,在子进程的shell里输入ps,top等命令,我们还是可以看得到所有进程。说明并没有完全隔离。这是因为,像ps, top这些命令会去读/proc文件系统,所以,因为/proc文件系统在父进程和子进程都是一样的,所以这些命令显示的东西都是一样的。

所以,我们还需要对文件系统进行隔离。

Mount Namespace

下面的例程中,我们在启用了mount namespace并在子进程中重新mount了/proc文件系统。

int container_main(void* arg)
{
    printf("Container [%5d] - inside the container!\n", getpid());
    sethostname("container",10);
    /* 重新mount proc文件系统到 /proc下 */
    system("mount -t proc proc /proc");
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent [%5d] - start a container!\n", getpid());
    /* 启用Mount Namespace - 增加CLONE_NEWNS参数 */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

运行结果如下:

hchen@ubuntu:~$ sudo ./pid.mnt
Parent [ 3502] - start a container!
Container [    1] - inside the container!
root@container:~# ps -elf 
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
4 S root         1     0  0  80   0 -  6917 wait   19:55 pts/2    00:00:00 /bin/bash
0 R root        14     1  0  80   0 -  5671 -      19:56 pts/2    00:00:00 ps -elf

上面,我们可以看到只有两个进程 ,而且pid=1的进程是我们的/bin/bash。我们还可以看到/proc目录下也干净了很多:

root@container:~# ls /proc
1          dma          key-users   net            sysvipc
16         driver       kmsg        pagetypeinfo   timer_list
acpi       execdomains  kpagecount  partitions     timer_stats
asound     fb           kpageflags  sched_debug    tty
buddyinfo  filesystems  loadavg     schedstat      uptime
bus        fs           locks       scsi           version
cgroups    interrupts   mdstat      self           version_signature
cmdline    iomem        meminfo     slabinfo       vmallocinfo
consoles   ioports      misc        softirqs       vmstat
cpuinfo    irq          modules     stat           zoneinfo
crypto     kallsyms     mounts      swaps
devices    kcore        mpt         sys
diskstats  keys         mtrr        sysrq-trigger

下图,我们也可以看到在子进程中的top命令只看得到两个进程了。

这里,多说一下。在通过CLONE_NEWNS创建mount namespace后,父进程会把自己的文件结构复制给子进程中。而子进程中新的namespace中的所有mount操作都只影响自身的文件系统,而不对外界产生任何影响。这样可以做到比较严格地隔离。

你可能会问,我们是不是还有别的一些文件系统也需要这样mount? 是的。

Docker的 Mount Namespace

下面我将向演示一个“山寨镜像”,其模仿了Docker的Mount Namespace。

首先,我们需要一个rootfs,也就是我们需要把我们要做的镜像中的那些命令什么的copy到一个rootfs的目录下,我们模仿Linux构建如下的目录:

hchen@ubuntu:~/rootfs$ ls
bin  dev  etc  home  lib  lib64  mnt  opt  proc  root  run  sbin  sys  tmp  usr  var

然后,我们把一些我们需要的命令copy到 rootfs/bin目录中(sh命令必需要copy进去,不然我们无法 chroot )

hchen@ubuntu:~/rootfs$ ls ./bin ./usr/bin

./bin:
bash   chown  gzip      less  mount       netstat  rm     tabs  tee      top       tty
cat    cp     hostname  ln    mountpoint  ping     sed    tac   test     touch     umount
chgrp  echo   ip        ls    mv          ps       sh     tail  timeout  tr        uname
chmod  grep   kill      more  nc          pwd      sleep  tar   toe      truncate  which

./usr/bin:
awk  env  groups  head  id  mesg  sort  strace  tail  top  uniq  vi  wc  xargs

注:你可以使用ldd命令把这些命令相关的那些so文件copy到对应的目录:

hchen@ubuntu:~/rootfs/bin$ ldd bash
    linux-vdso.so.1 =>  (0x00007fffd33fc000)
    libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007f4bd42c2000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f4bd40be000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4bd3cf8000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f4bd4504000)

下面是我的rootfs中的一些so文件:

hchen@ubuntu:~/rootfs$ ls ./lib64 ./lib/x86_64-linux-gnu/

./lib64:
ld-linux-x86-64.so.2

./lib/x86_64-linux-gnu/:
libacl.so.1      libmemusage.so         libnss_files-2.19.so    libpython3.4m.so.1
libacl.so.1.1.0  libmount.so.1          libnss_files.so.2       libpython3.4m.so.1.0
libattr.so.1     libmount.so.1.1.0      libnss_hesiod-2.19.so   libresolv-2.19.so
libblkid.so.1    libm.so.6              libnss_hesiod.so.2      libresolv.so.2
libc-2.19.so     libncurses.so.5        libnss_nis-2.19.so      libselinux.so.1
libcap.a         libncurses.so.5.9      libnss_nisplus-2.19.so  libtinfo.so.5
libcap.so        libncursesw.so.5       libnss_nisplus.so.2     libtinfo.so.5.9
libcap.so.2      libncursesw.so.5.9     libnss_nis.so.2         libutil-2.19.so
libcap.so.2.24   libnsl-2.19.so         libpcre.so.3            libutil.so.1
libc.so.6        libnsl.so.1            libprocps.so.3          libuuid.so.1
libdl-2.19.so    libnss_compat-2.19.so  libpthread-2.19.so      libz.so.1
libdl.so.2       libnss_compat.so.2     libpthread.so.0
libgpm.so.2      libnss_dns-2.19.so     libpython2.7.so.1
libm-2.19.so     libnss_dns.so.2        libpython2.7.so.1.0

包括这些命令依赖的一些配置文件:

hchen@ubuntu:~/rootfs$ ls ./etc
bash.bashrc  group  hostname  hosts  ld.so.cache  nsswitch.conf  passwd  profile  
resolv.conf  shadow

你现在会说,我靠,有些配置我希望是在容器起动时给他设置的,而不是hard code在镜像中的。比如:/etc/hosts,/etc/hostname,还有DNS的/etc/resolv.conf文件。好的。那我们在rootfs外面,我们再创建一个conf目录,把这些文件放到这个目录中。

hchen@ubuntu:~$ ls ./conf
hostname     hosts     resolv.conf

这样,我们的父进程就可以动态地设置容器需要的这些文件的配置, 然后再把他们mount进容器,这样,容器的镜像中的配置就比较灵活了。

好了,终于到了我们的程序。

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char container_stack[STACK_SIZE];
char* const container_args[] = {
    "/bin/bash",
    "-l",
    NULL
};

int container_main(void* arg)
{
    printf("Container [%5d] - inside the container!\n", getpid());

    //set hostname
    sethostname("container",10);

    //remount "/proc" to make sure the "top" and "ps" show container's information
    if (mount("proc", "rootfs/proc", "proc", 0, NULL) !=0 ) {
        perror("proc");
    }
    if (mount("sysfs", "rootfs/sys", "sysfs", 0, NULL)!=0) {
        perror("sys");
    }
    if (mount("none", "rootfs/tmp", "tmpfs", 0, NULL)!=0) {
        perror("tmp");
    }
    if (mount("udev", "rootfs/dev", "devtmpfs", 0, NULL)!=0) {
        perror("dev");
    }
    if (mount("devpts", "rootfs/dev/pts", "devpts", 0, NULL)!=0) {
        perror("dev/pts");
    }
    if (mount("shm", "rootfs/dev/shm", "tmpfs", 0, NULL)!=0) {
        perror("dev/shm");
    }
    if (mount("tmpfs", "rootfs/run", "tmpfs", 0, NULL)!=0) {
        perror("run");
    }
    /* 
     * 模仿Docker的从外向容器里mount相关的配置文件 
     * 你可以查看:/var/lib/docker/containers/<container_id>/目录,
     * 你会看到docker的这些文件的。
     */
    if (mount("conf/hosts", "rootfs/etc/hosts", "none", MS_BIND, NULL)!=0 ||
          mount("conf/hostname", "rootfs/etc/hostname", "none", MS_BIND, NULL)!=0 ||
          mount("conf/resolv.conf", "rootfs/etc/resolv.conf", "none", MS_BIND, NULL)!=0 ) {
        perror("conf");
    }
    /* 模仿docker run命令中的 -v, --volume=[] 参数干的事 */
    if (mount("/tmp/t1", "rootfs/mnt", "none", MS_BIND, NULL)!=0) {
        perror("mnt");
    }

    /* chroot 隔离目录 */
    if ( chdir("./rootfs") != 0 || chroot("./") != 0 ){
        perror("chdir/chroot");
    }

    execv(container_args[0], container_args);
    perror("exec");
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent [%5d] - start a container!\n", getpid());
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

sudo运行上面的程序,你会看到下面的挂载信息以及一个所谓的“镜像”:

hchen@ubuntu:~$ sudo ./mount
Parent [ 4517] - start a container!
Container [    1] - inside the container!
root@container:/# mount
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
none on /tmp type tmpfs (rw,relatime)
udev on /dev type devtmpfs (rw,relatime,size=493976k,nr_inodes=123494,mode=755)
devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000)
tmpfs on /run type tmpfs (rw,relatime)
/dev/disk/by-uuid/18086e3b-d805-4515-9e91-7efb2fe5c0e2 on /etc/hosts type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/disk/by-uuid/18086e3b-d805-4515-9e91-7efb2fe5c0e2 on /etc/hostname type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/disk/by-uuid/18086e3b-d805-4515-9e91-7efb2fe5c0e2 on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro,data=ordered)

root@container:/# ls /bin /usr/bin
/bin:
bash   chmod  echo  hostname  less  more    mv   ping  rm   sleep  tail  test     top    truncate  uname
cat    chown  grep  ip        ln    mount   nc   ps    sed  tabs   tar   timeout  touch  tty       which
chgrp  cp     gzip  kill      ls    mountpoint  netstat  pwd   sh   tac    tee   toe      tr     umount

/usr/bin:
awk  env  groups  head  id  mesg  sort  strace  tail  top  uniq  vi  wc  xargs

关于如何做一个chroot的目录,这里有个工具叫DebootstrapChroot,你可以顺着链接去看看(英文的哦)

接下来的事情,你可以自己玩了,我相信你的想像力 。:)

在下一篇,我将向你介绍User Namespace、Network Namespace以及Namespace的其它东西。

jack.zh 标签:docker 继续阅读

745 ℉

15.04.17

如何构建高扩展性网站?

本篇通过阅读《高扩展性网站的50条原则》,总结出以下内容。

一方面博主没有实际的架构经验,另一方面知识面也不够宽阔,所以只能系统的总结书中的要点,并根据自己的理解做些归纳。

主要内容

本书从多个方面围绕高扩展性提出了50条建议,一个高扩展性的网站会随着业务的发展、用户的增加,自由的扩展架构,从而轻松的应付网站的快速发展。下面看看本书的具体内容:

化简方程

1 不要过度的设计

过度的设计相当于给系统增加了复杂度与维护的成本。而这些过度的设计,在正常的使用中,却没有太大的作用。往往是设计者自己认为很重要或者锦上添花的功能,实际用处不大。

2 设计时考虑到扩展性

在设计时要遵循一下的设计原则:设计时考虑20倍的容量,实现时考虑3倍的容量,部署时考虑1.5的容量。一面项目扩大时,临时扩展造成的困难。

3 把方案一简再简

应该遵循帕累托法则,20%的设计做了80%的工作,所以80%的时间,都应该放在这20%的设计上。

一个产品主要的功能其实都集中在几个点上,把这几个点设计好了,其他的都是些附加的功能而已。所以这核心的业务一定要保证足够的简洁易用。

4 减少DNS查询

每个不同的域下的文件,加载时都需要查询DNS。比如cnblogs.com与i.cnblogs.com就属于不同的域。那么在查询DNS的时候,就会查询两次。当业务量很大时,就会造成一定的影响。

5 尽可能减少对象

由于对象在浏览器访问时,需要加载。所以可以考虑减少请求文件的数量(数量与浏览器并发加载数有关),把一些对象尽量的合并。比如图标类的文件,可以合并成一个大的图片。合理的文件数量,会加速浏览器的访问加载。

6 使用同一品牌的网络设备

由于一个http请求,可能通过很多物理设备。比如负载均衡器,交换机,路由器。所以尽量使用同一品牌的设备,会避免一些意外的情况。

分布工作

7 X轴,横向复制

这种事最简单的服务扩充,通过克隆或者复制实现,比如你的应用放在多个服务器上进行服务。常见的比如集群,负载均衡等等,数据库的读写分离。

8 Y轴,拆分不同的东西

大型系统中,拆分不同的功能,比如注册、购买、查询、云盘。等等

9 Z轴,拆分不同的相似的东西

比如按照用户的级别,或者用户的地理位置等等拆分。

横向扩展设计

10 设计横向的扩展方案

扩展包括横向、纵向。横向就是通过复制克隆应用,利用小型机集群扩展。纵向就是提高服务器的硬件以及网络设施。

通过很多的案例都可以发现,单纯的升级硬件实现的纵向扩展,仅仅能解决一点点现实压力。而通过横向的集群扩展,却能够自由的实现伸缩。

11 采用经济型系统

与上面的原则类似,采用高价格的服务器,并不能保证日后的良好性能。应该使用普通的小型机集群扩展。

12 横向扩展数据中心

数据中心有很多的设计方案,比如

热冷站配置:使用热站提供服务,当热站崩溃时,使用冷站继续服务。

推荐使用多个实时站点,成本更低,动态调用。缺点是增加了运维的难度。

13 利用云技术进行设计

云计算的有点就是虚拟化,可以在业务峰值时,弹性的扩充设备。并且在日常处理用,归还该扩展。

缺点是提高了应用于虚拟环境的耦合。后面提到利用物理设备,隔离业务,在虚拟化的云计算中,可能会对业务隔离错误排查造成一定的干扰。

使用正确的工具

14 合理使用数据库

目前有许多的数据库版本,比如传统的关系型数据库Oracle、MySQl,还有比较新的非关系型数据库NoSql,比如MongoDB,以及内存数据库FastDB,还有专门针对SSD固态硬盘的Aerospike等等。

但是到了选型的时候,还是要一句个人的业务需求来定。看你的数据库要求的是速度,还是安全性等等。

15 防火墙,到处都是防火墙

防火墙可以对一些无效的访问进行拦截过滤。通常把一些CSS,静态文件,图片,JS等不采用防火墙,而关键的业务涉及到个人信息时采用。合理的设计防火墙,也会对网站的性能产生一定的影响。

16 积极的利用日志文件

利用各种日志以及工具,实时的监控业务。不仅仅是监控服务器的内存CPU,还应该监控业务上的数据。比如splunk(提供日志的搜集,存储,搜索,图形化展示)。

不要做重复的工作

17 不要立即检查刚做过的工作

比如刚刚写如了数据,不要立即读取。虽然有些客户需要保证数据的完整,不能丢失。但是可以通过日志等记录,写完查这种做法,还是不推荐。

18 停止重定向

重定向会消耗一定的延迟,计算资源。应该尽量避免

19 放松时序约束

大多数的关系型数据库讲究ACID属性,扩展时就造成一定的困扰。因此某些业务适当的放松时序约束,可以提高网站的性能。

比如某站在预定酒店时,用户预定后,会等待酒店的审核。比如某宝,在提款时,进行范围时间的确认。这种就是扩大了时序约束,进而提高网站性能以及事务安全。

积极利用缓存

20 利用CDN

可以利用CDN保存客户的数据和内容。大概的过程是,用户在进行网站访问时,转到CDN的服务器,CDN执行DNS查询,把用户请求分摊到不同的服务器。有很多的CDN服务商提供这种服务。

21 使用过期头

针对不同的对象类型,使用过期头,减少对象请求。常见的HTTP对应属性为:public no-cahe max-age等等

22 缓存Ajax调用

正确修改Http头Last-Modified Cache-Control Expires等属性。

23 利用页面缓存

缓存响应之前的冬天请求,降低web服务器的负载。

24 利用应用缓存

比如针对某些特殊的用户,缓存其请求数据。

25 利用对象缓存

适用于反复查询使用的数据对象。比如一个购物网站,缓存器热销产品数据。

26 把对象缓存放在自己的层上

使用单独的缓层,易于扩展和维护。

从错误中吸取教训

27 积极的学习

一个公司有学习的氛围,才会衍生出更好的产品。学习的内容一方面包括客户的业务知识,一方面来自技术和运维领域。

28 不要依靠QA发现失误

雇佣测试或者质量保证人员,最大的目的是为了检测产品的正确性。它能减少成本,提高开发人员的开发速度,因为开发人员不需要时刻关注代码的正确性,可以交给QA来测试。

但是QA只负责发现问题,如何避免为题还是得依靠开发人员。

29 没有回退的设计是失败的设计

这里的回退,指的是产品发布的回退。如果碰上某些版本的BUG,可能需要交付之前可运行的版本,此时没有回退,就无法交付产品了。

这里推荐学习持续集成的相关内容。

30 讨论失败并从中吸取教训

不应该在同一个问题上失败两次,每次失败多进行总结是不可缺少的。

数据库原则

关系型数据库的ACID属性:

原子性:一个事务要么全执行,要么都不执行,

一致性:事务开始和结束时,所有数据状态要一致,

隔离性:事务的表现,是事务对数据库唯一的操作,

持久性:事务完成,操作不能更改。

31 注意代价高的关系

应该在设计阶段完善的设计表的结构,等开发开始时,在增加某些列,可能会花费很高的代价。

32 使用正确的数据库锁

数据库有很多锁的概念,比如隐式锁、显式锁、行锁、页锁、范围锁、表锁、数据库锁等等。

不合理的使用锁,会影响网站的吞吐量。

33 不要使用多阶段提交

比如两阶段提交:先表决,在提交。这回降低扩展性,因为在其提交事务完成前,是不能作其他操作的。

34 不要使用select for update

因为FOR UPDATE从句会导致锁定行,降低事务处理的速度。

35 不要选择所有的数据

比如select * from xxx;

这种做法第一是不开与数据的扩展,比如本来有四列数据,业务处理代码直接写死。当增加了一列数据时,就会导致出错;另外就是会查询出不必要的数据。

或者inset into xxx values(xxxx);

这是当列信息不匹配时,也会出错。

容错设计与故障控制

36 采用隔离故障的”泳道“

服务与数据的划分有很多种,比如容器,集群,池,分片,泳道。泳道意味着每个业务有自己的领域,不能跨泳道调用。

37 不要信任单点故障

有很多系统设计成单点模式,当整个系统只是用该模块时,当出现单点故障,整个系统也就崩溃了。

38 避免系统串联

比如一个系统有很多的组件组成,每个组件99.9%的安全性,当串联3个组件时,整个系统的可用性就变成了99.7%。

39 确保能够启用/禁用功能

对于某些共享库,第三方服务,应该提供开启或者关闭的功能。

避免或分发状态

40 努力实现无状态

实现状态会限制扩展性,增大成本

41 尽可能在浏览器端维护会话

一方面降低服务器压力,另一方面任何的请求可以发送给任何的服务器。

42 利用分布式缓存存放状态

使用独立的缓存层,利于扩展。有很多分布式的缓存方案,比如memcached。

异步通信和消息总线

43 尽可能使用异步通信

异步通信,可以确保每个服务和层之间的独立性,这样易于早呢更加系统的扩展性和减小耦合度。

44 确保消息总线能够扩展

尽量采用Y轴或者Z轴扩展,即按业务需求和功能扩展。因为单纯的复制或者克隆,反而会增加各个消息订阅者的监听数目。按照业务隔离,可以分离业务压力。

45 避免让消息总线过度拥挤

衡量价值与消息的成本。

其他原则

46 慎用第三方解决方案扩展

企业如果出现问题,那么寻找第三方能够解决燃眉之急。但是却不是长久之计,因为解决方案的提供商有很多客户,你的危机并不是他们的危机,所以不可能在关键时刻,尽职尽责。因此企业还是应该有一定的掌控力(这个词真是高大上!)。

47 清除、归档和成本合理的存储

有一些不必要的数据,就应该定期的删除。一些略有价值的数据进行定期的归档直接删除。一些很有价值的数据,应该进行备份以及快速访问。

48 删除事务处理中的商业智能

应该把产品系统与业务系统分离,提高产品的扩展性。

避免业务扩展时,受到系统架构的限制。

49 设计能够监控的应用

应该设计全局的监控策略,保证回答

”发生了 问题了吗?“

”哪里发生了问题?“

”发生了什么问题?“

”会发生问题吗?“

”能自动修复吗?“

50 要能胜任

应该在每个设计中涉及到最优秀的架构,不能完全依赖第三方的解决方案。

一个简单优秀的架构,都是小而精的,如果单纯的依靠开源解决架构,虽然解决了问题,却会导致应用的臃肿。

jack.zh 标签:web 继续阅读

628 ℉

15.03.24

如何学好C++

昨天写了一篇如何学好C语言,就有人回复问我如何学好C++,所以,我把我个人的一些学习经验写在这里,希望对大家有用。首先,因为如何学好C语言中谈到了算法和系统,所以这里就只谈C++语言。

  1. C++是最难的语言。这个世界上最难的编程语言可能非C++莫属了。你千万不要以为几天就可以学好C++,C++的学习曲线是相当BT的,你可以看看这篇文章。C++是一门很自由的语言,自由到了有点BT和恐怖的地步。我甚至认为C++并不是一门成熟的编程语言,因为太容易犯错了。所以,你一定要在一开始就要有很小心谨慎的态度,并把C++当成一种难以训服的猛兽来看待

  2. 多问“为什么要这样”的问题。学习C++一定要多问几个“为什么是这样”,“凭什么要这样”的问题。比如:很多人知道C++有拷贝构造函数和初始化列表,但你真的知道为什么要有拷贝构造函数?为什么要有初始化列表吗?为什么要有template,为什么要有RTTI,为什么不是别的呢?难道就是为了让一门语言变得Cool一些吗?完全不是这样的,C++中的任何一个feature都有些实实在在的原因,你一定要去了解为什么要把C++设计成这样的原因,你才能学好C++。有空看看《C++演化和设计》一书。

  3. 看书,大量的C++书。你可以按如下先后顺序阅读(下面这些书,我花了大约4-5年的时间,今天我还在随时温习)

    • 《C++ Primer》,这本初级读本可能让会你啃得很痛苦,所有的语言的特性和为什么都在里面了,好好读读。当然由C++之父写的《C++程序设计语言》也不错。两本看一本就好了(我看的是前者)。
    • 了解C++的语法仅仅是万里长征的第一步,你还需要看看《Effective C++》《More Effective C++》这两本书并不厚,但我从02年就一直看到现在,每次读我都有新的体会,这两本书太经典了。如果你对C语言不熟,这两本书会让你回去补C语言的课。
    • 《Think in C++》同样是另一本经典之极的书,学c++必读,但是中文版的翻译的很不好,所以还是去读英文版的吧。
    • 《C++沉思录》同样非常值得一读,这里教的不是编程,而是思考的方法,这是相当珍贵的。
    • 《Exceptional C++》《More Exceptional C++》让你看看各种问题的解决方法和一些常见的经典错误。
    • 《Advanced C++》和《Modern C++》可以让你知道C++各种神奇的用法。
    • 《泛型编程与STL》是把C++实践到了极致的东西。很强大。STL——神一样的模板库(容器,算法和函数对象),不得不服。
    • 《深入探索C++对象模型》让你了解编译器下的C++是什么样的,让你了解C++的性能并不差。这个对于C++的程序员太关键了。我以前写过的《C++虚函数表解析》还有《C++对象内存布局》属于这个范畴。
  4. 和Java语言做对比。我个人以为Java对C++这个并不成熟的语言做了很多调整,规范和限制。所以,对比一下Java和C++,想一想,为什么一些东西在C++中可以做,但在Java中却不行。比如:Java的异常是必需要catch的,不然就会编译不通过。为什么Java不提供操作符重载?为什么Java会引入接口来做多重继承?为什么Java没有像C++那样的I/O字符流?为什么Java不支持指针?为什么Java可以做到垃圾回收?等等。Java体现着很多面向对象设计的东西,学习Java有助于你学会怎么更好地使用C++来编程。

  5. 面向对象设计 。虽然面向对象可能是个骗局。但是我觉得面向对象设计中的一些实践非常的不错,比如,单一原则,依赖倒置原则,等等,都非常地经典。《设计模式》必需一读,《面向对象的分析和设计》可以一读。但不可以设计模式为中心来编程,而应该是用设计模式来解藕。

  6. 类库学习。看看MFC是怎么封装Windows API的,看看ACE是怎么面向对象的,看看boost是怎么玩面向对象的,看看CPPUnit又是怎么设计的。当然,Java的JDK中有太多的设计模式,可以参考。

希望没有吓到大家,并欢迎大家补充。

更新几个观点:

  • 1)我不擅长写书评,所以推荐的这些书可能会让你有点看点没有感觉,你可以上豆瓣或是China-pub上看看书评。
  • 2)C++有很多奇淫技巧,有的很BT,包括虚函数表,也许会有人觉得有点没意思,但我觉得很有意思,一方面可以了解一门语言的实现细节,另一方面可以开阔思路。我从学习这些知识中受益很多。
  • 3)上述是我的个人的学习历程,我觉得对我很有效,所以是经验之谈。
  • 4)这类的文章在网上有很多很多,我不是第一个写这样的文章,我也不是写得最好的,我并不希望用长篇大论来谈论什么。只是想给大家了解一下大概的学习样子。毕竟,C++博大精深,任何一篇文章都无法说好。不如就简单一些。

转自coolshell

jack.zh 标签:C++ 继续阅读

677 ℉

15.03.24

如何学好C语言

编程编到一定的时候,发现能力到了瓶颈,既不深,也不扎实,半吊子。比如:你长期地使用Java和.NET ,这些有虚拟机的语言对于开发便利是便利,但是对于程序员来说可能并不太好,原因有两个:

  1. 虚拟机屏蔽了操作系统的系统调用,以及很多底层机制。
  2. 大量的封装好的类库也屏蔽了很多实现细节。

一段时间后,你会发现你知其然,不知所以然。。我以前在CSDN上写过一篇 《Java NIO类库Selector机制解析()》 ,在那篇文章中我说提到过(有讥讽的语气)Java的程序员不懂底层实现,所以很难把技术学得更扎实。此时,一部分程序员会不自然地想学学底层的技术,很自然的,C语言就被提了上来。

下面是我给这位朋友的一些建议:

  1. 鼓励并为你叫好。我鼓励你想要去学C语言的想法和精神,很多人都觉得C语言好学,其实并不然。(你可以看看《C语言的迷题》)现在的这个社会更多地去关注那些时髦的技术,而忽略了这个流行了40+年的C语言。一门技术如果能够流行40多年,这才是你需要去关注和学习的技术,而不是那些刚出来的技术(过度炒作的技术Windows编程史)。这才是踏踏实实的精神。

  2. 不要找借口。这一条路走下来并不容易,不要给自己找借口。我最不喜欢听到的就是“很忙,没有时间”这样的借口。我以前在银行做项目,早9点到晚10点,周一到周六,我一样可以每天抽1个小时来看书和专研,一年下来也能精读5、6本书。我现在的工作项目和招聘任务很紧张,刚生的小孩只有自己和老婆两人带,还需要准备讲课,但是我还是能够找到时间看文章写文章维护酷壳。所以,我可以告诉你,“时间就像乳沟,只要你肯挤,就一定会有”。

  3. 学好C语言和系统编程。我认为,学好编程有四个方面:语言、算法和数据结构、系统调用和设计

如果你能在2-3年内精读完这些书,并全部融会贯通,那么你就明白什么是一览众山小的感觉了!我足足花了5年时间才算是真正全部读完这些书的。最后,祝你好运!努力!

我想,这篇文章主要想告诉大家这么几件事:

  • 编程编到一定时候,你就需要了解底层系统的机制,否则,知其然不知所以然。
  • 我没有否定非C的程序员的逻辑,真正的逻辑是——如果你想要了解底层机制,请学习C语言和操作系统。
  • 40多年的Unix/C影响深远。包括影响了Windows。如果你想一通百通,一定要了解Unix。那是计算机文化真正的根。
  • 不要肤浅地去思考问题。比如,不要以为一个DBA就不会考虑数据库引擎的内存页面的问题。也不要以为Web程序员就不需要了解后台的服务器和脚本的运行性能以及TCP/IP的问题。

高手往往都是有很强的系统的基础知识的,表面的东西永远是肤浅的.

转自coolshell

jack.zh 标签:C 继续阅读

1011 ℉

15.03.10

介绍 Linux 的命名空间

背景

从Linux 2.6.24版的内核开始,Linux 就支持6种不同类型的命名空间。它们的出现,使用户创建的进程能够与系统分离得更加彻底,从而不需要使用更多的底层虚拟化技术。

  • CLONE_NEWIPC: 进程间通信(IPC)的命名空间,可以将 SystemV 的 IPC 和 POSIX 的消息队列独立出来。
  • CLONE_NEWPID: 进程命名空间。空间内的PID 是独立分配的,意思就是命名空间内的虚拟 PID 可能会与命名空间外的 PID 相冲突,于是命名空间内的 PID 映射到命名空间外时会使用另外一个 PID。比如说,命名空间内第一个 PID 为1,而在命名空间外就是该 PID 已被 init 进程所使用。
  • CLONE_NEWNET: 网络命名空间,用于隔离网络资源(/proc/net、IP 地址、网卡、路由等)。后台进程可以运行在不同命名空间内的相同端口上,用户还可以虚拟出一块网卡。
  • CLONE_NEWNS: 挂载命名空间,进程运行时可以将挂载点与系统分离,使用这个功能时,我们可以达到 chroot 的功能,而在安全性方面比 chroot 更高。
  • CLONE_NEWUTS: UTS 命名空间,主要目的是独立出主机名和网络信息服务(NIS)。
  • CLONE_NEWUSER: 用户命名空间,同进程 ID 一样,用户 ID 和组 ID 在命名空间内外是不一样的,并且在不同命名空间内可以存在相同的 ID。

下面我们介绍一下进程命名空间和网络命名空间。

进程命名空间

本文用 C 语言介绍上述概念,因为演示进程命名空间的时候需要用到 C 语言。下面的测试过程在 Debian 6 和 Debian 7 上执行。首先,在栈内分配一页内存空间,并将指针指向内存页的末尾。这里我们使用 alloca() 函数来分配内存,不要用 malloc() 函数,它会把内存分配在堆上。

void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE);

然后使用 clone() 函数创建子进程,传入我们的子栈空间地址 “mem”,并指定命名空间的标记。同时我们还指定“callee”作为子进程运行的函数。

mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL);

clone 之后我们要在父进程中等待子进程先退出,否则的话,父进程会继续运行下去,并马上进程结束,留下子进程变成孤儿进程:

while (waitpid(mypid, &r, 0) < 0 && errno == EINTR)
{
continue;
}

最后当子进程退出后,我们会回到 shell 界面,并返回子进程的退出码。

if (WIFEXITED(r))
{
return WEXITSTATUS(r);
}
return EXIT_FAILURE;

上文介绍的 callee 函数功能如下:

static int callee()
{
int ret;
mount("proc", "/proc", "proc", 0, "");
setgid(u);
setgroups(0, NULL);
setuid(u);
ret = execl("/bin/bash", "/bin/bash", NULL);
return ret;
}

程序挂载了 /proc 文件系统,设置用户 ID 和组 ID,值都为“u”,然后运行 /bin/bash 程序,LXC 是一个操作系统级的虚拟化工具,使用 cgroups 和命名空间来完成资源的分离。现在我们把所有代码放在一起,变量“u”的值设为65534,在 Debian 系统中,这是“nobody”和“nogroup”:

#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <grp.h>
#include <alloca.h>
#include <errno.h>
#include <sched.h>
static int callee();
const int u = 65534;
int main(int argc, char *argv[])
{
int r;
pid_t mypid;
void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE);
mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL);
while (waitpid(mypid, &r, 0) < 0 && errno == EINTR)
{
continue;
}
if (WIFEXITED(r))
{
return WEXITSTATUS(r);
}
return EXIT_FAILURE;
}
static int callee()
{
int ret;
mount("proc", "/proc", "proc", 0, "");
setgid(u);
setgroups(0, NULL);
setuid(u);
ret = execl("/bin/bash", "/bin/bash", NULL);
return ret;
}

执行以下命令来运行上面的代码:

root@w:~/pen/tmp# gcc -O -o ns.c -Wall -Werror -ansi -c89 ns.c
root@w:~/pen/tmp# ./ns
nobody@w:~/pen/tmp$ id
uid=65534(nobody) gid=65534(nogroup)
nobody@w:~/pen/tmp$ ps auxw
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
nobody 1 0.0 0.0 4620 1816 pts/1 S 21:21 0:00 /bin/bash
nobody 5 0.0 0.0 2784 1064 pts/1 R+ 21:21 0:00 ps auxw
nobody@w:~/pen/tmp$ 

注意上面的结果,UID 和 GID 被设置成 nobody 和 nogroup 了,特别是 ps 工具只输出两个进程,它们的 ID 分别是1和5(LCTT注:这就是上文介绍 CLONE_NEWPID 时提到的功能,在线程所在的命名空间内,进程 ID 可以为1,映射到命名空间外是另外一个 PID;而命名空间外的 ID 为1的进程一直是 init)。

网络命名空间

接下来轮到使用 ip netns 来设置网络的命名空间。第一步先确定当前系统没有命名空间:

root@w:~# ip netns list
Object "netns" is unknown, try "ip help".

如果报了上述错误,你需要更新你的系统内核,以及 ip 工具程序。这里假设你的内核版高于2.6.24,ip 工具版本也差不多,高于2.6.24(LCTT注:ip 工具由 iproute 安装包提供,此安装包版本与内核版本相近)。更新好后,ip netns list 在没有命名空间存在的情况下不会输出任务信息。加个名为“ns1”的命名空间看看:

root@w:~# ip netns add ns1
root@w:~# ip netns list
ns1

列出网卡:

root@w:~# ip link list
1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000
link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff

创建新的虚拟网卡,并加到命名空间。虚拟网卡需要成对创建,互相关联——就像交叉电缆一样:

root@w:~# ip link add veth0 type veth peer name veth1
root@w:~# ip link list
1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000
link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff
4: veth0: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
link/ether f2:f7:5e:e2:22:ac brd ff:ff:ff:ff:ff:ff

这个时候 ifconfig -a 命令也能显示新添加的 veth0 和 veth1 两块网卡。

很好,现在将这两份块网卡加到命名空间中去。注意一下,下面的 ip netns exec 命令用于将后面的命令在命名空间中执行(LCTT注:下面的结果显示了在 ns1 这个网络命名空间中,只存在 lo 和 veth1 两块网卡):

root@w:~# ip link set veth1 netns ns1
root@w:~# ip netns exec ns1 ip link list
1: lo: mtu 65536 qdisc noop state DOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff

这个时候 ifconfig -a 命令只能显示 veth0,不能显示 veth1,因为后者现在在 ns1 命名空间中。

如果想删除 veth0/veth1,可以执行下面的命令:

ip netns exec ns1 ip link del veth1

我们可以为 veth0 分配 IP 地址:

ifconfig veth0 192.168.5.5/24

在命名空间内为 veth1 分配 IP 地址:

ip netns exec ns1 ifconfig veth1 192.168.5.10/24 up

在命名空间内外执行 ip addr list 命令:

root@w:~# ip addr list
1: lo: mtu 65536 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
inet 192.168.3.122/24 brd 192.168.3.255 scope global eth0
inet6 fe80::20c:29ff:fe65:259e/64 scope link
valid_lft forever preferred_lft forever
6: veth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 86:b2:c7:bd:c9:11 brd ff:ff:ff:ff:ff:ff
inet 192.168.5.5/24 brd 192.168.5.255 scope global veth0
inet6 fe80::84b2:c7ff:febd:c911/64 scope link
valid_lft forever preferred_lft forever
root@w:~# ip netns exec ns1 ip addr list
1: lo: mtu 65536 qdisc noop state DOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff
inet 192.168.5.10/24 brd 192.168.5.255 scope global veth1
inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link
valid_lft forever preferred_lft forever

在命名空间内外查看路由表:

root@w:~# ip route list
default via 192.168.3.1 dev eth0 proto static
192.168.3.0/24 dev eth0 proto kernel scope link src 192.168.3.122
192.168.5.0/24 dev veth0 proto kernel scope link src 192.168.5.5
root@w:~# ip netns exec ns1 ip route list
192.168.5.0/24 dev veth1 proto kernel scope link src 192.168.5.10 

最后,将虚拟网卡连到物理网卡上,我们需要用到桥接。这里做的是将 veth0 桥接到 eth0,而 ns1 命名空间内则使用 DHCP 自动获取 IP 地址:

root@w:~# brctl addbr br0
root@w:~# brctl addif br0 eth0
root@w:~# brctl addif br0 veth0
root@w:~# ifconfig eth0 0.0.0.0
root@w:~# ifconfig veth0 0.0.0.0
root@w:~# dhclient br0
root@w:~# ip addr list br0
7: br0: mtu 1500 qdisc noqueue state UP
link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
inet 192.168.3.122/24 brd 192.168.3.255 scope global br0
inet6 fe80::20c:29ff:fe65:259e/64 scope link
valid_lft forever preferred_lft forever

为网桥 br0 分配的 IP 地址为192.168.3.122/24。接下来为命名空间分配地址:

root@w:~# ip netns exec ns1 dhclient veth1
root@w:~# ip netns exec ns1 ip addr list
1: lo: mtu 65536 qdisc noop state DOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff
inet 192.168.3.248/24 brd 192.168.3.255 scope global veth1
inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link
valid_lft forever preferred_lft forever

现在, veth1 的 IP 被设置成 192.168.3.24824 了。


原文:http://www.howtoforge.com/linux-namespaces

作者: aziods

jack.zh 标签:linux 继续阅读

1054 ℉

15.02.28

不用担心喝茶-Fuck GFW

我要说的是,不用代理,vpn的浏览器绕过gfw的方式。

下面说一下步骤,全是图。

1 打开网站 https://www.modern.ie/zh-cn

2 点击下载虚拟机

3 选择选项,平台

4 选好之后点击Sign in

5 登陆你的hotmail账号,没有去注册一个

6 选择location

7 根据你的宿主机,选择你的下载项

8 安装去吧

如果App Store安装不上,去这里下载,我上传了一份

链接: http://pan.baidu.com/s/1nt5Myc5 密码: hlvh

9 设置(mac)

9.1 点击Microsoft RemoteApp

9.2 切记到这一步一定勾选上IE 记得 勾选上 True Enable,然后关掉这个界面

9.3 双击IE启动

10 设置 (win)

10.0 下载后双击安装

10.1 安装好之后点击Get Started

10.2 登陆你的hotmail账号

10.3 切记到这一步一定勾选上IE 记得 勾选上 True Enable

10.4 启动界面

10.5 点击IE图标等一会

10.6 Duang

11 秀一下

jack.zh 标签:FGFW 继续阅读

873 ℉

15.02.16

Docker 1 - 入门记录

1. Docker之防火墙设置

sudo vim /etc/default/ufw
# jack-zh
# DEFAULT_FORWARD_POLICY="DROP"
DEFAULT_FORWARD_POLICY="ACCEPT"

# 重启防火墙
sudo ufw reload

2. 安装Docker

sudo sh -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
curl -s https://get.docker.io/gpg | sudo apt-key add -
sudo apt-get update
sudo apt-get install lxc-docker

3. 启动一个Docker

# 简单例子
sudo docker run -i -t ubuntu /bin/bash

# 指定名字
 sudo docker run --name docker_ubuntu -i -t ubuntu /bin/bash 

 # 指定端口映射 8080为宿主机端口 80为Docker端口
 sudo docker run --name docker_ubuntu  -p 8080:80 -i -t ubuntu /bin/bash

4. Docker进程控制

 # 查看状态
sudo status docker

# stop
sudo stop docker

# start
sudo start docker

5. 查看状态

# Docker启动状态
sudo status docker

# Docker实例状态
sudo docker ps -a

# Docker镜像状态
sudo docker images

6. Docker基本控制

# 下面的docker实例name可以替换成ID

# 启动Docker实例
sudo docker start  docker_ubuntu

# 进入Docker
sudo docker attach  docker_ubuntu

# Stop Docker实例
 sudo docker stop  docker_ubuntu

7. 不用该死的sudo

# 把当前用户加入到docker用户组
# 加入用户组之后需要退出重新登陆才生效
sudo gpasswd -a ${USER} docker

8. 注册与登录Docker Hub

sudo docker login

jack.zh 标签:docker 继续阅读

1408 ℉

15.02.06

Python SocketServer聊天室

带新人,让他熟悉Socket和多线程,写一个简易聊天室

代码下载chat.zip

不废话,见代码:

server.py
try:
  import SocketServer
except Exception as e:
  import socketserver as SocketServer

import re
import socket

class ClientError(Exception):
  pass


class PythonChatServer(SocketServer.ThreadingTCPServer):

  def __init__(self, server_address, RequestHandlerClass):
    SocketServer.ThreadingTCPServer.__init__(self, server_address, RequestHandlerClass)
    self.users = {}


class RequestHandler(SocketServer.StreamRequestHandler):

  NICKNAME = re.compile('^[A-Za-z0-9_-]+$')

  def handle(self):
    self.nickname = None

    self.privateMessage("Who are you?")
    nickname=self._readline()
    done = False
    try:
      self.nickCommand(nickname)
      self.privateMessage('Hello %s, welcome to the Python Chat Server.' % nickname)
      self.broadcast('%s has joined the chat.' %nickname,False)
    except (ClientError) as error:
      self.privateMessage(error.args[0])
      done = True
    except socket.error:
      done = True

    while not done:
      try:
        done = self.processInput()
      except (ClientError) as error:
        self.privateMessage(str(error))
      except socket.error:
        done = True

  def finish(self):
    if self.nickname:
      message = '%s has quit.' % self.nickname
      if hasattr(self, 'partingWords'):
        message = '%s has quit: %s' % (self.nickname, self.partingWords)
      self.broadcast(message, False)

      if self.server.users.get(self.nickname):
        del(self.server.users[self.nickname])
    self.request.close()

  def processInput(self):
    done = False
    l = self._readline()
    command, arg = self._parseCommand(l)
    if command:
      done = command(arg)
    else:
      l = '<%s> %s\n' % (self.nickname, l)
      self.broadcast(l)
    return done

  def nickCommand(self,nickname):
    if not nickname:
      raise ClientError('No nickname provided.')
    if not self.NICKNAME.match(nickname):
      raise ClientError('Invalid nickname: %s' % nickname)
    if nickname == self.nickname:
      raise ClientError('You\'re already known as %s.' % nickname)
    if self.server.users.get(nickname,None):
      raise ClientError('There\'s already a user named "%s" here.' %nickname)
    oldNickname = None
    if self.nickname:
      oldNickname=self.nickname
      del(self.server.users[self.nickname])
    self.server.users[nickname]=self.wfile
    self.nickname=nickname
    if oldNickname:
      self.broadcast('%s is now known as %s' % (oldNickname, self.nickname))

  def quitCommand(self, partingWords):
    if partingWords:
      self.partingWords = partingWords
    return True

  def namesCommand(self, ignored):
    self.privateMessage(', '.join(self.server.users.keys()))

  def broadcast(self, message, includeThisUser=True):
    message = self._ensureNewline(message)
    for user, output in self.server.users.items():
      if includeThisUser or user != self.nickname:
        output.write(message.encode('utf-8'))

  def privateMessage(self, message):
    self.wfile.write(self._ensureNewline(message).encode('utf-8'))

  def _readline(self):
    return self.rfile.readline().strip().decode('utf-8')

  def _ensureNewline(self, s):
    if s and s[-1] !='\n':
      s += '\r\n'
    return s

  def _parseCommand(self, input):
    commandMethod, arg = None, None
    if input and input[0]=='/':
      if len(input) < 2:
        raise ClientError('Invalid command: "%s"' % input)
      commandAndArg = input[1:].split(' ',1)
      if len(commandAndArg) == 2:
        command, arg = commandAndArg
      else:
        command = commandAndArg[0]
      commandMethod = getattr(self, command + 'Command', None)
      if not commandMethod:
        raise ClientError('No such command: "%s"' %command)
    return commandMethod, arg


if __name__ == '__main__':
  import sys
  if len(sys.argv) < 3:
    print('Usage: %s [hostname] [port number]' %sys.argv[0])
    sys.exit(1)
  hostname = sys.argv[1]
  port = int(sys.argv[2])
  PythonChatServer((hostname,port),RequestHandler).serve_forever()
client.py
import socket
import select
import sys
import os
from threading import Thread


class ChatClient:

    def __init__(self, host, port, nickname):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect((host, port))
        self.input = self.socket.makefile('rb', 0)
        self.output = self.socket.makefile('wb', 0)

        authenticationDemand = self.input.readline().decode('utf-8')
        if not authenticationDemand.startswith("Who are you?"):
            raise Exception ("This doesn't seem to be a Python Chat Server.")
        self.output.write((nickname + '\r\n').encode('utf-8'))
        response = self.input.readline().strip().decode('utf-8')
        if not response.startswith("Hello"):
            raise Exception (response)
        print(response)

        self.output.write(('/names\r\n').encode('utf-8'))
        print("Currently in the chat room:", self.input.readline().decode('utf-8').strip())

        self.run()

    def run(self):        
        propagateStandardInput = self.PropagateStandardInput(self.output)
        propagateStandardInput.start()
        inputText = True
        while inputText:
            inputText = self.input.readline().decode('utf-8')
            if inputText:
                print (inputText.strip())
        propagateStandardInput.done = True

    class PropagateStandardInput(Thread):
        def __init__(self, output):
            Thread.__init__(self)
            self.setDaemon(True)
            self.output = output
            self.done = False

        def run(self):
            while not self.done:
                inputText = sys.stdin.readline().strip()
                if inputText:
                    self.output.write((inputText + '\r\n').encode('utf-8'))


if __name__ == '__main__':
    import sys
    try:
        import pwd
        defaultNickname = pwd.getpwuid(os.getuid())[0]
    except ImportError:
        defaultNickname = None

    if len(sys.argv) < 3 or not defaultNickname and len(sys.argv) < 4:
        print('Usage: %s [hostname] [port number] [username]' % sys.argv[0])
        sys.exit(1)

    hostname = sys.argv[1]
    port = int(sys.argv[2])

    if len(sys.argv) > 3:
        nickname = sys.argv[3]
    else:
        nickname = defaultNickname

    ChatClient(hostname, port, nickname)

jack.zh 标签:python 继续阅读

744 ℉

15.01.13

2014年终总结暨2015展望

对不起这个总结来的有点晚,还好,还是来了

2014总结

打分 10分为满分 6分及格

生活上

  • 生了一个可爱的宝宝 10
  • 把新房子装修好并入住 7
  • 生了两场病 0

总结改进:身体不是很好,以后要多加强锻炼,多吃蔬菜,争取保证睡眠,争取养成午睡的习惯

工作上

  • 工作有条不紊的保质保量的完成 8
  • 脾气过于急躁 记住:项目经理不是自己的小弟 6
  • 运用新技术上有点保守 7

总结改进:在工作上还是要沉着冷静,不要有太多的急脾气。多想然后动手,提高文档能力。在允许的情况下,多做尝试,牛刀杀鸡。

提高

  • Golang的初步入门 5
  • Python有了些许提高 5
  • GUI技术有了长进 6
  • Web技术有了些许长进4
  • 英语有了提高4
  • 更加讨厌强制OOP 10

总结与改进:2014是相对成长较慢的一年,借口是家庭的事情较多,还不能适应家庭带来的生活改变。年末有较大的改观.

2015展望

重点 10分为满分(必须做的) 6分及格

生活上

  • 照顾好家人 包括宝宝 妈妈和老婆 10
  • 考驾照 10
  • 照顾好自己的身体 10

工作上

  • 保质保量,在喜欢的岗位上做出出色的贡献 10
  • 保持激情,在工作中学习提高 8
  • 保持谦逊与开放 8

提高

  • 英语的阅读与书写 9
  • Golang 全栈 出色完成ztodo 10
  • Linux C开发技术 8
  • Java基础和Android基础 6
  • Android ztodo开发 5
  • zLua 的http 框架完成,学习web安全 7
  • Learn lua by example 完成初稿 4
  • 新学一门语言 一个月后定 更新在这里 初步为 Clojure|common lisp|Haskell 5

每一项都不展开了说了。

箴言

  • 生命短暂 我用Python
  • Talk is cheap. Show me the code.
  • 有能力的人一直在干活,没能力的人一直在抱怨。
  • “软件工程”是穷途末路的领域,因为它的目标是:如果我不会写程序的话,怎么样才能写出程序
  • “可移植性”的概念是为那些不会写新程序的人准备的。

jack.zh 继续阅读

1 2 3 4 5 6 7 8 9 10
Fork me on GitHub