C++Linux服务器学习之路——1

2023-05-16

前言:为了让所学的计网知识融合于实际,让操作系统里的理论去满足工程需求,故通过借鉴30dayMakeServer的路线以及进行相应知识点的学习。

part1
首先我们要理解socket
在这里插入图片描述
为应用层和传输层提供应用编程接口(API)。

在这里插入图片描述
同层功能协议是水平交流,实际是
在这里插入图片描述
信息传输的时候是需要一个接口去完成对应信息的转换和传输,这里我们用的那个接口就是在Linux系统下使用的socket。

引入的头文件是

#include <sys/socket.h>

其中

socket(AF_INET, SOCK_STREAM, 0)

这三个参数含义分别为:

  • 第一个参数:IP地址类型,AF_INET表示使用IPv4,如果使用IPv6请使用AF_INET6。
  • 第二个参数:数据传输方式,SOCK_STREAM表示流格式、面向连接,多用于TCP。SOCK_DGRAM表示数据报格式、无连接,多用于UDP。
  • 第三个参数:协议,0表示根据前面的两个参数自动推导协议类型。设置为IPPROTO_TCP和IPPTOTO_UDP,分别表示TCP和UDP。

每一个服务器都应该有一个唯一的标识符,而客户端要与其建立联系及传输信息,就要和这个标识符进行连接,
这个标识符由IP地址和网络端口组成,
客户端首先建立了socket这个应用编程接口,接下来要确定这个接口具体的连接对象的位置,这里我们使用

#include <arpa/inet.h>  //这个头文件包含了<netinet/in.h>

这个头文件的,

在这里插入图片描述
这个结构体有三个子对象,

serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(8888);

分别是设置地址族、IP地址和端口。

因为一个服务器的端口不应该只能提供给一个客户端,所以要将这个端口变成都可访问。

这里使用了一个函数,对socket这个接口进行泛化。

bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));

有接口,并且都能让其他客户端去连接,接下来在服务器端就要加一个监听函数,来的确认运气情况。
listen函数监听这个socket端口,这个函数的第二个参数是listen函数的最大监听队列长度,系统建议的最大值SOMAXCONN被定义为128。

listen(sockfd, SOMAXCONN);

要接受一个客户端连接,需要使用accept函数。对于每一个客户端,我们在接受连接时也需要保存客户端的socket地址信息,于是有以下代码:

struct sockaddr_in clnt_addr;
socklen_t clnt_addr_len = sizeof(clnt_addr);
bzero(&clnt_addr, sizeof(clnt_addr));
int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);
printf("new client fd %d! IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));

要注意和acceptbind的第三个参数有一点区别,对于bind只需要传入serv_addr的大小即可,而accept需要写入客户端socket长度,所以需要定义一个类型为socklen_t的变量,并传入这个变量的地址。另外,accept函数会阻塞当前程序,直到有一个客户端socket被接受后程序才会往下运行。

客户端的写法和服务器很相似,但实现功能不同,是有点不一样的地方,在建立好接口以及绑定之后,是不需要泛化接口,之后就通过一个函数使它和服务器端连接。

connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));  

这幅图就是服务器和客户端信息交换的流程图。
在这里插入图片描述
附上的代码是来自30dayMakeServer。
其GitHub地址:
https://github.com/yuesong-feng/30dayMakeCppServer
server部分:

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);//ip4/6 ,udp/tcp,0/??

    struct sockaddr_in serv_addr;
    bzero(&serv_addr, sizeof(serv_addr));//功能:置字节字符串s的前n个字节为零且包括‘\0’。
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(8888);

    bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));//第二个参数是一个指向特定协议的地址结构的指针,第三个参数是该地址结构的长度。对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。

    listen(sockfd, SOMAXCONN);//第二个参数是一个指向特定协议的地址结构的指针,第三个参数是该地址结构的长度。对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。
    
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_len = sizeof(clnt_addr);
    bzero(&clnt_addr, sizeof(clnt_addr));

    int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);//要接受一个客户端连接,需要使用`accept`函数。对于每一个客户端,我们在接受连接时也需要保存客户端的socket地址信息

    printf("new client fd %d! IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));
    return 0;
}

clinet部分:

#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in serv_addr;
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(8888);

    //bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)); 客户端不进行bind操作

    connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));    
    
    return 0;
}

part2

各种语言中,都有对异常的处理机制,这里我们就简单写一个函数来显示异常所在的地方,而不是捕获异常之后进行处理。

为了方便编码以及代码的可读性,可以封装一个错误处理函数:

void errif(bool condition, const char *errmsg){
    if(condition){
        perror(errmsg);
        exit(EXIT_FAILURE);
    }
}

第一个参数是是否发生错误,如果为真,则表示有错误发生,会调用<stdio.h>头文件中的perror,这个函数会打印出errno的实际意义,还会打印出我们传入的字符串,也就是第函数第二个参数,让我们很方便定位到程序出现错误的地方。然后使用<stdlib.h>中的exit函数让程序退出并返回一个预定义常量EXIT_FAILURE

在使用的时候:

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
errif(sockfd == -1, "socket create error");

通过这种方式就能在遇到错误的时候很快找到。

对于所有的函数,我们都使用这种方式处理错误:

errif(bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1, "socket bind error");
errif(listen(sockfd, SOMAXCONN) == -1, "socket listen error");
int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);
errif(clnt_sockfd == -1, "socket accept error");
errif(connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1, "socket connect error");

这只是简单的一种对错误的一种处理方式,这只是一个简单的处理。

part3
服务器会面向多个客户端,所以前面我们用到的bind函数,但这只是提供端口,怎么去处理多个服务并行?
方法很多,这里我用的epoll,接下来我会讲一下我对它的理解和使用,
这里参照的是
https://zhuanlan.zhihu.com/p/63179839,
https://cloud.tencent.com/developer/article/1816835,
从Linux源码看epoll可借鉴:https://my.oschina.net/alchemystar/blog/3008840

epoll实现的过程
数据经由网卡传送到内存(步骤②),然后网卡通过中断信号通知cpu有数据到达,cpu执行中断程序(步骤③)。此处的中断程序主要有两项功能,先将网络数据写入到对应socket的接收缓冲区里面(步骤④),再唤醒进程A(步骤⑤),重新将进程A放入工作队列中。

在这里插入图片描述

首先网卡接收网线传来的数据,通过硬件电路传输,之后写入内存上。
在这里插入图片描述

问题来了,如何确定接受到了数据?

这里我们就要讲一下epoll实现的又一个方法:中断,

在运行多个任务的时候,cpu是会有优先级的处理方式,优先级高的就会优先处理,一般情况下,硬件指令的优先级会高于软件。

一般而言,由硬件产生的信号需要cpu立马做出回应(不然数据可能就丢失),所以它的优先级很高。cpu理应中断掉正在执行的程序,去做出响应;当cpu完成对硬件的响应后,再重新执行用户程序。中断的过程如下图,和函数调用差不多。只不过函数调用是事先定好位置,而中断的位置由“信号”决定。

在这里插入图片描述
以键盘为例,当用户按下键盘某个按键时,键盘会给cpu的中断引脚发出一个高电平。cpu能够捕获这个信号,然后执行键盘中断程序。下图展示了各种硬件通过中断与cpu交互。

在这里插入图片描述

之后就是这么一个过程:
网卡将数据写入内存,同时网卡向cpu发送一个中断信号让cpu知道数据已经来了,之后进行进程调度来处理这一数据。

在这里插入图片描述

在这里插入图片描述

接下来我们思考两个问题

其一,如何同时监视多个socket的数据?

其二,操作系统如何知道网络数据对应于哪个socket?(以及处理)

为了更好的介绍epoll,这里我们先介绍select,
首先我们要建立多个监督socket的方式,

int s = socket(AF_INET, SOCK_STREAM, 0);  
bind(s, ...)
listen(s, ...)

int fds[] =  存放需要监听的socket

while(1){
    int n = select(..., fds, ...)
    for(int i=0; i < fds.count; i++){
        if(FD_ISSET(fds[i], ...)){
            //fds[i]的数据处理
        }
    }
}

这里是用一个数组fds,把所有的socket都放进去,

在这里插入图片描述
这一块就是来查看那一块是有数据的,如果存入中的socket中至少有一个是有数据的,则将存入他们的进程从等待态唤醒。

在这里插入图片描述
在这里插入图片描述
这个方法会有两个问题,查找没数据的时候需要遍历一次,进程就绪找出有数据的去处理,又要遍历一次。
详细一点:
select/poll模型。
情况1. 为了能够让一个进程,监控多个socket.
就把一个socket列表,传入到selector选择器上。
这样selector,循环遍历socket列表。
把就绪状态的 socket 选择出来,然后进行处理。
什么是就绪状态:假设socket的读缓冲区有可读的数据,那么此socket就处于就绪状态。
就绪状态的socket,可以这样处理,
当读缓冲区有数据时,就读数据。
当写缓冲区有空间可以写时,就写数据。
当ServerSocket有新建了连接时,就接收 clinetSocket ,并且把clinetSocket添加到seletor选择器中。
完成以上处理后,再次遍历socket列表,按照以上逻辑反复处理。

情况2. 遍历一次socket列表,没有一个socket处于就绪状态,那么进程A将阻塞。
具体流程:遍历socket列表,发现socket处于未就绪状态。那么就在该socket的等待队列中,添加进程A的引用。
当遍历完整个socket列表,却没有任何一个socket处于就绪状态。那么就把进程A从工作队列移除,让进程处于阻塞状态。
由于CPU每次执行完指令,都会检查是否有中断信号。
假设10S以后,socket列表中,有一个socket处于就绪状态。
那么网卡对应的硬件,会向CPU发一个中断信号。CPU收到一个中断信号,从而调用对应的中断程序。
此中断程序,会把进程A唤醒,加入到工作队列。
并且遍历socket列表,将进程A的引用,从所有的等待队列中移除。
selector再次遍历socket列表,把就绪的 socket选出来,然后进行读或者写的操作。
再来看等待队列的作用:就是当socket就绪时,能够通过等待队列的进程引用,找到对应的进程。

(ps:还有一个思考方面是从用户态和内核态思考,
如:关于select,最low的就是在用户代码中自旋实现所有阻塞socket的监听。但是每次判断socket是否产生数据,都涉及到用户态到内核态的切换。
于是select改进:将fd_set传入内核态,由内核判断是否有数据返回;
然后最low的只能使用自旋来时刻的去判断socket列表中是否有数据达到。
于是select改进:使用等待队列,让线程在没有资源时park(阻塞),当有数据到达时唤醒select线程,去处理socket。)

接下来我们就讲解epoll,

首先我们讲一下多路复用,
多路复用I/O:是指内核负责监听多个 I/O 流,当任何一个 I/O 流处于就绪状态(可读或可写)时都会通知进程,以便可以处理该 I/O 流上的数据。如 图1 所示:
在这里插入图片描述
包括select、poll也是为了实现这个,
其中epoll的实现方式是,
采用了一个红黑树,epoll 内部使用红黑树来保存所有监听的 socket,红黑树是一种平衡二叉树,添加和查找元素的时间复杂度为 O(log n),其结构如 图2 所示:
在这里插入图片描述
epoll 通过 socket 句柄来作为 key,把 socket 保存在红黑树中。如 图2 所示,每个节点中的数字代表着 socket 句柄。

把监听的 socket 保存在红黑树中的目的是,为了在修改监听 socket 的读写事件时,能够通过 socket 句柄快速找到对应的 socket 对象。

这样就加快了索引的速度。

第二个就是
就绪列表

select低效的另一个原因在于程序不知道哪些socket收到数据,只能一个个遍历。如果内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。如下图所示,计算机共有三个socket,收到数据的sock2和sock3被rdlist(就绪列表)所引用。当进程被唤醒后,只要获取rdlist的内容,就能够知道哪些socket收到数据。
在这里插入图片描述

整体实现

epoll原理再解剖以及使用流程

首先是epoll_create创建一个eventpoll对象,并将其添加到文件系统中,和普通socket一样,也有等待队列(存储的是等待的进程),eventpoll对象也是文件系统中的一员,和socket一样,它也会有等待队列。
在这里插入图片描述
之后要对监视列表进行维护如:有新的socket要跟踪。
创建epoll对象后,可以用epoll_ctl添加或删除所要监听的socket。以添加socket为例,如下图,如果通过epoll_ctl添加sock1、sock2和sock3的监视,内核会将eventpoll添加到这三个socket的等待队列中。

在这里插入图片描述
以上准备好之后就可以工作了,当socket接收到了数据,那么中断程序会给eventPoll的rdlist就会对对应的socket添加引用,如图:
在这里插入图片描述
我们可以简单的讲这个理解为:socket和进程高频高数量的交互是需要一个媒介的,而eventpoll就是这个媒介。

这里我们就要思考一个问题,如果socket为空对应的进程应该怎么处理?
这里我们引用了一个epoll_wait函数。
当程序执行到epoll_wait时,如果rdlist已经引用了socket,那么epoll_wait直接返回,如果rdlist为空,阻塞进程。
我们先讲一下进程的阻塞和唤醒在此的实现,
假设计算机中正在运行进程A和进程B,在某时刻进程A运行到了epoll_wait语句。如下图所示,内核会将进程A放入eventpoll的等待队列中,阻塞进程。
在这里插入图片描述当socket接收到数据,中断程序一方面修改rdlist,另一方面唤醒eventpoll等待队列中的进程,进程A再次进入运行状态(如下图)。也因为rdlist的存在,进程A可以知道哪些socket发生了变化。
在这里插入图片描述
这里要注意:
epoll不会让每个 socke t的等待队列都添加进程A引用,而是在等待队列,添加 eventPoll对象的引用。
当socket就绪时,中断程序会操作eventPoll,在eventPoll中的就绪列表(rdlist),添加scoket引用。
这样的话,进程A只需要不断循环遍历rdlist,从而获取就绪的socket。
从代码来看每次执行到epoll_wait,其实都是去遍历 rdlist。

如果rdlist为空,那么就阻塞进程。
当有socket处于就绪状态,也是发中断信号,再调用对应的中断程序。
此时中断程序,会把socket加到rdlist,然后唤醒进程。进程再去遍历rdlist,获取到就绪socket。

总之: poll是翻译轮询的意思,我们可以看到poll和epoll都有轮询的过程。
不同点在于:
poll轮询的是所有的socket。
而epoll只轮询就绪的socket。

练习实现代码:
用的是前面的已经放的三十天,
clint.cpp

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include "util.h"

#define BUFFER_SIZE 1024 

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    errif(sockfd == -1, "socket create error");

    struct sockaddr_in serv_addr;
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(8888);

    errif(connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1, "socket connect error");
    
    while(true){
        char buf[BUFFER_SIZE];  //在这个版本,buf大小必须大于或等于服务器端buf大小,不然会出错,想想为什么?
        bzero(&buf, sizeof(buf));
        scanf("%s", buf);
        ssize_t write_bytes = write(sockfd, buf, sizeof(buf));
        if(write_bytes == -1){
            printf("socket already disconnected, can't write any more!\n");
            break;
        }
        bzero(&buf, sizeof(buf));
        ssize_t read_bytes = read(sockfd, buf, sizeof(buf));
        if(read_bytes > 0){
            printf("message from server: %s\n", buf);
        }else if(read_bytes == 0){
            printf("server socket disconnected!\n");
            break;
        }else if(read_bytes == -1){
            close(sockfd);
            errif(true, "socket read error");
        }
    }
    close(sockfd);
    return 0;
}

server.cpp

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
#include "util.h"

#define MAX_EVENTS 1024
#define READ_BUFFER 1024

void setnonblocking(int fd){
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    errif(sockfd == -1, "socket create error");

    struct sockaddr_in serv_addr;
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(8888);

    errif(bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1, "socket bind error");

    errif(listen(sockfd, SOMAXCONN) == -1, "socket listen error");
    
    int epfd = epoll_create1(0);
    errif(epfd == -1, "epoll create error");

    struct epoll_event events[MAX_EVENTS], ev;
    bzero(&events, sizeof(events));

    bzero(&ev, sizeof(ev));
    ev.data.fd = sockfd;
    ev.events = EPOLLIN | EPOLLET;
    setnonblocking(sockfd);
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

    while(true){
        int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
        errif(nfds == -1, "epoll wait error");
        for(int i = 0; i < nfds; ++i){
            if(events[i].data.fd == sockfd){        //新客户端连接
                struct sockaddr_in clnt_addr;
                bzero(&clnt_addr, sizeof(clnt_addr));
                socklen_t clnt_addr_len = sizeof(clnt_addr);

                int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);
                errif(clnt_sockfd == -1, "socket accept error");
                printf("new client fd %d! IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));

                bzero(&ev, sizeof(ev));
                ev.data.fd = clnt_sockfd;
                ev.events = EPOLLIN | EPOLLET;
                setnonblocking(clnt_sockfd);
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sockfd, &ev);
            } else if(events[i].events & EPOLLIN){      //可读事件
                char buf[READ_BUFFER];
                while(true){    //由于使用非阻塞IO,读取客户端buffer,一次读取buf大小数据,直到全部读取完毕
                    bzero(&buf, sizeof(buf));
                    ssize_t bytes_read = read(events[i].data.fd, buf, sizeof(buf));
                    if(bytes_read > 0){
                        printf("message from client fd %d: %s\n", events[i].data.fd, buf);
                        write(events[i].data.fd, buf, sizeof(buf));
                    } else if(bytes_read == -1 && errno == EINTR){  //客户端正常中断、继续读取
                        printf("continue reading");
                        continue;
                    } else if(bytes_read == -1 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))){//非阻塞IO,这个条件表示数据全部读取完毕
                        printf("finish reading once, errno: %d\n", errno);
                        break;
                    } else if(bytes_read == 0){  //EOF,客户端断开连接
                        printf("EOF, client fd %d disconnected\n", events[i].data.fd);
                        close(events[i].data.fd);   //关闭socket会自动将文件描述符从epoll树上移除
                        break;
                    }
                }
            } else{         //其他事件,之后的版本实现
                printf("something else happened\n");
            }
        }
    }
    close(sockfd);
    return 0;
}

到此epoll的讲解就结束了。

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

C++Linux服务器学习之路——1 的相关文章

  • 如何使用 sed 仅删除双空行?

    我找到了这个问题和答案 https stackoverflow com questions 4651591 howto use sed to remove only triple empty lines关于如何删除三重空行 但是 我只需要对
  • 在主目录中安装库

    在 Linux Ubuntu 中 我尝试运行一个工具 但它显示错误 库丢失 我无权在系统中安装任何内容 或者根本无法从我的用户帐户执行 sudo 是否可以在我的主目录 没有 sudo 中安装缺少的库 在我的例子中为 libstdc so 6
  • GCC 和 ld 找不到导出的符号...但它们在那里

    我有一个 C 库和一个 C 应用程序 尝试使用从该库导出的函数和类 该库构建良好 应用程序可以编译 但无法链接 我得到的错误遵循以下形式 app source file cpp text 0x2fdb 对 lib namespace Get
  • PHP 致命错误:未找到“MongoClient”类

    我有一个使用 Apache 的网站 代码如下 当我尝试访问它时 我在 error log 中收到错误 PHP Fatal Error Class MongoClient not found 以下是可能错误的设置 但我认为没有错误 php i
  • 嵌入式Linux poll()不断返回

    我有一个特别的问题 当我知道没有什么可读时 民意调查不断返回 因此设置如下 我有 2 个文件描述符 它们构成fd设置民意调查监视 一种用于引脚从高到低的变化 GPIO 另一个用于代理输入 代理输入出现问题 处理的顺序是 启动main函数 然
  • 拆分字符串以仅获取前 5 个字符

    我想去那个地点 var log src ap kernelmodule 10 001 100 但看起来我的代码必须处理 ap kernelmodule 10 002 100 ap kernelmodule 10 003 101 等 我想使用
  • Unix 命令列出包含字符串但*不*包含另一个字符串的文件

    如何递归查看包含一个字符串且不包含另一个字符串的文件列表 另外 我的意思是评估文件的文本 而不是文件名 结论 根据评论 我最终使用了 find name html exec grep lR base maps xargs grep L ba
  • Elasticsearch 无法写入日志文件

    我想激活 elasticsearch 的日志 当我运行 elasticsearch 二进制文件时 我意识到我在日志记录方面遇到问题 无法加载配置 这是输出 sudo usr share elasticsearch bin elasticse
  • 是否可以在Linux上将C转换为asm而不链接libc?

    测试平台为Linux 32位 但也欢迎 Windows 32 位上的某些解决方案 这是一个c代码片段 int a 0 printf d n a 如果我使用 gcc 生成汇编代码 gcc S test c 然后我会得到 movl 0 28 e
  • Android:ANT 构建失败,并显示 google-play-services-lib:“解析为没有项目的 project.properties 文件的路径”

    我正在尝试使用 ANT 构建我的应用程序 但在包含 google play services lib 库项目后 我惨遭失败 Step 1 我在 project properties 文件中设置了对库项目的引用 android library
  • 强制卸载 NFS 安装目录 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案
  • 跟踪 Linux 程序中活跃使用的内存

    我想跟踪各种程序在特定状态下接触了多少内存 例如 假设我有一个图形程序 最小化时 它可能会使用更少的内存 因为它不会重新绘制窗口 这需要读取图像和字体并执行大量库函数 这些对象仍然可以在内存中访问 但实际上并没有被使用 类似的工具top它们
  • 如何检测并找出程序是否陷入死锁?

    这是一道面试题 如何检测并确定程序是否陷入死锁 是否有一些工具可用于在 Linux Unix 系统上执行此操作 我的想法 如果程序没有任何进展并且其状态为运行 则为死锁 但是 其他原因也可能导致此问题 开源工具有valgrind halgr
  • 如何禁用 GNOME 桌面屏幕锁定? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 如何阻止 GNOME 桌面在几分钟空闲时间后锁定屏幕 我已经尝试过官方手册了在红帽 https access redhat com doc
  • Jenkins中找不到环境变量

    我想在詹金斯中设置很多变量 我试过把它们放进去 bashrc bash profile and profile of the jenkins用户 但 Jenkins 在构建发生时找不到它们 唯一有效的方法是将所有环境变量放入Jenkinsf
  • Linux中的CONFIG_OF是什么?

    我看到它在很多地方被广泛使用 但不明白在什么场景下我需要使用它 What is 配置 OF OF 的全名是什么 打开固件 这是很久以前发明的 当时苹果公司正在生产基于 PowerPC CPU 的笔记本电脑 而 Sun Microsystem
  • 在哪里可以找到并安装 pygame 的依赖项?

    我对 Linux 比较陌生 正在尝试安装 python 的 pygame 开发环境 当我运行 setup py 时 它说我需要安装以下依赖项 我找到并安装了其中之一 SDL 然而 其他人则更加难以捉摸 Hunting dependencie
  • 使用 sed 更新 xml 属性(Windows + cygwin 和 Linux)?

    我需要使用 sed 命令对 xml 文件进行更新 但我在这方面遇到了麻烦 它需要在 Windows 使用 cygwin 和 Linux 上运行 XML 具有以下元素
  • 所有平台上的java

    如果您想用 java 为 Windows Mac 和 Linux 编写桌面应用程序 那么所有这些代码都相同吗 您只需更改 GUI 即可使 Windows 应用程序更像 Windows 等等 如果不深入细节 它是如何工作的 Java 的卖点之
  • 如何有效截断文件头?

    大家都知道truncate file size 函数 通过截断文件尾部将文件大小更改为给定大小 但是如何做同样的事情 只截断文件的尾部和头部呢 通常 您必须重写整个文件 最简单的方法是跳过前几个字节 将其他所有内容复制到临时文件中 并在完成

随机推荐

  • STM32 RS232通信实验

    stm32F103 RS232通信实验 什么是RS232 软件设计 完整工程下载 什么是RS232 先来看看UART传输所存在的问题 于是就有了RS232协议 这里注意使用的是负逻辑电平信号 在规定范围内的电平信号代表逻辑1或0 xff0c
  • MDK中变量无法添加到逻辑分析仪中原因

    MDK中变量无法添加到逻辑分析仪中原因 解决方法 去掉static 提示无法将变量添加到逻辑分析仪中 解决方法 去掉static 设置为bit 全速运行
  • 三,FreeRTOS之——动态创建多任务+优先级

    声明 xff1a 本专栏参考韦东山 xff0c 野火 xff0c 正点原子以及其他博主的FreeRTOS教程 xff0c 如若侵权请告知 xff0c 马上删帖致歉 xff0c 个人总结 xff0c 如有不对 xff0c 欢迎指正 动态创建多
  • ESP8266组网+STM32数据传输项目

    ESP8266 43 STM32数据传输项目 实验硬件 xff1a 项目关键词 xff1a 项目描述项目涉及知识 xff1a 1 ESP8266开发2 MQTT协议3 STM32 整体开发流程 xff1a 实验硬件 xff1a ESP826
  • 十一,FreeRTOS之——互斥信号量(优先级反转,优先级继承,递归锁)

    声明 xff1a 本专栏参考韦东山 xff0c 野火 xff0c 正点原子以及其他博主的FreeRTOS教程 xff0c 如若侵权请告知 xff0c 马上删帖致歉 xff0c 个人总结 xff0c 如有不对 xff0c 欢迎指正 互斥量理论
  • (一)裸机开发框架构建之---开发框架思想

    裸机开发框架构建 声明 xff1a 本专栏通过查阅资料以及自己对开发框架的理解所编写 xff0c 如有错误 xff0c 还请指正 为什么要使用框架 xff1f xff1f xff1f 我的框架分层思想体现 声明 xff1a 本专栏通过查阅资
  • PX4添加外置IMU传感器MPU-9250

    使用PX4 v1 13 2代码 xff0c 淘宝购买的MPU 9250传感器 MPU 9250 芯片架构图 实物图 手册 寄存器 https invensense tdk com wp content uploads 2015 02 RM
  • (二)裸机开发框架构建之---点灯大师

    裸机开发框架构建 3 设备管理层抽象出结构体初始化结构体第一种初始化方法 xff08 c89标准 xff09 第二种初始化方法 xff08 C99标准 xff09 2 硬件接口层1 硬件层硬件LED层初始化函数硬件层LED控制函数 4 应用
  • 1.freertos应用系列之cubemx创建freertos

    freertos应用全系列 xff08 写完关联更新 xff09 01 freertos应用系列之cubemx创建freertos 11 freertos应用系列之cubemx创建freertos 02 freertos应用系列之cubem
  • docker中镜像源推荐

    1 xff0c 个人建议使用 网易镜像源 镜像源有以下5种 1 网易 http hub mirror c 163 com 2 Docker中国区官方镜像 https registry docker cn com 3 ustc https d
  • VScode创建C++项目

    VScode创建C 43 43 项目 假设系统已经安装了MinGW64 插件 常用插件 创建Project 配置json文件 需要修改的地方都在下方注释说明 根据MinGW64安装位置进行修改 c cpp properties json s
  • C++的一个问题点,数组作为参数传递到函数之后,不能直接求出长度

    YU 原数组 xff0c 传递参数之后 结果是作为参数传进去之后是作为指针 xff0c 是不能求出长度的 xff0c 所以需要把长度提前求出作为参数传入该函数 反思 xff1a 最近C 43 43 Python xff0c java轮流用
  • 基于from flask import Flask,render_template 上传网页遇到的问题

    我们要上传多个页面形成一个网站 xff0c 首先我们需要在index xff08 一般这个都是首页面 xff09 查看其源码 找到类似 这段代码里面包括了前面的网站 xff0c 所以这时候我们只需要把它变成带使用的状态 xff0c 操作就是
  • 跨交换机的VLAN设置

    实现目标 xff1a 进行多台主机多个vlan接口进行互相通信 需要知识 xff1a 1 不同的vlan接口的是不能进行通信的 2 在要跨越多个交换机进行通信的时候要对进行交互的交换机进行共享vlan端口的设置 3 在设置网络号的时候应该注
  • Wireshark抓取cookie:用户名...,TCP报文等信息实战

    这里我们要先安装Wireshark xff0c 这里要注意的是一些低级版本刚刚下下来的时候是找不到网络接口的 xff0c 所以这时候要更新 xff0c 然后再下应该WinPro xff08 应该是这个 xff09 xff0c 之后就有网络接
  • 计算机网络知识点总结提纲(谢希仁)

    1 IOS OSI对王道书上的缩减总结 清晰pdf xff1a 链接 xff1a https pan baidu com s 1f6DqMsHky4kP8i9WQLvCew pwd 61 the3 提取码 xff1a the3 来自百度网盘
  • C++getline和 cin的探讨

    从结果可以看出 xff0c cin是会把空格部分舍弃的 如果是输入一个 然后空格在输入其他的 xff0c 因为cin默认把空格去调 xff0c 则后面的字符我的理解就是溢出 xff1f 所以报错了 getline功能就比较强大了 xff0c
  • Pixhawk RPi CM4 Baseboard 树莓派CM4安装Ubuntu20.04 server 配置ros mavros mavsdk

    文章目录 硬件安装Ubuntu Server20 04下载rpiboot工具下载imager刷写系统配置USB配置WIFI 开机安装桌面配置wifi配置串口安装ROS安装mavros安装MAVSDK PythonInternet设置最后 参
  • docker迁移镜像

    docker迁移本地镜像 本文为docker基本镜像操作之一 查看本镜像 docker images 迁移 xff08 拷贝 xff09 本地镜像到其他设备 1 打包 docker save o 路径 目标包名 tar 源镜像名 标签 2
  • C++Linux服务器学习之路——1

    前言 xff1a 为了让所学的计网知识融合于实际 xff0c 让操作系统里的理论去满足工程需求 xff0c 故通过借鉴30dayMakeServer的路线以及进行相应知识点的学习 part1 首先我们要理解socket 为应用层和传输层提供