首页 > 系统相关 >Linux系统编程、网络编程

Linux系统编程、网络编程

时间:2024-08-12 11:25:14浏览次数:14  
标签:pthread addr int void 编程 网络 线程 Linux include

Linux系统编程、网络编程


前言

提示:这里可以添加本文要记录的大概内容:

linux系统编程一些疑惑点,
linux网络编程实现


一、进程的退出

正常退出

①Main函数调用return
②进程调用exit(),标准c库
③进程调用_exit()或者_Exit(),属于系统调用

补充:
④进程最后一个线程返回
⑤最后一个线程调用pthread_exit

异常退出

1.主动调用API abort
2.当进程收到某些信号时,如ctrl+C
3.最后一个线程对取消(cancellation),请求做出响应

父进程等待子进程退出

为什么要等待子进程退出

父进程等待子进程退出,并收集子进程退出状态
子进程退出状态不被收集,变成僵尸进程

1.不关心子进程的退出状态:wait()
2.关心子进程的的退出状态:wait(&status) ,
子进程退出exit(2)括号里面的数字自己设,是一种标记。退出后如果父进程里有调用wait(&status)函数,wait会收集子进程exit(2)返回的标识2,存在status中。需要用WEXITSTATUS(status)来翻译出来。

二、消息队列

消息队列操作:

1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

形象举例:

假设你有一个忙碌的家庭,父母和孩子们需要在家里留言通知彼此。父母有一个留言板,孩子们可以写上自己的消息并贴在留言板上。这里的留言板就相当于消息队列,孩子是发送者,父母是接收者。

msgget(key_t key, int flag):创建一个新的留言板,父母和孩子们需要确定留言板的ID号码以及权限,才能够使用这个留言板。key就是留言板的ID号码,flag就是设置留言板的权限。

msgsnd(int msqid, const void *ptr, size_t size, int flag):孩子们写好了消息,需要传递给留言板,msqid就是留言板的ID(哪一个留言板),ptr就是孩子们的消息内容,size就是消息的长度,flag就是发送消息的方式。

msgrcv(int msqid, void *ptr, size_t size, long type,int flag):父母需要去留言板上查看孩子们写的消息,msqid就是留言板的ID(哪一个留言板),ptr就是存放消息内容的地方,size就是接收消息的长度,type就是选择接收哪种类型的消息,flag就是接收消息的方式。

msgctl(int msqid, int cmd, struct msqid_ds *buf):父母需要对留言板进行一些操作,比如清空留言板或者删除留言板,msqid就是留言板的ID(哪一个留言板),cmd就是操作的指令,buf就是存放操作结果的地方。

疑惑点

疑惑点一

为什么下面这段代码里的msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);的第第三个参数不是sizeof(sendBuf),而只传了sendBuf.mtext的大小,那sendBuf结构体中的long mtype为什么不用传,为什么不是传整个结构体的长度?

//GPT回答:首先sizeof(sendBuf)包含了mtype和mtext的大小,但msgsnd()函数只需要消息内容(mtext)的大小,所以用strlen(sendBuf.mtext)。(消息类型)mtype不需要传入msgsnd()函数的长度参数中,因为mtype(数据类型)的作用只是:消息队列是根据mtype来进行消息分类和筛选的,msgsnd()函数在发送消息时会根据mtype进行筛选匹配。

//我简而言之:因为msgsnd函数中封装好了它做了处理,我们传&sendBuf地址给它,msgsnd函数内部会根据这个地址自动通过我们设置的类型 sendBuf.mtype 来进行消息分类和匹配,然后 msgsnd函数 内部再以 sendBuf.mtext 作为我们需要传送的数据为长度,并且我们也只需要发送这个数据。

msgSend.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
//        int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
//                      int msgflg);
struct msgbuf {
               
	long mtype;       /* message type, must be > 0 */
        char mtext[256];    /* message data */
};


int main()
{
	//1.huoqu
	struct msgbuf sendBuf = {888,"this is message from quen"};	
	struct msgbuf readBuf;

	memset(&readBuf,0,sizeof(struct msgbuf));
	key_t key;
        key = ftok(".",'m');
        printf("key=%x\n",key);

        int msgId = msgget(key, IPC_CREAT|0777);

	if(msgId == -1 ){
		printf("get que failuer\n");
	}
	
	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);  
	//为什么这里传sendbuf的地址,但是第三个参数传数据长度的时候却只传sendBuf.mtext的长度,而不是整个结构体的长度?
	//GPT回答:首先sizeof(sendBuf)包含了mtype和mtext的大小,但msgsnd()函数只需要消息内容的大小,所以用strlen(sendBuf.mtext)。mtype不需要传入msgsnd()函数的参数中,因为mtype(数据类型)的作用只是:消息队列是根据mtype来进行消息分类和筛选的,msgsnd()函数在发送消息时会根据mtype进行筛选匹配。
    //我简而言之:因为msgsnd函数中封装好了它做了处理,我们传&sendBuf地址给它,msgsnd函数内部会根据sendBuf.mtype进行消息分类和匹配,然后msgsnd函数内部会以sendBuf.mtext为我们需要传送的数据为长度,我们也只需要发送这个数据
    
	printf("send over\n");

        msgrcv(msgId, &readBuf,sizeof(readBuf.mtext),988,0);
	printf("reaturn from get:%s\n",readBuf.mtext);
	
	msgctl(msgId,IPC_RMID,NULL);
	
	return 0;
}

三、信号

疑惑点

1.signal函数的原型:

按照下图的紫色字体,一步一步拆解:
在这里插入图片描述

四、线程

疑惑点

1.线程的创建

#include <pthread.h>

/**
 * 创建一个新线程
 * 
 * pthread_t *thread: 指向线程标识符的指针,线程创建成功时,用于存储新创建线程的线程标识符
 * const pthread_attr_t *attr: pthead_attr_t结构体,这个参数可以用来设置线程的属性,如优先级、栈大小等。如果不需要定制线程属性,可以传入 NULL,此时线程将采用默认属性。 
 * void *(*start_routine)(void *): 一个指向函数的指针,它定义了新线程开始执行时的入口点。这个函数必须接受一个 void * 类型的参数,并返回 void * 类型的结果
 * void *arg: start_routine 函数的参数,可以是一个指向任意类型数据的指针
 * return: int 线程创建结果
 *             成功 0
 *             失败 非0
 */
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);

1.1 int pthread_create函数每个参数的含义

int pthread_create (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)

①pthread_t *thread

1.每个线程都有一个唯一的标识符(即线程ID),这个标识符是通过pthread_t类型的变量来表示的,当pthread_create成功创建一个线程时,它会将新线程的标识符存储在thread参数指向的位置。

2.pthread_t 定义在头文件<pthreadtypes.h>中,实际上是long类型(long和long int是相同类型的不同写法)的别名。

typedef unsigned long int pthread_t;

②const pthread_attr_t *attr

pthead_attr_t 结构体,这个参数可以用来设置线程的属性,如优先级、栈大小等。 如果不需要定制线程属性,可以传入 NULL,此时线程将采用默认属性。

③void*(* start_routine) (void * )

1.这个参数中的每个*的含义。
【(* start_routine)函数指针】的【变量start_routine】是一个指向函数的指针,
该函数有一个【void类型的参数】 (void * )
该函数返回一个【void类型的指针】void*
通俗易懂的解释就是,这行代码定义了一个指针变量, 该指针变量指向一个【可以接受任意类型的参数】并返回【任意类型的指针】的函数。 即:它的参数和返回值都是void *指针。

2.其实函数名是一种指针,它指向函数的起始地址。‌
所以 void*(* start_routine) (void * )就相当于void* start_routine (void * )

当我们通过函数名调用函数时,‌程序实际上是通过这个地址找到并执行函数的代码。‌因此,‌可以说函数名是一种指针,‌它指向函数的起始地址。‌这种特性使得我们可以将函数名赋值给一个指针变量,‌即函数指针,‌通过这个函数指针来间接调用函数。‌

④void *arg

void * arg是 pthread_create函数的 参数 void*(* start_routine) (void * ) 的参数;
void * arg 对应 void*(* start_routine) (void * ) 的 (void * ),
void * arg 传入的值给 void*(* start_routine) (void * ) 的 (void * ),
void * arg可以是一个指向任意类型数据的指针。

1.2 int pthread_create函数的返回值return

  • return: int 线程创建的返回值(结果)
  •         成功 0
    
  •         失败 非0
    

2.线程的退出

单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:

  • 线程只是从启动例程中返回,返回值是线程的退出码。
  • 线程可以被同一进程中的其他线程取消。
  • 线程调用pthread_exit

2.1 pthread_exit

#include <pthread.h>
int pthread_exit(void *rval_ptr);

rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。

3.互斥锁

3.1 死锁

前提条件:至少有两把锁
一开始,线程1手里拿着a锁,线程2手里拿着b锁;
然后呢,线程1又想去拿到b锁,线程2又想拿到a锁;
两个线程互相都想拿到对方手里的锁
,但是又拿不到,所以两个线程就一直卡拿锁那一步。导致死锁。

五.网络编程

1.字节序

在这里插入图片描述

Little endian 小端字节序 :把数据的低位先放在内存的中起始位置
Big endian 大端字节序 :把数据的高位先放在内存的中起始位置
一般网络传输时的字节序 =大端字节序
x86系列CPU都是 = 小端字节序.

在这里插入图片描述

1.1 字节序转换api

在这里插入图片描述
#include <netinet/in.h>

uint16_t htons(uint16_t host16bitvalue); //返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue); //返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue); //返回主机字节序的值
uint32_t ntohl(uint32_t net32bitvalue); //返回主机字节序的值

这里的h代表host主机,n代表net网络,s代表short(两个字节),l代表long(4个字节) 以第一条api为例:htons的意思就是h(host) to n(net) s(net),主机 to转换成 网络字节序 短型

2.Socket服务器和客户端的开发步骤

在这里插入图片描述
如上图,TCP服务器和TCP客户端连接的过程如下:

①首先TCP服务器这边,先调用socket()来创建套接字。
套接字这个术语的由来可以追溯到英文单词"socket",意思是 “插座"或"接口” 。在计算机网络编程中,套接字就像是一种插座,它提供了通信的接口,使得不同计算机之间能够进行数据传输和通信。
②调用bind() 给套接字绑定IP地址和端口号
③调用listen() 监听网络连接,等别人来连
————————————
④TCP客户端这边调用socket()来创建套接字
⑤如图红色线,因为TCP客户端知道TCP服务器的IP地址和端口号,所以TCP客户端直接调用connect() 去连接TCP服务器
————————————
⑥TCP服务器监听到有客户端申请接入,调用accept() 接受这个客户端的连接
————————————
⑦TCP客户端和TCP服务端,进行数据的交互
⑧关闭套接字,断开连接

3.Socket连接使用的API详解

【服务器部分】— — — — — — — — — — — — — — — —

3.1 int socket() —— 指定讲“汉语”(连接协议TCP/UDP)

int socket (int domain, int type, int protocol);

在这里插入图片描述

3.2 int bind() —— 地址准备好(我ip地址(楼号)是。我端口号(房间号)是。。)

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

在这里插入图片描述

3.2.1 端口号需要用到 字节序转换API(看1.1)

因为这里的端口号会传到网络上去,
我们现在主机是x86系列CPU,都是小端字节序
而网络传输用的是大端字节序
所以需要使用字节序转换API来转换一下。

3.2.2 地址转换API
int inet_aton(const char* straddr,struct in_addr *addrp);

字符串形式的"192.168.1.123"转为网络传输能识别的格式
例:int inet_aton(“192.168.1.123”, &s_addr.sin_addr);
此时变量s_addr.sin_addr的值就是网络能识别的格式。

char* inet_ntoa(struct in_addr inaddr);

网络格式的ip地址转为字符串形式(我们能看得懂的形式)

3.3 listen() —— 监听(等待大家的来访,等别人敲门)

int listen(int sockfd, int backlog);

在这里插入图片描述

3.4 accept() —— 接受连接(同意别人进来房间)

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

在这里插入图片描述
出错的时候返回值是-1,
成功的时候返回值是新的套接字描述符

3.5 数据收发

ssize_t write(int fd, const void*buf, size_t nbytes); ssize_t read(int fd, void *buf, size_t nbyte);

在这里插入图片描述

3.6 数据收发常用的第二套API(就比上面第一套的多一个flags参数,flags主要用来控制是否有阻塞等等。其它前面三个参数是一模一样)

ssize_t send(int s, const void *msg, size_t len, int flags); ssize_t recv(int s, void *buf, size_t len, int flags);

在这里插入图片描述

【客户端部分】— — — — — — — — — — — — — — — —

3.7 客户端创建套接字socket()

3.8 客户端的connect函数

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

在这里插入图片描述

4.代码——Socket服务器和客户端的开发代码

4.1 Socket服务器

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
//chenlichen
int main(int argc, char **argv)
{
	int s_fd;
	int c_fd;
	int n_read;
	char readBuf[128];
	
	int mark = 0;
	char msg[128] = {0};
	//	char *msg = "I get your connect";
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;

	if(argc != 3){
		printf("param is not good\n");
		exit(-1);
	}

	memset(&s_addr,0,sizeof(struct sockaddr_in));
	memset(&c_addr,0,sizeof(struct sockaddr_in));

	//1. socket
	s_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(s_fd == -1){
		perror("socket");
		exit(-1);
	}

	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&s_addr.sin_addr);


	//2. bind
	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));

	//3. listen
	listen(s_fd,10);
	//4. accept
	int clen = sizeof(struct sockaddr_in);
	while(1){

		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1){
			perror("accept");
		}

		mark++;
		printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
	
		if(fork() == 0){

			if(fork()==0){
				while(1){
					sprintf(msg,"welcom No.%d client",mark);
					write(c_fd,msg,strlen(msg));
					sleep(3);
				}	
			}	

			//5. read
			while(1){
				memset(readBuf,0,sizeof(readBuf));
				n_read = read(c_fd, readBuf, 128);
				if(n_read == -1){
					perror("read");
				}else if(n_read>0){
					printf("\nget: %d\n",n_read);
				}else{
					
					printf("client quit\n");
					break;
				}
			}
			break;
		}

	}
	return 0;
}

4.2 Socket客户端

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv)
{
	int c_fd;
	int n_read;
	char readBuf[128];
	int tmp;

	//	char *msg = "msg from client";
	char msg[128] = {0};
	struct sockaddr_in c_addr;

	memset(&c_addr,0,sizeof(struct sockaddr_in));

	if(argc != 3){
		printf("param is not good\n");
		exit(-1);
	}

	printf("%d\n",getpid());
	//1. socket
	c_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(c_fd == -1){
		perror("socket");
		exit(-1);
	}

	c_addr.sin_family = AF_INET;
	c_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&c_addr.sin_addr);

	//2.connect	
	if(connect(c_fd, (struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
		perror("connect");
		exit(-1);
	}
	while(1){

		if(fork()==0){
			while(1){
				memset(msg,0,sizeof(msg));
				printf("input: ");
				gets(msg);
				write(c_fd,msg,strlen(msg));
			}
		}

		while(1){
			memset(readBuf,0,sizeof(readBuf));
			n_read = read(c_fd, readBuf, 128);
			if(n_read == -1){
				perror("read");
			}else{
				printf("\nget:%s\n",readBuf);
			}
		}
	}
	//3.send

	//4.read


	return 0;
}

标签:pthread,addr,int,void,编程,网络,线程,Linux,include
From: https://blog.csdn.net/Thenunaoer/article/details/140064845

相关文章

  • Linux-文件编程
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档Linux-文件编程前言一、文件1.文件的打开和创建2.文件的写入3.文件的读取4.文件的光标移动5.实现cp命令(复制文件)二、进程父进程与子进程(创建进程发生了什么事)(面)main()二、使用步骤1.引入库2.读入数据总......
  • 通过JUnit源码分析学习编程的奇技淫巧
    打开Maven仓库,左边选项栏排在第一的就是测试框架与工具,今天的文章,V哥要来聊一聊程序员必备的测试框架JUnit的源码实现,整理的学习笔记,分享给大家。有人说,不就一个测试框架嘛,有必要去了解它的源码吗?确实,在平时的工作中,我们只要掌握如何使用JUnit框架来帮我们测试代码即可,搞什......
  • 341本阿里巴巴系列精品编程技术电子书合集
    获取方式341本阿里巴巴系列精品编程技术电子书合集。分享推荐电子资料《阿里巴巴Java开发手册》(终极版)《阿里云实时计算Flink版解决方案白皮书-2021版》《Python脚本速查手册》《Shell脚本速查手册》《Flutter企业级应用开发实战手册》《云原生开发者洞察白皮书》......
  • Realtek 网卡驱动程序是用于操作系统与 Realtek 网络适配器之间的通信软件。这些驱动
    Realtek网卡,特别是用于个人电脑和服务器的网卡,曾经发现过一些安全漏洞。以下是一些常见的Realtek网卡漏洞及其相关信息:CVE-2020-28015:这个漏洞存在于RealtekRTL8188EU驱动程序中,影响了在特定情况下的无线网络连接。攻击者可以利用这个漏洞执行任意代码或引发系统崩溃。......
  • 奥运会期间网络安全防护的重要性
    北京时间凌晨3点,历史17天的巴黎奥运会迎来落幕,在此先祝贺伟大的祖国获得了总奖牌榜第二,金牌榜并列第一的好成绩,对征战在奥运会赛场的运动健儿们致敬!奥运会,作为全球规模最大、影响力最广的体育盛事,吸引了世界各国的目光。然而,在这个盛大的舞台背后,网络安全防护扮演着至关重要的......
  • 车载网络测试实操源码_使用CAPL脚本对CAN总线上的错误帧进行实时监控
    系列文章目录车载网络测试实操源码_使用CAPL脚本解析hex、S19、vbf文件车载网络测试实操源码_使用CAPL脚本对CAN报文的Counter、CRC、周期、错误帧进行实时监控车载网络测试实操源码_使用CAPL脚本模拟发送符合协议要求(Counter和CRC)的CAN报文车载网络测试实操源码_使用......
  • linux笔记(1):ubuntu环境下,基于SDL2运行lvgl+ffmpeg播放mp4
    文章目录1.ubuntu安装ffmpeg1.1源码安装1.1克隆ffmpeg源码1.2配置编译条件,编译,安装1.2直接安装依赖包2.下载lvgl源码2.1测试原始代码2.2运行lv_example_ffmpeg_2()例程2.2.1配置LV_USE_FFMPEG为12.2.2lv_example_ffmpeg_2()替换lv_demo_widgets()2.2.3链接......
  • 二、Linux系统安装和基本使用
    Linux系统安装和基本使用这里我想记录自己在学习中遇到的有趣的、让自己觉得学到了的点。Vim的使用文章中举出的两个gitpower的例子非常有意思,我们来分析一下:宏录制Thefirstexampleistogeneratethefollowingfile:123.....9899100Thisfilecontains100......
  • Nexpose v6.6.264 for Linux & Windows - 漏洞扫描
    Nexposev6.6.264forLinux&Windows-漏洞扫描Rapid7VulnerabilityManagement,releaseAug07,2024请访问原文链接:https://sysin.org/blog/nexpose-6/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.org您的本地漏洞扫描程序搜集通过实时覆盖整个网络,随......
  • 【Linux】编辑器vim入门(概念+模式转换+技巧)
    vim编辑器1.1.什么是vim?1.2.下载vim:1.3.......