您的位置:首页 > 理论基础 > 计算机网络

UNIX 网络编程第五章读书笔记

2015-01-12 10:25 218 查看
刚看完 UNIX 第五章内容,我想按照自己的方式将自己获得的知识梳理一遍,以便日后查看!先贴上一段简单的 TCP 服务器端代码:

#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 有关的内容。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: