IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 进程编程进程间通信 -> 正文阅读

[系统运维]进程编程进程间通信

一、进程的概念

1.什么是程序:

存放在磁盘上的指令和数据的有序集合(文件)是静态的

2.什么是进程 :

进程是程序的一次执行过程,是动态的包括创建,调度,执行和消亡

3.进程内容:

在这里插入图片描述
系统数据包括:进程控制块(进程PID进程号,进程用户,状态,优先级,文件描述符表)CPU寄存器(进程调度,实现宏观上的并发),堆栈

4.进程类型:

交互进程
批处理进程
守护进程

5.进程状态

运行态
等待态
可中断
不可中断
停止态
死亡态
在这里插入图片描述

二、查看进程信息

1.ps 查看系统进程快照
2.top 查看进程动态信息
3. ./proc 文件查看

三 、进程相关命令

1.nice 按用户优先级运行进程

3.renice 改变正在运行的进程优先级
4.jobs 查看后台进程
5.bg 将挂起的进程在后台运行
6.fg 把后台运行的进程放到前台运行

四、进程相关命令

1.进程的创建-----fork()

创建信的进程,失败是返回-1;
成功是父进程返回进程的进程号,子进程返回0;
通过返回值区分父子进程;


#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main ()
{
    pid_t pid;
    if((pid = fork()) < 0){
        perror("fork");
        exit(-1);
    }else if(pid == 0){
        printf("child process: my pid = %d\n",getpid());  
    }else{
        printf("parent process: my pid = %d\n",getpid());  
    }
    return 0;
}

2.父子进程的关系

子进程继承了父进程的内容
父子进程有独立的地址空间,互不影响
若父进程先结束: 子进程成为孤儿进程,被init进程收养 ,子进程变成后台进程
若子进程先结束:父进程没有及时回收子进程变成僵尸态进程.
子进程从何时开始运行?
从fork下一条语句开始运行
父子进程谁先执行?
内核调度,都有可能先运行.

3.进程结束 exit/_exit

void exit(int status);
void _exit(int staus);
结束当前进程并将status返回
exit结束进程时回刷新缓冲区

int main ()
{
    printf("hello");
    exit(0);
    printf("word");
}

运行结果:hello

int main ()
{
    printf("hello");
    _exit(0);
    printf("word");
}

运行结果:没有任何显示.

4.exec 函数族

进程调用exec 函数族执行某个程序
进程当前的内容被指定程序替换
实现让父子进程执行不同的程序:
1.父进程创建子进程
2.子进程调用exec函数族
3.父进程不受影响
(shell终端)

① execl/execlp

int execl(const char *path,const char *arg,…);
const *path : 执行的程序名称,包含路径;
const char arg :传递给执行程序的参数列表,最后一个传NULL
成功返回指定程序;失败返回EOF
int execlp(const char
file,const char *arg,…);
const *file : 执行的程序名称,不包含路径;
在PATH中查找
const char *arg :传递给执行程序的参数列表,最后一个传NULL
成功返回指定程序;失败返回EOF

② execv/execvp

int execv(const char *path,char *const argv[],…);
对比:execl 只是把执行程序的参数换成字符指针数组
int execvp(const char *file,char *const argv[],…);
对比:execlp只是把执行程序的参数换成字符指针数组
比上面的更灵活

③system

int system (const char *command);

成功返回命令commamd 的返回值;失败返回EOF
自动创建子进程.
当前的进程等待commamd 进程结束才继续运行.

5.进程回收

①pid_t wait(int *status);

pid_t wait(int *status);
成功返回子进程进程号,失败返回EOF
若子进程没有结束,父进程一直阻塞
若多个子进程,哪个先结束就先回收.
status 指定保存子进程返回值的结束方式
status为NULL标识直接释放子进程PCB,不接收返回值
子进程通过exit/_exit/return 返回某个值(0-255);
父进程调用wait(&status)回收
WIFEXITED(status) 判断进程是否正常结束
WEXITSTATUS(status) 获取子进程返回值
WIFSIGNALED(status) 判断子进程是否被信号结束
WTERMSIG(status); 获取结束子进程的信号类型

② pid_t waitpid(pid_t pid ,int *status,int option);

pid_t waitpid(pid_t pid ,int *status,int option);
成功返回回收的子进程的pid或者0(子进程还没结束),失败返回EOF
pid 可用于指定回收哪个子进程或者任意子进程(传-1);
status指定用于保存子进程返回值和结束方式的地址
option 指定回收方式 0(阻塞方式) 或者WNOHANG(非阻塞)

五、守护进程

通常在系统启动是运行,系统关闭时结束
守护进程特点:始终在后台运行,独立于任何终端(避免终端关闭的时候守护进程退出),周期性的执行某种任务或者等待处理特定的事件.

1.守护进程的创建

1.创建子进程,父进程退出

if(fork()>0){
	exit(0);
}

子进程变成孤儿进程,被init 进程收养
子进程在后台运行
2.子进程创建新会话

if(setsid()<0){
	exit(-1);
}

子进程成为新的会话组组长
子进程脱离原先的终端
3.更改当前进程的工作目录

chdir("/");
chdir("/tmp");

守护进程一直在后台运行,其工作目录不能被卸载
4.重设文件权限掩码

if(umask(0)<0){
	exit(-1);
}

5.关闭打开的文件描述符


int i;
for(i=0;i<gettablesize();i++){
	close(i);
}

关闭所有从父进程继承的打开文件
已经脱离终端, stdin/stout/stderr无法再使用

创建守护进程,每隔一秒向time.log里写人系统时间

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    FILE *fp;
    time_t t;
    int i;
    if((pid = fork())<0){
        perror("fork");
        exit(-1);
    }else if(pid>0){
        exit(0);
    }
    setsid();
    umask(0);
    chdir("/tmp");
    for(i=0;i<getdtablesize();i++){
        close(i);
    }
    if((fp = fopen("time.log","a")) == NULL){
        perror("fopen");
        exit(-1);
    }
    while(1){
        time(&t);  
        fprintf(fp,"%s",ctime(&t));
        fflush(fp);
        sleep(1);
    }
    return 0;

}

在这里插入图片描述

六、进程间通信

1.无名管道(pipe)

在这里插入图片描述
无名管道的特点:
只能用于具有亲缘关系 的进程之间的通信(无名管道文件系统不可见,非亲缘关系的不认识此文件描述符)
单工(不能同时写和读)的通信模式,具有固定的读端和写端
无名管道创建是会返回两个文件描述符,分别用于读写管道

无名管道的创建:

int pipe(int pfd[2]);

成功返回0,失败返回-1;
pfd:包含两个元素的整形数组,用于保存文件描述符
pfd[0] 读管道 pfd[1] 写管道
实现子进程一和子进程二分别写消息,父进程读


#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main ()
{
    char buf[32];
    pid_t pid1,pid2;
    int pfd[2];

    if(pipe(pfd) <0){
        perror("pipe");
        exit(-1);
    }
    if((pid1 = fork())<0){
        perror("fork:");
        exit(-1);
    }else if(pid1 == 0){
        strcpy(buf,"I'm proess1");
        write(pfd[1],buf,32);
        exit(0);
    }
    if((pid2 = fork())<0){
        perror("fork:");
        exit(-1);
    }else if(pid2 == 0){
        sleep(1);
        strcpy(buf,"I'm proess2");
        write(pfd[1],buf,32);
        exit(0);
    }else{
        wait(NULL);
        read(pfd[0],buf,32);
        printf("%s\n",buf);
        wait(NULL);
        read(pfd[0],buf,32);
        printf("%s\n",buf);
    }
    
    return 0;
}

管道的读写特性

读无名管道:
1.写端存在
有数据 read返回实际读取的字节数
无数据 进程读堵塞
2.写端不存在
有数据 read返回实际读取的字节数
无数据 read返回0
写无名管道
1.读端存在
有空间 write返回实际写入的字节数
无空间 写进程堵塞
2.读端不存在
管道断裂(被信号结束进程);

2.有名管道(fifo)

有名管道的特点:
任意进程间的通信
打开管道时可以指定读写方式
通过文件IO,内容存放在内存中

有名管道的创建

int mkfifo(const char *path,mode_t mode);

成功返回0失败返回-1;
path :创建的管道文件的路径
mode: 管道文件的权限,如0666;
例: 进程1 循环往有名管道里输入,输入quit时候退出
进程2 读管道并且输出到终端上

创建管道:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    if(mkfifo("myfifo",0666)<0){
        perror("mkfifo:");
        exit(-1);
    }
    return 0;
}

写管道:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#define N 32
int main()
{
    int fd;
    char buf[N];
    if((fd=open("myfifo",O_WRONLY))<0){
        perror("open:");
        exit(-1);
    }
    printf("write open success\n");
    while(1){
        fgets(buf,N,stdin);
        if(strcmp(buf,"quit\n") == 0) break;
        write(fd,buf,N);
    }
    close(fd);
    return 0;
}

读管道:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#define N 32
int main()
{
    int fd;
    char buf[N];
    if((fd=open("myfifo",O_RDONLY))<0){
        perror("open:");
        exit(-1);
    }
    printf("read open success\n");
    while(read(fd,buf,N)>0){
        printf("From write:%s\n",buf);
    }
    close(fd);
    return 0;
}

3.信号(signal)

信号是在软件层次上对中断机制的一种模拟,是一种一部通信方式.

常用信号

在这里插入图片描述
在这里插入图片描述

信号相关命令 kill/killall

kill [-singnal] pid
默认发送SIGTERM
killall [-u user|prog]
prog :进程名字
-u user :指定用户的所有进程发信号

信号发送 kill/raise

int kill(pid_t pid,int sig);
int raise(int sig);

int kill(pid_t pid,int sig);
成功返回0,失败返回-1;
pid : 接收进程的进程号;0代表同组进程;-1代表所有进程
sig:信号类型
int raise(int sig);
给本进程发送信号;
定时器:

int alarm(unsigned int seconds);

成功返回上个定时器的剩余时间,失败返回-1
seconds:定时器时间
一个进程只能设定一个定时器,时间到时产生SIGALRM信号

int pause(void);

进程一直阻塞,知道被信号中断
被信号中断返回-1;

#include <stdio.h>
#include <unistd.h>

int main()
{
    alarm(3);
    pause();
    printf("hello world\n");
    return 0;
}

运行结果
在这里插入图片描述

设置信号的响应方式 signal

void (*signal(int signo,void (*handler)(int)))(int);

成功返回原先的信号处理函数,失败返回SIG_ERR
signo :要捕捉的信号
handler :信号处理函数 :SIG_DFL代表缺省方式,SIG_IGN代表忽略信号;

捕捉信号:

#include<stdio.h>
#include<signal.h>
#include <unistd.h>

void handler(int signo)
{
  if(signo == SIGINT){
    printf("From singal: SIGINT\n");
  }else if(signo == SIGQUIT){
    printf("From singal: SIGQUIT\n");
  }  
}
int main ()
{
    signal(SIGINT,handler);
    signal(SIGQUIT,handler);
    while(1) pause();
    return 0;
}

高级信号量

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
//signum->>要捕捉的信号  
//const struct sigaction *act    sigaction 结构体
// struct sigaction *oldact->>备份不备份NULL
struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
          //int ->信号   siginfo_t->>结构体库里的里面会收取信息
          // void *   发送过来的内容
               sigset_t   sa_mask;  默认阻塞
               int        sa_flags; 接受数据    SA_SIGINFO
               void     (*sa_restorer)(void);
           };

int sigqueue(pid_t pid, int sig, const union sigval value);
pid //接收信号进程的pid;
const union sigval value   //联合体绑定信息
 	union sigval {
               int   sival_int;    //整形
               void *sival_ptr;    //字符串
           };


接收信号:

#include <stdio.h>
#include <signal.h>
void handler(int signum, siginfo_t *info, void * context)
{
        printf("sifnum=%d\n",signum);
        if(context!=NULL){
                printf("form data=%d\n",info->si_int);
                printf("form data=%d\n",info->si_value.sival_int);

        }
}
int main ()
{
        struct sigaction act;
        act.sa_sigaction=handler;
        act.sa_flags=SA_SIGINFO;
        printf("pid=%d\n",getpid());
        sigaction(SIGUSR1,&act,NULL);
        while(1);
        return 0;
}

发出信号:

#include <stdio.h>
#include <signal.h>
int main (int argc,char **argv)
{
        int pid;
        int signum;
        signum=atoi(argv[1]);
        pid=atoi(argv[2]);
        union sigval value;
        value.sival_int=101;
        sigqueue(pid,signum,value);
        printf("done\n");
        return 0;
}

4.共享内存

key_t ftok(const char *path,int proj_id);
成功返回合法的key值,失败返回-1;
path:存在且课访问的文件的路径
proj_id:用于生成key的值,不能是0

共享内存的特点

共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
由于多个进程可同时访问共享内存,因此需要同步和互斥机制互相配合使用

共享内存的使用步骤

1.创建/打开共享内存

int shmget(key_t key,int size,int shmfig);
成功返回共享内存的ID,失败返回-1;
key :key和共享内存关联的key,IPC_PRIVATE(私有的共享内存)或ftok生成
size;共享内存的大小;
shmfig:共享内存标志位 IPC_CREAT|0666

2.映射共享内存(把指定的共享内存映射到进程的地址空间)

void *shmat(int shm_id, const void *addr, int flag);
成功返回映射后的地址
shm_id:要映射的共享内存ID
addr:映射后的地址,NULL表示系统自动映射
flag:标志位0表示可读可写;SHM_RDONLY表示只读

3.读写共享内存
通过指针访问共享内存;

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main ()
{
        key_t key;
        key=ftok(".",2);
        char *sh;
        int shmid=shmget(key,1024*4,0);
        if(shmid==-1){
                printf("shmget NO\n");
        }
        sh=shmat(shmid,0,0);
        printf("From write=%s\n",sh);
        shmdt(sh);
        return 0;
}


#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main ()
{
        key_t key;
        key=ftok(".",2);
        char *sh;
        int shmid=shmget(key,1024*4,IPC_CREAT|0666);
        if(shmid==-1){
                printf("shmget NO\n");
        }
        sh=shmat(shmid,0,0);
        strcpy(sh,"hello world");
        sleep(5);
        shmdt(sh);
        shmctl(shmid,IPC_RMID,0);
        return 0;
}


4.撤销共享内存映射

int shmdt(void *addr); 
成功返回0,失败返回-1
addr:映射后的地址

5.删除共享内存

 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
 成功返回0,失败返回-1
//shm_id:要操作共享内存的ID
// cmd :要对共享内存进行的操作IPC_STAT(表示获取共享内存的属性)IPC_SET(设置共享内存的属性)IPC_RMID(删除共享内存);
//buf:保存或者设置共享内存的结构体

共享内存注意事项

共享内存是有大小个数的限制的;
ipc -l
在这里插入图片描述

5.消息队列

消息队列有消息队列ID来唯一标识;
消息队列就是一个消息的列表,用户可以在消息队列中添加消息,读取消息等
消息队列可以按照类型来发送或者接收信息

消息队列的使用步骤

1.打开/创建消息队列

int msgget(key_t key,int msgfig);
成功返回消息队列的ID,失败返回-1;
key和消息队列关联的key IPC_PRIVATE或者ftok
msgfig :标志位,IPC_CREAT|0666

2.向消息队列发送消息

int msgsnd(int msgid, const void *msgp, size_t size, int msgflg)
成功:0
出错:-1,错误原因存于error中
msgid:消息队列标识符
msgp:消息缓冲区的地址
size:消息正文长度;
msgflg:0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列,IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回

3.从消息队列接收消息

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
成功返回收到的消息长度,失败返回-1;
msqid:消息队列的ID
msgp:消息缓存区地址;
msgsz :指定接收的消息长度;
msgtyp:指定接收的消息类型	
	0:接收第一个消息
	>0:接收类型等于msgtyp的第一个消息
	<0:接收类型等于或者小于msgtyp绝对值的第一个消息
msgflg:标志位0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG

4.控制消息队列

int msgctl(int msqid, int cmd, struct msqid_ds *buf)
成功:0出错:-1,错误原因存于error中
cmd 要执行的操作:IPC_STAT IPC_SET IPC_SETIPC_RMID;
buf:存放 消息队列属性的地址;

实现A,B进程轮流给对方发消息:

#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
typedef struct 
{
    long mtype;
    char msg[64];
}MSG;
#define LEN (sizeof(MSG) -sizeof(long))
#define TypeA 100
#define TypeB 200 

int main ()
{
    key_t key;
    int msgid;
    MSG buf;
    if((key = ftok(".",2))<0){
        perror("ftok");
        exit(-1);
    }
    if((msgid = msgget(key,IPC_CREAT|0666))<0){
        perror("msgget");
        exit(-1);
    }
    while(1){
        buf.mtype = TypeB;
        printf("Iput >> ");
        fgets(buf.msg,64,stdin);
        msgsnd(msgid,&buf,LEN,0);
        if(msgrcv(msgid,&buf,LEN,TypeA,0)<0){
            perror("msgrcv");
            exit(-1);
        }
            printf("From B : %s\n",buf.msg);
    }
    return 0;
}


#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
typedef struct 
{
    long mtype;
    char msg[64];
}MSG;
#define LEN (sizeof(MSG) -sizeof(long))
#define TypeA 100
#define TypeB 200 

int main ()
{
    key_t key;
    int msgid;
    MSG buf;
    if((key = ftok(".",2))<0){
        perror("ftok");
        exit(-1);
    }
    if((msgid = msgget(key,IPC_CREAT|0666))<0){
        perror("msgget");
        exit(-1);
    }
    while(1){
        if(msgrcv(msgid,&buf,LEN,TypeB,0)<0){
            perror("msgrcv");
            exit(-1);
        }
        printf("From A : %s\n",buf.msg);
        buf.mtype = TypeA;
        printf("Iput >> ");
        fgets(buf.msg,64,stdin);
        msgsnd(msgid,&buf,LEN,0);
    }
    return 0;
}


6.信号灯集

可用是操作集合中的信号灯
避免死锁;
1.打开/创建信号灯

 int semget(key_t key, int nsems, int semflg);

成功返回信号灯的ID失败返回-1 ;
key和信号灯的key IPC_PRIVATE或者ftok
nsems:集合中包含的计数信号灯的个数
semflg :标志位,IPC_CREAT|0666 ; IPC_EXCL(检查对象是否已经存在);

2.信号灯初始化

int semctl(int semid, int semnum, int cmd, ...);
semid:要操作的信号灯集合ID
semnum:要操作的集合中的信号灯编号
cmd:执行的操作 SETVAL 
union semun :取决于cmd
           union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };

3.P/V操作

int semop (int semid,struct sembuf *sops,unsigned nsops);
成功返回0,失败返回-1;
semid:要操作的信号灯集ID
sops:描述对信号灯操作的结构体(数组)
nsops : 要操作的信号灯的个数
struct sembuf
{
	 short sem_num;  /* semaphore number */
     short  sem_op;   /* semaphore operation */
     short sem_flg;  /* operation flags */
}
sem_num:信号灯编号
sem_op:-1:P操作 1:V操作
sem_flg:0(阻塞模式)IPC_NOWAIT(立刻返回);

父进程从键盘输入字符串到共享内存

子进程删除字符串中的空格并打印

父进程输入quit后删除共享内存和信号灯集,程序结束

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>

#define N 64
#define READ 0
#define WRITE 1
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo  *__buf;
};
void init_sem(int semid ,int m[] ,int n)
{
    union semun mu;
    int i;
    for(i=0;i<n;i++){
        mu.val = m[i];
        semctl(semid,i,SETVAL,mu);
    }

}
void PV(int semid,int num,int op)
{
    struct sembuf buf;
    buf.sem_num = num;
    buf.sem_op  = op;
    buf.sem_flg = 0;
    semop(semid,&buf,1);
}
int main()
{
    int semid,shmid, m[]={0,1};
    char *addr;
    pid_t pid;
    key_t key;
    if((key = ftok(".",'a'))<0){
        perror("ftok");
        exit(-1);
    }
    if((shmid = shmget(key,N,IPC_CREAT|0666))<0){
        perror("shmget");
        exit(-1);
    }
    if((semid = semget(key,2,IPC_CREAT|0666))<0)
    {
        perror("semget");
        goto _error1;
    }
    init_sem(semid,m,2);
    if((addr = (char *)shmat(shmid,NULL,0)) == (char *)-1){
        perror("shmat");
        goto _error2;
    }
    if((pid = fork())<0){
        perror("fork");
        goto _error2;
    }else if(pid == 0){
        char *p,*q;
        while( 1 ){
            PV(semid,READ,-1);
            p = q = addr;
            while( *p ){
                if( *p != ' '){
                    *q++ = *p;
                }
                p++;
            }
            *q = '\0';
            printf("%s",addr);
            PV(semid,WRITE,1);
        }    
    }else{
        while( 1 ){
            PV(semid,WRITE,-1);
            printf("Iput >>");
            fgets(addr,N,stdin);
            if(strcmp(addr,"quit\n") == 0 ) break;
            PV(semid,READ,1);
        }
        kill(pid,SIGUSR1);
    }
_error2:
    semctl(semid,0,IPC_RMID);
_error1:
    shmctl(shmid,IPC_RMID,NULL);
    return 0;
}

运行结果:

Iput >>aa ddad
aaddad
Iput >>ddd aa dd dd dd 
dddaadddddd
Iput >>quit

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-04-06 16:28:46  更:2022-04-06 16:31:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/8 5:30:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码