1. fork and exec Functions
笔者在APUE中比较详细的记录过fork
函数,这里相关细节不在赘述,只看看和网络编程相关的概念:
- The parent calls accept and then calls fork. The connected socket is then shared between the parent and child. Normally, the child then reads and writes the connected socket and the parent closes the connected socket.
- A process makes a copy of itself so that one copy can handle one operation
while the other copy does another task. This is typical for network servers.
2. getsockname and getpeername Functions
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
Both return: 0 if OK, −1 on error
具体这两个函数的作用,简单来说就是为了找到由系统决定bind
函数中绑定的地址和端口:
- After connect successfully returns in a TCP client that does not call bind, getsockname returns the local IP address and local port number assigned to the connection by the kernel.
- After calling bind with a port number of 0 (telling the kernel to choose the local
port number), getsockname returns the local port number that was assigned.
- getsockname can be called to obtain the address family of a socket
- In a TCP server that binds the wildcard IP address, once a connection is established with a client (accept returns successfully), the server can call getsockname to obtain the local IP address assigned to the connection. The socket descriptor argument in this call must be that of the connected socket, and not the listening socket.
- When a server is execed by the process that calls accept, the only way the server can obtain the identity of the client is to call getpeername. This is what happens whenever inetd forks and execs a TCP server. inetd calls accept (top left box) and two values are returned: the connected socket descriptor, connfd, is the return value of the function, and the small box we label ‘‘peer ’s address’’ (an Internet socket address structure) contains the IP address and port number of the client. fork is called and a child of inetd is created. Since the child starts with a copy of the parent’s memory image, the socket address structure is available to the child, as is the connected socket descriptor (since the descriptors are shared between the parent and child). But when the child execs the real server (say the Telnet server that we show), the memory image of the child is replaced with the new program file for the Telnet server (i.e., the socket address structure containing the peer’s address is lost), and the connected socket descriptor remains open across the exec. One of the first function calls performed by the Telnet server is getpeername to obtain the IP address and port number of the client.
第三点配上下图就比较清楚,也就是fork-exec
会用新的程序替代老程序从而丢失了原本accept
返回的地址信息,但是因为文件描述符依然被新进程继承,所以可以通过getpeername
找回client的相关地址信息。
3. Concurrent Servers
一句话概括:The simplest way to write a concurrent server under Unix is to fork a child process to handle each client.
其代码模板如下:
pid_t pid;
int listenfd, connfd;
listenfd = Socket( ... );
/* fill in sockaddr_in{} with server’s well-known port */
Bind(listenfd, ... );
Listen(listenfd, LISTENQ);
for ( ; ; ) {
connfd = Accept(listenfd, ... ); /* probably blocks */
if ( (pid = Fork()) == 0) {
Close(listenfd); /* child closes listening socket */
doit(connfd); /* process the request */
Close(connfd); /* done with this client */
exit(0); /* child terminates */
}
Close(connfd); /* parent closes connected socket */
}
其实上面程序最好玩的地方就是close
在父进程调用,这点也是上一篇强调的地方,因为一个socketfd
,经过fork
以后其引用数就变为2,父进程必须调用close
关闭它,否则会造成socketfd
在子进程调用close
无法真正关闭的现象。以上程序执行的过程,看下图会有比较直观的感受: