csapp Chapter11 网络编程

主要记录一下socket相关内容,网络协议不多做描述。

下图是基本的步骤图

getaddrinfo

一句话:获取套接字地址,从url,也就是dns获取真正的ip地址,返回的结构中是一个链表,有多个sockaddr(也就是dns会给我们返回多个ip地址),我们通过遍历来确定哪一个可用,如果都没有用,就error了。

注意点:

P.S.:csapp中有些函数讲的是全小写,使用时首字母却大写了,例如getaddrinfo,使用时却是Getaddrinfo,其实是做了一个封装。例如

void Getaddrinfo(const char *node, const char *service, 
                 const struct addrinfo *hints, struct addrinfo **res)
{
    int rc;
	//加了一个错误处理,其他都差不多
    if ((rc = getaddrinfo(node, service, hints, res)) != 0) 
        gai_error(rc, "Getaddrinfo error");
}

client创建

getaddrinfo -> socket -> connect ** open_clientfd

open_clientfd是用来替换上面几个步骤的。

server创建

getaddrinfo -> socket -> bind -> listen -> accept

注意一个问题,我们在getaddrinfo的时候hints.ai_flags设置了参数AI_PASSIVE,这是说我们host参数设置为0,也就是发送到本主机的所有ip地址我们都会接受。host值是通配符

也就是说你舍友和你在同一个局域网内,是可以访问到你的。如果你设置了,也就是指定了ip,只有这个满足的ip才可以访问你,可以使用通配符。

这样的话你就可以使用局域网利用http协议和你室友传输文件了。

HTTP

http请求注意格式,头部,实际内容。其实我们往socket里面写的就是字符串,但是是按照http的格式来写的,开头怎么样,头部怎么样,都是约定好的。所以我们解析http请求(注意uri是http请求内的一部分字段),做出不同的响应

静态内容响应

根据uri中的文件位置,我们返回文件。(如果是直接/,我们手动定位到home.html文件。),那么文件怎么返回/发送呢

 /* Send response body to client */
    //打开文件,readonly
srcfd = Open(filename, O_RDONLY, 0);                       

//原做法
//虚拟内存内容相关,将文件/利用文件描述符 映射到一块虚拟空间,srcp。
srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); 
//关闭文件描述符号,已经有了虚拟内存空间上面的映射,不需要再read->write,防止内存泄露
Close(srcfd);                                               
//写入coonfd,send response body to client
Rio_writen(fd, srcp, filesize);                             
//释放虚拟内存中的空间,防止内存泄露
Munmap(srcp, filesize);

//作业11-9 做法
srcp = (char *) malloc(filesize * sizeof(char));
Rio_t rio;
Rio_readinitb(&rio, srcfd);
Rio_readnb(&rio, srcp, filesize);
//上面三步步骤可以用Rio_readn(srcfd, srcp, filesize)无缓冲函数; 直接代替.题目要求这种。
Rio_writen(fd, srcp, filesize);
free(srcp);

所以其实还是打开文件,写入另一个文件描述符,就是发送了。

有一个问题:为什么不直接用srcfd,先读取srcfd,到buf,在写入coonfd中。

作业11-9就让你修改这种方式,直接用malloc和rio_readn,rio_writen写入fd

还记得我们上面说可以和室友传输文件吗?但是我们首先要实现支持MPG等视频文件的web

作业11-7就是让我们手写支持MPG视频文件。

我们可以get_filetype加入一个mp4->viedo/mpge4,但是返回的直接是文件,还没有名字。

动态内容响应 cgi

parse_uri中会将uri中的参数,也就是调用的cgi程序的位置表明出来。

 { /* Dynamic content */    
        //返回 '?'在uri中出现的index
        ptr = index(uri, '?'); 
        if (ptr)
        {
            strcpy(cgiargs, ptr + 1); //把?后面的参数copy给cgiargs
            *ptr = '\0';
        }

        //不存在?的情况
        else
            strcpy(cgiargs, ""); 
        //下面是把filename变成了.uri(./cgi-bin/adder),也就是linux下的相对文件名字
        strcpy(filename, ".");   
        strcat(filename, uri);   
        printf("\ndynamic filename=%s uri=%s\n", filename, uri);
        return 0;
    }

所以我们在cgi中调用如下

    if (Fork() == 0)
    { /* Child */ //line:netp:servedynamic:fork
        /* Real server would set all CGI vars here */
        setenv("QUERY_STRING", cgiargs, 1);                         
        Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client */    
        Execve(filename, emptylist, environ); /* Run CGI program */ /
    }
    //等待子进程die,完成后函数才结束
    Wait(NULL); /* Parent waits for and reaps child */ 

有一个问题,上面是直接wait,等待子进程死亡,在结束这个函数,联想一下,异常控制流那一章节,直接处理SIGCHLD信号,回收子进程的操作

也就是作业11-8,怎么操作呢?未定,怎么解决呢?

void ch_signal(int sig){
    pid_t pid;
    //子进程中都还没有终止,就立即返回0.否则返回终止子进程号,这样的话,在等待子进程的时候,我们还是可以做自己的事情的。
    while( (pid = waitpid(-1, NULL, WHOHANG)) > 0 )
        ;
}
//main函数中,加一个
signal(SIGCHILD, ch_signal);

注意几点

可以去看我在tiny文件上做的笔记。tiny.c