原文: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 -
如果你知道更多的,請你告訴我們。(全文完)