Tinyhttpd项目学习及httpd学习

2023-10-30

在参考网络有关Tinyhttpd的内容后,我打算写下自己的学习过程与自己的理解,我会在结尾附上参考的链接。我把代码放在了gitee:Tinyhttpd学习

1.Tinyhttpd是一个轻量级的HTTP服务器。

2.学习该项目可以学习web服务器在收到静态页面请求和CGI请求的一些基本的处理逻辑。

在开始这一切之前,我并不了解http,所以我先从分析程序开始,到了最后面再写http相关。

在源码httpd.c中,主要有以下函数

void accept_request(void *);                //线程函数

void bad_request(int);                //出错

void cat(int, FILE *);                //send 文件描述符中的数据。

void cannot_execute(int);                //出错

void error_die(const char *);                //出错

void execute_cgi(int, const char *, const char *, const char *);        //执行CGI脚本的函数

int get_line(int, char *, int);                //读取socket文件描述符中的数据

void headers(int, const char *);        //send 相关数据

void not_found(int);                //出错

void serve_file(int, const char *);                //send 普通文件

int startup(u_short *);        //该函数主要创建socket bind,并且进行监听,并返回socket 函数的返回值(文件描述符)。该函数的传参是u_short类型的端口号。

void unimplemented(int);                //出错

 下面的图片是源程序的结构

 该图片来源与网络,但我根据我的阅读,添加了些许内容,详情请看参考链接。

当执行CGI脚本时候程序的运行结构(图片来源与网络): 

以下是源程序,我对代码进行了部分的注释: 

/* J. David's webserver */
/* This is a simple webserver.
 * Created November 1999 by J. David Blackstone.
 * CSE 4344 (Network concepts), Prof. Zeigler
 * University of Texas at Arlington
 */
/* This program compiles for Sparc Solaris 2.6.
 * To compile for Linux:
 *  1) Comment out the #include <pthread.h> line.
 *  2) Comment out the line that defines the variable newthread.
 *  3) Comment out the two lines that run pthread_create().
 *  4) Uncomment the line that runs accept_request().
 *  5) Remove -lsocket from the Makefile.
 */
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdint.h>

#define ISspace(x) isspace((int)(x))

#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
#define STDIN   0
#define STDOUT  1
#define STDERR  2

void accept_request(void *);
void bad_request(int);
void cat(int, FILE *);
void cannot_execute(int);
void error_die(const char *);
void execute_cgi(int, const char *, const char *, const char *);
int get_line(int, char *, int);
void headers(int, const char *);
void not_found(int);
void serve_file(int, const char *);
int startup(u_short *);
void unimplemented(int);

/**********************************************************************/
/* A request has caused a call to accept() on the server port to
 * return.  Process the request appropriately.
 * Parameters: the socket connected to the client */
/**********************************************************************/
void accept_request(void *arg)
{
    int client = (intptr_t)arg;
    char buf[1024];
    size_t numchars;
    char method[255];
    char url[255];
    char path[512];
    size_t i, j;
    struct stat st;
    int cgi = 0;      /* becomes true if server decides this is a CGI
                       * program */
    char *query_string = NULL;
    //从client 中读取内容到buf,numchars是字符串中的字符个数。
    numchars = get_line(client, buf, sizeof(buf));
    i = 0; j = 0;
    while (!ISspace(buf[i]) && (i < sizeof(method) - 1))
    {
        //将buf的内容读取到method,最多255个
        method[i] = buf[i];
        i++;
    }
    
    j=i;

    method[i] = '\0';
    printf("method==%s\n",method);
    //比较两个字符串,忽略大小写
    if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
    {
        //if不是get和post,

        unimplemented(client);
        return;
    }
    //如果是POST请求,cgi=1
    if (strcasecmp(method, "POST") == 0)
        cgi = 1;

    i = 0;
    //这里的buf[j]是上面没有写入method[]中的内容,即第256个字符之外的内容。
    //这里主要是为了过滤空格。
    while (ISspace(buf[j]) && (j < numchars))
        j++;

    while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars))
    {
        //吧buf中的url传给数组url[]
        url[i] = buf[j];
        i++; j++;
    }
    url[i] = '\0';
    //如果是GET请求
    printf("url==%s\n",url);
    if (strcasecmp(method, "GET") == 0)
    {
        
        query_string = url;
        //在url中找除'?'和'\0'
        while ((*query_string != '?') && (*query_string != '\0'))
            query_string++;
        if (*query_string == '?')
        {
            //如果有'?',cgi=1,'?'变'\0'
            cgi = 1;
            printf("iiiii\n");
            *query_string = '\0';
            query_string++;
        }
    }

    sprintf(path, "htdocs%s", url);
    printf("path=%s\n",path);
    if (path[strlen(path) - 1] == '/')
        strcat(path, "index.html");
    printf("path2%s\n",path);
    if (stat(path, &st) == -1) {
        //请求的页面为找到
        while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
            numchars = get_line(client, buf, sizeof(buf));
        not_found(client);
        //结束线程
    }
    else
    {
        //如果能够找到
        if ((st.st_mode & S_IFMT) == S_IFDIR)
            strcat(path, "/index.html");
        if ((st.st_mode & S_IXUSR) ||
                (st.st_mode & S_IXGRP) ||
                (st.st_mode & S_IXOTH)    )
            cgi = 1;
        printf("cgi=%d\n",cgi);
        if (!cgi)
        {
            
            serve_file(client, path);
        }
        else
            execute_cgi(client, path, method, query_string);
    }

    close(client);
}

/**********************************************************************/
/* Inform the client that a request it has made has a problem.
 * Parameters: client socket */
/**********************************************************************/
void bad_request(int client)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "Content-type: text/html\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "<P>Your browser sent a bad request, ");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "such as a POST without a Content-Length.\r\n");
    send(client, buf, sizeof(buf), 0);
}

/**********************************************************************/
/* Put the entire contents of a file out on a socket.  This function
 * is named after the UNIX "cat" command, because it might have been
 * easier just to do something like pipe, fork, and exec("cat").
 * Parameters: the client socket descriptor
 *             FILE pointer for the file to cat */
/**********************************************************************/
void cat(int client, FILE *resource)
{
    char buf[1024];

    fgets(buf, sizeof(buf), resource);
    while (!feof(resource))
    {
        send(client, buf, strlen(buf), 0);
        fgets(buf, sizeof(buf), resource);
    }
}

/**********************************************************************/
/* Inform the client that a CGI script could not be executed.
 * Parameter: the client socket descriptor. */
/**********************************************************************/
void cannot_execute(int client)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "Content-type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
    send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* Print out an error message with perror() (for system errors; based
 * on value of errno, which indicates system call errors) and exit the
 * program indicating an error. */
/**********************************************************************/
void error_die(const char *sc)
{
    perror(sc);
    exit(1);
}

/**********************************************************************/
/* Execute a CGI script.  Will need to set environment variables as
 * appropriate.
 * Parameters: client socket descriptor
 *             path to the CGI script */
/**********************************************************************/
void execute_cgi(int client, const char *path,
        const char *method, const char *query_string)
{
    printf("client=%d\n",client);
    char buf[1024];
    int cgi_output[2];
    int cgi_input[2];
    pid_t pid;
    int status;
    int i;
    char c;
    int numchars = 1;
    int content_length = -1;

    buf[0] = 'A'; buf[1] = '\0';
    //这里还有GET请求
    if (strcasecmp(method, "GET") == 0)
    {
        printf("the get\n");
        while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
            numchars = get_line(client, buf, sizeof(buf));
    }
    else if (strcasecmp(method, "POST") == 0) /*POST*/
    {
        printf("the post\n");
        numchars = get_line(client, buf, sizeof(buf));
        printf("post buf %s\n",buf);
        while ((numchars > 0) && strcmp("\n", buf))
        {
            buf[15] = '\0';
            if (strcasecmp(buf, "Content-Length:") == 0)
                content_length = atoi(&(buf[16]));
            numchars = get_line(client, buf, sizeof(buf));
        }
        if (content_length == -1) {
            bad_request(client);
            return;
        }
    }
    else/*HEAD or other*/
    {
    }

    //管道
    printf("%d\n",__LINE__);
    if (pipe(cgi_output) < 0) {
        cannot_execute(client);
        return;
    }
    if (pipe(cgi_input) < 0) {
        cannot_execute(client);
        return;
    }

    if ( (pid = fork()) < 0 ) {
        cannot_execute(client);
        return;
    }
    //响应请求
    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    send(client, buf, strlen(buf), 0);
    if (pid == 0)  /* child: CGI script */
    {
        char meth_env[255];
        char query_env[255];
        char length_env[255];
        // sprintf(meth_env, "REQUEST_METHOD=%s", method);
        // printf("meth_env=%s\n",meth_env);
        dup2(cgi_output[1], STDOUT);
        dup2(cgi_input[0], STDIN);
        close(cgi_output[0]);
        close(cgi_input[1]);
        sprintf(meth_env, "REQUEST_METHOD=%s", method);
        printf("meth_env=%s\n",meth_env);
        //putenv改变或增加一个环境变量
        putenv(meth_env);
        if (strcasecmp(method, "GET") == 0) {
            sprintf(query_env, "QUERY_STRING=%s", query_string);
            putenv(query_env);
        }
        else {   /* POST */
            sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
            putenv(length_env);
        }
        //执行CGI脚本。
        execl(path, NULL);
        exit(0);
    } else {    /* parent */
        close(cgi_output[1]);
        close(cgi_input[0]);
        if (strcasecmp(method, "POST") == 0)
            for (i = 0; i < content_length; i++) {
                recv(client, &c, 1, 0);
                //从client来的请求发送给子进程
                printf("c=%c\n",c);
                write(cgi_input[1], &c, 1);
            }
        while (read(cgi_output[0], &c, 1) > 0)
        {
            printf("cc=%c\n",c);
            //将从管道中读取(子进程的来的数据)的数据发给client,也就是网页。
            send(client, &c, 1, 0);
        }
        close(cgi_output[0]);
        close(cgi_input[1]);
        waitpid(pid, &status, 0);
    }
}

/**********************************************************************/
/* 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) */
/**********************************************************************/
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')
            {
                //MSG_PEEK,此标志使接收操作从接收队列的开头返回数据,而无需从队列中删除该数据。
                //因此,随后的接收呼叫将返回相同的数据。
                n = recv(sock, &c, 1, MSG_PEEK);
                /* DEBUG printf("%02X\n", c); */
                if ((n > 0) && (c == '\n'))
                    //如果\r后面是\n 则继续读取数据,(这里有点奇怪)
                    recv(sock, &c, 1, 0);
                else
                    //if \r 后面不是\n ,那就让他是\n,不久后循环退出。
                    c = '\n';
            }
            buf[i] = c;
            i++;
        }
        else
            c = '\n';
    }
    buf[i] = '\0';

    return(i);
}

/**********************************************************************/
/* Return the informational HTTP headers about a file. */
/* Parameters: the socket to print the headers on
 *             the name of the file */
/**********************************************************************/
void headers(int client, const char *filename)
{
    char buf[1024];
    (void)filename;  /* could use filename to determine file type */

    strcpy(buf, "HTTP/1.0 200 OK\r\n");
    send(client, buf, strlen(buf), 0);
    printf("buf1=%s\n",buf);
    strcpy(buf, SERVER_STRING);
    send(client, buf, strlen(buf), 0);
    printf("buf2=%s\n",buf);
    
    sprintf(buf, "Content-Type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    printf("buf3=%s\n",buf);
    strcpy(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
    printf("buf4=%s\n",buf);

}

/**********************************************************************/
/* Give a client a 404 not found status message. */
/**********************************************************************/
void not_found(int client)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, SERVER_STRING);
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "Content-Type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "your request because the resource specified\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "is unavailable or nonexistent.\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</BODY></HTML>\r\n");
    send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* Send a regular file to the client.  Use headers, and report
 * errors to client if they occur.
 * Parameters: a pointer to a file structure produced from the socket
 *              file descriptor
 *             the name of the file to serve */
/**********************************************************************/
void serve_file(int client, const char *filename)
{
    FILE *resource = NULL;
    int numchars = 1;
    char buf[1024];

    buf[0] = 'A'; buf[1] = '\0';
    //没有看到这个while的用处
    while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
        numchars = get_line(client, buf, sizeof(buf));

    resource = fopen(filename, "r");
    if (resource == NULL)
        not_found(client);
    else
    {
        headers(client, filename);
        cat(client, resource);
    }
    fclose(resource);
}

/**********************************************************************/
/* This function starts the process of listening for web connections
 * on a specified port.  If the port is 0, then dynamically allocate a
 * port and modify the original port variable to reflect the actual
 * port.
 * Parameters: pointer to variable containing the port to connect on
 * Returns: the socket */
/**********************************************************************/
int startup(u_short *port)
{
    int httpd = 0;
    
    int on = 1;
    struct sockaddr_in name;
    //PF_INET is AF_INET 
    httpd = socket(PF_INET, SOCK_STREAM, 0);
    if (httpd == -1)
        error_die("socket");
    //init name value
    memset(&name, 0, sizeof(name));
    name.sin_family = AF_INET;
    name.sin_port = htons(*port);
    //INADDR_ANY=0.0.0.0
    name.sin_addr.s_addr = htonl(INADDR_ANY);
   
    //设置socket属性
    if ((setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0)  
    {  
        error_die("setsockopt failed");
    }
    //绑定
    if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
        error_die("bind");
    //
    //动态的分配端口,从对端得到sin_port
    if (*port == 0)  /* if dynamically allocating a port */
    {
        socklen_t namelen = sizeof(name);
        if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
            error_die("getsockname");
        *port = ntohs(name.sin_port);
    }
    if (listen(httpd, 5) < 0)
        error_die("listen");
    return(httpd);
}

/**********************************************************************/
/* Inform the client that the requested web method has not been
 * implemented.
 * Parameter: the client socket */
/**********************************************************************/
void unimplemented(int client)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, SERVER_STRING);
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "Content-Type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</TITLE></HEAD>\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</BODY></HTML>\r\n");
    send(client, buf, strlen(buf), 0);
}

/**********************************************************************/

int main(void)
{
    int server_sock = -1;

    //u_short port = 4000;
    u_short port = 9734;
    //刚开始看程序还有点懵,这client_sock怎么是-1呢,后来看到534行,就明白了
    int client_sock = -1;
    struct sockaddr_in client_name;
    socklen_t  client_name_len = sizeof(client_name);
    pthread_t newthread;
    /*建立socket,bind,listen,返回socket的文件描述符*/
    server_sock = startup(&port);//自定义函数,
    printf("httpd running on port %d\n", port);
    /********************************************/
    while (1)
    {
        client_sock = accept(server_sock,   
                (struct sockaddr *)&client_name,
                &client_name_len);
        if (client_sock == -1)
            error_die("accept");
        /* accept_request(&client_sock); */
        if (pthread_create(&newthread , NULL, (void *)accept_request, (void *)(intptr_t)client_sock) != 0)
            perror("pthread_create");
    }

    close(server_sock);

    return(0);
}

 以下是GET和POST,以及CGI脚本的相关知识:

http请求:http请求由三部分组成,分别是:起始行、消息报头、请求正文

Request Line<CRLF>
 
Header-Name: header-value<CRLF>
 
Header-Name: header-value<CRLF>
 
//一个或多个,均以<CRLF>结尾
 
<CRLF>
 
body//请求正文

1、起始行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本,格式如下:

Method Request-URI HTTP-Version CRLF

其中 Method表示请求方法;Request-URI是一个统一资源标识符;HTTP-Version表示请求的HTTP协议版本;CRLF表示回车和换行(除了作为结尾的CRLF外,不允许出现单独的CR或LF字符)。 

2、请求方法(所有方法全为大写)有多种,各个方法的解释如下:

  • GET 请求获取Request-URI所标识的资源
  • POST 在Request-URI所标识的资源后附加新的数据
  • HEAD 请求获取由Request-URI所标识的资源的响应消息报头
  • PUT 请求服务器存储一个资源,并用Request-URI作为其标识
  • DELETE 请求服务器删除Request-URI所标识的资源
  • TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断
  • CONNECT 保留将来使用
  • OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求

 对应到程序中,void accept_request(void *arg)中有一个method[256]数组,他就是用来储存请求方法的。在源程序中,有GET和POST请求方法。

下面通过运行代码来理解这个过程(这个程序该如何在linux上运行,你需要注意makefile中pthread链接选项,在执行cgi脚本时,你需要注意脚本的解析器在在哪,which perl,这个时候可能还是会有问题,但是不要烦躁,百度一下)。

执行./httpd,然后在浏览器中输入127.0.0.1:9734,注意9734是我自己选的端口号,你的根据你自己程序中定义的来(我是在window下打开的浏览器,因为我使用的是WSL2+ubuntu,在vscode中写程序),输入red点击提交就可以看到。

当输入127.0.0.0:9734回车后响应GET请求,执行serve_file(client, path),打开html页面。打印他们的值如图所示。

 打印headers()函数中的数组buf

可以得到如下内容

 源代码中的cat()函数主要是发送index.html中的内容,我没有打印cat中发送的内容,有需要自己打印一下。

这样一来你就能看到HTTP对响应GET请求的,他的报文的回复格式

 图片来源与网络。

到这里我想我差不都对GET请求有了基本的了解。

一个意外:意外的在GET后得到了下面的结果(待研究,不太清楚是什么原因搞出来的).

 输入red,点击提交是POST请求,打印如图所示

我一直奇怪这color.cgi到底是怎么发送给s端的,后来我在index.html中看到了答案。

现在还剩下最后一个问题,那就是管道在程序中的作用是什么。

在程序中子进程中有一行execl(path, NULL),这个是用来执行CGI脚本的。

什么是CGI脚本呢?

看完这个,我认为CGI脚本(子进程)通过cgi_input[0]也即STDIN来从父进程cgi_input[1]获取数据,通过cgi_output[1]也即STDOUT发送数据给父进程cgi_output[0].但是直到现在我还是不能特别清楚的解释那两个环境变量的作用。

对于那两个环境变量的问题,可能需要学习CGI脚本。如果以后有机会接触,在解决这个问题。

如果你有什么疑问或者你认为我写错了,或者知道我在文中遗留问题的答案,欢迎联系我909244296@qq.com 

参考文献:

Tinyhttpd精读解析 - nengm - 博客园 (cnblogs.com)

Tinyhttpd项目解析_changfei_1995的博客-CSDN博客_tinyhttpd

HTTP服务器的本质:tinyhttpd源码分析及拓展_IT 哈的博客-CSDN博客_http服务器的本质:tinyhttpd

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Tinyhttpd项目学习及httpd学习 的相关文章

  • diff 文件仅比较每行的前 n 个字符

    我有2个文件 我们将它们称为 md5s1 txt 和 md5s2 txt 两者都包含a的输出 find type f print0 xargs 0 md5sum sort gt md5s txt 不同目录下的命令 许多文件被重命名 但内容保
  • 在Linux上编译C# + WPF以便在Windows上运行

    我有一个 C 应用程序 其中某些部分是使用 WPF 编写的 Mono 不支持 可以在 Linux 上编译这个应用程序吗 最终 该应用程序将在 Windows 上运行 但它是更大框架的一部分 并且我们的整个构建过程在 Linux 上运行 因此
  • ansible 重新启动 2.1.1.0 失败

    我一直在尝试创建一个非常简单的 Ansible 剧本 它将重新启动服务器并等待它回来 我过去在 Ansible 1 9 上有一个可以运行的 但我最近升级到 2 1 1 0 并且失败了 我正在重新启动的主机名为 idm IP 为 192 16
  • 拆分字符串以仅获取前 5 个字符

    我想去那个地点 var log src ap kernelmodule 10 001 100 但看起来我的代码必须处理 ap kernelmodule 10 002 100 ap kernelmodule 10 003 101 等 我想使用
  • 修改linux下的路径

    虽然我认为我已经接近 Linux 专业人士 但显然我仍然是一个初学者 当我登录服务器时 我需要使用最新版本的R 统计软件 R 安装在 2 个地方 当我运行以下命令时 which R I get usr bin R 进而 R version
  • 使用 find - 删除除任何一个之外的所有文件/目录(在 Linux 中)

    如果我们想删除我们使用的所有文件和目录 rm rf 但是 如果我希望一次性删除除一个特定文件之外的所有文件和目录怎么办 有什么命令可以做到这一点吗 rm rf 可以轻松地一次性删除 甚至可以删除我最喜欢的文件 目录 提前致谢 find ht
  • bluetoothctl 到 hcitool 等效命令

    在 Linux 中 我曾经使用 hidd connect mmac 来连接 BT 设备 但自 Bluez5 以来 这种情况已经消失了 我可以使用 bluetoothctl 手动建立连接 但我需要从我的应用程序使用这些命令 并且使用 blue
  • Linux 中的无缓冲 I/O

    我正在写入大量的数据 这些数据数周内都不会再次读取 由于我的程序运行 机器上的可用内存量 显示为 空闲 或 顶部 很快下降 我的内存量应用程序使用量不会增加 其他进程使用的内存量也不会增加 这让我相信内存正在被文件系统缓存消耗 因为我不打算
  • 抑制 makefile 中命令调用的回显?

    我为一个作业编写了一个程序 该程序应该将其输出打印到标准输出 分配规范需要创建一个 Makefile 当调用它时make run gt outputFile应该运行该程序并将输出写入一个文件 该文件的 SHA1 指纹与规范中给出的指纹相同
  • 如何使用 xterm.js 创建基于 Web 的终端以 ssh 进入本地网络上的系统

    我偶然发现了这个很棒的图书馆xterm js https xtermjs org 这也是 Visual Studio Code 终端的基础 我有一个非常普遍的问题 我想通过基于网络的终端 不在网络中 可能位于 aws 服务器上 访问本地网络
  • nginx 上的多个网站和可用网站

    通过 nginx 的基本安装 您的sites available文件夹只有一个文件 default 怎么样sites available文件夹的工作原理以及如何使用它来托管多个 单独的 网站 只是为了添加另一种方法 您可以为您托管的每个虚拟
  • 如何根据 HTTP 请求使用 Python 和 Flask 执行 shell 命令并流输出?

    下列的这个帖子 https stackoverflow com questions 15092961 how to continuously display python output in a webpage 我能够tail f网页的日志
  • gdb查找行号的内存地址

    假设我已将 gdb 附加到一个进程 并且在其内存布局中有一个文件和行号 我想要其内存地址 如何获取文件x中第n行的内存地址 这是在 Linux x86 上 gdb info line test c 56 Line 56 of test c
  • 大多数 Linux 系统头文件与 C++ 兼容吗?

    大多数 Linux 系统头文件 API C 兼容吗 今天我试图做这样的事情 include
  • Linux中的定时器类

    我需要一个计时器来以相对较低的分辨率执行回调 在 Linux 中实现此类 C 计时器类的最佳方法是什么 有我可以使用的库吗 如果您在框架 Glib Qt Wx 内编写 那么您已经拥有一个具有定时回调功能的事件循环 我认为情况并非如此 如果您
  • sendfile64 只复制约2GB

    我需要使用 sendfile64 复制大约 16GB 的文件 到目前为止我所取得的成就是 include
  • fopen 不返回

    我在 C 程序中使用 fopen 以只读模式 r 打开文件 但就我而言 我观察到 fopen 调用没有返回 它不返回 NULL 或有效指针 执行在 fopen 调用时被阻止 文件补丁绝对正确 我已经验证过 并且不存在与权限相关的问题 任何人
  • 域套接字“sendto”遇到“errno 111,连接被拒绝”

    我正在使用域套接字从另一个进程获取值 就像 A 从 B 获取值一样 它可以运行几个月 但最近 A 向 B 发送消息时偶尔会失败 出现 errno 111 连接被拒绝 我检查了B域套接字绑定文件 它是存在的 我也在另一台机器上做了一些测试 效
  • 如何使用GDB修改内存内容?

    我知道我们可以使用几个命令来访问和读取内存 例如 print p x 但是如何更改任何特定位置的内存内容 在 GDB 中调试时 最简单的是设置程序变量 参见GDB 分配 http sourceware org gdb current onl
  • linux perf:如何解释和查找热点

    我尝试了linux perf https perf wiki kernel org index php Main Page今天很实用 但在解释其结果时遇到了困难 我习惯了 valgrind 的 callgrind 这当然是与基于采样的 pe

随机推荐

  • C# Json的添加方式

    json的几种添加方式 1 只能添加JProperty类的Object 可以一次性添加多个 Add方法添加只能针对于json中的第一层 第二层添加目前还无法得知 但是有其他方法进行弥补 string json ReqCode errcode
  • 【报错】Python:WARNING: You are using pip version 21.1.3; however, version 23.0.1 is available.

    想使用pip安装Scrapy时 报了这个错误 WARNING You are using pip version 21 1 3 however version 23 0 1 is available You should consider
  • 《深入理解Java虚拟机 3》类加载机制与字节码执行引擎

    本系列是用来记录 深入理解Java虚拟机 这本书的读书笔记 方便自己查看 也方便大家查阅 欲速则不达 欲达则欲速 第六章 类文件结构 讲完了自动内存管理 我们来说说执行子系统 执行子系统讲解的是JVM如何执行程序 Class文件概述 这篇我
  • QEMU-KVM基本操作

    本文主要介绍KVM虚拟机的一些基本实践操作 对KVM虚拟机的管理操作主要是基于libvirt的命令行工具virsh进行的 一 安装与启动 1 KVM模块检查 1 查看当前Linux系统核心是否包含KVM模块 Linux内核2 6 20及以上
  • Cookie和Session

    Cookie和Session Cookie是浏览器给HTTP协议提供的一个持久化存储数据的方案 由于当前浏览器站在安全的角度考虑 不敢让页面直接来访问文件系统 Cookie是存储的键值对 不能存复杂的对象 只能存字符串 Cookie是按照域
  • [每周知识碎片] 2

    使用DistributedDataParallel 在Ctrl C 退出后留下许多僵尸进程 kill之后显卡掉了 类似情况1 2 解决方法 使用 ps aux grep python 查看python进程 然后按照顺序执行 kill 9 P
  • pycharm问题一(No module named 'selenium')

    pycharm上搭建python selenium自动化测试环境 背景 小白刚尝试摸门 明明安装了selenium pycharm中还是报No module named selenium 1 首先 竟然报没有selenium 就安装呗 cm
  • 期货开户金融市场非常残酷

    大多数交易者喜欢从零基础开始他们的交易旅程 毕竟 对于它们来说 点击鼠标确认交易真的非常简单 这个数字时代使我们易于交易 交易过程看起来非常简单 但是金融市场却非常残酷 在交易市场当中 所谓的 聪明 根本没有用 市场会一次又一次地给予你教训
  • 计算机vcruntime140.dll丢失的解决方法,重新安装教程

    vcruntime140 dll是Microsoft Visual C Redistributable文件中的一个动态链接库 DLL 这个文件是由Microsoft开发的 用于支持C 编程语言的运行环境 vcruntime140 dll是W
  • Linux的设置地区

    2023年7月21日 周五上午 本来想试试把这篇文章设置成VIP可见的 因为我挺好奇设置了VIP可见后会发生什么 但后来想想觉得这有违自己写博客的初心 于是就放弃了 我写博客的初心就是传递其他人写博客的那种无私的分享精神 为社会 中文社区和
  • python同一文件内class类的调用

    class 类名 def init self self a None self b None def 函数 self x y self x x self y y A 类名 B 类名 A 函数 10 20 print B x B y 将会输出
  • python实现二分查找的四种变体

    本文用python3实现了二分查找的四种变体 一 查找第一个值等于给定值的元素 二 查找最后一个值等于给定值的元素 三 查找第一个大于等于给定值的元素 四 查找最后一个小于等于给定值的元素 python3 一 查找第一个值等于给定值的元素
  • git rebase

    目录 一 开发分支落后于主干分支 个人修复用的分支落后于被修复分支 模拟环境 开始rebase操作 二 本地分支落后于远程分支 多人共用一个分支的情况下其他人有提交 在本地模拟环境 熟悉的可以跳过 比较啰嗦 详细步骤 开始模拟情景 解决 u
  • 【Java学习笔记(一百零七)】之字节码执行引擎,栈帧结构

    本文章由公号 开发小鸽 发布 欢迎关注 老规矩 妹妹镇楼 一 字节码执行引擎 一 概述 物理机和虚拟机都有代码执行能力 物理机的执行引擎建立在处理器 缓存机 指令集和操作系统之上 而虚拟机的执行引擎则是由软件实现的 不会受到物理条件制约地定
  • 稀疏数组(尚硅谷课程的笔记)

    2 稀疏数组 文章来自于听了尚硅谷的课自己所敲 https www bilibili com video BV1E4411H73v p 10 2 1 稀疏数组 我们来看一个实际问题 此时就需要稀疏数组来压缩 稀疏数组基本介绍 当一个数组中大
  • pycharm配置解释器

    因为没有系统学习过pycharm的使用 所以在换了新电脑之后重新配置pycharm一头雾水 查了很多资料 此文用来自己记录 1 解释器选择 virtual Enviroment 第一个是虚拟解释器 我的理解是 直接从pycharm里下载一个
  • 安徽旅游可视化

    安徽旅游可视化 此系统有详细的录屏 下面只是部分截图 需要看完整录屏联系博主 系统开发语言python 框架为django 数据库mysql 分为爬虫和可视化分析
  • 没看错!selenium自动化集成REST api实践!

    01 问题 当我们描述一个 好的自动化测试用例 时 经常出现标准是 精确 自动化测试用例应该测试一件事 只有一件事 与测试用例无关的应用程序的某个部分中的错误不应导致测试用例失败 独立 自动化测试用例不应该受测试套件中任何其他测试用例影响
  • 计算机视觉 相机标定

    目录 一 相机标定原理 1 相机标定简介 2 求解原理 2 1 针孔相机模型 2 2 畸变现象 2 3 像主点偏移 2 4 单应性矩阵H 二 相机标定策略 2 1 相关策略 2 2 棋盘格标定 三 实验内容 1 实验数据 2 实验代码 3
  • Tinyhttpd项目学习及httpd学习

    在参考网络有关Tinyhttpd的内容后 我打算写下自己的学习过程与自己的理解 我会在结尾附上参考的链接 我把代码放在了gitee Tinyhttpd学习 1 Tinyhttpd是一个轻量级的HTTP服务器 2 学习该项目可以学习web服务