有关并发

app2e中有几个很好的关于并发实现的例子,这里加以详细解析。 ###简单的echo服务器 所谓echo服务器就是将客户端的输入简单的通过socket回送回来。代码实现如下:

#include <csapp.h>

void echo(int connfd);

int main(int argc, char **argv)
{
	int listenfd, connfd, port, clientlen;
	struct sockaddr_in clientaddr;
	struct hostent *hp;
	char *haddrp;

	if(argc != 2) {
		fprintf(stderr, "usage: %s <port>\n", argv[0]);
		return 1;
	}
	port = atoi(argv[1]);

	listenfd = Open_listenfd(port);
	while(1) {
		clientlen = sizeof(clientaddr);
		connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);

		/* determin the domain name and IP address of the client */
		hp = Gethostbyaddr((const char*)&clientaddr.sin_addr.s_addr, 
				sizeof(clientaddr.sin_addr.s_addr), AF_INET);
		haddrp = inet_ntoa(clientaddr.sin_addr);
		printf("server conected to %s (%s)\n", hp->h_name, haddrp);
		echo(connfd);
		Close(connfd);
	}
	return 0;
}

void echo(int connfd)
{
	size_t n;
	char buf[MAXLINE];
	rio_t rio;

	Rio_readinitb(&rio, connfd);
	while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
		printf("server received %d bytes\n", n);
		Rio_writen(connfd, buf, n);
	}
}

从代码来看,这是一个很典型的socket通信的例子。连接一旦建立成功,server段会打印出client端的IP地址,并一直在echo程序中晃荡。因为echo()中有while()函数会一直等着从connfd文件描述符读入输入行。当得到来自socket fd的输入时,会打印出接收到的字符个数,并将其写入到socket文件描述符中,由此client段会得到回显字符。 ###利用进程实现并发 上面的简单echo服务器是没法接受一个以上的连接的。因此我们写出echo服务器的第二版,利用子进程实现echo服务器。

/* 
 * echoserverp.c - A concurrent echo server based on processes
 */
/* $begin echoserverpmain */
#include "csapp.h"
void echo(int connfd);

void sigchld_handler(int sig) //line:conc:echoserverp:handlerstart
{
    while (waitpid(-1, 0, WNOHANG) > 0)
	;
    return;
} //line:conc:echoserverp:handlerend

int main(int argc, char **argv) 
{
    int listenfd, connfd, port;
    socklen_t clientlen=sizeof(struct sockaddr_in);
    struct sockaddr_in clientaddr;

    if (argc != 2) {
	fprintf(stderr, "usage: %s <port>\n", argv[0]);
	exit(0);
    }
    port = atoi(argv[1]);

    Signal(SIGCHLD, sigchld_handler);
    listenfd = Open_listenfd(port);
    while (1) {
	connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
	if (Fork() == 0) { 
	    Close(listenfd); /* Child closes its listening socket */
	    echo(connfd);    /* Child services client */ //line:conc:echoserverp:echofun
	    Close(connfd);   /* Child closes connection with client */ //line:conc:echoserverp:childclose
	    exit(0);         /* Child exits */
	}
	Close(connfd); /* Parent closes connected socket (important!) */ //line:conc:echoserverp:parentclose
    }
} 

void echo(int connfd)
{
	size_t n;
	char buf[MAXLINE];
	rio_t rio;

	Rio_readinitb(&rio, connfd);
	while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
		printf("server received %d bytes\n", n);
		Rio_writen(connfd, buf, n);
	}
}


/* $end echoserverpmain */

编译和运行命令如下:

	$ gcc -o echoserverp echoserverp.c -lcsapp -lpthread
	$ ./echoserverp 3344
	$ ./echoclient localhost 3344

在多个终端上执行完./echoclient localhost 3344后,我们可以用ps -ef | grep echoserverp来检查当前系统中的进程个数:

	$ ps -ef | grep echoserverp
	Trusty     30404  8497  0 17:19 pts/9    00:00:00 ./echoserverp 3344
	Trusty     30651 30404  0 17:19 pts/9    00:00:00 ./echoserverp 3344
	Trusty     31174 30404  0 17:20 pts/9    00:00:00 ./echoserverp 3344

这里看到,在有3个client端连接时,存在3个echoserverp运行实例。

实现的关键在于:

  1. 使用信号, SIGCHLD用于回收僵死进程。
  2. Fork()函数创建子进程。
  3. 创建完子进程后,父进程需要关闭已经建立的socket连接。而子进程则需要关闭它的监听描述符。

优缺点比较: 父子进程共享文件表,但是不共享用户地址空间。使得一个进程不可能不小心覆盖到另一个进程的虚拟存储器。但是独立的地址空间使得进程共享状态信息变得困难,它们需要用IPC来显示通信。而且进程通常比较慢,因为进程控制和IPC的开销很高。IPC,进程间通信。 ###基于I/O多路复用的并发编程

/* $begin select */
#include "csapp.h"
void echo(int connfd);
void command(void);

int main(int argc, char **argv) 
{
    int listenfd, connfd, port;
    socklen_t clientlen = sizeof(struct sockaddr_in);
    struct sockaddr_in clientaddr;
    fd_set read_set, ready_set;

    if (argc != 2) {
	fprintf(stderr, "usage: %s <port>\n", argv[0]);
	exit(0);
    }
    port = atoi(argv[1]);
    listenfd = Open_listenfd(port);  //line:conc:select:openlistenfd

    FD_ZERO(&read_set);              /* Clear read set */ //line:conc:select:clearreadset
    FD_SET(STDIN_FILENO, &read_set); /* Add stdin to read set */ //line:conc:select:addstdin
    FD_SET(listenfd, &read_set);     /* Add listenfd to read set */ //line:conc:select:addlistenfd

    while (1) {
	ready_set = read_set;
	Select(listenfd+1, &ready_set, NULL, NULL, NULL); //line:conc:select:select
	if (FD_ISSET(STDIN_FILENO, &ready_set)) //line:conc:select:stdinready
	    command(); /* Read command line from stdin */
	if (FD_ISSET(listenfd, &ready_set)) { //line:conc:select:listenfdready
	    connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
	    echo(connfd); /* Echo client input until EOF */
	    Close(connfd);
	}
    }
}

void command(void) {
    char buf[MAXLINE];
    if (!Fgets(buf, MAXLINE, stdin))
	exit(0); /* EOF */
    printf("%s", buf); /* Process the input command */
}

void echo(int connfd)
{
	size_t n;
	char buf[MAXLINE];
	rio_t rio;

	Rio_readinitb(&rio, connfd);
	while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
		printf("server received %d bytes\n", n);
		Rio_writen(connfd, buf, n);
	}
}

/* $end select */

这个例子测试时需要注意的是,当客户端有连接时,终端输入将失效。一个更好的解决方案是使用更细粒度的多路复用,服务器每次循环回送一个文本行。

Remote SSH and VNC Forwarding

因为有中转服务器的存在,我们需要建立ssh端口转发,以便一步到位通过中转服务器登录到远程主机。

###ssh转发 建立ssh转发:

	ssh -L 2121:192.168.1xx.xxx.xxx 1xx.xxx.xxx.xxx -l Tomcat

建立以后则可以:

	ssh root@localhost -p 2121

Autossh without entering password:

	cat id_rsa.pub | ssh Tomcat@1xx.xxx.xx.xxx 'cat >>.ssh/authorized_keys'
	# After login on to 170, run:
	chmod 600 ~/.ssh/authorized_keys

###VNC设置 配置VNC自动启动:

	vim /etc/init.d/vncserver
	# 添加:
	VNCSERVERS="1:rohc"
	VNCSERVERARGS[1]="-geometry 1280x1024"
	$ chkconfig vncserver on

设置VNC转发:

	ssh -L 2333:192.168.1xx.xxx:5901 1xx.xxx.xxx.xxx -l Tomcat

之后就可以通过:

	vncviewer localhost:5901来访问VNC了。	

Configure Davmail and Thunderbird

Install davmail via: $ pacman -S davmail $ davmail & Configure it as:

/images/davmail.jpg

Now configure the thunderbird:

imap, to the localhost/port/ non-encrypt, Normal passwords smtp, to the localhost/port/ non-encrypt, Normal passwords And the usename should be the domain/username.

Basic ArchLinux Setting(i386)

###ArchLinux Installation First we download the iso from the archlinux.org, then using iso to boot. Partition it into many disks as you like.

Now begin to install:

	$ mount /dev/sda2 /mnt
	$ swapon /dev/sda1
	$ pacstrap /mnt base
	$ genfstab -p /mnt >> /mnt/etc/fstab

Chroot into the newly installed system:

	$ arch-chroot /mnt
	$ ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
	$ vi /etc/locale.gen
	# enable en_US related
	$ locale-gen
	$ vi /etc/mkinitcpio.conf
	# we can remain the default, notice if you use usb, enable usb related. 
	$ mkinitcpio -p linux
	$ passwd root

Grub install and configure:

	$ pacman -S grub
	$ grub-install --target=i386-pc --recheck --debug /dev/sda
	$ grub-install --target=i386-pc --recheck --debug /dev/sda

Install vim:

	$ pacman -S vim

Install dhcpcd:

	$ pacman -S dhcpcd
	$ systemctl enable dhcpcd@enp0s3
	$ pacman -S net-tools # for using ifconfig

Install openssh:

	$ pacman -S openssh
	$ systemctl start sshd
	$ systemcrl enable sshd.service

###Build rsim3 on ArchLinux First download the package from rsim3. Install the base-devel:

	pacman -S base-devel

Install boost, boost-libs, libpcap, cppunit:

	pacman -S boost boost-libs libpcap cppunit

Then you can enjoy the compiling and get the result.

Use Redis For Inter-Communication

###Play With Redis On ArchLinux, we install redis via:

	$ sudo pacman -S redis

Enalbe and start the redis.service:

	$ sudo systemctl enable redis.service
	$ sudo systemctl start redis.service
	$ ps -ef | grep redis
	redis     7609     1  0 16:03 ?        00:00:00 /usr/bin/redis-server 127.0.0.1:6379

Play with redis:

	[Trusty@DashArch queue]$ redis-cli 
	127.0.0.1:6379> set name leezk
	OK
	127.0.0.1:6379> get name
	"leezk"
	127.0.0.1:6379> del name
	(integer) 1
	127.0.0.1:6379> exists name
	(integer) 0
	127.0.0.1:6379> exit

###RQ: Simple Job Queue For Python Install redis and rq:

	$ sudo pip2 install redis
	$ sudo pip2 install rq

Install requests for debugging(Not related with RQ and Redis):

	$ sudo pip2 install requests

Use following file for RedisQueue:

import redis

class RedisQueue(object):
    """Simple Queue with Redis Backend"""
    def __init__(self, name, namespace='queue', **redis_kwargs):
        """The default connection parameters are: host='localhost', port=6379, db=0"""
        self.__db= redis.Redis(**redis_kwargs)
        self.key = '%s:%s' %(namespace, name)

    def qsize(self):
        """Return the approximate size of the queue."""
        return self.__db.llen(self.key)

    def empty(self):
        """Return True if the queue is empty, False otherwise."""
        return self.qsize() == 0

    def put(self, item):
        """Put item into the queue."""
        self.__db.rpush(self.key, item)

    def get(self, block=True, timeout=None):
        """Remove and return an item from the queue. 

        If optional args block is true and timeout is None (the default), block
        if necessary until an item is available."""
        if block:
            item = self.__db.blpop(self.key, timeout=timeout)
        else:
            item = self.__db.lpop(self.key)

        if item:
            item = item[1]
        return item

    def get_nowait(self):
        """Equivalent to get(False)."""
        return self.get(False)


Testing the RedisQueue with following: Put something into the RedisQueue:

	>>> from RedisQueue import RedisQueue
	>>> q = RedisQueue('test')
	>>> q.put('hello World!')

Fetch something from the RedisQueue:

	>>> from RedisQueue import RedisQueue
	>>> q = RedisQueue('test')
	>>> q.get()
	'hello World!'

###Rewrite Dictionary Program