UNIX 网络编程第五章读书笔记
2015-01-12 10:25
218 查看
刚看完 UNIX 第五章内容,我想按照自己的方式将自己获得的知识梳理一遍,以便日后查看!先贴上一段简单的 TCP 服务器端代码:
以上是一个最基本的 TCP 服务器端代码,其功能很简单:一旦一个客户发起连接,服务器端就 fork() 一个进程为其服务,该进程从其所拥有的连接套接字读数据然后再将数据写回套接字。客户端运行效果如下:
View Code
#include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <error.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #define MAXLINE 5 #define SA struct sockaddr int main() { int listenfd, connfd; pid_t childpid; int readn, writen; socklen_t clilen; char buf[MAXLINE]; struct sockaddr_in servaddr, cliaddr; //创建监听套接字 if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("socket() error!"); exit(0); } //先要对协议地址进行清零 bzero(&servaddr,sizeof(servaddr)); //设置为 IPv4 or IPv6 servaddr.sin_family = AF_INET; //绑定本地端口号 servaddr.sin_port = htons(9804); //任何一个 IP 地址,让内核自行选择 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //绑定套接口到本地协议地址 if(bind(listenfd, (SA *) &servaddr,sizeof(servaddr)) < 0) { printf("bind() error!"); exit(0); } //服务器开始监听 if(listen(listenfd,5) < 0) { printf("listen() error!"); exit(0); } for(;;) { clilen = sizeof(cliaddr); //accept 的后面两个参数都是值-结果参数,他们的保留的远程连接电脑的信息,如果不管新远程连接电脑的信息,可以将这两个参数设置为 NULL connfd = accept(listenfd, (SA *) &cliaddr, &clilen); if(connfd < 0) { continue; } //我们采用 TCP 并发服务器模式,每个客户一个进程 if((childpid = fork()) == 0) { //子进程关闭 listenfd:因为父子进程都各拥有一个 listefd,而子进程不负责监听,所以关闭 listenfd,以免浪费资源! close(listenfd); //子进程做的事很简单,从套接字上读取数据然后再写回 while((readn = read(connfd, buf,MAXLINE)) > 0) { writen = write(connfd, buf, readn); if(writen < 0) { printf("writen() error!"); continue; } else { printf("write %d bytes!\n", writen); } } exit(0); } //父进程关闭 connfd,类似的父进程只负责监听,不需要 connfd close(connfd); } }
以上是一个最基本的 TCP 服务器端代码,其功能很简单:一旦一个客户发起连接,服务器端就 fork() 一个进程为其服务,该进程从其所拥有的连接套接字读数据然后再将数据写回套接字。客户端运行效果如下:
#include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <error.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/wait.h> #include <signal.h> #define MAXLINE 5 #define SA struct sockaddr void str_cli(FILE *fp, int sockfd) { char sendline[MAXLINE], recvline[MAXLINE]; while(fgets(sendline,MAXLINE,fp) != NULL) { writen(sockfd, sendline, strlen(sendline)); if(readline(sockfd, recvline, MAXLINE) == 0) { printf("str_cli:server terminated prematurely!\n"); exit(0); } fputs(recvline, stdout); } } int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if(argc != 2) { printf("useage: tcpcli <IPaddress>"); exit(0); } if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("socket() error!"); exit(0); } bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(9806); if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0) { printf("inet_pton() error!"); exit(0); } if(connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0) { printf("inet_pton() error!"); exit(0); } str_cli(stdin, sockfd); exit(0); }
View Code
1. 当我们杀死子进程,服务器子进程会向客户端发送一个 FIN,而客户端则响应一个 ACK。这是 TCP 连接终止的前一半工作。 2. SIGCHLD 信号发送给服务器进程,并得到正确处理。 3. 客户端上没有发生任何特殊的事情。虽然客户端响应了一个 ACK,然而客户端进程阻塞在 fgets 调用上 (见以上客户端代码),等待从终端接受一行文本。所以没有任何反应。 4. 此时,我们用 netstat 查看套接口状态: (一个netstat 是在杀死子进程之前运行一个是在杀死子进程之后运行的) 上图很完全符合 TCP 连接终止过程的状态变化。因为只完成了 TCP 连接终止的前一半,所以主动关闭的一方(这里是服务器端)处于 FIN_WAIT2 状态,被动关闭一方(这里是客户端)处于 CLOSE_WAIT 状态。 5.我们在客户端上键入 another line ,返回: str_cli: server terminated prematurely 。当我们键入 another line 的时候,客户端调用 writen,客户 TCP 接着把数据发送给服务器。TCP 允许这么做,因为 TCP 收到 FIN 只是表示服务器进程已经关闭了连接端的服务器端,从而不再往其中发送任何数据而已。不代表连接的客户端不能往连接中发送数据。FIN 的接收并没有告知客户端 TCP 服务器端进程已经终止(本例中确实已经终止)。当服务器 TCP 接收到来自客户端的数据时,既然先前打开那个套接口的进程已经终止,于是响应一个 RST。 6.然而客户端进程看不到这个 RST ,因为它在调用 writen 后立即调用 readline,并且由于第 1 步接受的 FIN,所调用的 readline 立即返回 0(表示 EOF),所以执行 if 中代码,输出错误信息:server terminated prematurely(服务器进程过早终止)。然后退出! 7.客户端终止时,它所有打开着的描述字都被关闭。 从上个例子可以看出,服务器端终止的时候,客户端并没有及时的感觉到。问题在于:当 FIN 到达套接口的时候,客户正阻塞在 fgets 调用上。客户实际上在应对两个描述字---套接口描述字和用户数如,它不能单纯的阻塞在两个源中的某个特定源上。这时候,我们希望服务器进程需要一种预先告知内核内核的能力,使得进程指定的一个或多个 I/O 条件就绪(也就是说输入已经准备好读取,或者描述字已经能承接更多的输出),它就通知进程。这个能力称为 I/O 复用,是由 select 和 poll 和更高级的 epoll 支持的。到这里,我们终于搞清楚什么是 I/O 复用了。。。 I/O 复用典型使用下列网络应用场合: 1.当客户处理多个描述字(通常是交互式输入和网络套机口),必须使用 I/O 复用。 2.一个客户同时处理多个套接口是可能的,不过比较少见。 3.如果一个 TCP 服务器端既要处理监听套接字又要处理已连接套接口,一般要用 I/O 复用。 4.如果一个服务器既要处理 TCP,又要处理 UDP,一般使用 I/O 复用。 5.如果一个服务器要处理多个服务或者协议,一般就要用 I/O 复用。 限于篇幅,我们将会单独用一篇博客来写 select、poll 和 epoll 有关的内容。
相关文章推荐
- Unix 网络编程 读书笔记2
- UNIX环境高级编程-读书笔记-网络编程(四)
- unix 网络编程 第五章
- UNIX环境高级编程-读书笔记-网络编程(一)
- Unix 网络编程 读书笔记3
- 《UNIX 网络编程 卷2》读书笔记
- UNIX环境高级编程-读书笔记-网络编程(二)
- UNIX环境高级编程-读书笔记-网络编程(三)
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程1 – Unix环境高级编程7章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程1 – Unix环境高级编程7章读书笔记
- 进程编程1 – Unix环境高级编程7章读书笔记
- 进程编程1 – Unix环境高级编程7章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程1 – Unix环境高级编程7章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记