linux操作系统与window操作系统的区别:

1) Linux 命令行操作系统 Windows 图形化界面
2) Linux 开源项目 (免费获取Linux操作系统的源码)。 内核源码,免费
Windows 不开源项目,收费
3) Linux :服务器端开发,手机和嵌入式设备(智能家具,遥控器…)
Windows : 个人电脑
4) Linux: 多用户系统 Windows : 单用户系统

版本

看内核版本:
在这里插入图片描述
版本号:5-> 主版本号, 4-次版本号 0-> 修正版本号 109-> 修正版本第几次微调
稳定 -> 取决于修正版本号 奇数 不稳定 偶数 稳定版本

发行版本:
红帽系统: 大型开源技术厂商,稳定性极强的Linux套件。 服务价格太贵。
CentOs: 社区企业操作系统 -> 基于 红帽系统
Debain:
Ubuntu -> 基于Debian 桌面版。图形化界面,稳定。

linux终端

终端介绍:
打开:ubuntu上右击 打开终端选项
快捷键:ctrl shift + 三键组合–放大字体
ctrl - 两键组合 --缩小字体
在这里插入图片描述

linux目录

在这里插入图片描述/bin : 存放命令
/etc: 配置文件
/home: 普通用户的家目录 (普通用户活动范围,具有权限)
/root : 管理员 家目录
/proc: 虚拟文件系统目录,以进程为单位存储内存映射关系。(进程:程序代码 + 运行起来)
/dev: 设备文件
/mnt : 临时挂载点
/lib : 库文件
/boot: 系统内核和启动所需要的文件
/tmp: 临时文件
/var : 系统日志 存放随时修改的一些文件。
/usr : 存放系统应用程序及文档 : include 目录里存在 stdio.h …

在这里插入图片描述

linux基础命令

注:tab键补全文件信息
1. ls命令 查看路径下 所有文件
ls 查看当前路径下所存在的文件。(Linux一切皆文件)
ls -l 查看当前路径下所存在的文件 的 详细信息。 权限问题,创建日期,大小 …
ls -a 显示该路径下所有文件(包含隐藏文件)
在这里插入图片描述
ls -i: 显示文件 的 inode 节点号
在这里插入图片描述
ls + 路径 : 查看指定路径下的文件
在这里插入图片描述
2. cd 命令: 切换路径
cd + 路径
路径:绝对路径 相对路径
cd ~ 切换到普通用户的家目录里
cd /home/stu 切换到普通用户的家目录 跟上述相同
cd … 切换到上一级目录
在这里插入图片描述
cd . 切换到当前路径
cd mnt <=> cd ./mnt
3. clear 命令
清屏命令
4. pwd 命令
查看当前路径
5. mkdir 命令 创建目录
mkdir 目录名 在当前路径下创建目录文件
6. rmdir 命令 删除目录(空目录)
7. rm 删除文件 命令
rm -r 强制删除文件(目录是否是空都可以)
8. touch 命令 创建普通文件
可以指定一次创建多个文件
在这里插入图片描述
*模糊匹配:
rm *.c -> 删除当前目录下的以.c 结尾的所有普通文件
rm * -> 删除当前目录下的所有普通文件
指定路径下删除,创建,ls访问。
创建文件,删除 批量删除 : * touch a.c b.c c.c
文件:不以后缀名区分文件类型。 -> Linux
main.c 文件 -> c编译器要求 编译c程序 找文件必须见到.c
windows: 以后缀名区分文件类型

文件类型

在这里插入图片描述
-普通文件
d目录文件
p管道文件
s套接字文件
b 块设备文件 c 字符设备文件
l 链接文件

vi/vim相关操作

1.三种模式
在这里插入图片描述
2.模式切换
命令模式切换到插入模式
在这里插入图片描述
命令模式切换到末行模式
在这里插入图片描述
3.vi/vim常用命令
在这里插入图片描述
4.末行模式下的操作
在这里插入图片描述
5.cat,more,less,head,tail,wc命令
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

runleve,find,grep (与 | 管道搭配使用)命令及文件的压缩与解压

在这里插入图片描述
1.find 命令
find 路径 -name 文件名
2.grep命令
过滤文件内容
grep “int” main.c 过滤出main.c 中包含"int"字符的所有行进行输出
3.管道和grep搭配使用和命令
ls /bin | grep man 查看/bin所有基础命令,过滤条件是命令名包含man内容
4.文件压缩与解压
main.c add.c mul.c
压缩步骤:将三个文件先打包再压缩
解压步骤:将压缩包解压 解包
tar将文件打包和解包
c 创建包文件
f 指定目标为文件而不是设备
v 显示详细过程
t 显示包中的内容而不释放
x 释放包中的内容
z GUN版本新加,使tar右压缩和解压的功能
分步压缩:
在这里插入图片描述
分布解压:
在这里插入图片描述
一步压缩和解压命令:
在这里插入图片描述

linux系统下C程序的编译与调试

1.程序的编译链接过程
文件时一个外存的概念,文件只存在于“外存”(硬盘,U盘,网盘)中,文件是由两部分构成,文件名和文件主体。文件的分类:可执行文件,不可执行文件。
可执行文件:在windows操作系统中,扩展名为:*。exe,*bat等的文件是可执行文件,可执行文件是由指令和数据构成。Linux是靠文件属性来判断是否可执行。
不可执行文件:其内容是由数据构成。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.linux系统上C程序的编译流程
(1)预编译:
gcc -E main.c -o main.i
(2) 编译:
gcc -S main.i -o main.s
(3)汇编:
gcc -c main.s -o main.o
(4)链接:
gcc main.o -o main

一步编译执行命令:
gcc -o 可执行文件名称 源文件名
例如 gcc -o main main.c

3.make命令以及makefile文件的编写
add.c文件:int add(int a,int b){
int res = a+b;
return res;
}
mul.c文件:int mul(int a,int b){
int res = a*b;
return res;
}
main.c文件:
int add(int,int);
int mul(int,int);
int main(){
函数调用
}

对main.c add,c mul.c三个文件进行编译(前面必须是table建缩进)
在这里插入图片描述
make命令根据makefile文件的规则生成可执行程序
在这里插入图片描述
4.gdb调试
Debug版本:可调式版本
Release版本:发行版本给用户使用
gdb调试命令:

  1. gcc -c main main.c -g 或者-G 生成Debug可调式版本
  2. gdb filename 指定调试文件
  3. b 行号 下断点
  4. r 程序运行
    n) 输入命令进行程序调试
    gdb调试命令:
    在这里插入图片描述

在这里插入图片描述

linux系统上库文件的生成与使用

1.什么是库文件

在这里插入图片描述
2.静态库的生成步骤与使用
静态库是多个.o文件的打包的结果,前面已经说过,其实不一定非要多个文件,一个.o文件也可以打包为.a文件。这一使用ar工具来操作。
以下是需要生成静态库的“.c”文件,其中“foo.h”中是函数的声明,
“add.c”和“max.c”是函数的定义
在这里插入图片描述

  1. 先将需要生成库文件的所有“.c”文件编译成“.o”文件
    在这里插入图片描述
  2. 使用ar命令将第一步编译所有“.o”文件生成静态库
    c 是创建库
    r 是将方法添加到库中
    v 是显示过程
    在这里插入图片描述
    3)静态库的使用
    在这里插入图片描述
    3.动态库的生成和使用
    以下是需要生成共享库的“.c”文件,其中“foo.h”中是函数的声明,“add.c”和"max.c"是函数的定义:
    在这里插入图片描述
  3. 先将需要生成库文件的所有“.c”文件编译成".o"文件
    在这里插入图片描述
  4. 使用gcc命令将第一步编译的所有的“.o”文件生成共享库
    在这里插入图片描述
    以下是测试代码“mian.c ”中的内容
    在这里插入图片描述
    注:
    在这里插入图片描述
    4.静态库和动态库的区别
    在这里插入图片描述

进程(ps命令)

程序:指令+数据
进程:运行中的程序
操作系统os 通过管理进程。让进程完成用户任务。
os对进程描述:pcb(进程控制块) 记录当前的进程的运行状态
pid进程号 os区分进程标识
一个pcb对应一个进程
在这里插入图片描述
进程处理:
并行(在同一时刻,能够同时执行多个进程,每核,CPU在每一时刻执行一个进程,所以要同时进行多个进程的运行要使用多核CPU)
串行(多个任务,单核CPU,一个进程全部处理完成,接下来处理下一个进程,等待该进程处理完,再下一个进程)
并发(在一个时间段,需要处理多个任务。单核CPU,在一个时间段只能处理一个任务。多个进程通过进程切换,使得进程执行) cpu时间片轮转法
创建进程:先创建pcb 后创建进程实体
销毁进程:先释放进程实体,再释放pcb

# 1.ps命令
默认显示当前终端进程信息

-e 显示系统中所有的进程信息
-f 显示更多的进程属性信息(全格式)
-L 展示当前终端上进程信息,线程LWP信息
在这里插入图片描述
2.& 后台运行程序
启动程序: ./main -> 路径+可执行程序 默认执行前台
./main & -> 后台运行进程
3.前台程序 和后台程序 模式切换
在这里插入图片描述
kill 结束进程
kill pid 结束当前进程
kill -9 pid 强制结束
kill -STOP pid 挂起进程
bg_% 任务号 进程/挂起程序,调到后台执行
fg_% 任务号 将后台进程调的前台执行
在这里插入图片描述
bash
用户->计算机硬件->shell->内核fwrite()->内核函数write(系统调用函数)
shell 是用户 和Linux内核交互的接口程序
shell 终端
在提示符输入命令,经过shell先命令的解释后传递内核。
shell 通过$PATH寻找可执行程序(应用程序),若找到可执行程序,被分解为系统调用并传递给内核执行。

main主程序及printf打印函数

在这里插入图片描述
printf 函数 window无缓存
Linux有缓存
\n 行缓冲
例:
printf(“hello”);
printf(“hello”);
printf(“hello”);
缓冲区中数据为hellohellohello
刷新缓冲区:1 程序结束前
2 碰见\n
3 碰见fflush
4 缓冲区存放满
return exit _exit 退出的区别:
return 关键字,语言应用
当前功能的结束

exit 函数调用,系统识别,栈开辟,进程的退出(结束进程前先刷新缓冲区后调用_exit函数)

_exit 内核级别的函数 只结束程序,不会刷新缓冲区
exit函数内部实现结束 调用_exit 进程终止
在这里插入图片描述

内存管理

物理内存
物理内存,在应用中,自然是顾名思义,物理上,真实存在的插在主板槽上的内存条的容量的大小。看计算机配置的时候,主要看的就是个物理内存。

​ 物理内存(Physical memory)是相对于虚拟内存而言的。物理内存指通过物理内存条而获得的内存空间,而虚拟内存则是指将硬盘的一块区域划分来作为内存。内存主要作用是在计算机运行时为操作系统和各种程序提供临时储存。常见的物理内存规格有256M、512M、1G、2G等,现如今随着计算机硬件的发展,已经出现4G、8G甚至更高容量的内存规格。当物理内存不足时,可以用虚拟内存代替。

​ 物理内存,CPU的地址线可以直接进行寻址的内存空间大小。比如8086只有20根地址线,那它的寻址空间就是1MB。我们就说8086能支持1MB的物理内存。即使我们安装了128M的内存条在板子上,我们也只能说8086拥有1MB的物理内存空间。同理32位的386以上CPU,就可以支持最大4GB的物理内存空间了。

虚拟内存与物理内存的区别:虚拟内存就与物理内存相反,是指根据系统需要从硬盘虚拟地匀出来的内存空间,是一种计算机系统内存管理技术,属于计算机程序,而物理内存为硬件。因为有时候当你处理大的程序时候系统内存不够用,此时就会把硬盘当内存来使用,来交换数据做缓存区,不过物理内存的处理速度是虚拟内存的30倍以上。
虚拟内存
1) 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,根据需要在磁盘和主存之间来回传送数据,使得能够运行比内存大的多的进程。
2) 它为每个进程提供了一致的地址空间,从而简化了存储器管理
3) 它保护每个进程的地址空间不被其他进程破坏

​ 虚拟内存别称虚拟存储器(Virtual Memory)。电脑中所运行的程序均需经由内存执行,若执行的程序占用内存很大或很多,则会导致内存消耗殆尽。为解决该问题,Windows中运用了虚拟内存技术,即匀出一部分硬盘空间来充当内存使用。当内存耗尽时,电脑就会自动调用硬盘来充当内存,以缓解内存的紧张。若计算机运行程序或操作所需的随机存储器(RAM)不足时,则 Windows 会用虚拟存储器进行补偿。它将计算机的RAM和硬盘上的临时空间组合。当RAM运行速率缓慢时,它便将数据从RAM移动到称为"分页文件"的空间中。将数据移入分页文件可释放RAM,以便完成工作。 一般而言,计算机的RAM容量越大,程序运行得越快。若计算机的速率由于RAM可用空间匮乏而减缓,则可尝试通过增加虚拟内存来进行补偿。

​ 定义的虚拟地址空间是连续,使程序编写难度降低。因为每个进程都提供了一个一致的、私有的、连续完整的地址空间,让每个进程都产生了一种自己在独享主存的错觉。

在这里插入图片描述
物理地址和逻辑地址的地址映射

pcd中保存的页表信息中记载有页表
在这里插入图片描述
在这里插入图片描述

fork进程复制,僵死进程和孤儿进程及写时拷贝技术

fork进程复制
fork()函数: pid_t fork(void);
函数返回值类型pid_t 实质上是int类型,Linux内核2.4.0版本定义
fork()函数会新生成一个进程,调用fork函数的进程为父进程,新生成的进程为子进程。在父进程中返回
子进程的pid,在子进程中返回0,失败范围返回-1。
进程的分裂跟细胞的分裂几乎一致,一个进程通过fork函数来自我复制,新出现的子进程拥有跟父进
程几乎一样的外表和内在。
这个fork()函数接口本身非常简单,简单到连参数都没有,但是这个函数有个非常与众不同的地方:他
会使得进程一分为二!就像细胞分裂一样。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
int main(int argc,char*argv[],char*envp[]) {
char *s = NULL; 
int n = 0; 
pid_t pid = fork(); 
assert(pid!=-1); 
if(pid == 0){ 
   s = "child";
     n = 4; 
  }else{
      s = "parent"; 
      n=7; 
      }
for(int i = 0;;i<n;i++){ 
     printf("pid = %d,n=%d,&n = %d,s=%s\n",getpid(),n,&n,s); sleep(1);
      }
     return 0;
   }

程序从fork开始一分为二。
写时拷贝技术
传统的fork()系统调用直接把所有的资源复制给新创建的进程。这种实现过于简单并且效率低下,因
为它拷贝的数据也许并不共享,更糟的情况是,如果新进程打算立即执行一个新的映像,那么所有的拷
贝都将前功尽弃。Linux的fork()使用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至
免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。
只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资源的复制只
有在需要写入的时候才进行,在此之前,只是以只读方式共享。这种技术使地址空间上的页的拷贝被推
迟到实际发生写入的时候。在页根本不会被写入的情况下—举例来说,fork()后立即调用exec()—它们就
无需复制了。fork()的实际开销就是复制父进程的页表以及给子进程创建惟一的进程描述符。
在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程此后会和exce系统调用相
结合,出于效率考虑,linux中引入了"写时拷贝"技术,也就是:只有进程空间的各段的内容要发生变化
时,才将会父进程的内容赋值一份给子进程。
在fork函数之后exec之前,两个进程用的是相同的物理空间(内存),子进程的代码段,数据段,堆栈
都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子
进程中有更改相应段的行为发生时,再给子进程分配相应的物理空间。
若不存在exec,那么内核只会给子进程的数据段,堆栈段分配相应的物理空间(至此两者都是各自的
进程空间,互不影响),而代码段则共享父进程的物理空间(因为父子进程代码完全相同)。

int main(int argc,char*argv[],char*envp[]){ 
int i = 0; 
for(int i = 0;i<2;i++){ 
    fork(); 
    printf("A\n"); 
    }exit(0); 
  }



   int main(int argc,char*argv[],char*envp[]){ 
   int i = 0; 
   for(int i = 0;i<2;i++){ 
   fork(); 
   printf("A"); 
   }
   exit(0); 
   }


   int main(int argc,char*argv[],char*envp[]){
    fork()||fork(); 
    printf("A\n"); 
    exit(0); 
  }

若存在exec,由于进程被替换为其他程序,两者执行的代码不同,子进程代码段也会分配单独的物理
空间。
fork之后内核会通过将子进程放在队列的前面,先让子进程执行,以免父进程执行导致写时拷贝,而
后子进程执行exec系统调用,无意义的写时拷贝造成效率的下降。

僵尸进程及孤儿进程
子进程先于父进程结束,而父进程又没有回收子进程(父进程没有获取到子进程的退出码),释放子进程占用的资源,此时子进程将成为一个僵尸进程。

## 僵尸进程的危害

由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结
束,而丢失子进程结束时的状态信息呢?
答案:不会。
因为Linux提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息,就可以得到。
机制:在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是
仍然为其保留一定的信息(包括进程号,退出状态,运行时间等),直到父进程通过wait或waitpid来取时
才释放。
但导致了问题,如果进程不调用wait/waitpid,那么保留的那段信息就不会被释放,其进程号就会一
直被占用,但是系统能使用的进程号是有限的,如果产生大量的僵尸进程,将因为没有可用的进程号而
导致系统不能产生新的进程。

如何解决僵尸进程

  1. 父进程通过waIt或waitpid等函数等待子进程结束,这回导致父进程挂起。
  2. 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收
    到该信号,可以在handler中调用wait回收。
  3. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN)通知内核,自己对
    子进程不感兴趣,那么子进程结束后,内核会回收,并不会给父进程发送信号。
  4. fork()两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被
    init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。
    在这里插入图片描述

孤儿进程
若父程序先于子程序结束,则子程序会变成一个孤儿进程,系统会调用init进程来回收。

文件操作(mycp)

int fd = open(“将要打开的文件路径或名称”,打开标志,权限);
例如 int fd = open(“a.txt”,O_RDONLY|)_CREAT,0600); r 4 w 2 e 1
if(fd == -1) exit(-1);
close(fd);

打开标志:O_WDONLY 只写打开
O_RDONLY 读写方式打开
O_CREAT 文件不存在则创建
O_APPEND 文件末尾追加
O_TRUNC 清空文件,重新写入
read,write函数

   char buff[128] = {0};
   int num = read(fd,buff,128);
   printf("num=%d,buff=%s\n",num,buff);
  
 
   int num = write(fd,"hello",5);
   printf("num=%d\n",num);

拷贝

  6 //将a.txt拷贝到b.txt
  7 int main(){
  8 char* s_name = "a.txt";
  9 char* new_name = "b.txt";
 10 int fdr = open(s_name,O_RDONLY);
 11 assert(fdr!=-1);
 12 int fdw = open(new_name,O_WRONLY|O_CREAT,0600);
 13 assert(fdw!=-1);
 14 
 15 //依次读a.txt写入b.txt  大文件
 16 
 17 char buff[128]={0};
 18 int num = 0;
 19 while((num = read(fdr,buff,128))>0){//num = 0 读取完成 一次从fdr中读取128个到buff中
 20    write(fdw,buff,num);
 21 }
 22 close(fdr);
 23 close(fdw);
 24 exit(0);
 25 
 26 }
6 //将/etc/passwd 拷贝到当前目录下 /bin/cp:  /etc/passwd    ./newpasswd
  7 int main(){
  8 char* s_name = "/etc/passwd";
  9 char* new_name = "./newpasswd";
 10 int fdr = open(s_name,O_RDONLY);
 11 assert(fdr!=-1);
 12 int fdw = open(new_name,O_WRONLY|O_CREAT,0600);
 13 assert(fdw!=-1);
 14 
 15 //依次读a.txt写入b.txt  大文件
 16 
 17 char buff[128]={0};
 18 int num = 0;
 19 while((num = read(fdr,buff,128))>0){//num = 0 读取完成 一次从fdr中读取128个到buff中
 20    write(fdw,buff,num);
 21 }
 22 close(fdr);
 23 close(fdw);
 24 exit(0);
 25 
 26 }

mycp

  7 int main(int argc,char*argv[]){
  8 char* s_name = argv[1];
  9 char* new_name = argv[2];
 10 int fdr = open(s_name,O_RDONLY);
 11 assert(fdr!=-1);
 12 int fdw = open(new_name,O_WRONLY|O_CREAT,0600);
 13 assert(fdw!=-1);
 14 
 15 //依次读a.txt写入b.txt  大文件
 16 
 17 char buff[128]={0};
 18 int num = 0;
 19 while((num = read(fdr,buff,128))>0){//num = 0 读取完成 一次从fdr中读取128个到buff中
 20    write(fdw,buff,num);
 21 }
 22 close(fdr);
 23 close(fdw);
 24 exit(0);
 25 
 26 }

后续可以将mycp通过sudo mv ./mycp /bin移动到bin目录下

打开文件与fork的结合

先open再fork

 6 int main(){
  7     int fd = open("a.txt",O_RDONLY);
  8     assert(fd != -1);
  9     pid_t pid =fork();
 10     assert(pid != -1);
 11     if(pid == 0){
 12      char buff[10] = {0};
 13      read(fd,buff,1);
 14      printf("son:buff=%s\n",buff);
 15      sleep(1);
 16      read(fd,buff,1);
 17      printf("son:buff=%s\n",buff);
 18     }
 19     else{
 20       char buff[10] = {0};
 21       read(fd,buff,1);
 22       printf("father:buff=%s",buff);
 23       sleep(1);
 24       read(fd,buff,1);
 25       printf("father:buff=%s",buff);
 26     }
 27     close(fd);
 28     exit(0);
 29 }

在这里插入图片描述
先fork再open

int main(){
  7     pid_t pid =fork();
  8     assert(pid != -1);
  9     int fd = open("a.txt",O_RDONLY);
 10     assert(fd != -1);
 11     if(pid == 0){
 12      char buff[10] = {0};
 13      read(fd,buff,1);
 14      printf("son:buff=%s\n",buff);
 15      sleep(1);
 16      read(fd,buff,1);
 17      printf("son:buff=%s\n",buff);
 18     }
 19     else{
 20       char buff[10] = {0};
 21       read(fd,buff,1);
 22       printf("father:buff=%s",buff);
 23       sleep(1);
 24       read(fd,buff,1);
 25       printf("father:buff=%s",buff);
 26     } 
 27     close(fd);
 28     exit(0);
 29 }

在这里插入图片描述

进程替换

把当前进程替换为其他进程实行,替换的进程实体
例如:把当前进程替换为ps -f
进程替换函数(#include<unistd.h>):
execl execlp execle execv execvp 库函数
execve系统调用函数

execl("/usr/bin/ps","-f",(char*)0);   //路径,替换的进程,结尾标记                          
execlp("ps","ps","-f",(char*)0);//第一个ps是系统变量PATH中的,如果要使用用户自定义的路径则要将自定义路径复制到$PATH  使用export PATH = $PATH:路径   可以通过echo ¥PATH 查看PATH中的值
execle("/usr/bin/ps","ps","-f",(char*)0,envp);//envp是main函数中的char*envp[]

char* buff[] = {"ps","-f","0"};
execv(".usr/bin/ps",buff);
execvp("ps",buff);
execvc("/usr/bin/ps",buff,envp);

ls 使用ls --color --后解析一个单词
ps -ef -后解析一个单词
在这里插入图片描述

   #include <stdio.h>
   #include<unistd.h>
   #include<stdlib.h>
   #include<sys/wait.h>
   int main(){//1个进程 main 替换为ps -f
      pid_t pid = fork();
      if(pid == 0){
      //execl("/usr/bin/ls","ls","--color","-a",(char*)0);//execl失败运行下面的代码
      //printf("进程替换失败");
     execlp("ps","ps","-f",(char*)0);
     perror("exec error");
     }else{
     wait(NULL);//获取子进程的退出码
     sleep(5);
     printf("parent run....");
     }
     exit(0);
     }

Linux信号的使用

1.信号的基本概念
信号是系统响应某个条件而产生的事件,进程接收到信号会执行相应的操作。
与信号有关的系统调用在“signal.h”头文件中有声明
常见信号的值,及对应的功能说明:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.修改信号的响应方式 -signal()
typedef void(*sighandle_t)(int);
sighandle_t(*signal)(int sig,sighandle_t sig_fun);
忽略:SIG_IGN
默认:SIG_DFL
自定义:自己写的信号处理函数

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<signal.h>
  5 #include<assert.h>
  6 #include<sys/wait.h>
  7 void fun(int sig){
  8    printf("sig = %d\n",sig);
  9    wait(NULL);//处理僵死进程
 10 }
 11 int main(){
 12    pid_t pid = fork();
 13    assert(pid != -1);
 14    char* s = NULL;
 15    int n = 0;
 16    if(pid == 0){
 17        s = "child";n=3;
 18      }else{
 19         s = "parent";n = 7;
 20         signal(SIGCHLD,fun);
 21      }
 22    for(int i = 0;i<n;i++){
 23      printf("%s\n",s);
 24      sleep(2);
 25    }
 26    exit(0);
 27 }

3.发送信号-kill()
kill() 可以向指定的进程发送指定的信号:
int kill(pid_t pid, int sig);
pid > 0 指定将信号发送个那个进程
pid == 0 信号被发送到和当前进程在同一个进程组的进程
pid == -1 将信号发送给系统上有权限发送的所有的进程
pid < -1 将信号发送给进程组 id 等于 pid 绝对值,并且有权限发送的所有的进程。
sig 指定发送信号的类型。

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<signal.h>
  5 
  6 int main(int argc,char*argv[]){
  7     //pid 进程的id -> 先启动pid 对应的进程
  8   if(argc !=2 ){
  9     printf("argc err\n");
 10     exit(1);
 11   }
 12   int pid;
 13   //atoi(argv[1]); 
 14   sscanf(argv[1],"%d",&pid);//字符串 转 整型
 15   if(kill(pid,SIGINT) == -1){
 16       perror("kill error");
 17       exit(1);
 18   }
 19   exit(0);
 20 }

进程间通讯-管道

进程间通讯(IPC机制):管道 信号量 共享内存 消息队列 套接字
(信号量 消息队列 共享内存)

在这里插入图片描述

管道:只能以只读只写来操作管道。(只能控制一端 一端读一端写)
管道文件:在内存中分配 目的:进程间通讯
**内存:**断电不保存任何数据

有名管道(任意俩个进程间通讯):
创建:mkfifo FIFO
管道中没有数据,read会阻塞

无名管道(父子间通信):

int fd[2];//fd[0]读  fd[1]写

管道通讯的特点:
1.半双工 套接字:全双工
2.管道没有数据,read阻塞
3.管道的写端关闭,read返回0
4.管道读端关闭,写端产生异常(发送信号,SIGPIPE,程序收到信号 异常终止)

文件描述符辅助:
dup dup2 重定向
0 stdin
1 stdout
2 stderr

duo2(fd,1)将fd的我文件描述符复制给 1 即标准输出
dup(fd)将fd复制一份,分配给文件表中未被使用的最小的一项

进程间通讯-信号量

**信号量:**多个进程访问相同资源冲突,通过信号量控制资源访问
命令: ipcs 查看消息队列 共享内存,信号量
ipcrm -s semid 删除信号量
**进程间同步:**同一个时刻,只能有一个进程来访问资源。
特殊的变量 值 >= 0 值 可用资源的数目
试衣间:3个(可用资源) 值:3
红绿灯,实现对资源的访问进行控制
p操作:获取资源 -1 可用资源=0 p操作阻塞
b操作:释放资源 +1 v操作 不会被阻塞
临界资源: 同一个时刻只允许一个进程访问的资源
**临界区:**访问临界资源的代码段
信号量API: semget() 创建或获取 已存在 信号量
semop() P,V操作 IPC_UNDO:
P() 获取资源,程序异常终止,未释放资源
,内核释放
IPC_NOWAIT,打印错误信息
semctl() 对信号量 初始化,删除
SETVAL IPC_RMID

封装:信号量:初始化 sem_init();//semget+semctl 创建
//semget 获取信号量
p操作:sem_p(); //semop() -1
v操作:sem_b(); //semop() +1
批量删除信号量:sem_destory() //semctl
sem.h

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/sem.h>
//sem.h
union semun{
    int val;
};
void sem_init(); //信号量初始值: 1
void sem_p();
void sem_v();
void sem_destory();

sem.c

#include "sem.h"   //sem.c

static int semid; //静态全局变量 --- .data区    本文件有效

void sem_init(){
    semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);
    if(semid == -1){ // 创建失败(只需要获取) 
        semid = semget((key_t)1234,1,0600); //获取
        if(semid == -1) exit(1);
    }
    else{
        union semun a;
        a.val = 1;
        if(semctl(semid,0,SETVAL,a) == -1){
            perror("semctl error");
            exit(1);
        }
    }
}
void sem_p(){
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_op = -1;  //获取资源
    buf.sem_flg = SEM_UNDO;

    if(semop(semid,&buf,1) == -1){
        perror("semop err");
        exit(1);
    }
}
void sem_v(){
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_op = 1;  //释放资源
    buf.sem_flg = SEM_UNDO;

    if(semop(semid,&buf,1) == -1){
        perror("semop err");
        exit(1);
    }
}
void sem_destory(){
    if(semctl(semid,0,IPC_RMID) == -1){
        perror("semctl rm  error");
        exit(1);
    }
}

进程间通讯-共享内存

**原理:**共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。

在这里插入图片描述
shmget 创建,获取
shmat 映射
shmdt 断开映射
shmctl 删除共享内存
命令: ipcrm -m semid
a.c与b.c通讯:

**a.c:**
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include "sem.h"
//a.c   向共享内存中写入数据
int main(){
    int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    if(shmid == -1)
        exit(1);
    //映射物理内存  shmat
    char*s = (char*)shmat(shmid,NULL,0) ;//共享内存地址 内核分配 返回共享内存地址
    if(s == (char*)-1)  exit(1);
    //向s指向的共享内存 写入数据
    sem_init();
    while(1){
        printf("input:");
        char buff[128]={0};
        fgets(buff,128,stdin);
        sem_p(SEM1);
        strcpy(s,buff);
        sem_v(SEM2);
        if(strncmp(buff,"end",3) == 0){
            break;
        }
    }
    sem_destory();
    shmdt(s);
    shmctl(shmid,IPC_RMID,NULL);  //删除共享内存
    exit(0);
}
**b.c:**

```c
#include "sem.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
//b.c   从共享内存中读数据
int main(){
    int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    if(shmid == -1)
        exit(1);
    //映射物理内存  shmat
    char*s = (char*)shmat(shmid,NULL,0) ;//共享内存地址 内核分配 返回共享内存地址
    if(s == (char*)-1)  exit(1);
    //向s指向的共享内存 写入数据
    sem_init(); //初始化信号量
    while(1){
        sem_p(SEM2);
        if(strncmp(s,"end",3) == 0){
            break;
        }
        sem_v(SEM1);
        printf("read:%s\n",s);
        sleep(1);
    }
    shmdt(s);
    exit(0);
}

sem.c:

#include "sem.h"   //sem.c

static int semid; //静态全局变量 --- .data区    本文件有效

void sem_init(){
    semid = semget((key_t)1234,SEM_MAX,IPC_CREAT|IPC_EXCL|0600);
    if(semid == -1){ // 创建失败(只需要获取)
        semid = semget((key_t)1234,SEM_MAX,0600); //获取
        if(semid == -1) exit(1);
    }
    else{
        union semun a;
        int arr[2] = {1,0}; //两个信号量的初值
        for(int i=0;i<SEM_MAX;i++){
            a.val = arr[i];
            if(semctl(semid,i,SETVAL,a) == -1){
                perror("semctl error");
                exit(1);
            }
        }
    }
}
void sem_p(int index){
    if(index < 0 || index >= SEM_MAX) exit(1);
    struct sembuf buf;
    buf.sem_num = index;
    buf.sem_op = -1;  //获取资源
    buf.sem_flg = SEM_UNDO; //异常终止,信号量内核释放

    if(semop(semid,&buf,1) == -1){
        perror("semop err 1111");
        exit(1);
    }
}
void sem_v(int index){
    struct sembuf buf;
    buf.sem_num = index;
    buf.sem_op = 1;  //释放资源
    buf.sem_flg = SEM_UNDO;

    if(semop(semid,&buf,1) == -1){
        perror("semop err  2222");
        exit(1);
    }
}
void sem_destory(){
    if(semctl(semid,0,IPC_RMID) == -1){
        perror("semctl rm  error");
        exit(1);
    }
}

线程

**概念:**线程是进程内部的一条执行序列或执行路径,一个进程可以包含多条线程。
在这里插入图片描述
Linux中线程的实现: Linux 实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux 把
所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来
表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯
一隶属于自己的 task_struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和
其他一些进程共享某些资源,如地址空间)。
线程与进程的区别: ◼ 进程是资源分配的最小单位,线程是 CPU 调度的最小单位
◼ 进程有自己的独立地址空间,线程共享进程中的地址空间
◼ 进程的创建消耗资源大,线程的创建相对较小
◼ 进程的切换开销大,线程的切换开销相对较小
线程的实现:
在这里插入图片描述
用户级:开销小,可以创建很多,但是不能利用多处理器资源。
内核级:开销大,由内核直接管理,可以利用多处理器资源。

线程同步

**线程同步方法:**条件变量,信号量,互斥锁,读写锁

信号量:三个线程循环分别打印a b c 通过信号量使其输出结果为abc

  #include<stdio.h>
  #include<stdlib.h>
  #include<unistd.h>
  #include<string.h>
  #include<semaphore.h>
  #include<pthread.h>
    sem_t sema;
    sem_t semb;
    sem_t semc;
 void* funa(void* arg){
     for(int i = 0;i<5;i++){
         sem_wait(&sema);
         printf("a");
         fflush(stdout);
         sem_post(&semb);
     }
 }
 
 void* funb(void* arg){
     for(int i = 0;i<5;i++){
      sem_wait(&semb);
      printf("b");
      fflush(stdout);
      sem_post(&semc);
      }
 }
 void* func(void* arg){
      for(int i = 0;i<5;i++){
         sem_wait(&semc);
         printf("c");
         fflush(stdout);
         sem_post(&sema);
      }
 
 }
 int main(){
    sem_init(&sema,0,1);
    sem_init(&semb,0,0);
    sem_init(&semc,0,0);
 
    pthread_t id1,id2,id3;
    pthread_create(&id1,NULL,funa,NULL);
    pthread_create(&id2,NULL,funb,NULL);
    pthread_create(&id3,NULL,func,NULL);
 
    pthread_join(id1,NULL);
    pthread_join(id2,NULL);
    pthread_join(id3,NULL);
 
    sem_destroy(&sema);
    sem_destroy(&semb);
    sem_destroy(&semc);
 }

互斥锁: pthread_mutex_init()
pthread_mutex_destroy()
pthread_mutex_lock()
pthread_mutex_unlock()
五个线程同时对一个一个全局变量i++;

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
 
 #define MAXID  5
 int val = 1;
 pthread_mutex_t mutex;
 void* fun(void* arg){
   for(int i = 0;i<1000;i++){
    pthread_mutex_lock(&mutex);
    printf("val = %d\n",val++);
    pthread_mutex_unlock(&mutex);
   }
 }
 
 int main(){
    pthread_mutex_init(&mutex,NULL);
    pthread_t id[MAXID];
    int i = 0;
    for(;i < MAXID;i++){
      pthread_create(&id[i],NULL,fun,NULL);
    }
 
    for(i = 0;i < MAXID;i++){
      pthread_join(id[i],NULL);
    }
 
    pthread_mutex_destroy(&mutex);
    exit(0);
 }

读写锁: pthread_rwlock_rdlock
pthread_rwlock_wrlock
pthread_rwlock_unlock
pthread_rwlock_destroy

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
 
pthread_rwlock_t lock;
void* fun_r1(void* arg){
     for(int i = 0;i < 10;i++){
      pthread_rwlock_rdlock(&lock);
      printf("fun1 read\n");
      sleep(1);
      printf("fun1 end\n");
      pthread_rwlock_unlock(&lock);
      sleep(1);
     }
 }
 void* fun_r2(void* arg){
 
     for(int i = 0;i < 5;i++){
      pthread_rwlock_rdlock(&lock);
      printf("fun2 read\n");
      sleep(2);
      printf("fun2 end\n");
      pthread_rwlock_unlock(&lock);
      sleep(1);
     }
 }
 void* fun_w(void* arg){
    for(int i = 0;i<3;i++){
     pthread_rwlock_wrlock(&lock);
     printf("write start\n");
     sleep(3);
     printf("write end\n");
     pthread_rwlock_unlock(&lock);
     sleep(1);
    }
 
 }
 int main(){
     pthread_rwlock_init(&lock,NULL);
     pthread_t id1,id2,id3;
     pthread_create(&id1,NULL,fun_r1,NULL);
     pthread_create(&id1,NULL,fun_r2,NULL);
     pthread_create(&id1,NULL,fun_w,NULL);
 
     pthread_join(id1,NULL);
     pthread_join(id2,NULL);
     pthread_join(id3,NULL);
 
     pthread_rwlock_destroy(&lock);
 
     exit(0);
 }

条件变量:如果说互斥锁是用于同步线程对共享数据的访问的话,那么条件变量则是用于在线程之间同步共享数据的值。条件变量提供了一种线程间的通知机制;当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。
在这里插入图片描述

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

pthread_mutex_t mutex;
pthread_cond_t cond;
void* funa(void* arg){
    char* s = (char*)arg;
    while(1){
    pthread_mutex_lock(&mutex);
    //加入条件变量的等待队列 --阻塞
    pthread_cond_wait(&cond,&mutex);
    pthread_mutex_unlock(&mutex);

   if( strncmp(s,"end",3) == 0){
      break;   
   }
  printf("A thread read:%s",s);   
  
}

} 
 void* funb(void* arg){
    char* s = (char*)arg;
    while(1){
     pthread_mutex_lock(&mutex);//加锁  
     pthread_cond_wait(&cond,&mutex);//解锁,加锁
     pthread_mutex_unlock(&mutex); //解锁
     if( strncmp(s,"end",3) == 0){
      break;   
}
  printf("B thread read:%s",s);   
}
} 
  
int main(){
  pthread_mutex_init(&mutex,NULL);
  pthread_cond_init(&cond,NULL);
  
char buff[128] = {0};
 pthread_t ida,idb;
 pthread_create(&ida,NULL,funa,buff);
 pthread_create(&idb,NULL,funb,buff);  
  
 while(1){
    fgets(buff,128.stdin);
    if(strncmp(buff,"end",3) == 0){
        pthread_cond_broadcast(&cond);//唤醒所有线程
        break;
}else{
       pthread_cond_signal(&cond);
}
}
pthread_join(ida,NULL);
pthread_join(idb,NULL);  
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);

exit(0);
}

pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。

   pthread_cond_wait() 用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。 pthread_cond_wait() 必须与pthread_mutex 配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread_cond_signal()或pthread_cond_broadcast,把该线程唤醒,使pthread_cond_wait()通过(返回)时,该线程又自动获得该mutex。

   使用pthread_cond_signal一般不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。

   但是pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程. 

线程安全

线程安全即就是在多线程运行的时候,不论线程的调度顺序怎样,最终的结果都是一样的、正确的。那么就说这些线程是安全的。
要保证线程安全需要做到:
1)对线程同步,保证同一时刻只有一个线程访问临界资源。
2)在多线程中使用线程安全的函数(可重入函数),所谓线程安全的函数指的是:如果一个
函数能被多个线程同时调用且不发生竟态条件,则我们程它是线程安全的。
不安全:

 #include <stdio.h>
 #include <stdlib.h>
 #include <assert.h>
 #include <unistd.h>
 #include <string.h>
 #include <pthread.h>

 void* PthreadFun(void *arg)
 {
 char buff[] = "a b c d e f g h i";

 char *p = strtok(buff, " ");
 while(p != NULL)
 {
 printf("fun:: %c\n", *p);
 p = strtok(NULL, " ");
 sleep(1);
 }
 }
 int main()
 {
 pthread_t id;
 int res = pthread_create(&id, NULL, PthreadFun, NULL);
 assert(res == 0);

char buff[] = "1 2 3 4 5 6 7 8 9";

 char *p = strtok(buff, " ");

 while(p != NULL)
 {
 printf("main:: %c\n", *p);
 p = strtok(NULL, " ");
 sleep(1);
 }
 }

安全:

 #include <stdio.h>
 #include <stdlib.h>
 #include <assert.h>
 #include <unistd.h>
 #include <string.h>
 #include <pthread.h>

 void* PthreadFun(void *arg)
 {
 char buff[] = "a b c d e f g h i";

 char *q = NULL;
 char *p = strtok_r(buff, " ", &q);
 while(p != NULL)
 {
 printf("fun:: %c\n", *p);
 p = strtok_r(NULL, " ", &q);
 sleep(1);
 }
 }

 int main()
 {
 pthread_t id;
 int res = pthread_create(&id, NULL, PthreadFun, NULL);
 assert(res == 0);
 28. char buff[] = "1 2 3 4 5 6 7 8 9";

 char *q = NULL;
 char *p = strtok_r(buff, " ", &q);

 while(p != NULL)
 {
 printf("main:: %c\n", *p);
 p = strtok_r(NULL, " ", &q);
 sleep(1);
 }
 }

pthread_atfork函数

void fun_mutex_lock(){pthread_mutex_lock(&mutex);}
void fun_mutex_unlock(){pthread_mutex_unlock(&mutex);}
fork会复制父进程中锁的状态。
pthread_atfork(fun_mutex_lock,fun_mutex_unlock,fun_mutex_unlock);//在fork之前调用,在fork之后父进程中调用,在fork后子进程中调用。

网络编程

1.ip地址 分层 :OSI 七层 四层(应用层 传输层 网络层 数据链路层)
2.端口号 tcp(可靠的) udp (不可靠)
3.协议 tcp连接建立
tcp服务器客户端编程流程 (面向连接的可靠的流式服务)
在这里插入图片描述
在这里插入图片描述
服务器端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{                               //服务类型
    int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建套接字
    if ( sockfd == -1 )
    {
        exit(0);
    }
                        
    struct sockaddr_in saddr, caddr; //套接字接口ipv4   通用:bind struct sockaddr   caddr 客户端
    memset(&saddr,0,sizeof(saddr));//清空
    saddr.sin_family = AF_INET;//地址族,AF_INET(Ipv4)
    saddr.sin_port = htons(6000);//端口号 网络大端  主机转网络  1024以内需要管理员权限 1024—4096保留端口
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //字符串转为无符号整型127.0.0.1测试用IP地址
     
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//指定套接字的 地址(ip  port). 告诉内核将saddr中的服务器套接字地址和套接字描述符联系起来
    if ( res == -1 )
    {
        printf("bind err\n");
        exit(0);
    }

    listen(sockfd,5);//设置监听队列大小 5:已完成三次握手的大小 listen函数将一个主动套接字转化为一个监听套接字 即服务器调用listen告诉内核描述符是被服务器使用而不是客户端

    while( 1 )
    {
        int len = sizeof(caddr);//caddr客户端的地址
        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);//accept返回的是一个连接套接字 成功则为非负描述符,失败则为-1 阻塞在这。
        if ( c < 0 )
        {
            continue;
        }
        printf("c=%d,ip=%s,port=%d\n",c,inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));

        char buff[128] = {0};
        int n = recv(c,buff,127,0);//read  接收数据
        printf("buff=%s\n",buff);

        send(c,"ok",2,0);

        close(c);

    }


    
}

客户端

#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if ( sockfd == -1 )
    {
        exit(0);
    }
    //指定服务器
    struct sockaddr_in saddr;//ser:ip port
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//发起连接
    if ( res == -1 )
    {
        printf("connect err\n");
        exit(0);
    }

    char buff[128] = {0};
    fgets(buff,128,stdin);
    send(sockfd,buff,strlen(buff),0);
    memset(buff,0,128);
    recv(sockfd,buff,127,0);//
    printf("buff=%s\n",buff);
    
    close(sockfd);
    exit(0);

}
Logo

更多推荐