【Linux网络编程学习】socket API(socket、bind、listen、accept、connect)及简单应用

此为牛客Linux C++课程和黑马Linux系统编程笔记。

1. 什么是socket

所谓 socket(套接字),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。
一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处
的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,
是应用程序与网络协议根进行交互的接口。

socket 可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概
念。它是网络环境中进程间通信的 API,也是可以被命名和寻址的通信端点,使用中的每一个套接
字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在
主机的 socket 中,该 socket 通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台
主机的 socket 中,使对方能够接收到这段信息。socket 是由 IP 地址和端口结合的,提供向应用
层进程传送数据包的机制。

socket 本身有“插座”的意思,在 Linux 环境下,用于表示进程间网络通信的特殊文件类型。本质为
内核借助缓冲区形成的伪文件。既然是文件,那么理所当然的,我们可以使用文件描述符引用套接
。与管道类似的,Linux 系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文
件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传
递。

套接字的内核实现较为复杂,不宜在学习初期深入学习。

在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。
套接字通信原理如下图所示:
在这里插入图片描述
在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。

2. socket相关函数

2.1 socket( )函数

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
int socket(int domain, int type, int protocol);

功能:创建一个套接字

参数:

  • domain: 协议族:
    AF_INET : ipv4
    AF_INET6 : ipv6
    AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信)

  • type: 通信过程中使用的协议类型:
    SOCK_STREAM : 流式协议
    SOCK_DGRAM : 报式协议

  • protocol : 具体的一个协议。一般写0:
    SOCK_STREAM : 流式协议默认使用 TCP
    SOCK_DGRAM : 报式协议默认使用 UDP

  • 返回值: 成功:返回文件描述符,操作的就是内核缓冲区。失败:-1

socket( )打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。对于IPv4,domain参数指定为AF_INET。对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协议。protocol参数的介绍从略,指定为0即可。

2.2 bind( )函数

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命名

功能:绑定,将文件描述符fd和本地的<IP , 端口>进行绑定

参数:

  • sockfd : 通过socket()函数得到的文件描述符
  • addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息
  • addrlen : 第二个参数结构体占的内存大小

返回值:成功返回0,失败返回-1, 设置errno

服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。

bind()的作用是将参数sockfd和addr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。上一篇讲过,struct sockaddr *是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。

2.3 listen( )函数

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
int listen(int sockfd, int backlog);

功能:监听这个socket上的连接

参数:

  • sockfd : 通过socket()函数得到的文件描述符
  • backlog : 未连接的和已经连接的和的最大值(排队建立3次握手队列和刚刚建立3次握手队列的链接数和的最大值)

典型的服务器程序可以同时服务于多个客户端,当有客户端发起连接时,服务器调用的accept()返回并接受这个连接,如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接待状态,如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。

2.4 accept( )函数

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接

参数:

  • sockfd : 用于监听的文件描述符
  • addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)
  • addrlen : 传入传出参数(值-结果), 指定第二个参数的对应的内存大小,传出第二个参数传出时的内存大小

返回值:
成功:返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno

三次握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。addr是一个传出参数,accept()返回时传出客户端的地址和端口号。addrlen参数是一个传入传出参数(value-result argument),传入的是调用者提供的缓冲区addr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给addr参数传NULL,表示不关心客户端的地址。

一般来说,我们的服务器程序结构是这样的:

while (1) {cliaddr_len = sizeof(cliaddr);connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);n = read(connfd, buf, MAXLINE);......close(connfd);
}

整个是一个while死循环,每次循环处理一个客户端连接。由于cliaddr_len是传入传出参数,每次调用accept()之前应该重新赋初值。accept()的参数listenfd是先前的监听文件描述符,而accept()的返回值是另外一个文件描述符connfd,之后与客户端之间就通过这个connfd通讯,最后关闭connfd断开连接,而不关闭listenfd,再次回到循环开头listenfd仍然用作accept的参数。accept()成功返回一个文件描述符,出错返回-1。

一定要区分开connfd和listenfd的作用,listenfd仅用于监听,监听到了以后并不用它来进行信息传输。

2.5 connect( )函数

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能: 客户端连接服务器
参数:

  • sockfd : 用于通信的文件描述符
  • addr : 客户端要连接的服务器的地址信息
  • addrlen : 第二个参数的内存大小

返回值:成功 0, 失败 -1

客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。

3. 实现一个简单的服务端-客户端通信

接下来使用以上函数实现一个简单的服务端客户端通信,实现将客户端的小写字母转换为大写字母。

3.1 通信流程

在这里插入图片描述
上图给了一个C/S模型网络编程的socket模板。

服务端:

  1. 调用 socket() 建立套接字
  2. 创建 struct sockaddr_in ,并初始化服务端的ip和端口号
  3. 调用 bind() 将第一步建立的套接字与第二步的sockaddr_in(ip和端口号)绑定
  4. 调用 listen() 设置最大同时发起连接数量
  5. 调用 accept() 阻塞等待客户端发起连接
  6. 调用 read() 读取客户端发出的数据
  7. 处理请求
  8. 调用 write() 发送数据给客户端
  9. 调用 close() 关闭socket伪文件

客户端:

  1. 调用 socket() 建立套接字
  2. 调用 connect() 向客户端发起连接
  3. 调用 write() 发送数据给服务端
  4. 调用 read() 读取服务端返回的数据
  5. 调用 close() 关闭socket伪文件

其中还有很多细节,需要写代码的时候才能体会。

3.2 服务端

/*实现一个简单的服务器-客户端通信*/#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>// 设定一个服务器端口号
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666int main()
{int lfd; // 用于监听的socket的文件描述符,真正用于通信的套接字是接下来accept函数返回的cfd套接字struct sockaddr_in serv_addr;lfd = socket(AF_INET, SOCK_STREAM, 0);serv_addr.sin_family = AF_INET;// 注意这里,要把小端存储的端口号改为大端存储serv_addr.sin_port = htons(SERV_PORT); inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); // 调用ip转换函数,把字符串ip转化为网络字节序bind(lfd, (struct sockaddr * )&serv_addr, sizeof(serv_addr));listen(lfd, 128); // 最大连接与待连接数设为128int cfd; // 已连接的客户端的socket的文件描述符, 以便一会儿read用struct sockaddr_in clie_addr;                // 作为accept的第二个参数,为传出参数,传出的是客户端的sockadd_insocklen_t clie_addr_len = sizeof(clie_addr); // 作为accept的第三个参数,为传入传出参数,之所以要单独定义出来是因为要传出cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len);while(1) {// 此处输出连接的客户端的ip和端口char clie_IP[BUFSIZ];printf("Client IP: %s, client port: %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),ntohs(clie_addr.sin_port));char buf[BUFSIZ]; // 给read使用,存储读出的数据,BUFSIZ宏是系统用来专门给buf赋长度的宏,为8k(Default buffer size)int len; // read的返回值,是读入字符的长度len = read(cfd, buf, sizeof(buf));// 把小写字母转化为大写字母int i;for(i = 0; i < len; ++i) {if(buf[i] <= 'z' && buf[i] >= 'a') {buf[i] -= 32;}}write(cfd, buf, len);}close(lfd);close(cfd);return 0;
}

以上代码为突出主体,没有写错误判断与错误提示。带错误提示代码如下:

/*实现一个简单的服务器-客户端通信*/#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>// 设定一个服务器端口号
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666int main()
{int lfd; // 用于监听的socket的文件描述符struct sockaddr_in serv_addr;int ret; // 用于错误检测lfd = socket(AF_INET, SOCK_STREAM, 0);if(lfd == -1) {perror("socket error");exit(1);}serv_addr.sin_family = AF_INET;// 注意这里,要把小端存储的端口号改为大端存储serv_addr.sin_port = htons(SERV_PORT); inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); // 调用ip转换函数,把字符串ip转化为网络字节序ret = bind(lfd, (struct sockaddr * )&serv_addr, sizeof(serv_addr));if(ret == -1) {perror("bind error");exit(1);}ret = listen(lfd, 128); // 最大连接与待连接数设为128if(ret == -1) {perror("listen error");exit(1);}int cfd; // 已连接的客户端的socket的文件描述符, 以便一会儿read用struct sockaddr_in clie_addr;                // 作为accept的第二个参数,为传出参数,传出的是客户端的sockadd_insocklen_t clie_addr_len = sizeof(clie_addr); // 作为accept的第三个参数,为传入传出参数,之所以要单独定义出来是因为要传出cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len);if(cfd == -1) {perror("accept error");exit(1);}// 此处输出连接的客户端的ip和端口char clie_IP[BUFSIZ];printf("Client IP: %s, client port: %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),ntohs(clie_addr.sin_port));while(1) {char buf[BUFSIZ]; // 给read使用,存储读出的数据,BUFSIZ宏是系统用来专门给buf赋长度的宏,为8k(Default buffer size)int len; // read的返回值,是读入字符的长度len = read(cfd, buf, sizeof(buf));if(len == -1) {perror("read error");exit(1);}// 把小写字母转化为大写字母int i;for(i = 0; i < len; ++i) {if(buf[i] <= 'z' && buf[i] >= 'a') {buf[i] -= 32;}}ret = write(cfd, buf, len);if(ret == -1) {perror("write error");exit(1);}}close(cfd);close(lfd);return 0;
}

3.3 客户端

#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>// 服务器的ip和端口
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666int main()
{int cfd; // 用于写入数据传输给服务端的socket的文件描述符cfd = socket(AF_INET, SOCK_STREAM, 0);// bind()  可以不调用bind(), linux会隐式地绑定struct sockaddr_in serv_addr; // 因为要连接服务端,这里的sockadd_in是用于指定服务端的ip和端口bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); // 调用ip转换函数,把字符串ip转化为网络字节序connect(cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));while(1) {// 从终端读取内容char buf[BUFSIZ];fgets(buf, sizeof(buf), stdin);  // 读一行// 写入到cfd中,传输给服务端write(cfd, buf, strlen(buf)); // 注意不要写成sizeof(buf),sizeof是在内存中所占的大小,strlen是到第一个'\0'位止。// read在读socket时默认时阻塞的,阻塞等待服务端传输数据int len;len = read(cfd, buf, sizeof(buf));printf("%s", buf);}close(cfd);return 0;
}

以上代码为突出主体,没有写错误判断与错误提示。带错误提示代码如下:

#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>// 服务器的ip和端口
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666int main()
{int ret; // 用于错误检测int cfd; // 用于写入数据传输给服务端的socket的文件描述符cfd = socket(AF_INET, SOCK_STREAM, 0);if(cfd == -1) {perror("socket error");exit(1);}// bind()  可以不调用bind(), linux会隐式地绑定struct sockaddr_in serv_addr; // 因为要连接服务端,这里的sockadd_in是用于指定服务端的ip和端口bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); // 调用ip转换函数,把字符串ip转化为网络字节序ret = connect(cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));if(ret == -1) {perror("connect error");exit(1);}// 从终端读取内容while(1) {char buf[BUFSIZ];fgets(buf, sizeof(buf), stdin);  // 读一行// 写入到cfd中,传输给服务端ret = write(cfd, buf, strlen(buf)); // 注意不要写成sizeof(buf),sizeof是在内存中所占的大小,strlen是到第一个'\0'位止。if(ret == -1) {perror("write error");exit(1);}// read在读socket时默认时阻塞的,阻塞等待服务端传输数据int len;len = read(cfd, buf, sizeof(buf));if(len == -1) {perror("read error");exit(1);}printf("%s", buf);}close(cfd);return 0;
}

3.4 程序运行结果及注意事项

先启动server后启动client,在client的终端输入小写字符后,可见翻译成了大写:
在这里插入图片描述
同时在server端输出了client的ip和端口号
在这里插入图片描述
程序有两个注意事项:

  1. 先启动服务端,再启动客户端。因为先启动客户端的话,服务器来不及监听,客户端就connect了,会导致connect失败。
    在这里插入图片描述
  2. 关闭时先关闭客户端,再关闭服务端。如果先关闭服务端,服务器程序处于TIME_WAIT状态,程序中写死的6666端口号仍然被占用,可以使用netstat -apn | grep 6666查看程序对6666端口占用情况:
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384557.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux网络编程学习】使用socket实现简单服务器——多进程多线程版本

此为牛客Linux C课程和黑马Linux系统编程笔记。 1. 多进程版 1.1 思路 大体思路与上一篇的单进程版服务器–客户端类似&#xff0c;都是遵循下图&#xff1a; 多进程版本有以下几点需要注意&#xff1a; 由于TCP是点对点连接&#xff0c;服务器主进程连接了一个客户端以后…

【Linux网络编程学习】I/O多路复用——select和poll

此为牛客Linux C课程和黑马Linux系统编程笔记。 0. I/O多路复用 所谓I/O就是对socket提供的内存缓冲区的写入和读出。 多路复用就是指程序能同时监听多个文件描述符。 之前的学习中写了多进程和多线程版的简单服务器模型&#xff0c;但是有个问题&#xff1a;每次新来一个客…

【Linux网络编程学习】阻塞、非阻塞、同步、异步以及五种I/O模型

文章目录1. 基本概念1.1 阻塞与非阻塞1.2 同步与异步1.3 为什么没有“异步阻塞”2. 五种IO模型2.1 阻塞 blocking2.2 非阻塞 non-blocking2.3. IO复用&#xff08;IO multiplexing&#xff09;2.4 信号驱动&#xff08;signal-driven&#xff09;2.5 异步&#xff08;asynchron…

STM32时钟树解析

本人之前其实也用STM32做过一些小东西&#xff0c;但因为时钟的初始化一般是直接在SystemInit时钟系统初始化函数里直接配置为72MHz&#xff0c;所以对于STM32的时钟框图并没有怎么理会&#xff0c;今天刚好有空就重新看了一下并写一篇博客记录一下吧&#xff0c;以免以后又忘了…

S3C2440时钟体系

S3C2440在默认情况下&#xff0c;整个系统全靠一个12MHz的外部晶振提供频率来工作运行的&#xff0c;也就是说CPU、内存、UART、ADC等所有需要用到时钟频率的硬件都工作在12MHz下&#xff0c;但是通过查阅芯片手册我们知道CPU时钟最高可为400MHZ&#xff0c;那么怎么设置时钟让…

关于MCU、CPU扩展SDRAM的一个小知识

像上图这种硬件电路图上的16个数据位和我们在初始化SDRAM的时候设置的16位数据位宽是指我们读写SDRAM的时候可以同时读写16个数据位&#xff0c;数据线越多肯定越快&#xff0c;但是数据线也不可能无限增加&#xff0c;我们在程序里是可以读写8位&#xff0c;16位&#xff0c;3…

S3C2440扩展SDRAM

本文主要目的是记录一下S3C2440扩展SDRAM的一些知识&#xff0c;方便以后查阅。 通过查阅手册我们知道&#xff0c;2440有8个可以用来扩展内存的BANK&#xff0c;其中第6和第7还可用来扩展SDRAM 下面我们来看一下2440扩展SDRAM需要设置哪些寄存器。 一、BWSCON寄存器 该寄存器…

汇编语言的相对跳转和绝对跳转以及反汇编代码解析

上图第一行的b1 main为相对跳转&#xff0c;即跳转到pcoffset,其中pc为当前pc值&#xff0c;offset可以理解为偏移地址&#xff0c;也就是根据当前所在地址加上偏移地址实现跳转&#xff0c;为相对跳转。 我们来看看它的反汇编代码 上图清除完bss区后使用b1指令跳转到30000668…

韦东山嵌入式第一期14课第004节_und异常模示程序示例_P笔记

本节课的第一个程序韦老师是想让大家见识一下未定义异常&#xff0c;而第二个程序是对第一个程序进行改进&#xff0c;防止在某些条件下执行不了&#xff0c;下面就来讲一下第2个程序改进了哪些地方并且有什么用。 程序在此路径中&#xff1a;源码文档图片\源码\源码_20180321…

关于NOR FLASH地址左右移的问题

问题引入&#xff1a;不知道你会不会有这样的疑问&#xff1a;为什么在发送解锁命令时&#xff0c;我们不用右移一位&#xff0c;而发送扇区地址时却要右移一位&#xff08;nor_cmd函数内部已经左移一位&#xff09;&#xff0c;这里先补充说明一下说明是cpu角度和nor角度&…

在linux下利用ls命令进行模糊查找

如上图&#xff0c;我们当前路径下有三个文件&#xff0c;分别为helloworld.c以及helloworld和1.c&#xff0c;直接输入命令ls则显示所有文件&#xff0c;我们可以利用ls 加*的方向进行模糊查找。 输入ls 目录名 形式的命令行&#xff0c;则是对该目录名下的文件全部进行显示&a…

Linux下没有包含头文件(不知是哪个)导致编译无法通过的解决心得

最近写程序的时候编译出错了&#xff0c;提示信息为&#xff1a;invalid use of undefined type fb_var_screeninfo。显示根据英文知道是没有定义 fb_var_screeninfo这个类型&#xff0c;明显是缺少了某个头文件&#xff0c;但是缺少哪个头文件以及有什么又快又好的解决方法呢&…

Linux编译程序时加-I指定头文件位置

Linux下编译出现以下错误&#xff0c;错误的原因是在/usr/local/arm/arm-2009q3/bin/../arm-none-linux-gnueabi/libc/usr/include/freetype/config/下找不到ftheader.h&#xff0c;而我到该目录下看&#xff0c;发现路径是这样的rootubuntu:/usr/local/arm/arm-2009q3/arm-non…

关于源文件用不同的编码方式编写,会导致执行结果不一样的现象及解决方法

如果我们编写以下程序&#xff0c;并分别另存为ANSI和UTF-8两种不同的编码方式保存&#xff0c;放到Linux下编译并运行如下图&#xff0c;两端相同的程序以不同的编码方式保存编译后的运行结果不一样&#xff0c;./ansi采用ANSI编码方式&#xff0c;会自动采用GBK方式来保存中文…

arm-linux-gcc静态编译和动态编译的区别

很多教程会提到加上-static是静态编译&#xff0c;但对于新手来说没有用例子来说明可能不太好理解&#xff0c;今天我就介绍一下关于这方面知识的一个例子&#xff1a; 最近在做一个关于freetype字体的东西&#xff0c;需要依赖freetype官方提供的库&#xff0c;我已经把电脑这…

从0到1写RT-Thread内核——线程定义及切换的实现

从0写RT-Thread内核之线程定义及切换的实现具体可以分为以下六步来实现 一&#xff1a;分别定义线程栈、线程函数、线程控制块&#xff1b; ALIGN(RT_ALIGN_SIZE)//设置4字节对齐 /* 定义线程栈 */ rt_uint8_t rt_flag1_thread_stack[512]; rt_uint8_t rt_flag2_thread_stack…

从0到1写RT-Thread内核——临界段的保护

临界段就是一段在执行的时候不能被中断的代码段&#xff0c;在RT-Thread里&#xff0c;临界段最常出现的就是对全局变量的操作&#xff08;类似Linux下的锁&#xff09;。RT-Thread对临界段的保护是直接把中断全部关了&#xff0c;NMI FAULT和硬FAULT除外。下图是3个关于中断屏…

从0到1写RT-Thread内核——空闲线程与阻塞延时的实现

在之前写的另外一篇文章——<从0到1写RT-Thread内核——线程定义及切换的实现>中线程体内的延时使用的是软件延时&#xff0c;即还是让CPU空等来达到延时的效果。RTOS中的延时叫阻塞延时&#xff0c;即线程需要延时的时候&#xff0c;线程会放弃CPU的使用权&#xff0c;C…

从0到1写RT-Thread内核——支持多优先级

在本章之前&#xff0c;RT-Thread还没有支持多优先级&#xff0c;我们手动指定了第一个运行的线程&#xff0c;并在此之后三个线程&#xff08;包括空闲线程&#xff09;互相切换&#xff0c;在本章中我们加入优先级的功能&#xff0c;第一个运行的程序是就绪列表里优先级最高的…

AD软件之模块化原理图

首先我们创建两个原理图文件 然后我们在Sheet2.SchDoc里放置一个页面符并双击绿色的方框 选择目标文件 我们选择我们刚才创建的Sheet4.SchDoc 然后在 视图——>面板——>Navigator选项 里点一下交互式导航 就可以看到Sheet4.SchDoc被添加到Sheet2.SchDoc下面了 通过上面…