到處都是Unix的胎記

原文:http://coolshell.cn/?p=1532 (酷殼

如果你想知道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是怎麼實現的。

001./**
002. * A simple preforking echo server in C.
003. *
004. * Building:
005. *
006. * $ gcc -Wall -o echo echo.c
007. *
008. * Usage:
009. *
010. * $ ./echo
011. *
012. *   ~ then in another terminal ... ~
013. *
014. * $ echo 'Hello, world!' | nc localhost 4242
015. *
016. */
017.  
018.#include <unistd.h> /* fork, close */
019.#include <stdlib.h> /* exit */
020.#include <string.h> /* strlen */
021.#include <stdio.h> /* perror, fdopen, fgets */
022.#include <sys/socket.h>
023.#include <sys/wait.h> /* waitpid */
024.#include <netdb.h> /* getaddrinfo */
025.  
026.#define die(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)
027.  
028.#define PORT "4242"
029.#define NUM_CHILDREN 3
030.  
031.#define MAXLEN 1024
032.  
033.int readline(int fd, char *buf, int maxlen); // forward declaration
034.  
035.int
036.main(int argc, char** argv)
037.{
038.    int i, n, sockfd, clientfd;
039.    int yes = 1; // used in setsockopt(2)
040.    struct addrinfo *ai;
041.    struct sockaddr_in *client;
042.    socklen_t client_t;
043.    pid_t cpid; // child pid
044.    char line[MAXLEN];
045.    char cpid_s[32];
046.    char welcome[32];
047.  
048.    /* Create a socket and get its file descriptor -- socket(2) */
049.    sockfd = socket(AF_INET, SOCK_STREAM, 0);
050.    if (sockfd == -1) {
051.    die("Couldn't create a socket");
052.    }
053.  
054.    /* Prevents those dreaded "Address already in use" errors */
055.    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&yes, sizeof(int)) == -1) {
056.    die("Couldn't setsockopt");
057.    }
058.  
059.    /* Fill the address info struct (host + port) -- getaddrinfo(3) */
060.    if (getaddrinfo(NULL, PORT, NULL, &ai) != 0) {
061.    die("Couldn't get address");
062.    }
063.  
064.    /* Assign address to this socket's fd */
065.    if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) != 0) {
066.    die("Couldn't bind socket to address");
067.    }
068.  
069.    /* Free the memory used by our address info struct */
070.    freeaddrinfo(ai);
071.  
072.    /* Mark this socket as able to accept incoming connections */
073.    if (listen(sockfd, 10) == -1) {
074.    die("Couldn't make socket listen");
075.    }
076.  
077.    /* Fork you some child processes. */
078.    for (i = 0; i < NUM_CHILDREN; i++) {
079.    cpid = fork();
080.    if (cpid == -1) {
081.        die("Couldn't fork");
082.    }
083.  
084.    if (cpid == 0) { // We're in the child ...
085.        for (;;) { // Run forever ...
086.        /* Necessary initialization for accept(2) */
087.        client_t = sizeof client;
088.  
089.        /* Blocks! */
090.        clientfd = accept(sockfd, (struct sockaddr *)&client, &client_t);
091.        if (clientfd == -1) {
092.            die("Couldn't accept a connection");
093.        }
094.  
095.        /* Send a welcome message/prompt */
096.        bzero(cpid_s, 32);
097.        bzero(welcome, 32);
098.        sprintf(cpid_s, "%d", getpid());
099.        sprintf(welcome, "Child %s echo> ", cpid_s);
100.        send(clientfd, welcome, strlen(welcome), 0);
101.  
102.        /* Read a line from the client socket ... */
103.        n = readline(clientfd, line, MAXLEN);
104.        if (n == -1) {
105.            die("Couldn't read line from connection");
106.        }
107.  
108.        /* ... and echo it back */
109.        send(clientfd, line, n, 0);
110.  
111.        /* Clean up the client socket */
112.        close(clientfd);
113.        }
114.    }
115.    }
116.  
117.    /* Sit back and wait for all child processes to exit */
118.    while (waitpid(-1, NULL, 0) > 0);
119.  
120.    /* Close up our socket */
121.    close(sockfd);
122.  
123.    return 0;
124.}
125.  
126./**
127. * Simple utility function that reads a line from a file descriptor fd,
128. * up to maxlen bytes -- ripped from Unix Network Programming, Stevens.
129. */
130.int
131.readline(int fd, char *buf, int maxlen)
132.{
133.    int n, rc;
134.    char c;
135.  
136.    for (n = 1; n < maxlen; n++) {
137.    if ((rc = read(fd, &c, 1)) == 1) {
138.        *buf++ = c;
139.        if (c == '/n')
140.        break;
141.    } else if (rc == 0) {
142.        if (n == 1)
143.        return 0; // EOF, no data read
144.        else
145.        break; // EOF, read some data
146.    } else
147.        return -1; // error
148.    }
149.  
150.    *buf = '/0'; // null-terminate
151.    return n;
152.}

Ruby

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

01.   
02.  
03.# simple preforking echo server in Ruby
04.require 'socket'
05.  
06.# Create a socket, bind it to localhost:4242, and start listening.
07.# Runs once in the parent; all forked children inherit the socket's
08.# file descriptor.
09.acceptor = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
10.address = Socket.pack_sockaddr_in(4242, 'localhost')
11.acceptor.bind(address)
12.acceptor.listen(10)
13.  
14.# Close the socket when we exit the parent or any child process. This
15.# only closes the file descriptor in the calling process, it does not
16.# take the socket out of the listening state (until the last fd is
17.# closed).
18.#
19.# The trap is guaranteed to happen, and guaranteed to happen only
20.# once, right before the process exits for any reason (unless
21.# it's terminated with a SIGKILL).
22.trap('EXIT') { acceptor.close }
23.  
24.# Fork you some child processes. In the parent, the call to fork
25.# returns immediately with the pid of the child process; fork never
26.# returns in the child because we exit at the end of the block.
27.3.times do
28.  fork do
29.    # now we're in the child process; trap (Ctrl-C) interrupts and
30.    # exit immediately instead of dumping stack to stderr.
31.    trap('INT') { exit }
32.  
33.    puts "child #$$ accepting on shared socket (localhost:4242)"
34.    loop {
35.      # This is where the magic happens. accept(2) blocks until a
36.      # new connection is ready to be dequeued.
37.      socket, addr = acceptor.accept
38.      socket.write "child #$$ echo> "
39.      socket.flush
40.      message = socket.gets
41.      socket.write message
42.      socket.close
43.      puts "child #$$ echo'd: '#{message.strip}'"
44.    }
45.    exit
46.  end
47.end
48.  
49.# Trap (Ctrl-C) interrupts, write a note, and exit immediately
50.# in parent. This trap is not inherited by the forks because it
51.# runs after forking has commenced.
52.trap('INT') { puts "/nbailing" ; exit }
53.  
54.# Sit back and wait for all child processes to exit.
55.Process.waitall

Python

01."""
02.Simple preforking echo server in Python.
03."""
04.  
05.import os
06.import sys
07.import socket
08.  
09.# Create a socket, bind it to localhost:4242, and start
10.# listening. Runs once in the parent; all forked children
11.# inherit the socket's file descriptor.
12.acceptor = socket.socket()
13.acceptor.bind(('localhost', 4242))
14.acceptor.listen(10)
15.  
16.# Ryan's Ruby code here traps EXIT and closes the socket. This
17.# isn't required in Python; the socket will be closed when the
18.# socket object gets garbage collected.
19.  
20.# Fork you some child processes. In the parent, the call to
21.# fork returns immediately with the pid of the child process;
22.# fork never returns in the child because we exit at the end
23.# of the block.
24.for i in range(3):
25.    pid = os.fork()
26.  
27.    # os.fork() returns 0 in the child process and the child's
28.    # process id in the parent. So if pid == 0 then we're in
29.    # the child process.
30.    if pid == 0:
31.        # now we're in the child process; trap (Ctrl-C)
32.        # interrupts by catching KeyboardInterrupt) and exit
33.        # immediately instead of dumping stack to stderr.
34.        childpid = os.getpid()
35.        print "Child %s listening on localhost:4242" % childpid
36.        try:
37.            while 1:
38.                # This is where the magic happens. accept(2)
39.                # blocks until a new connection is ready to be
40.                # dequeued.
41.                conn, addr = acceptor.accept()
42.  
43.                # For easier use, turn the socket connection
44.                # into a file-like object.
45.                flo = conn.makefile()
46.                flo.write('Child %s echo> ' % childpid)
47.                flo.flush()
48.                message = flo.readline()
49.                flo.write(message)
50.                flo.close()
51.                conn.close()
52.                print "Child %s echo'd: %r" % /
53.                          (childpid, message.strip())
54.        except KeyboardInterrupt:
55.            sys.exit()
56.  
57.# Sit back and wait for all child processes to exit.
58.#
59.# Trap interrupts, write a note, and exit immediately in
60.# parent. This trap is not inherited by the forks because it
61.# runs after forking has commenced.
62.try:
63.    os.waitpid(-1, 0)
64.except KeyboardInterrupt:
65.    print "/nbailing"
66.    sys.exit()

Perl

01.#!/usr/bin/perl
02.use 5.010;
03.use strict;
04.  
05.# simple preforking echo server in Perl
06.use Proc::Fork;
07.use IO::Socket::INET;
08.  
09.sub strip { s//A/s+//, s//s+/z// for my @r = @_; @r }
10.  
11.# Create a socket, bind it to localhost:4242, and start listening.
12.# Runs once in the parent; all forked children inherit the socket's
13.# file descriptor.
14.my $acceptor = IO::Socket::INET->new(
15.    LocalPort => 4242,
16.    Reuse     => 1,
17.    Listen    => 10,
18.) or die "Couln't start server: $!/n";
19.  
20.# Close the socket when we exit the parent or any child process. This
21.# only closes the file descriptor in the calling process, it does not
22.# take the socket out of the listening state (until the last fd is
23.# closed).
24.END { $acceptor->close }
25.  
26.# Fork you some child processes. The code after the run_fork block runs
27.# in all process, but because the child block ends in an exit call, only
28.# the parent executes the rest of the program. If a parent block were
29.# specified here, it would be invoked in the parent only, and passed the
30.# PID of the child process.
31.for ( 1 .. 3 ) {
32.    run_fork { child {
33.        while (1) {
34.            my $socket = $acceptor->accept;
35.            $socket->printflush( "child $$ echo> " );
36.            my $message = $socket->getline;
37.            $socket->print( $message );
38.            $socket->close;
39.            say "child $$ echo'd: '${/strip $message}'";
40.        }
41.        exit;
42.    } }
43.}
44.  
45.# Trap (Ctrl-C) interrupts, write a note, and exit immediately
46.# in parent. This trap is not inherited by the forks because it
47.# runs after forking has commenced.
48.$SIG{ 'INT' } = sub { print "bailing/n"; exit };
49.  
50.# Sit back and wait for all child processes to exit.
51.1 while 0 < waitpid -1, 0;
52.  

PHP

01.<?
02./*
03.Simple preforking echo server in PHP.
04.Russell Beattie (russellbeattie.com)
05.*/
06.  
07./* Allow the script to hang around waiting for connections. */
08.set_time_limit(0);
09.  
10.# Create a socket, bind it to localhost:4242, and start
11.# listening. Runs once in the parent; all forked children
12.# inherit the socket's file descriptor.
13.$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
14.socket_bind($socket,'localhost', 4242);
15.socket_listen($socket, 10);
16.  
17.pcntl_signal(SIGTERM, 'shutdown');
18.pcntl_signal(SIGINT, 'shutdown');
19.  
20.function shutdown($signal){
21.    global $socket;
22.    socket_close($socket);
23.    exit();
24.}
25.# Fork you some child processes. In the parent, the call to
26.# fork returns immediately with the pid of the child process;
27.# fork never returns in the child because we exit at the end
28.# of the block.
29.for($x = 1; $x <= 3; $x++){
30.     
31.    $pid = pcntl_fork();
32.     
33.    # pcntl_fork() returns 0 in the child process and the child's
34.    # process id in the parent. So if $pid == 0 then we're in
35.    # the child process.
36.    if($pid == 0){
37.  
38.        $childpid = posix_getpid();
39.         
40.        echo "Child $childpid listening on localhost:4242 /n";
41.  
42.        while(true){
43.            # This is where the magic happens. accept(2)
44.            # blocks until a new connection is ready to be
45.            # dequeued.
46.            $conn = socket_accept($socket);
47.  
48.            $message = socket_read($conn,1000,PHP_NORMAL_READ);
49.             
50.            socket_write($conn, "Child $childpid echo> $message");
51.         
52.            socket_close($conn);
53.         
54.            echo "Child $childpid echo'd: $message /n";
55.         
56.        }
57.  
58.    }
59.}
60.#
61.# Trap interrupts, write a note, and exit immediately in
62.# parent. This trap is not inherited by the forks because it
63.# runs after forking has commenced.
64.try{
65.  
66.    pcntl_waitpid(-1, $status);
67.  
68.} catch (Exception $e) {
69.  
70.    echo "bailing /n";
71.    exit();
72.  
73.}

Haskell

01.import Network
02.import Prelude hiding ((-))
03.import Control.Monad
04.import System.IO
05.import Control.Applicative
06.import System.Posix
07.import System.Exit
08.import System.Posix.Signals
09.  
10.main :: IO ()
11.main = with =<< (listenOn - PortNumber 4242) where
12.  
13.  with socket = do
14.    replicateM 3 - forkProcess work
15.    wait
16.  
17.    where
18.    work = do
19.      installHandler sigINT (Catch trap_int) Nothing
20.      pid <- show <$> getProcessID
21.      puts - "child " ++ pid ++ " accepting on shared socket (localhost:4242)"
22.       
23.      forever - do
24.        (h, _, _) <- accept socket
25.  
26.        let write   = hPutStr h
27.            flush   = hFlush h
28.            getline = hGetLine h
29.            close   = hClose h
30.  
31.        write - "child " ++ pid ++ " echo> "
32.        flush
33.        message <- getline
34.        write - message ++ "/n"
35.        puts - "child " ++ pid ++ " echo'd: '" ++ message ++ "'"
36.        close
37.  
38.    wait = forever - do
39.      ( const () <$> getAnyProcessStatus True True  ) `catch` const trap_exit
40.  
41.    trap_int = exitImmediately ExitSuccess
42.  
43.    trap_exit = do
44.      puts "/nbailing"
45.      sClose socket
46.      exitSuccess
47.  
48.    puts = putStrLn
49.  
50.  (-) = ($)
51.  infixr 0 -

如果你知道更多的,請你告訴我們。(全文完)

發佈了120 篇原創文章 · 獲贊 785 · 訪問量 590萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章