到处都是Unix的胎记

一说起Unix编程,不必多说,最著名的系统调用就是fork,pipe,exec,kill或是socket了(fork(2),execve(2)pipe(2)socketpair(2)select(2)kill(2)sigaction(2))这些系统调用都像是Unix编程的胎记或签名一样,表明着它来自于Unix。

下面这篇文章,将向大家展示Unix下最经典的socket的编程例子——使用fork + socket来创建一个TCP/IP的服务程序。这个编程模式很简单,首先是创建Socket,然后把其绑定在某个IP和Port上上侦听连接,接下来的一般做法是使用一个fork创建一个client服务进程再加上一个死循环用于处理和client的交互。这个模式是Unix下最经典的Socket编程例子。

下面,让我们看看用C,Ruby,Python,Perl,PHP和Haskell来实现这一例子,你会发现这些例子中的Unix的胎记。如果你想知道这些例子中的技术细节,那么,向你推荐两本经典书——《Unix高级环境编程》和《Unix网络编程》。

C语言

我们先来看一下经典的C是怎么实现的。

/**
 * A simple preforking echo server in C.
 *
 * Building:
 *
 * $ gcc -Wall -o echo echo.c
 *
 * Usage:
 *
 * $ ./echo
 *
 *   ~ then in another terminal ... ~
 *
 * $ echo 'Hello, world!' | nc localhost 4242
 *
 */
#include <unistd.h> /* fork, close */
#include <stdlib.h> /* exit */
#include <string.h> /* strlen */
#include <stdio.h> /* perror, fdopen, fgets */
#include <sys/socket.h>
#include <sys/wait.h> /* waitpid */
#include <netdb.h> /* getaddrinfo */
#define die(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)
#define PORT "4242"
#define NUM_CHILDREN 3
#define MAXLEN 1024
int readline(int fd, char *buf, int maxlen); // forward declaration
int
main(int argc, char** argv)
{
    int i, n, sockfd, clientfd;
    int yes = 1; // used in setsockopt(2)
    struct addrinfo *ai;
    struct sockaddr_in *client;
    socklen_t client_t;
    pid_t cpid; // child pid
    char line[MAXLEN];
    char cpid_s[32];
    char welcome[32];
    /* Create a socket and get its file descriptor -- socket(2) */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
die("Couldn't create a socket");
    }
    /* Prevents those dreaded "Address already in use" errors */
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&yes, sizeof(int)) == -1) {
die("Couldn't setsockopt");
    }
    /* Fill the address info struct (host + port) -- getaddrinfo(3) */
    if (getaddrinfo(NULL, PORT, NULL, &ai) != 0) {
die("Couldn't get address");
    }
    /* Assign address to this socket's fd */
    if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) != 0) {
die("Couldn't bind socket to address");
    }
    /* Free the memory used by our address info struct */
    freeaddrinfo(ai);
    /* Mark this socket as able to accept incoming connections */
    if (listen(sockfd, 10) == -1) {
die("Couldn't make socket listen");
    }
    /* Fork you some child processes. */
    for (i = 0; i < NUM_CHILDREN; i++) {
cpid = fork();
if (cpid == -1) {
    die("Couldn't fork");
}
if (cpid == 0) { // We're in the child ...
    for (;;) { // Run forever ...
/* Necessary initialization for accept(2) */
client_t = sizeof client;
/* Blocks! */
clientfd = accept(sockfd, (struct sockaddr *)&client, &client_t);
if (clientfd == -1) {
    die("Couldn't accept a connection");
}
/* Send a welcome message/prompt */
bzero(cpid_s, 32);
bzero(welcome, 32);
sprintf(cpid_s, "%d", getpid());
sprintf(welcome, "Child %s echo> ", cpid_s);
send(clientfd, welcome, strlen(welcome), 0);
/* Read a line from the client socket ... */
n = readline(clientfd, line, MAXLEN);
if (n == -1) {
    die("Couldn't read line from connection");
}
/* ... and echo it back */
send(clientfd, line, n, 0);
/* Clean up the client socket */
close(clientfd);
    }
}
    }
    /* Sit back and wait for all child processes to exit */
    while (waitpid(-1, NULL, 0) > 0);
    /* Close up our socket */
    close(sockfd);
    return 0;
}
/**
 * Simple utility function that reads a line from a file descriptor fd,
 * up to maxlen bytes -- ripped from Unix Network Programming, Stevens.
 */
int
readline(int fd, char *buf, int maxlen)
{
    int n, rc;
    char c;
    for (n = 1; n < maxlen; n++) {
if ((rc = read(fd, &c, 1)) == 1) {
    *buf++ = c;
    if (c == '\n')
break;
} else if (rc == 0) {
    if (n == 1)
return 0; // EOF, no data read
    else
break; // EOF, read some data
} else
    return -1; // error
    }
    *buf = ''; // null-terminate
    return n;
}

Ruby

下面是Ruby,你可以看到其中的fork

# simple preforking echo server in Ruby
require 'socket'
# Create a socket, bind it to localhost:4242, and start listening.
# Runs once in the parent; all forked children inherit the socket's
# file descriptor.
acceptor = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
address = Socket.pack_sockaddr_in(4242, 'localhost')
acceptor.bind(address)
acceptor.listen(10)
# Close the socket when we exit the parent or any child process. This
# only closes the file descriptor in the calling process, it does not
# take the socket out of the listening state (until the last fd is
# closed).
#
# The trap is guaranteed to happen, and guaranteed to happen only
# once, right before the process exits for any reason (unless
# it's terminated with a SIGKILL).
trap('EXIT') { acceptor.close }
# Fork you some child processes. In the parent, the call to fork
# returns immediately with the pid of the child process; fork never
# returns in the child because we exit at the end of the block.
3.times do
  fork do
    # now we're in the child process; trap (Ctrl-C) interrupts and
    # exit immediately instead of dumping stack to stderr.
    trap('INT') { exit }
    puts "child #$$ accepting on shared socket (localhost:4242)"
    loop {
      # This is where the magic happens. accept(2) blocks until a
      # new connection is ready to be dequeued.
      socket, addr = acceptor.accept
      socket.write "child #$$ echo> "
      socket.flush
      message = socket.gets
      socket.write message
      socket.close
      puts "child #$$ echo'd: '#{message.strip}'"
    }
    exit
  end
end
# Trap (Ctrl-C) interrupts, write a note, and exit immediately
# in parent. This trap is not inherited by the forks because it
# runs after forking has commenced.
trap('INT') { puts "\nbailing" ; exit }
# Sit back and wait for all child processes to exit.
Process.waitall

Python

"""
Simple preforking echo server in Python.
"""
import os
import sys
import socket
# Create a socket, bind it to localhost:4242, and start
# listening. Runs once in the parent; all forked children
# inherit the socket's file descriptor.
acceptor = socket.socket()
acceptor.bind(('localhost', 4242))
acceptor.listen(10)
# Ryan's Ruby code here traps EXIT and closes the socket. This
# isn't required in Python; the socket will be closed when the
# socket object gets garbage collected.
# Fork you some child processes. In the parent, the call to
# fork returns immediately with the pid of the child process;
# fork never returns in the child because we exit at the end
# of the block.
for i in range(3):
    pid = os.fork()
    # os.fork() returns 0 in the child process and the child's
    # process id in the parent. So if pid == 0 then we're in
    # the child process.
    if pid == 0:
        # now we're in the child process; trap (Ctrl-C)
        # interrupts by catching KeyboardInterrupt) and exit
        # immediately instead of dumping stack to stderr.
        childpid = os.getpid()
        print "Child %s listening on localhost:4242" % childpid
        try:
            while 1:
                # This is where the magic happens. accept(2)
                # blocks until a new connection is ready to be
                # dequeued.
                conn, addr = acceptor.accept()
                # For easier use, turn the socket connection
                # into a file-like object.
                flo = conn.makefile()
                flo.write('Child %s echo> ' % childpid)
                flo.flush()
                message = flo.readline()
                flo.write(message)
                flo.close()
                conn.close()
                print "Child %s echo'd: %r" % \
                          (childpid, message.strip())
        except KeyboardInterrupt:
            sys.exit()
# Sit back and wait for all child processes to exit.
#
# Trap interrupts, write a note, and exit immediately in
# parent. This trap is not inherited by the forks because it
# runs after forking has commenced.
try:
    os.waitpid(-1, 0)
except KeyboardInterrupt:
    print "\nbailing"
    sys.exit()

Perl

END { $acceptor->close }
# Fork you some child processes. The code after the run_fork block runs
# in all process, but because the child block ends in an exit call, only
# the parent executes the rest of the program. If a parent block were
# specified here, it would be invoked in the parent only, and passed the
# PID of the child process.
for ( 1 .. 3 ) {
    run_fork { child {
        while (1) {
            my $socket = $acceptor->accept;
            $socket->printflush( "child $$ echo> " );
            my $message = $socket->getline;
            $socket->print( $message );
            $socket->close;
            say "child $$ echo'd: '${\strip $message}'";
        }
        exit;
    } }
}
# Trap (Ctrl-C) interrupts, write a note, and exit immediately
# in parent. This trap is not inherited by the forks because it
# runs after forking has commenced.
$SIG{ 'INT' } = sub { print "bailing\n"; exit };
# Sit back and wait for all child processes to exit.
1 while 0 < waitpid -1, 0;

PHP

<?
/*
Simple preforking echo server in PHP.
Russell Beattie (russellbeattie.com)
*/
/* Allow the script to hang around waiting for connections. */
set_time_limit(0);
# Create a socket, bind it to localhost:4242, and start
# listening. Runs once in the parent; all forked children
# inherit the socket's file descriptor.
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket,'localhost', 4242);
socket_listen($socket, 10);
pcntl_signal(SIGTERM, 'shutdown');
pcntl_signal(SIGINT, 'shutdown');
function shutdown($signal){
    global $socket;
    socket_close($socket);
    exit();
}
# Fork you some child processes. In the parent, the call to
# fork returns immediately with the pid of the child process;
# fork never returns in the child because we exit at the end
# of the block.
for($x = 1; $x <= 3; $x++){
   
    $pid = pcntl_fork();
   
    # pcntl_fork() returns 0 in the child process and the child's
    # process id in the parent. So if $pid == 0 then we're in
    # the child process.
    if($pid == 0){
        $childpid = posix_getpid();
       
        echo "Child $childpid listening on localhost:4242 \n";
        while(true){
            # This is where the magic happens. accept(2)
            # blocks until a new connection is ready to be
            # dequeued.
            $conn = socket_accept($socket);
            $message = socket_read($conn,1000,PHP_NORMAL_READ);
           
            socket_write($conn, "Child $childpid echo> $message");
       
            socket_close($conn);
       
            echo "Child $childpid echo'd: $message \n";
       
        }
    }
}
#
# Trap interrupts, write a note, and exit immediately in
# parent. This trap is not inherited by the forks because it
# runs after forking has commenced.
try{
    pcntl_waitpid(-1, $status);
} catch (Exception $e) {
    echo "bailing \n";
    exit();
}

Haskell

import Network
import Prelude hiding ((-))
import Control.Monad
import System.IO
import Control.Applicative
import System.Posix
import System.Exit
import System.Posix.Signals
main :: IO ()
main = with =<< (listenOn - PortNumber 4242) where
  with socket = do
    replicateM 3 - forkProcess work
    wait
    where
    work = do
      installHandler sigINT (Catch trap_int) Nothing
      pid <- show <$> getProcessID
      puts - "child " ++ pid ++ " accepting on shared socket (localhost:4242)"
     
      forever - do
        (h, _, _) <- accept socket
        let write   = hPutStr h
            flush   = hFlush h
            getline = hGetLine h
            close   = hClose h
        write - "child " ++ pid ++ " echo> "
        flush
        message <- getline
        write - message ++ "\n"
        puts - "child " ++ pid ++ " echo'd: '" ++ message ++ "'"
        close
    wait = forever - do
      ( const () <$> getAnyProcessStatus True True  ) `catch` const trap_exit
    trap_int = exitImmediately ExitSuccess
    trap_exit = do
      puts "\nbailing"
      sClose socket
      exitSuccess
    puts = putStrLn
  (-) = ($)
  infixr 0 -

如果你知道更多的,请你告诉我们。(全文完)

转自:http://coolshell.cn/articles/1532.html

原创文章,作者:s19930811,如若转载,请注明出处:http://www.178linux.com/2435

(0)
s19930811s19930811
上一篇 2015-04-03 22:07
下一篇 2015-04-03 22:07

相关推荐

  • 程序包管理之制作yum本地源

    Yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的Shell前端软件包管理器。基于RPM包管理,能够从指定的服务器自动下载RPM包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖的软件包,无须繁琐地一次次下载、安装。 使用本地yum源有两种方式:第一直接使用光盘镜像,不过此方…

    Linux干货 2016-08-24
  • Net22-第一周作业-linux基础知识

    1、描述计算机的组成及其功能。    计算机硬件由五大部件组成,分别是运算器、控制器、存储器、输入和输出设备组成。 其中,运算器和控制器合称为cpu,接受指令,加工数据。 存储器又分为内存和外存(硬盘、U盘等等),这里指的是内存,给cpu提供数据和指令。 输入设备对数据进行输入,如鼠标,键盘等。 输出设备对计算结果进行显示或者打印等,如显…

    Linux干货 2016-08-22
  • 常用的MySQL数据库备份

    常用的MySQL数据库备份方式 前言 为什么需要备份数据? 数据的备份类型 MySQL备份数据的方式 备份需要考虑的问题 设计合适的备份策略 实战演练 使用cp进行备份 使用mysqldump+复制BINARY LOG备份 使用lvm2快照备份数据 使用Xtrabackup备份 总结 前言 我们试着想一想, 在生产环境中什么最重要?如果我们服务器的硬件坏了可…

    2015-06-13
  • 一个“蝇量级” C 语言协程库

    协程(coroutine)顾名思义就是“协作的例程”(co-operative routines)。跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻辑上类似多任务的编程技巧。实际上协程的概念比线程还要早,按照 Knuth 的说法“子例程是协程的特例”,一个子例程就是一次子函数调用,那么实际上协程就是类函数一样的程序组件,你可以…

    Linux干货 2016-08-15
  • httpd的特性(初阶应用)

    httpd的特性   httpd常用配置/etc/httpd/conf/httpd.conf 1、修改监听的IP和PORT Listen [IP:]PORT (1)省略IP表示为0.0.0.0 (2)Listen指令可重复多次 Listen 8080 Listen 80 (3)先修改监听的socket,重启服务后方可生效 2、持久连续 KeepAl…

    2017-09-30
  • Linux的启动流程

    启动流程  POST: Power-On-Self-Test,加电自检,是BIOS功能的一个主要部分。负责完成对CPU、主板、内存、硬盘子系统、显示子系统、 串并行接口、键盘、 CD-ROM光驱等硬件情况的检测。 ROM: BIOS, Basic Input and Output System,保存着有关计算机系统最重要的基本输入输出程序,系统信息设置、 …

    Linux干货 2016-09-13