原文:http://coolshell.cn/?p=1532 (酷壳)
如果你想知道Unix的一些故事,你可以查看下面这些文章:
《Unix40年:昨天,今天和明天》
《Unix传奇》上篇,下篇
《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 -
如果你知道更多的,请你告诉我们。(全文完
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/haoel/archive/2009/10/15/4677757.aspx