嵌入式是Linux(第三天)——线程与进程和网络通讯

2023-12-08 10:50

本文主要是介绍嵌入式是Linux(第三天)——线程与进程和网络通讯,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

线程与进程

线程与进程的概念和区别

进程简单来说就是一个正在运行的程序。包括其运行代码和运行代码所用的资源,一个CPU可以存在多个进程但是同一时间只允许一个进程工作。但CPU切换速度很快,给我们感觉像是所有进程同时运行。

线程是操作系统最小度量单位。线程和进程最大的区别就是共不共享数据,同时线程是进程的一部分,也就是进程可以由多个线程构成。进程好比火车,线程好比车厢。不同火车之间的信息当然不共享,所以用起来比较麻烦,比如说打个电话。而线程好比同一列火车上的不同车厢,走过去打个招呼就能就交流。

使用多线程还是多进程?…Emmm其实我也不知道…曾经我想处理一个图片,但是如果只用一个程序跑的话太慢了,只用一个CPU核,所以我分别尝试了多线程和多进程…结果我的多进程是好用的…所有CPU核全部跑满看着很得劲(当然可能是因为操作系统是WIN的,WIN本身更偏向于多进程,而UNIX类的更多偏向多进程)

创建线程与进程

创建进程
创建进程使用的是fork函数,工作原理如下:
在这里插入图片描述
从上图可以看出,进程是完完整整的把父进程的所有都复制给了子进程,包括数据段空间,代码段空间和堆栈空间等。

fork函数会返回一个值,如果这个值是0的话,就是子进程,是其他的话就是父进程。其他值是子进程的进程号。
下面做个小测试:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{pid_t pid;pid = fork();if (pid == -1){printf("Error");return 0;}else if (pid == 0){printf("Child");}else{printf("Parent %d\n",pid);}return 0;
}

在这里插入图片描述
结果如下…Child其实emmmm是输出结果但是吧,我猜父进程结束,子进程结束。命令行只管父进程?当然不是了…是因为父进程先于子进程结束所以会导致这样的状况,所以emmm就有了等待进程结束的命令waitpid。

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>int main()
{pid_t pid,pid_wait;int status;pid = fork();if (pid == -1){printf("Error");return 0;}else if (pid == 0){printf("Child\n");printf("?");}else{printf("Parent %d\n",pid);pid_wait = waitpid(pid,&status,0);printf("Child process %d returned!\n",pid_wait);}printf("What happened?: %d",pid);return 0;
}

在这里插入图片描述
这就是先进入父进程然后输出了Parent 57616父进程卡住,等待子进程然后子进程结束输出what happened(pid=0判断子进程)然后父进程的What happen。破案了

进程之间的通讯
进程之间的通讯是个很麻烦的事情,有两种方式,一种是管道,一种是共享内存。管道这种方式其实蛮简单的感觉像UART半双工通讯,两个进程之间只能单独写或单独读。
在这里插入图片描述

例子如下:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{int fd[2];pid_t pid;char buf[64] = "I'm parent!\n";char line[64];if (pipe(fd)!=0){fprintf(stderr,"Fauk to create pipe!\n");return 0;}pid=fork();if (pid<0){fprintf(stderr,"Fail to create");return 0;}else if (0==pid){close(fd[0]);//shutdown readwrite(fd[1],buf,strlen(buf));close(fd[1]);//shutdown write}else{close(fd[1]);read(fd[0],line,64);printf("Date from parents:%s",line);close(fd[0]);}	return 0;
}

第二种就是共享内存,共享内存就是在内存中开辟一段空间供不同进程访问。
在这里插入图片描述
写共享内存:
shmget()函数用来创建共享内存,第一个参数是一个特殊标识,只要不重复就可,但一般由ftok()函数生成,第二个参数是大小字节数,第三个是内存操作方式
shmat()是获得一个共享内存ID对应的起始地址。第二个参数是指定共享内存地址,0是首地址,第三个参数一般写0,让代表需要让系统决定共享内存地址
shmdt()分离一块共享内存,估摸着就是释放掉。

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main()
{int shmid;							 // 定义共享内存idchar *ptr;char *shm_str = "string in a share memory";shmid = shmget(0x90, 1024, SHM_W|SHM_R|IPC_CREAT|IPC_EXCL); // 创建共享内存if (-1==shmid)perror("create share memory");ptr = (char*)shmat(shmid, 0, 0);			// 通过共享内存id获得共享内存地址if ((void*)-1==ptr)perror("get share memory");strcpy(ptr, shm_str);					// 把字符串写入共享内存shmdt(ptr);return 0;
}

读共享内存:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main()
{int shmid;							 // 定义共享内存idchar *ptr;char *shm_str = "string in a share memory";shmid = shmget(0x90, 1024, SHM_W|SHM_R|IPC_CREAT|IPC_EXCL); // 创建共享内存if (-1==shmid)perror("create share memory");ptr = (char*)shmat(shmid, 0, 0);			// 通过共享内存id获得共享内存地址if ((void*)-1==ptr)perror("get share memory");strcpy(ptr, shm_str);					// 把字符串写入共享内存shmdt(ptr);return 0;
}

如果再次运行写程序就会报错
在这里插入图片描述
原因是当前共享地址Key用过,毕竟我们写的是0x90是固定的,使用ipcs可以看到在这里插入图片描述
其中的1024是我们创建的,可以用ipcrm -m 4751372释放掉 (4751372是shmid)

创建线程
我们先看个例子然后从例子中学习:

#include <pthread.h>                                               
#include <stdio.h>                                                 
#include <stdlib.h>                                                
#include <unistd.h> void* thread_func(void *arg)							// 线程函数              
{                                                                  int *val = arg;                                                  printf("Hi, I'm a thread!\n");                                   if (NULL!=arg)									// 如果参数不为空,打印参数内容  printf("argument set: %d\n", *val);                            
}                                                                  int main()                                                         
{                                                                  pthread_t tid;									// 线程ID                        int t_arg = 100;								// 给线程传入的参数值            if (pthread_create(&tid, NULL, thread_func, &t_arg))	// 创建线程perror("Fail to create thread");                               sleep(1);										// 睡眠1秒,等待线程执行             printf("Main thread!\n");                                        return 0;                                                        
}                                                                  

可以看到pthread_create函数有4个参数,第一个是线程ID最后会回写的,第二个是用来设置线程属性的,没有就NULL,第三个就是函数指针,指定线程函数,第四个就是指定函数的传入参数。如果创建成功就会返回0,不成功返回错误号。
PS:如果直接gcc -o 编译的话会报错,因为pthread.h不是标准库中的函数,所以要加上参数 -lphread进行编译
在这里插入图片描述
取消线程,看例子就能理解:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> void* thread_func(void *arg)								// 线程函数
{int *val = arg;printf("Hi, I'm a thread!\n");if (NULL!=arg) {									// 如果参数不为空,打印参数内容while(1)printf("argument set: %d\n", *val);}
}int main()
{pthread_t tid;										// 线程IDint t_arg = 100;									// 给线程传入的参数值if (pthread_create(&tid, NULL, thread_func, &t_arg))		// 创建线程perror("Fail to create thread");sleep(1);											// 睡眠1秒,等待线程执行printf("Main thread!\n");pthread_cancel(tid);									// 取消线程return 0;
}

输出结果:

在这里插入图片描述
…最前面一个Hi,I’m thread!然后无数个argumen…然后结束

网络通讯

基础就是大学生计算机基础课程里应该有学过这个图:
在这里插入图片描述
我们主要先看TCP/IP协议也就是传输层和网络互联层的。
IP协议负责数据包的传输管理,实现两个基本功能:寻址和分段
寻址:就是IP协议根据数据报头中的地址传送数据报文。而IP协议根据目的地址选择报文在网络中的传输路径的过程叫做路由。(大家是不是知道…路由器为啥叫路由器了…)
分段:就是为了适应在不同网络中传输TCP/IP协议产生的分段机制…
IPV4协议图
TCP协议是传输层协议,TCP是一个面向连接可靠传输的协议,TCP协议层会对数据包进行排列并错误检测,如果缺少数据包就会重传丢失数据包。(感觉UDP就是TCP的不稳定不安全版本)

Socket通讯

之前废话一堆…其实Socket通讯我觉得最重要,毕竟…怎么实现才最重要么…
面向连接的Socket通信
这是面向连接的Socket通信框图
在这里插入图片描述
我们实现的时候就根据这个框图走。
总结如下:
服务器端工作流程图:

  1. 使用Socket函数创建socket
  2. 通过bind函数把创建的socket句柄绑定到指定TCP端口
  3. 调用listen函数使socket处于监听状态,并设置监听队列大小
  4. 当客户机发送连接请求后,调用accept()函数接收客户端请求,与客户端建立连接
  5. 与客户端发送或接收数据
  6. 通讯完成后,用close关闭socket函数

客户端工作流程

  1. 使用socket函数创建socket
  2. 调用connect函数向服务器socket发起连接
  3. 连接建立后,进行数据读写
  4. 传输完毕后,使用close关闭socket

依旧从程序看操作,演示本机和本机通讯的例子:
服务器:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>#define EHCO_PORT 8080
#define MAX_CLIENT_NUM 10int main()
{int sock_fd;struct sockaddr_in serv_addr;int clientfd;struct sockaddr_in clientAdd;char buff[101];socklen_t len;int closing =0;int n;/* 创建socket */sock_fd = socket(AF_INET, SOCK_STREAM, 0);if(sock_fd==-1) {perror("create socket error!");return 0;} else {printf("Success to create socket %d\n", sock_fd);}/* 设置server地址结构 */bzero(&serv_addr, sizeof(serv_addr));				// 初始化结构占用的内存serv_addr.sin_family = AF_INET;					// 设置地址传输层类型serv_addr.sin_port = htons(EHCO_PORT);			// 设置监听端口serv_addr.sin_addr.s_addr = htons(INADDR_ANY);		// 设置服务器地址bzero(&(serv_addr.sin_zero), 8);/* 把地址和套接字绑定 */if(bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr))!= 0) {printf("bind address fail! %d\n", errno);close(sock_fd);return 0;} else {printf("Success to bind address!\n");}/* 设置套接字监听 */if(listen(sock_fd ,MAX_CLIENT_NUM) != 0) {perror("listen socket error!\n");close(sock_fd);return 0;} else {printf("Success to listen\n");}/* 创建新连接对应的套接字 */len = sizeof(clientAdd);clientfd = accept(sock_fd, (struct sockaddr*)&clientAdd, &len);if (clientfd<=0) {perror("accept() error!\n");close(sock_fd);return 0;}/* 接收用户发来的数据 */while((n = recv(clientfd,buff, 100,0 )) > 0) {buff[n] = '\0'; // 给字符串加入结束符printf("number of receive bytes = %d data = %s\n", n, buff);		// 打印字符串长度和内容fflush(stdout);send(clientfd, buff, n, 0);			// 发送字符串内容给客户端if(strncmp(buff, "quit", 4) == 0)		// 判断是否是退出命令break;}close(clientfd);						// 关闭新建的连接close(sock_fd);					// 关闭服务端监听的socketreturn 0;
}

客户端:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>#define EHCO_PORT 8080
#define MAX_COMMAND 5int main()
{int sock_fd;struct sockaddr_in serv_addr;char *buff[MAX_COMMAND] = {"abc", "def", "test", "hello", "quit"};char tmp_buf[100];socklen_t len;int n, i;/* 创建socket */sock_fd = socket(AF_INET, SOCK_STREAM, 0);if(sock_fd==-1) {perror("create socket error!");return 0;} else {printf("Success to create socket %d\n", sock_fd);}/* 设置server地址结构 */bzero(&serv_addr, sizeof(serv_addr));				// 初始化结构占用的内存serv_addr.sin_family = AF_INET;					// 设置地址传输层类型serv_addr.sin_port = htons(EHCO_PORT);			// 设置监听端口serv_addr.sin_addr.s_addr = htons(INADDR_ANY);		// 设置服务器地址bzero(&(serv_addr.sin_zero), 8);/* 连接到服务端 */if (-1==connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr))) {perror("connect() error!\n");close(sock_fd);return 0;}printf("Success connect to server!\n");/* 发送并接收缓冲的数据 */for (i=0;i<MAX_COMMAND;i++) {send(sock_fd, buff[i], 100, 0);						// 发送数据给服务端n = recv(sock_fd, tmp_buf, 100, 0);					// 从服务端接收数据tmp_buf[n] = '\0';  // 给字符串添加结束标志printf("data send: %s receive: %s\n", buff[i], tmp_buf);		// 打印字符串if (0==strncmp(tmp_buf, "quit", 4))					// 判断是否是退出命令break;}close(sock_fd);									// 关闭套接字return 0;
}

其中AF_INET代表IPv4协议,地址的INADDR_ANY是本机地址也就是0.0.0.0
结果分析:运行服务器程序
在这里插入图片描述
会发现它阻塞在listen,等待客户端发送建立连接请求
然后运行客户端
在这里插入图片描述
接收发来的字符串,遇到quit就关闭连接。
无连接的Socket通讯
实现框图如下:
在这里插入图片描述
最大差别就是没有listen、accpet和connect这些连接环节。其次就是发送和接收函数的改变。
服务器代码:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>#define TIME_PORT 9090
#define DATA_SIZE 256int main()
{int sock_fd;struct sockaddr_in local;struct sockaddr_in from;int fromlen, n;char buff[DATA_SIZE];time_t cur_time;sock_fd = socket(AF_INET, SOCK_DGRAM, 0);		// 建立套接字if (sock_fd<=0) {perror("create socket error!");return 0;}perror("Create socket");/* 设置要绑定的IP和端口 */local.sin_family=AF_INET;local.sin_port=htons(TIME_PORT);// 监听端口local.sin_addr.s_addr=INADDR_ANY;//本机/* 绑定本机到套接字 */if (0!=bind(sock_fd,(struct sockaddr*)&local,sizeof(local))) {perror("bind socket error!");close(sock_fd);return 0;}printf("Bind socket");fromlen =sizeof(from);printf("waiting request from client...\n");while (1){n = recvfrom(sock_fd, buff, sizeof(buff), 0, (struct sockaddr*)&from, &fromlen);	// 接收数据if (n<=0) {perror("recv data!\n");close(sock_fd);return 0;}buff[n]='\0';									// 设置字符串结束符printf("client request: %s\n", buff);					// 打印接收到的字符串if (0==strncmp(buff, "quit", 4))					// 判断是否退出break;if (0==strncmp(buff, "time", 4)) {					// 判断是否请求时间cur_time = time(NULL);strcpy(buff, asctime(gmtime(&cur_time)));			// 生成当前时间字符串sendto(sock_fd, buff,sizeof(buff), 0,(struct sockaddr*)&from,fromlen);	// 发送时间给客户端}}close(sock_fd);								// 关闭套接字return 0;
}

运行服务器,卡在while等待数据
在这里插入图片描述
运行客户端

在这里插入图片描述
Socket超时处理
getsockopt()和setsockopt()
在这里插入图片描述
使用Select处理多连接
因为当recv()函数是阻塞的,导致等待一个客户端返回数据的时候造成整个进程阻塞,而无法接受其他客户端的数据。所以Socket库提供两个函数select()和poll()来解决这个问题。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从书上截图…等用的时候再说…这些标志太多了没有用的话太难懂了。

这篇关于嵌入式是Linux(第三天)——线程与进程和网络通讯的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/469580

相关文章

基于Linux的ffmpeg python的关键帧抽取

《基于Linux的ffmpegpython的关键帧抽取》本文主要介绍了基于Linux的ffmpegpython的关键帧抽取,实现以按帧或时间间隔抽取关键帧,文中通过示例代码介绍的非常详细,对大家的学... 目录1.FFmpeg的环境配置1) 创建一个虚拟环境envjavascript2) ffmpeg-py

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

Windows的CMD窗口如何查看并杀死nginx进程

《Windows的CMD窗口如何查看并杀死nginx进程》:本文主要介绍Windows的CMD窗口如何查看并杀死nginx进程问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录Windows的CMD窗口查看并杀死nginx进程开启nginx查看nginx进程停止nginx服务

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

详解Linux中常见环境变量的特点与设置

《详解Linux中常见环境变量的特点与设置》环境变量是操作系统和用户设置的一些动态键值对,为运行的程序提供配置信息,理解环境变量对于系统管理、软件开发都很重要,下面小编就为大家详细介绍一下吧... 目录前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变

Linux系统中的firewall-offline-cmd详解(收藏版)

《Linux系统中的firewall-offline-cmd详解(收藏版)》firewall-offline-cmd是firewalld的一个命令行工具,专门设计用于在没有运行firewalld服务的... 目录主要用途基本语法选项1. 状态管理2. 区域管理3. 服务管理4. 端口管理5. ICMP 阻断

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Java中常见队列举例详解(非线程安全)

《Java中常见队列举例详解(非线程安全)》队列用于模拟队列这种数据结构,队列通常是指先进先出的容器,:本文主要介绍Java中常见队列(非线程安全)的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一.队列定义 二.常见接口 三.常见实现类3.1 ArrayDeque3.1.1 实现原理3.1.2

SpringBoot3中使用虚拟线程的完整步骤

《SpringBoot3中使用虚拟线程的完整步骤》在SpringBoot3中使用Java21+的虚拟线程(VirtualThreads)可以显著提升I/O密集型应用的并发能力,这篇文章为大家介绍了详细... 目录1. 环境准备2. 配置虚拟线程方式一:全局启用虚拟线程(Tomcat/Jetty)方式二:异步