Linux系统编程、网络编程

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;
}

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值