嵌入式是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

相关文章

kali linux 无法登录root的问题及解决方法

《kalilinux无法登录root的问题及解决方法》:本文主要介绍kalilinux无法登录root的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录kali linux 无法登录root1、问题描述1.1、本地登录root1.2、ssh远程登录root2、

Linux ls命令操作详解

《Linuxls命令操作详解》通过ls命令,我们可以查看指定目录下的文件和子目录,并结合不同的选项获取详细的文件信息,如权限、大小、修改时间等,:本文主要介绍Linuxls命令详解,需要的朋友可... 目录1. 命令简介2. 命令的基本语法和用法2.1 语法格式2.2 使用示例2.2.1 列出当前目录下的文

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

Spring Boot3虚拟线程的使用步骤详解

《SpringBoot3虚拟线程的使用步骤详解》虚拟线程是Java19中引入的一个新特性,旨在通过简化线程管理来提升应用程序的并发性能,:本文主要介绍SpringBoot3虚拟线程的使用步骤,... 目录问题根源分析解决方案验证验证实验实验1:未启用keep-alive实验2:启用keep-alive扩展建

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc