Linux网络编程--tinyhttpd

CRLF:回车换行(Carriage-Return Line-Feed)。
CR:回车,ASCII 0x0d,转义字符 ,
LF:换行,ASCII 0x0a,转义字符 。
windows下使用 换行,linux使用 换行。
在wireshark抓包中看到,不管是服务器还是客户端,HTTP协议中的换行是0x0d 0x0a,空格是0x20

//客户端步骤
socket()
connect()//阻塞
write()
read()

//服务端步骤
socket()
bind()
listen()
accept()//阻塞
read()
write()

echo服务器重点语句

//客户端
connect()//阻塞
while(fget()) {
    write();
    if(readline()==0)
        error();
    fpus();
}

//服务端
listen(sockfd, 1);//listen

while(1) {
   if((connfd = accept(sockfd, (struct sockaddr*)&cliaddr, &clilen)) < 0) {//阻塞
       if(errno == EINTR) {
           printf("accept EINTR
");
           continue;
       }
       else
           perror("accept error
");
   }
   if((childpid = fork()) == 0) {//进程
       printf("It's child.
");
       close(sockfd);
       str_echo(connfd);
       exit(0);
   }
   close(connfd);
}

引入IO复用

在上述的服务器和客户端实际运行的过程中,当客户端阻塞在fgets,我们终止服务器程序。这时的客户端还是阻塞在fgets,用户不知道服务器那边发生了什么。为了让服务器关闭时,客户端也相应地关闭,我们需要使用IO复用。
在服务器方面,使用select可以将上述例子由多进程改为单进程,减少创建新进程的开销。

select函数

select函数的参数是值-结果类型
跟select函数一起使用的还有:
FD_SET
FD_ZERO
FD_ISSET

//使用select的客户端
FD_ZERO(&rset);//复位描述字集
while(1) {
    FD_SET(fileno(fp), &rset);//置位感兴趣的描述字,一个是标准输入,一个是socket
    FD_SET(sockfd, &rset);
    maxfdp1 = max(fileno(fp), sockfd) + 1;
    select(maxfdp1, &rset, NULL, NULL, NULL);//阻塞。若描述字集里有就绪好的描述字,就往下执行

    if(FD_ISSET(sockfd, &rset)) {//sockfd是否就绪好,若服务器突然关闭,则sockfd会就绪好
        if(readline(sockfd, recvline, MAXLINE) == 0) {
            exit(1);
        }
        fputs(recvline, stdout);
    }

    if(FD_ISSET(fileno(fp), &rset)) {//fp是否就绪好
        if(fgets(sendline, MAXLINE, fp) == NULL)
            return;
        writen(sockfd, sendline, strlen(sendline));
    }
}

//使用select的单进程服务器
fd_set rset;
int client[FD_MAX];//客户端描述字集
FD_ZERO();
FD_SET(linstenfd);
while(1) {
    select(&rset);//阻塞,当客户与服务器建立连接时,监听描述字就绪
    if(FD_ISSET(linstenfd)) {//有新的客户连接进来
        connfd = accept();
        FD_SET(connfd);//把连接描述字加入感兴趣集
    }
    for() {//遍历客户描述字集client,找出非负描述字,即目前保持连接状态的客户
        if(FD_SET(&rset)){//该客户是否就绪可以读取内容了
            read();
            write();
        }
    }
}

poll函数

poll函数的参数不同于select的值-结果类型,poll既有输入参数,也有输出参数。
select需要维护一个client[]数组和select函数所需要的描述字集fd_set,而poll只需要维护一个结构体数组pollfd即可。

//使用poll的单进程服务器
struct pollfd client[FD_MAX];//客户端描述字集
client[0].fd = linstenfd;
client[0].events = POLLRDNORM;
while(1) {
    poll(client);//阻塞,当客户与服务器建立连接时,监听描述字就绪
    if(client[0].revents & POLLRDNORM) {//有新的客户连接进来
        connfd = accept();
        client[0].fd = connfd;//把连接描述字加入感兴趣集
        client[0].events = POLLRDNORM;//设置感兴趣的事件
    }
    for() {//遍历客户描述字集client,找出非负描述字,即目前保持连接状态的客户
        if(client[i].revents & (POLLRDNORM | POLLRDERR){//该客户是否就绪可以读取内容了
            read();
            write();
        }
    }
}
原文地址:https://www.cnblogs.com/season-peng/p/6759522.html