到处都是Unix的胎记

2009年10月11日 发表评论 阅读评论 4,995 次点击    

一说起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是怎么实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
 * 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 = '\0'; // null-terminate
    return n;
}

Ruby

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
  
 
# 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
"""
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/perl
use 5.010;
use strict;
 
# simple preforking echo server in Perl
use Proc::Fork;
use IO::Socket::INET;
 
sub strip { s/\A\s+//, s/\s+\z// for my @r = @_; @r }
 
# 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.
my $acceptor = IO::Socket::INET->new(
    LocalPort => 4242,
    Reuse     => 1,
    Listen    => 10,
) or die "Couln't start server: $!\n";
 
# 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).
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?
/*
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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 -

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

分类: Unix/Linux 标签: , , , , , , , ,
好烂啊有点差凑合看看还不错很精彩 (7 人打了分,平均分: 5.00 )
::...
免责声明:
当前网页内容, 由 大妈 ZoomQuiet 使用工具: ScrapBook :: Firefox Extension 人工从互联网中收集并分享;
内容版权归原作者所有;
本人对内容的有效性/合法性不承担任何强制性责任.
若有不妥, 欢迎评注提醒:

或是邮件反馈可也:
askdama[AT]googlegroups.com


点击注册~> 获得 100$ 体验券: DigitalOcean Referral Badge

订阅 substack 体验古早写作:


关注公众号, 持续获得相关各种嗯哼:
zoomquiet


自怼圈/年度番新

DU22.4
关于 ~ DebugUself with DAMA ;-)
粤ICP备18025058号-1
公安备案号: 44049002000656 ...::