嵌入式是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 read
		write(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;							 // 定义共享内存id
  char *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;							 // 定义共享内存id
  char *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;										// 线程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");
  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 10

int 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);					// 关闭服务端监听的socket

  return 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 5

int 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 256

int 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()来解决这个问题。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从书上截图…等用的时候再说…这些标志太多了没有用的话太难懂了。

猜你喜欢

转载自blog.csdn.net/u010594449/article/details/105622859