C++ 주니어 프로젝트 웹서버 프로젝트 프로세스 소개(2)

I. 소개

C++ 웹서버 프로젝트는 네트워크 프로그래밍을 배운 후 온라인 강좌의 내용을 토대로 진행한 예비 네트워크 프로그래밍 프로젝트입니다.

이 프로젝트의 효과는 브라우저에 네트워크 IP 주소와 포트를 입력한 다음 해당 파일 디렉터리를 열 수 있다는 것입니다.

효과는 다음과 같습니다.

폴더를 열고 디렉터리를 클릭하여 해당 폴더를 열 수도 있습니다.

이것은 간단한 웹서버 기능입니다. 나중에 코드를 수정하여 간단한 프런트 엔드 대화형 인터페이스를 만드는 등 더 많은 플레이 방법을 얻을 수도 있습니다.

2. 코드 개발 과정

이 프로젝트에서 제가 주로 사용하는 구현 방법은 epoll을 사용하는 것인데, epoll은 네트워크 서버 프로그래밍을 구현할 수 있으며 다음과 같은 장점이 있습니다.

1. 효율성: epoll은 이벤트 중심 모델을 사용하며 IO 이벤트가 발생할 때만 활성화되므로 폴링 비용을 방지하고 서버 효율성을 향상시킵니다.

2. 확장성:epoll은 많은 수의 동시 연결을 지원하고 수천 개의 연결을 처리할 수 있으며 연결 수가 증가하면 성능이 천천히 저하됩니다.

3. 높은 신뢰성:epoll은 데이터를 읽거나 쓸 수 있을 때만 애플리케이션에 알리는 에지 트리거 모드를 사용하여 네트워크 정체 및 기타 이유를 방지합니다. 오탐, 서버 개선 신뢰할 수 있음.

4. 유연성:epoll은 읽기, 쓰기, 예외 등을 포함한 다양한 이벤트 유형을 지원하며 다양한 요구에 따라 맞춤설정할 수 있습니다.

5. 크로스 플랫폼:epoll은 Linux 시스템 커널에서 제공하는 메커니즘이며 크로스 플랫폼 개발을 달성하기 위해 다양한 Linux 시스템에서 사용할 수 있습니다.

다음은 epoll 개발 웹서버 프로젝트 흐름도이다. (특정 기능 구현 제외)

 

int main()
{
	 //若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, 
    //则web服务器就会收到SIGPIPE信号
    struct sigaction act;
    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGPIPE, &act, NULL);
    
	int lfd = tcp4bind(9999,NULL);
	Listen(lfd,128);
    int epfd = epoll_create(1024);
    if(epfd < 0)
    {
    	perror("epoll_create error");
    	close(lfd);
    	return -1;
	}
	struct epoll_event ev;
	struct epoll_event events[1024];
    ev.data.fd = lfd;
    ev.events = EPOLLIN;
    
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
    
    int nready;
    int i;
    int cfd;
    int sockfd;
    while(1)
    {
    	nready = epoll_wait(epfd,events,1024,-1);
    	if(nready < 0)
    	{
    		perror("epoll wait error");
    		if(nready == EINTR)
    		{
    			continue;
			}
			break;
		     
		}
		for(i = 0;i < nready;i ++)
		{
		    sockfd = events[i].data.fd;
		    if(sockfd == lfd)
		    {
		    	cfd = Accept(lfd,NULL,NULL);
		    	//设置cfd为非阻塞,防止其在 while((n = Readline(cfd,buf,sizeof(buf))) > 0)处阻塞 
		    	//设置cfd为非阻塞
                int flag = fcntl(cfd, F_GETFL);
                flag |= O_NONBLOCK;
                fcntl(cfd, F_SETFL, flag);
                
		    	ev.data.fd = cfd;
		    	ev.events = EPOLLIN;
		    	epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
		    	
			}
			else {
				http_request(sockfd,epfd);
			}
		}
	}
}

위의 tcp4bind는 캡슐화된 함수이며, 구체적인 구현을 보고 싶다면 기사 마지막 부분에 있는 Wrap.c 전체 코드를 살펴보세요.

암호. Listen 및 Accept와 마찬가지로 캡슐화된 기능이기도 합니다.

3. http_request 함수

이 기능은 파일 열기 및 폴더 열기 기능의 특정 구현입니다.

클라이언트가 HTTP 요청을 시작하면 서버는 http_request 함수를 호출하여 요청을 처리합니다. 함수 흐름은 다음과 같습니다.

함수 흐름은 다음과 같습니다.

  1. 요청 라인 데이터를 읽고 요청할 리소스 파일 이름을 분석합니다.
  2. 요청한 파일이 존재하는지 확인하고 존재하지 않는 경우 404 NOT FOUND 헤더 정보와 error.html 파일의 내용을 전송한다.
  3. 파일이 존재하면 파일 형식을 판단하고, 일반 파일이면 200 OK 헤더 정보와 파일 내용을 보내고, 디렉터리 파일이면 200 OK 헤더 정보와 디렉터리 파일 목록의 html 내용을 보낸다. 정보.
  4. 데이터를 보낸 후 연결을 닫고 epoll 트리에서 파일 설명자를 삭제합니다.

암호

int http_request(int cfd, int epfd)
{
    int n;
    char buf[1024];
    //读取请求行数据, 分析出要请求的资源文件名
    memset(buf, 0x00, sizeof(buf));
    n = Readline(cfd, buf, sizeof(buf));
    if(n<=0)
    {
        //printf("read error or client closed, n==[%d]\n", n);
        //关闭连接
        close(cfd);
         
        //将文件描述符从epoll树上删除
        epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
        return -1;  
    }
    printf("buf==[%s]\n", buf);
    //GET /hanzi.c HTTP/1.1
    char reqType[16] = {0};
    char fileName[255] = {0};
    char protocal[16] = {0};
    sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);
    //printf("[%s]\n", reqType);
    printf("--[%s]--\n", fileName);
    //printf("[%s]\n", protocal);
     
    char *pFile = fileName;
    if(strlen(fileName)<=1)
    {
        strcpy(pFile, "./");
    }
    else
    {
        pFile = fileName+1;
    }
     
    //转换汉字编码
    strdecode(pFile, pFile);
    printf("[%s]\n", pFile);
     
    //循环读取完剩余的数据,避免产生粘包
    while((n=Readline(cfd, buf, sizeof(buf)))>0);
     
    //判断文件是否存在
    struct stat st;
    if(stat(pFile, &st)<0)
    {
        printf("file not exist\n");
         
        //发送头部信息
        send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
         
        //发送文件内容
        send_file(cfd, "error.html");   
    }
    else //若文件存在
    {
        //判断文件类型
        //普通文件
        if(S_ISREG(st.st_mode))  //man 2 stat查询,S_ISREG表示普通文件        
        {
            printf("file exist\n");
            //发送头部信息
            send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);
             
            //发送文件内容
            send_file(cfd, pFile);
        }
        //目录文件
        else if(S_ISDIR(st.st_mode))
        {
            printf("目录文件\n");
             
            char buffer[1024];
            //发送头部信息
            send_header(cfd, "200", "OK", get_mime_type(".html"), 0);   
             
            //发送html文件头部
            send_file(cfd, "html/dir_header.html"); 
             
            //文件列表信息
            struct dirent **namelist;
            int num;
 
            num = scandir(pFile, &namelist, NULL, alphasort);
            if (num < 0)
            {
               perror("scandir");
               close(cfd);
               epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
               return -1;
                
            }
            else
            {
               while (num--) 
               {
                   printf("%s\n", namelist[num]->d_name);
                   memset(buffer, 0x00, sizeof(buffer));
                   if(namelist[num]->d_type==DT_DIR)
                   {
                        sprintf(buffer, "<li><a href=%s/>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
                   }
                   else
                   {
                        sprintf(buffer, "<li><a href=%s>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
                   }
                   free(namelist[num]);
                   Write(cfd, buffer, strlen(buffer));
               }
               free(namelist);
            }
            //发送html尾部
            sleep(10);
            send_file(cfd, "html/dir_tail.html");       
        }
    }
     
    return 0;
}

4. 세부사항

1.cfd는 비차단으로 설정되어야 합니다.

//设置cfd为非阻塞
 int flag = fcntl(cfd, F_GETFL);
 flag |= O_NONBLOCK;
 fcntl(cfd, F_SETFL, flag);

이 코드의 목적은 파일 설명자 cfd를 비차단 모드로 설정하는 것입니다.

먼저 fcntl 함수와 F_GETFL 명령을 사용하여 cfd의 파일 상태 플래그를 얻습니다. 이러한 플래그에는 파일의 읽기 및 쓰기 모드, 차단 여부 및 기타 정보가 포함됩니다. 획득한 플래그는 플래그 변수에 저장됩니다.

다음으로 비트별 OR 연산자(|)를 사용하여 O_NONBLOCK 플래그(비차단 모드 표시)를 플래그 변수에 추가합니다. 이는 파일 설명자의 상태 플래그에 O_NONBLOCK 플래그를 추가하여 파일 설명자가 비차단 모드로 설정되었음을 나타냅니다.

마지막으로 fcntl 함수와 F_SETFL 명령을 사용하여 수정된 플래그 플래그를 파일 설명자 cfd로 다시 설정하여 cfd를 비차단 모드로 설정합니다.

따라서 이 코드가 하는 일은 파일 설명자 cfd를 비차단 모드로 설정하여 I/O 작업을 수행할 때 읽을 데이터가 없거나 쓸 공간이 부족한 경우 프로세스 실행이 차단되지 않도록 하는 것입니다. , 그러나 프로세스가 계속해서 다른 작업을 수행할 수 있도록 오류나 특수 상태를 즉시 반환합니다.

2. 환경 작업 디렉터리를 변경하려면

전제는 홈 디렉토리에 웹 경로를 설정하는 것입니다.

char path[255] = {0};
sprintf(path, "%s/%s", getenv("HOME"), "webpath");
chdir(path);

이 코드가 하는 일은 경로를 구성하고 현재 작업 디렉터리를 해당 경로로 전환하는 것입니다.

이 코드를 단계별로 설명하겠습니다.

문자 경로[255] = {0};

- 길이가 255인 문자 배열 경로를 정의하고 0으로 초기화합니다. 이 배열은 구성된 경로를 저장하는 데 사용됩니다.

sprintf(경로, "%s/%s", getenv("HOME"), "웹 경로");

- sprintf 함수를 이용하여 $HOME/webpath 형태로 경로를 구성합니다. getenv("HOME")은 현재 사용자의 홈 디렉터리 경로를 얻은 다음 이를 "webpath"와 연결하여 전체 경로를 얻는 데 사용됩니다.

chdir(경로);

- chdir 함수를 사용하여 현재 작업 디렉터리를 구성된 경로로 전환합니다. 이런 방식으로 프로그램의 현재 작업 디렉터리는 $HOME/webpath가 됩니다.

종합하면, 이 코드의 기능은 경로를 구성하고 현재 작업 디렉터리를 해당 경로로 전환하는 것입니다. 일반적으로 이러한 작업은 파일에 올바르게 액세스하고 처리할 수 있도록 프로그램이 올바른 디렉터리에서 실행되는지 확인하는 데 사용됩니다.

3.fileName 읽기 위치 +1, "/" 건너뛰기

그렇지 않으면 이렇겠죠

 

 

4.scandir 기능

scandir 함수는 지정된 디렉터리를 검색하여 해당 디렉터리에 있는 파일 목록을 반환하는 함수입니다. 이는 디렉토리의 항목에 대한 정보를 각각 포함하는 디렉토리 구조에 대한 포인터 배열을 반환합니다.

다음은 scandir 함수의 프로토타입입니다.

int scandir(const char *dirp, struct dirent ***namelist,
int (*filter)(const struct dirent *),
int (*compar)(const struct dirent **, const struct dirent **));

dirp: 스캔할 디렉터리의 경로 이름입니다.

namelist: 각 디렉토리 항목에 대한 포인터를 저장하는 데 사용되는 포인터 배열에 대한 포인터입니다.

필터: 반환되어야 하는 디렉터리 항목을 결정하는 데 사용되는 선택적 필터 함수입니다. 필터링이 필요하지 않은 경우 NULL로 설정할 수 있습니다.

비교: 반환된 디렉터리 항목을 정렬하는 데 사용되는 선택적 비교 함수입니다. 정렬이 필요하지 않은 경우 NULL로 설정할 수 있습니다.

다음은 scandir 함수를 사용하여 디렉터리에 있는 파일을 나열하는 방법을 보여주는 간단한 예입니다.

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>

int main() {
    struct dirent **namelist;
    int n;

    n = scandir(".", &namelist, NULL, alphasort);
    if (n < 0) {
        perror("scandir");
        exit(EXIT_FAILURE);
    } else {
        for (int i = 0; i < n; i++) {
            printf("%s\n", namelist[i]->d_name);
            free(namelist[i]);
        }
        free(namelist);
    }

    return 0;
}

이 예에서 scandir 함수는 현재 디렉터리를 검색하고 alphasort 함수를 사용하여 반환된 파일 목록을 정렬합니다. 그런 다음 목록을 반복하고 각 파일의 이름을 인쇄합니다.

5. 기본 경로 추가

http://192.168.44.3:9999 기본 홈 디렉터리 아래의 폴더 내용에 액세스할 수 있습니다

char *pFile = fileName;
if(strlen(fileName)<=1)    //添加默认为主目录下面 
{
     strcpy(pFile, "./");
}
else
{
     pFile = fileName+1;
}

char *pFile fileName = NULL은 이와 같이 설정할 수 없습니다. 그렇지 않으면 segfault가 발생합니다.

 

6. 한자와 마주치는 문제 해결

웹서버 코드에서 함수가 호출됩니다.

strdecode(pFile, pFile); 이 함수는 pub.c에 있으며, 브라우저에 다시 쓸 때 영숫자 및 /_.-~ 이외의 문자를 이스케이프하는 데 사용되는 "인코딩"을 작성합니다.​ 

//strencode(encoded_name, sizeof(encoded_name), name);
void strencode(char* to, size_t tosize, const char* from)
{
    int tolen;
 
    for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {
        if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) {
            *to = *from;
            ++to;
            ++tolen;
        } else {
            sprintf(to, "%%%02x", (int) *from & 0xff);
            to += 3;
            tolen += 3;
        }
    }
    *to = '\0';
}

 

5. SIGPIPE 신호 처리 방법을 무시하도록 설정

SIGPIPE 신호의 처리 방법은 무시로 설정됩니다. 즉, 프로세스가 SIGPIPE 신호를 수신하면 아무런 처리도 수행되지 않습니다. 이는 네트워크 프로그래밍에서 SIGPIPE 오류를 방지하기 위해 자주 사용됩니다. 프로세스가 닫힌 소켓에 데이터를 보낼 때 시스템은 SIGPIPE 신호를 프로세스에 보내고, 신호가 처리되지 않으면 프로세스가 종료되기 때문입니다. SIGPIPE 신호 처리를 무시하도록 설정하여 프로세스 종료를 방지할 수 있습니다.

//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, 
//则web服务器就会收到SIGPIPE信号
struct sigaction act;
act.sa_handler = SIG_IGN;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGPIPE, &act, NULL);

5. 완전한 코드

webserver.c

//web服务端程序--使用epoll模型
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>
 
#include "pub.h"
#include "wrap.h"
 
int http_request(int cfd, int epfd);
 
int main()
{
    //若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, 
    //则web服务器就会收到SIGPIPE信号
    struct sigaction act;
    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGPIPE, &act, NULL);
     
    //改变当前进程的工作目录
    char path[255] = {0};
    sprintf(path, "%s/%s", getenv("HOME"), "webpath");
    chdir(path);
     
    //创建socket--设置端口复用---bind
    int lfd = tcp4bind(9999, NULL);
     
    //设置监听 
    Listen(lfd, 128);
 
    //创建epoll树
    int epfd = epoll_create(1024);
    if(epfd<0)
    {
        perror("epoll_create error");
        close(lfd);
        return -1;
    }
     
    //将监听文件描述符lfd上树
    struct epoll_event ev;
    ev.data.fd = lfd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
     
    int i;
    int cfd;
    int nready;
    int sockfd;
    struct epoll_event events[1024];
    while(1)
    {
        //等待事件发生
        nready = epoll_wait(epfd, events, 1024, -1);
        if(nready<0)
        {
            if(errno==EINTR)
            {
                continue;
            }
            break;
        }
         
        for(i=0; i<nready; i++)
        {
            sockfd = events[i].data.fd;
            //有客户端连接请求
            if(sockfd==lfd)
            {
                //接受新的客户端连接
                cfd = Accept(lfd, NULL, NULL);
                 
                //设置cfd为非阻塞
                int flag = fcntl(cfd, F_GETFL);
                flag |= O_NONBLOCK;
                fcntl(cfd, F_SETFL, flag);
                 
                //将新的cfd上树
                ev.data.fd = cfd;
                ev.events = EPOLLIN;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
            }
            else
            {
                //有客户端数据发来
                http_request(sockfd, epfd);
            }           
        }       
    }
}
 
int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{
    char buf[1024] = {0};
    sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);
    sprintf(buf+strlen(buf), "Content-Type:%s\r\n", fileType);
    if(len>0)
    {
        sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);
    }
    strcat(buf, "\r\n");
    Write(cfd, buf, strlen(buf));
    return 0;
}
 
int send_file(int cfd, char *fileName)
{
    //打开文件
    int fd = open(fileName, O_RDONLY);
    if(fd<0)
    {
        perror("open error");
        return -1;
    }
     
    //循环读文件, 然后发送
    int n;
    char buf[1024];
    while(1)
    {
        memset(buf, 0x00, sizeof(buf));
        n = read(fd, buf, sizeof(buf));
        if(n<=0)
        {
            break;
        }
        else
        {
            Write(cfd, buf, n);
        }
    }
}
 
int http_request(int cfd, int epfd)
{
    int n;
    char buf[1024];
    //读取请求行数据, 分析出要请求的资源文件名
    memset(buf, 0x00, sizeof(buf));
    n = Readline(cfd, buf, sizeof(buf));
    if(n<=0)
    {
        //printf("read error or client closed, n==[%d]\n", n);
        //关闭连接
        close(cfd);
         
        //将文件描述符从epoll树上删除
        epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
        return -1;  
    }
    printf("buf==[%s]\n", buf);
    //GET /hanzi.c HTTP/1.1
    char reqType[16] = {0};
    char fileName[255] = {0};
    char protocal[16] = {0};
    sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);
    //printf("[%s]\n", reqType);
    printf("--[%s]--\n", fileName);
    //printf("[%s]\n", protocal);
     
    char *pFile = fileName;
    if(strlen(fileName)<=1)
    {
        strcpy(pFile, "./");
    }
    else
    {
        pFile = fileName+1;
    }
     
    //转换汉字编码
    strdecode(pFile, pFile);
    printf("[%s]\n", pFile);
     
    //循环读取完剩余的数据,避免产生粘包
    while((n=Readline(cfd, buf, sizeof(buf)))>0);
     
    //判断文件是否存在
    struct stat st;
    if(stat(pFile, &st)<0)
    {
        printf("file not exist\n");
         
        //发送头部信息
        send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
         
        //发送文件内容
        send_file(cfd, "error.html");   
    }
    else //若文件存在
    {
        //判断文件类型
        //普通文件
        if(S_ISREG(st.st_mode))  //man 2 stat查询,S_ISREG表示普通文件        
        {
            printf("file exist\n");
            //发送头部信息
            send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);
             
            //发送文件内容
            send_file(cfd, pFile);
        }
        //目录文件
        else if(S_ISDIR(st.st_mode))
        {
            printf("目录文件\n");
             
            char buffer[1024];
            //发送头部信息
            send_header(cfd, "200", "OK", get_mime_type(".html"), 0);   
             
            //发送html文件头部
            send_file(cfd, "html/dir_header.html"); 
             
            //文件列表信息
            struct dirent **namelist;
            int num;
 
            num = scandir(pFile, &namelist, NULL, alphasort);
            if (num < 0)
            {
               perror("scandir");
               close(cfd);
               epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
               return -1;
                
            }
            else
            {
               while (num--) 
               {
                   printf("%s\n", namelist[num]->d_name);
                   memset(buffer, 0x00, sizeof(buffer));
                   if(namelist[num]->d_type==DT_DIR)
                   {
                        sprintf(buffer, "<li><a href=%s/>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
                   }
                   else
                   {
                        sprintf(buffer, "<li><a href=%s>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
                   }
                   free(namelist[num]);
                   Write(cfd, buffer, strlen(buffer));
               }
               free(namelist);
            }
            //发送html尾部
            sleep(10);
            send_file(cfd, "html/dir_tail.html");       
        }
    }
     
    return 0;
}

pub.c

#include "pub.h"
//通过文件名字获得文件类型
char *get_mime_type(char *name)
{
    char* dot;
 
    dot = strrchr(name, '.');   //自右向左查找‘.’字符;如不存在返回NULL
    /*
     *charset=iso-8859-1    西欧的编码,说明网站采用的编码是英文;
     *charset=gb2312        说明网站采用的编码是简体中文;
     *charset=utf-8         代表世界通用的语言编码;
     *                      可以用到中文、韩文、日文等世界上所有语言编码上
     *charset=euc-kr        说明网站采用的编码是韩文;
     *charset=big5          说明网站采用的编码是繁体中文;
     *
     *以下是依据传递进来的文件名,使用后缀判断是何种文件类型
     *将对应的文件类型按照http定义的关键字发送回去
     */
    if (dot == (char*)0)
        return "text/plain; charset=utf-8";
    if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
        return "text/html; charset=utf-8";
    if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
        return "image/jpeg";
    if (strcmp(dot, ".gif") == 0)
        return "image/gif";
    if (strcmp(dot, ".png") == 0)
        return "image/png";
    if (strcmp(dot, ".css") == 0)
        return "text/css";
    if (strcmp(dot, ".au") == 0)
        return "audio/basic";
    if (strcmp( dot, ".wav") == 0)
        return "audio/wav";
    if (strcmp(dot, ".avi") == 0)
        return "video/x-msvideo";
    if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
        return "video/quicktime";
    if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
        return "video/mpeg";
    if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
        return "model/vrml";
    if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
        return "audio/midi";
    if (strcmp(dot, ".mp3") == 0)
        return "audio/mpeg";
    if (strcmp(dot, ".ogg") == 0)
        return "application/ogg";
    if (strcmp(dot, ".pac") == 0)
        return "application/x-ns-proxy-autoconfig";
 
    return "text/plain; charset=utf-8";
}
/**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,
 * carriage return, or a CRLF combination.  Terminates the string read
 * with a null character.  If no newline indicator is found before the
 * end of the buffer, the string is terminated with a null.  If any of
 * the above three line terminators is read, the last character of the
 * string will be a linefeed and the string will be terminated with a
 * null character.
 * Parameters: the socket descriptor
 *             the buffer to save the data in
 *             the size of the buffer
 * Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
//获得一行数据,每行以\r\n作为结束标记
int get_line(int sock, char *buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;
 
    while ((i < size - 1) && (c != '\n'))
    {
        n = recv(sock, &c, 1, 0);
        /* DEBUG printf("%02X\n", c); */
        if (n > 0)
        {
            if (c == '\r')
            {
                n = recv(sock, &c, 1, MSG_PEEK);//MSG_PEEK 从缓冲区读数据,但是数据不从缓冲区清除
                /* DEBUG printf("%02X\n", c); */
                if ((n > 0) && (c == '\n'))
                    recv(sock, &c, 1, 0);
                else
                    c = '\n';
            }
            buf[i] = c;
            i++;
        }
        else
            c = '\n';
    }
    buf[i] = '\0';
 
    return(i);
}
 
//下面的函数第二天使用
/*
 * 这里的内容是处理%20之类的东西!是"解码"过程。
 * %20 URL编码中的‘ ’(space)
 * %21 '!' %22 '"' %23 '#' %24 '$'
 * %25 '%' %26 '&' %27 ''' %28 '('......
 * 相关知识html中的‘ ’(space)是 
 */
void strdecode(char *to, char *from)
{
    for ( ; *from != '\0'; ++to, ++from) {
 
        if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) { //依次判断from中 %20 三个字符
 
            *to = hexit(from[1])*16 + hexit(from[2]);//字符串E8变成了真正的16进制的E8
            from += 2;                      //移过已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符
        } else
            *to = *from;
    }
    *to = '\0';
}
 
//16进制数转化为10进制, return 0不会出现
int hexit(char c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
 
    return 0;
}
 
//"编码",用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。
//strencode(encoded_name, sizeof(encoded_name), name);
void strencode(char* to, size_t tosize, const char* from)
{
    int tolen;
 
    for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {
        if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) {
            *to = *from;
            ++to;
            ++tolen;
        } else {
            sprintf(to, "%%%02x", (int) *from & 0xff);
            to += 3;
            tolen += 3;
        }
    }
    *to = '\0';
}

랩.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
//绑定错误显示和退出
void perr_exit(const char *s)
{
    perror(s);
    exit(-1);
}
 
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
    int n;
 
again:
    if ((n = accept(fd, sa, salenptr)) < 0) {
        if ((errno == ECONNABORTED) || (errno == EINTR))//ECONNABORTED 代表连接失败 ETINTR 代表被信号打断
            goto again;
        else
            perr_exit("accept error");
    }
    return n;
}
 
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;
 
    if ((n = bind(fd, sa, salen)) < 0)
        perr_exit("bind error");
 
    return n;
}
 
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;
 
    if ((n = connect(fd, sa, salen)) < 0)
        perr_exit("connect error");
 
    return n;
}
 
int Listen(int fd, int backlog)
{
    int n;
 
    if ((n = listen(fd, backlog)) < 0)
        perr_exit("listen error");
 
    return n;
}
 
int Socket(int family, int type, int protocol)
{
    int n;
 
    if ((n = socket(family, type, protocol)) < 0)
        perr_exit("socket error");
 
    return n;
}
 
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
    ssize_t n;
 
again:
    if ( (n = read(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)//被信号打断应该继续读
            goto again;
        else
            return -1;
    }
    return n;
}
 
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
    ssize_t n;
 
again:
    if ( (n = write(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)
            goto again;
        else
            return -1;
    }
    return n;
}
 
int Close(int fd)
{
    int n;
    if ((n = close(fd)) == -1)
        perr_exit("close error");
 
    return n;
}
 
/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{
    size_t  nleft;              //usigned int 剩余未读取的字节数
    ssize_t nread;              //int 实际读到的字节数
    char   *ptr;
 
    ptr = vptr;
    nleft = n;
 
    while (nleft > 0) {
        if ((nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR)
                nread = 0;
            else
                return -1;
        } else if (nread == 0)
            break;
 
        nleft -= nread;//防止一次数据没有读完
        ptr += nread;//指针需要向后移动
    }
    return n - nleft;
}
 
ssize_t Writen(int fd, const void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;
 
    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
            if (nwritten < 0 && errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }
 
        nleft -= nwritten;
        ptr += nwritten;
    }
    return n;
}
 
static ssize_t my_read(int fd, char *ptr)
{
    static int read_cnt;
    static char *read_ptr;
    static char read_buf[100];//定义了100的缓冲区
 
    if (read_cnt <= 0) {
again:
        //使用缓冲区可以避免多次从底层缓冲读取数据--为了提高效率
        if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
            if (errno == EINTR)
                goto again;
            return -1;
        } else if (read_cnt == 0)
            return 0;
        read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;//从缓冲区取数据
 
    return 1;
}
//读取一行
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t n, rc;
    char    c, *ptr;
 
    ptr = vptr;
    for (n = 1; n < maxlen; n++) {
        if ( (rc = my_read(fd, &c)) == 1) {
            *ptr++ = c;
            if (c  == '\n')//代表任务完成
                break;
        } else if (rc == 0) {//对端关闭
            *ptr = 0;//0 = '\0'
            return n - 1;
        } else
            return -1;
    }
    *ptr  = 0;
 
    return n;
}
 
int tcp4bind(short port,const char *IP)
{
    struct sockaddr_in serv_addr;
    int lfd = Socket(AF_INET,SOCK_STREAM,0);
    bzero(&serv_addr,sizeof(serv_addr));//清空serv_addr地址 对比 memset()
    if(IP == NULL){
        //如果这样使用 0.0.0.0,任意ip将可以连接
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    }else{
        if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){
            perror(IP);//转换失败
            exit(1);
        }
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port   = htons(port);
    int opt = 1;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    return lfd;
}

전체 프로젝트 패키지는 이전 기사에서 확인할 수 있으며 직접 선택할 수도 있습니다. 도와 주셔서 감사합니다

추천

출처blog.csdn.net/qq_64691289/article/details/134606761