C语言学习-linux系统编程以及网络编程
1.I/O: input &output 是一切实现的基础
stdio标准IO
sysio系统调用IO(文件IO)
如果一个系统环境下,2中io都可以使用,当然优先使用标准io
2.标准库函数都在man手册的第三章
man手册的第七章是讲解机制的
stdio:FILE类类型贯穿始终
读写操作的函数:
fopen():打开一个流
fclose():
字符读写:
fgetc():
fputc()
字符串读写:
fgets():
fputs():
二进制读写:
fread():
fwrite():
printf():
scanf():
文件指针:
fseek()
ftell()
rewind()
缓存:
fflush()
[root@SHSH-PuDong-BG-INT04 ~]# more /usr/include/asm-x86_64/errno.h #ifndef _X8664_ERRNO_H #define _X8664_ERRNO_H 宏名 宏值 #define EPERM 1 /* Operation not permitted */ #define ENOENT 2 /* No such file or directory */ #define ESRCH 3 /* No such process */ #define EINTR 4 /* Interrupted system call */ #define EIO 5 /* I/O error */ #define ENXIO 6 /* No such device or address */ #define E2BIG 7 /* Arg list too long */ #define ENOEXEC 8 /* Exec format error */ #define EBADF 9 /* Bad file number */ #define ECHILD 10 /* No child processes */ #define EAGAIN 11 /* Try again */ #define ENOMEM 12 /* Out of memory */ #define EACCES 13 /* Permission denied */ #define EFAULT 14 /* Bad address */ #define ENOTBLK 15 /* Block device required */ #define EBUSY 16 /* Device or resource busy */ #define EEXIST 17 /* File exists */ #define EXDEV 18 /* Cross-device link */ #define ENODEV 19 /* No such device */ #define ENOTDIR 20 /* Not a directory */ #define EISDIR 21 /* Is a directory */ #define EINVAL 22 /* Invalid argument */ #define ENFILE 23 /* File table overflow */ #define EMFILE 24 /* Too many open files */ #define ENOTTY 25 /* Not a typewriter */ #define ETXTBSY 26 /* Text file busy */ #define EFBIG 27 /* File too large */ #define ENOSPC 28 /* No space left on device */ #define ESPIPE 29 /* Illegal seek */ #define EROFS 30 /* Read-only file system */ #define EMLINK 31 /* Too many links */ #define EPIPE 32 /* Broken pipe */ #define EDOM 33 /* Math argument out of domain of func */ #define ERANGE 34 /* Math result not representable */ #define EDEADLK 35 /* Resource deadlock would occur */ #define ENAMETOOLONG 36 /* File name too long */ #define ENOLCK 37 /* No record locks available */ #define ENOSYS 38 /* Function not implemented */ #define ENOTEMPTY 39 /* Directory not empty */ #define ELOOP 40 /* Too many symbolic links encountered */ #define EWOULDBLOCK EAGAIN /* Operation would block */ #define ENOMSG 42 /* No message of desired type */ #define EIDRM 43 /* Identifier removed */ #define ECHRNG 44 /* Channel number out of range */ #define EL2NSYNC 45 /* Level 2 not synchronized */ #define EL3HLT 46 /* Level 3 halted */ #define EL3RST 47 /* Level 3 reset */ #define ELNRNG 48 /* Link number out of range */ #define EUNATCH 49 /* Protocol driver not attached */ #define ENOCSI 50 /* No CSI structure available */ #define EL2HLT 51 /* Level 2 halted */ #define EBADE 52 /* Invalid exchange */ #define EBADR 53 /* Invalid request descriptor */ #define EXFULL 54 /* Exchange full */ #define ENOANO 55 /* No anode */ #define EBADRQC 56 /* Invalid request code */ #define EBADSLT 57 /* Invalid slot */ #define EDEADLOCK EDEADLK #define EBFONT 59 /* Bad font file format */ #define ENOSTR 60 /* Device not a stream */ #define ENODATA 61 /* No data available */ #define ETIME 62 /* Timer expired */ #define ENOSR 63 /* Out of streams resources */ #define ENONET 64 /* Machine is not on the network */ #define ENOPKG 65 /* Package not installed */ #define EREMOTE 66 /* Object is remote */ #define ENOLINK 67 /* Link has been severed */ #define EADV 68 /* Advertise error */ #define ESRMNT 69 /* Srmount error */ #define ECOMM 70 /* Communication error on send */ #define EPROTO 71 /* Protocol error */ #define EMULTIHOP 72 /* Multihop attempted */ #define EDOTDOT 73 /* RFS specific error */ #define EBADMSG 74 /* Not a data message */ #define EOVERFLOW 75 /* Value too large for defined data type */ #define ENOTUNIQ 76 /* Name not unique on network */ #define EBADFD 77 /* File descriptor in bad state */ #define EREMCHG 78 /* Remote address changed */ #define ELIBACC 79 /* Can not access a needed shared library */ #define ELIBBAD 80 /* Accessing a corrupted shared library */ #define ELIBSCN 81 /* .lib section in a.out corrupted */ #define ELIBMAX 82 /* Attempting to link in too many shared libraries */ #define ELIBEXEC 83 /* Cannot exec a shared library directly */ #define EILSEQ 84 /* Illegal byte sequence */ #define ERESTART 85 /* Interrupted system call should be restarted */ #define ESTRPIPE 86 /* Streams pipe error */ #define EUSERS 87 /* Too many users */ #define ENOTSOCK 88 /* Socket operation on non-socket */ #define EDESTADDRREQ 89 /* Destination address required */ #define EMSGSIZE 90 /* Message too long */ #define EPROTOTYPE 91 /* Protocol wrong type for socket */ #define ENOPROTOOPT 92 /* Protocol not available */ #define EPROTONOSUPPORT 93 /* Protocol not supported */ #define ESOCKTNOSUPPORT 94 /* Socket type not supported */ #define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ #define EPFNOSUPPORT 96 /* Protocol family not supported */ #define EAFNOSUPPORT 97 /* Address family not supported by protocol */ #define EADDRINUSE 98 /* Address already in use */ #define EADDRNOTAVAIL 99 /* Cannot assign requested address */ #define ENETDOWN 100 /* Network is down */ #define ENETUNREACH 101 /* Network is unreachable */ #define ENETRESET 102 /* Network dropped connection because of reset */ #define ECONNABORTED 103 /* Software caused connection abort */ #define ECONNRESET 104 /* Connection reset by peer */ #define ENOBUFS 105 /* No buffer space available */ #define EISCONN 106 /* Transport endpoint is already connected */ #define ENOTCONN 107 /* Transport endpoint is not connected */ #define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ #define ETOOMANYREFS 109 /* Too many references: cannot splice */ #define ETIMEDOUT 110 /* Connection timed out */ #define ECONNREFUSED 111 /* Connection refused */ #define EHOSTDOWN 112 /* Host is down */ #define EHOSTUNREACH 113 /* No route to host */ #define EALREADY 114 /* Operation already in progress */ #define EINPROGRESS 115 /* Operation now in progress */ #define ESTALE 116 /* Stale NFS file handle */ #define EUCLEAN 117 /* Structure needs cleaning */ #define ENOTNAM 118 /* Not a XENIX named type file */ #define ENAVAIL 119 /* No XENIX semaphores available */ #define EISNAM 120 /* Is a named type file */ #define EREMOTEIO 121 /* Remote I/O error */ #define EDQUOT 122 /* Quota exceeded */ #define ENOMEDIUM 123 /* No medium found */ #define EMEDIUMTYPE 124 /* Wrong medium type */ #define ECANCELED 125 /* Operation Cancelled */ #define ENOKEY 126 /* Required key not available */ #define EKEYEXPIRED 127 /* Key has expired */ #define EKEYREVOKED 128 /* Key has been revoked */ #define EKEYREJECTED 129 /* Key was rejected by service */ #endif
3.gcc -E test.c -E表示预处理,凡是源码里面有#号的内容都是在预处理阶段进行处理
4.在man手册里面查看一个函数的时候,头文件有几个,一定要包含几个
5.errno:异常号
6.perror:打印系统异常信息,就是把异常号变成异常信息,perror能够自动关联全局变量errno
perror("fopen()");这个是直接输出异常信息
strerror(errno)就是把异常号变成异常信息
fprintf(stderr,"fopen():%s\n",strerror(errno))
7.如果一格函数有互逆操作的函数,那么这个函数开辟的空间一定是在堆上的
8.用宏的目的,就是一改全改
9.puts是在控制台输出
10.设计原则
谁打开,谁释放
谁申请,谁释放
一切皆文件
是资源就有上限
11.stdin,stdout,stderr这个是三个标准流,默认是打开的
12虚拟地址.内存图
13.pcb进程控制块
14.
15.read,write函数通常称为Unbuffered I/O,,指的是无用户级缓冲区,但不保证不使用内核缓冲区
16.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdio.h> #include <stdlib.h> #include <time.h> int main() { puts ( "+++++++++++++++++++++++++++++++++++++++++" ); printf ( "Hello world11!\n" ); puts ( "込込込込込!" ); time_t timeString; time (&timeString); printf ( "%d\n" ,timeString); struct tm *ltime= localtime (&timeString); printf ( "%d,%d\n" ,ltime->tm_year+1900,ltime->tm_mon); puts ( "+++++++++++++++++++++++++++++++++++++++++" ); char *string1[100000]; while (1){ gets (string1); puts ( "+++++++++++++++++++++++++++++++++++++++++" ); puts (string1); } sleep(1000); return 0; } |
17.
#include <stdio.h> #include <time.h> void get_time(char *temp) { time_t sec; struct tm *TheLocalTime; time(&sec); printf("%d\n",sec); TheLocalTime = localtime(&sec); sprintf(temp, "%04d-%02d-%02d %02d:%02d:%02d", TheLocalTime->tm_year+1900,TheLocalTime->tm_mon+1, TheLocalTime->tm_mday, TheLocalTime->tm_hour,TheLocalTime->tm_min, TheLocalTime->tm_sec); printf("%s\n",temp); } int main(int argc, char **argv) { char time_S[50]; time_t timeStamp; printf("%s\n","start..."); get_time(time_S); printf("%s\n", time_S); time(&timeStamp); printf("%s %d\n","主线程:",time_S); printf("%s %d\n","线程名称:",getpid()); printf("%s\n","请等待......"); sleep(1000); return 0; }
18.
19.fcntl函数
改变一个[已经打开]的文件的访问控制属性
重点掌握两个参数F_GETFL和F_SETFL
20.获取linux窗口的大小
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> int main(){ struct winsize size; if(isatty(STDOUT_FILENO)==0){{ exit(1); } if(ioctl(STDOUT_FILENO,TIOCGWINSZ,&size)<0){ perror("ioctl TIOCGWINSZ error"); exit(1); } printf("%d rows,%d columns\n",size.ws_row,size.ws_col); return 0; } }
21.inode
器本质为结构体,存储文件的属性信息,如:权限,类型,大小,时间,用户,盘快位置...也叫做文件属性管理结构,大多数inode都存储在磁盘上.少量常用,近期使用的inode会被缓存到内存中.
22.dentry:目录项,其本质是结构体,重要成员变量有两个{文件名,inode,...}而文件内容{data}保存在磁盘块中
23.获取文件大小
#include<unistd.h> #include<stdio.h> #include<stdlib.h> #include<sys/stat.h> int main(void){ struct stat sbuf; int len=0; int ret=0; ret=stat("D://1.pdf",&sbuf); if(ret==-1){ perror("stat error:"); exit(1); } printf("lend=%d\n",sbuf.st_size); return 0; }
24. getchar()从标准终端输入
25.目录操作函数
opendir()
closedir()
readdir()
26.
Linux 常用系统函数
一、进程控制
fork 创建一个新进程
clone 按指定条件创建子进程
execve 运行可执行文件
exit 中止进程
_exit 立即中止当前进程
getdtablesize 进程所能打开的最大文件数
getpgid 获取指定进程组标识号
setpgid 设置指定进程组标志号
getpgrp 获取当前进程组标识号
setpgrp 设置当前进程组标志号
getpid 获取进程标识号
getppid 获取父进程标识号
getpriority 获取调度优先级
setpriority 设置调度优先级
modify_ldt 读写进程的本地描述表
nanosleep 使进程睡眠指定的时间
nice 改变分时进程的优先级
pause 挂起进程,等待信号
personality 设置进程运行域
prctl 对进程进行特定操作
ptrace 进程跟踪
sched_get_priority_max 取得静态优先级的上限
sched_get_priority_min 取得静态优先级的下限
sched_getparam 取得进程的调度参数
sched_getscheduler 取得指定进程的调度策略
sched_rr_get_interval 取得按 RR 算法调度的实时进程的时间片长度
sched_setparam 设置进程的调度参数
sched_setscheduler 设置指定进程的调度策略和参数
sched_yield 进程主动让出处理器,并将自己等候调度队列队尾
vfork 创建一个子进程,以供执行新程序,常与 execve 等同时使用
wait 等待子进程终止
wait3 参见 wait
waitpid 等待指定子进程终止
wait4 参见 waitpid
capget 获取进程权限
capset 设置进程权限
getsid 获取会晤标识号
setsid 设置会晤标识号
二、文件系统控制
1、文件读写操作
fcntl 文件控制
open 打开文件
creat 创建新文件
close 关闭文件描述字
read 读文件
write 写文件
readv 从文件读入数据到缓冲数组中
writev 将缓冲数组里的数据写入文件
pread 对文件随机读
pwrite 对文件随机写
lseek 移动文件指针
_llseek 在 64 位地址空间里移动文件指针
dup 复制已打开的文件描述字
dup2 按指定条件复制文件描述字
flock 文件加 / 解锁
poll I/O 多路转换
truncate 截断文件
ftruncate 参见 truncate
umask 设置文件权限掩码
fsync 把文件在内存中的部分写回磁盘
2、文件系统操作
access 确定文件的可存取性
chdir 改变当前工作目录
fchdir 参见 chdir
chmod 改变文件方式
fchmod 参见 chmod
chown 改变文件的属主或用户组
fchown 参见 chown
lchown 参见 chown
chroot 改变根目录
stat 取文件状态信息
lstat 参见 stat
fstat 参见 stat
statfs 取文件系统信息
fstatfs 参见 statfs
readdir 读取目录项
getdents 读取目录项
mkdir 创建目录
mknod 创建索引节点
rmdir 删除目录
rename 文件改名
link 创建链接
symlink 创建符号链接
unlink 删除链接
readlink 读符号链接的值
mount 安装文件系统
umount 卸下文件系统
ustat 取文件系统信息
utime 改变文件的访问修改时间
utimes 参见 utime
quotactl 控制磁盘配额
三、系统控制
ioctl I/O 总控制函数
_sysctl 读 / 写系统参数
acct 启用或禁止进程记账
getrlimit 获取系统资源上限
setrlimit 设置系统资源上限
getrusage 获取系统资源使用情况
uselib 选择要使用的二进制函数库
ioperm 设置端口 I/O 权限
iopl 改变进程 I/O 权限级别
outb 低级端口操作
reboot 重新启动
swapon 打开交换文件和设备
swapoff 关闭交换文件和设备
bdflush 控制 bdflush 守护进程
sysfs 取核心支持的文件系统类型
sysinfo 取得系统信息
adjtimex 调整系统时钟
alarm 设置进程的闹钟
getitimer 获取计时器值
setitimer 设置计时器值
gettimeofday 取时间和时区
settimeofday 设置时间和时区
stime 设置系统日期和时间
time 取得系统时间
times 取进程运行时间
uname 获取当前 UNIX 系统的名称、版本和主机等信息
vhangup 挂起当前终端
nfsservctl 对 NFS 守护进程进行控制
vm86 进入模拟 8086 模式
create_module 创建可装载的模块项
delete_module 删除可装载的模块项
init_module 初始化模块
query_module 查询模块信息
*get_kernel_syms 取得核心符号,已被 query_module 代替
四、内存管理
brk 改变数据段空间的分配
sbrk 参见 brk
mlock 内存页面加锁
munlock 内存页面解锁
mlockall 调用进程所有内存页面加锁
munlockall 调用进程所有内存页面解锁
mmap 映射虚拟内存页
munmap 去除内存页映射
mremap 重新映射虚拟内存地址
msync 将映射内存中的数据写回磁盘
mprotect 设置内存映像保护
getpagesize 获取页面大小
sync 将内存缓冲区数据写回硬盘
cacheflush 将指定缓冲区中的内容写回磁盘
五、网络管理
getdomainname 取域名
setdomainname 设置域名
gethostid 获取主机标识号
sethostid 设置主机标识号
gethostname 获取本主机名称
sethostname 设置主机名称
六、socket 控制
socketcall socket 系统调用
socket 建立 socket
bind 绑定 socket 到端口
connect 连接远程主机
accept 响应 socket 连接请求
send 通过 socket 发送信息
sendto 发送 UDP 信息
sendmsg 参见 send
recv 通过 socket 接收信息
recvfrom 接收 UDP 信息
recvmsg 参见 recv
listen 监听 socket 端口
select 对多路同步 I/O 进行轮询
shutdown 关闭 socket 上的连接
getsockname 取得本地 socket 名字
getpeername 获取通信对方的 socket 名字
getsockopt 取端口设置
setsockopt 设置端口参数
sendfile 在文件或端口间传输数据
socketpair 创建一对已联接的无名 socket
七、用户管理
getuid 获取用户标识号
setuid 设置用户标志号
getgid 获取组标识号
setgid 设置组标志号
getegid 获取有效组标识号
setegid 设置有效组标识号
geteuid 获取有效用户标识号
seteuid 设置有效用户标识号
setregid 分别设置真实和有效的的组标识号
setreuid 分别设置真实和有效的用户标识号
getresgid 分别获取真实的,有效的和保存过的组标识号
setresgid 分别设置真实的,有效的和保存过的组标识号
getresuid 分别获取真实的,有效的和保存过的用户标识号
setresuid 分别设置真实的,有效的和保存过的用户标识号
setfsgid 设置文件系统检查时使用的组标识号
setfsuid 设置文件系统检查时使用的用户标识号
getgroups 获取后补组标志清单
setgroups 设置后补组标志清单
八、进程间通信
ipc 进程间通信总控制调用
1、信号
sigaction 设置对指定信号的处理方法
sigprocmask 根据参数对信号集中的信号执行阻塞 / 解除阻塞等操作
sigpending 为指定的被阻塞信号设置队列
sigsuspend 挂起进程等待特定信号
signal 参见 signal
kill 向进程或进程组发信号
*sigblock 向被阻塞信号掩码中添加信号,已被 sigprocmask 代替
*siggetmask 取得现有阻塞信号掩码,已被 sigprocmask 代替
*sigsetmask 用给定信号掩码替换现有阻塞信号掩码,已被 sigprocmask 代替
*sigmask 将给定的信号转化为掩码,已被 sigprocmask 代替
*sigpause 作用同 sigsuspend, 已被 sigsuspend 代替
sigvec 为兼容 BSD 而设的信号处理函数,作用类似 sigaction
ssetmask ANSI C 的信号处理函数,作用类似 sigaction
2、消息
msgctl 消息控制操作
msgget 获取消息队列
msgsnd 发消息
msgrcv 取消息
3、管道
pipe 创建管道
4、信号量
semctl 信号量控制
semget 获取一组信号量
semop 信号量操作
5、共享内存
shmctl 控制共享内存
shmget 获取共享内存
shmat 连接共享内存
shmdt 拆卸共享内存
25.pcb进程控制块,它是一个结构体,它存在与内核中
pcb进程控制块的结构体
struct task_struct { volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ struct thread_info *thread_info; atomic_t usage; unsigned long flags; /* per process flags, defined below */ unsigned long ptrace; int lock_depth; /* Lock depth */ int prio, static_prio; struct list_head run_list; prio_array_t *array; unsigned long sleep_avg; long interactive_credit; unsigned long long timestamp, last_ran; int activated; unsigned long policy; cpumask_t cpus_allowed; unsigned int time_slice, first_time_slice; #ifdef CONFIG_SCHEDSTATS struct sched_info sched_info; #endif struct list_head tasks; struct list_head ptrace_children; struct list_head ptrace_list; struct mm_struct *mm, *active_mm; /* task state */ struct linux_binfmt *binfmt; long exit_state; int exit_code, exit_signal; int pdeath_signal; /* The signal sent when the parent dies */ unsigned long personality; unsigned did_exec:1; pid_t pid; pid_t tgid; struct task_struct *real_parent; /* real parent process (when being debugged) */ struct task_struct *parent; /* parent process */ struct list_head children; /* list of my children */ struct list_head sibling; /* linkage in my parent's children list */ struct task_struct *group_leader; /* threadgroup leader */ struct pid pids[PIDTYPE_MAX]; wait_queue_head_t wait_chldexit; /* for wait4() */ #ifndef __GENKSYMS__ struct task_struct_aux *auxilliary; /* KABI-resistant auxilliary task data */ #else struct completion *vfork_done; /* for vfork() */ #endif int __user *set_child_tid; /* CLONE_CHILD_SETTID */ int __user *clear_child_tid; /* CLONE_CHILD_CLEARTID */ unsigned long rt_priority; unsigned long it_real_value, it_prof_value, it_virt_value; unsigned long it_real_incr, it_prof_incr, it_virt_incr; struct timer_list real_timer; unsigned long utime, stime; unsigned long nvcsw, nivcsw; /* context switch counts */ struct timespec start_time; /* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */ unsigned long min_flt, maj_flt; /* process credentials */ uid_t uid,euid,suid,fsuid; gid_t gid,egid,sgid,fsgid; struct group_info *group_info; kernel_cap_t cap_effective, cap_inheritable, cap_permitted; unsigned keep_capabilities:1; struct user_struct *user; /* limits */ struct rlimit rlim[RLIM_NLIMITS]; unsigned short used_math; char comm[16]; /* file system info */ int link_count, total_link_count; /* ipc stuff */ struct sysv_sem sysvsem; /* CPU-specific state of this task */ struct thread_struct thread; /* filesystem information */ struct fs_struct *fs; /* open file information */ struct files_struct *files; /* namespace */ struct namespace *namespace; /* signal handlers */ struct signal_struct *signal; struct sighand_struct *sighand; sigset_t blocked, real_blocked; struct sigpending pending; unsigned long sas_ss_sp; size_t sas_ss_size; int (*notifier)(void *priv); void *notifier_data; sigset_t *notifier_mask; /* TUX state */ void *tux_info; void (*tux_exit)(void); void *security; struct audit_context *audit_context; /* Thread group tracking */ u32 parent_exec_id; u32 self_exec_id; /* Protection of (de-)allocation: mm, files, fs, tty */ spinlock_t alloc_lock; /* Protection of proc_dentry: nesting proc_lock, dcache_lock, write_lock_irq(&tasklist_lock); */ spinlock_t proc_lock; /* context-switch lock */ spinlock_t switch_lock; /* journalling filesystem info */ void *journal_info; /* VM state */ struct reclaim_state *reclaim_state; struct dentry *proc_dentry; struct backing_dev_info *backing_dev_info; struct io_context *io_context; unsigned long ptrace_message; siginfo_t *last_siginfo; /* For ptrace use. */ wait_queue_t *io_wait; #ifdef CONFIG_NUMA struct mempolicy *mempolicy; short il_next; /* could be shared with used_math */ #endif };
PCB进程控制块:
进程id
文件描述符表
进程工作目录位置
umask掩码
信号相关信息资源
进程状态:初始态,就绪,运行太,挂起态,终止台,
用户id和租id
26.进程控制fork函数
fork()
getpid()
getppid()
27.fork函数
pid_t fork(void)
创建子进程,父子进程各自返回,父进程返回子进程pid.子进程返回0
getpid(),getpid()
循环创建n个子进程模型,每个进程表示自己的身份
28.父子进程相同:
刚fork后,data段,text段,堆,栈,环境变量,全局变量,宿主目录位置,进程工作目录位置,信号处理方式
父子进程不同:
进到id,返回值,各自的父进程,进程创建时间,闹钟,未决信号集.
父子进程共享:
读时共享,写时复制.-------------------------------------全局变量
1.文件描述符
2.mmap映射区
29.静态库:对空间要求较低,而对时间要求较高的核心程序中.
动态库:对时间要求较低,对空间要求较高
30.制作静态库的规范
必须已lib开头,以.a结尾:例如:lib*****.a
例如:ar rcs libmylib.a file1.o,ar rcs为制作库的命令,libmylib.a是静态库的名字,file1.0是使用的材料
1.gcc -c add.c -o add.o
2. ar rcs libmylib.a add.o
静态库制作步骤:
1.将.c生成.o文件
gcc -c add.c -o add.o
2.使用ar工具制作静态库
ar rcs lib库名.a add.o (可以是多个t.o文件)
静态库的使用:
gcc test.c libmymath.a -o test生成的test文件就包含了静态链接库
参考https://www.cnblogs.com/MXming/p/16255124.html
静态连接的制作过程:
1.编写add.c文件
int add(int a,int b) { return a+b; }
2.mymath.h
此文件主要是静态库函数的声明, 方便编码, 减少编译时的警告 (可以不存在).
#ifndef _MYMATH_H_ //判断是否声明了 _MYMATH_H_,若没有声明则执行以下三行, 作用是防止头文件被多次包含, 可省略 #define _MYMATH_H_ int add(int, int);#endif
3.制作静态库
gcc -c add.c -o add.o生成目标文件
静态库的制作命令一般是固定格式: ar rcs lib库名.a 编译文件(可以是多个, 也可以用*.o 指定当前目录下的所有 .o 文件)
ar rcs libmymath.a add.o sub.o
4.主调函数
#include <stdio.h> #include "mymath.h" int main() { int a = 10; int b = 5; printf("%d + %d = %d\n", a, b, add(a, b));return 0; }
5.静态库的使用
使用gcc编译同时加上静态库 源文件,并指明链接.
# 将源代码和静态库文件同时进行编译链接 # -l 参数指定链接的库 # -L 参数指定库文件的位置 # 如果库文件与源文件在同一目录下以上两个参数则可以省略 gcc main.c libmymath.a -l mymath -L ./ -o main
注意:
1.gcc后面不能加 -c
2.gcc后面的源码在前,静态链接库在后
3.源码和静态链接库顺序不可颠倒
# 执行可执行程序 ./main
如果在src下面创建了inc,lib目录,把头文件放入到inc中,把静态链接库放入到lib中,编译命令如下:
gcc test.c -L ./lib -lmymath -I ./inc/ -o test
注意:
-lmymath 参数符号和文件名字要挨在一起,mymath来自libmymath.a,去掉前缀lib和后缀.a就是静态库的名字
反汇编命令: objdump -dS antiporn.bin
31.动态库的制作以及使用:
1.动态库的制作:
地址段合并和地址回填
1.将.c生成.o文件,(生成与位置无关的代码 -fPIC,生成汇编文件的时候,用这个表示与位置无关的@plt,例如下面中的add@plt)
gcc -c add.c -o add.o -fPIC
2.使用gcc -shared 制作动态库
gcc -shared -o lib库名.so add.o
例子 gcc -shared -o libmymath.so add.o
file ./lib/libmymath.so
./lib/libmymath.so: ELF 64-bit LSB(小尾存储) shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=4f8a90463c7b5ddc607db30528e48ae881b9807e, not stripped
3.编译可执行 程序时,指定所使用的动态库.-l,指定库名,-L指定库路径
例子:gcc test.c -o test -lmymath -L ./lib -I ./inc
test.c主调程序
mymath库名
./lib库目录
./inc 头文件目录
4.运行可执行程序
执行报错
root@S:~/math/src# ./test ./test: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
原因:
连接器: 工作于链接阶段,工作时需要 -l和-L支持
动态连接器: 工作于程序运行阶段,工作需要提供动态库所在目录位置,程序会去某几个国定的地方去找动态链接库的,需要放到环境变量里面的 LD_LIBRARY_PATH=./lib
通过环境变量,export LD_LIBRARY_PATH=动态链接库路径./lib
例子:export LD_LIBRARY_PATH=./lib,这个时候就不报错了
可以把这个命令加到/etc/profile文件中 export LD_LIBRARY_PATH=./lib(这里建议使用绝对路径)或者写入~./bashrc文件,既可以永久生效,
也可以将这个libmymath.so放到操作系统的lib目录里面,就是/lib/目录下面即可
还可以在/etc/ld.so.conf里面增加libmymath的绝对路径,然后执行命令ldconfig -v,让配置生效,使用命令ldd ./test查看这个程序调用了那些动态链接库
总结:工作时机不一样
ldd test成功和失败的区别:
32.一个程序的内存图
.
33.动态库和静态库的注意事项:
1.动态库是否加载到内存,取决于 程序是否运行
2.动态库每次加载的位置不固定
3.动态,静态库共存时,编译器默认使用动态库.
34.gdb调试工具
使用步骤:
1.gcc -g test.c -o test -lmymath -L ./lib -I ./inc
上述命令的参-g,就是表示产生一个带有调试信息的可执行文件,使用改参数编译可以生成执行文件,得到调试表
2.gdb 可执行文件
例子 gdg test
3.输入命令l或者list,可以查看源代码
list 1,表示从第一行显示源码
b 2,表示在第二行设置断点,b的意思breakpoint
run,表示执行程序,运行到断点的地方停止
按n或者s,向下执行一步,n是next(跳过函数,不会进入到函数里面的),s是step,s会进到函数里面的(如果要从函数里面退出来,用命令until 13,13表示退回到源码的13行,也可以使用finish执行完这个函数,再退回来)
p 变量名,p就是查看变量的值,p a就是 查看a的值
其他命令:
run:使用run查找段错误的位置
finish:结束当前函数调用
set args:设置main函数命令行参数,必须在start命令之前
run 字符串1 字符串2 字符串...设置main函数命令行参数
info b:查看断点信息
b 20 if i=5:设置条件断点
continue:表示执行到下一个断点
ptype:查看变量类型
backtrace,bt列出当前程序中正存活着的栈帧
frame切换函数的栈帧,可以切换到main的栈帧或者其它函数的栈帧,然后可以通过p 变量名查看变量类型
display:设置跟踪变量
undisplay:取消设置跟踪变量,使用跟踪变量的编号
set follow-fork-mode-child 命令设置gdb在fork之后跟踪子进程
set follow-fork-mode parent 设置跟踪父进程
35.dgb调试段错误是最方便的,直接设置断点,然后run运行,程序停止在哪里?那么他上面的行就有错误,gdb调试段错误是最方便的
36.栈帧:随着函数调用而在stack上开辟的一片内存空间,用于存放函调调用时产生的局部变量和临时值
37.Makefile项目管理
命名:makefile Makefile
1个规则:
目标:依赖条件
(一个tab缩进) 命令
1.目标的时间必须晚于依赖条件的时间,否则,更新目标
2.依赖条件如果不存在,找寻新的规则去产生依赖
ALL:指定makefile的终极目标
2个函数:
1.wildcard:
src=$(wildcard *.c):找到当前目录下所有后缀为.c的文件,将文件名组成列表,赋值给变量src
2.patsubst
obj=$(patsubst %.c,%.o, $(src)):将 参数3中,包含参数1的部分,替换为参数2
clean:(没有依赖)
-rm -rf $(obj) a.out ""-":作用是,删除不存在的文件,不报错.顺序执行结束
3个自动变量:
$@:在规则的命令中,表示目标
$^::在规则的命令中,表示所有的依赖条件
$<:在规则的命令中,表示第一个依赖条件,如果将该变量应用在模式规则中,它可以将依赖条件列表中的依赖一次取出,套用模式规则.
模式规则:
%.o:%.c
gcc -c $< %@
静态模式规则:
$(obj)%.o:%.c
gcc -c $< %@
伪目标:
.PHONY:clean ALL
参数:
-n:模拟执行make,make clean 命令
-f:指定文件执行make命令
root@SHPD18F-SP05:~/math/src# root@SHPD18F-SP05:~/math/src# more makefile test:test.o gcc test.o -lmymath -L ./lib -o test test.o:test.c gcc -c test.c -o test.o -I ./inc root@SHPD18F-SP05:~/math/src#
makefile
38.exec函数族
39.init是进程孤儿院
40.wait回收子进程的pcb进程控制块
父进程调用wait函数可以回收子进程终止信息,该函数有三个功能: 回收子进程退出资源,阻塞回收任意一个
1.阻塞等待子进程退出
2.回收子进程残留资源
3.获取子进程结束状态(退出原因)
获取自进程正常终止值:
WIFEXITED(status)--->>为真--->>调用WEXITSTATUS(status)--->>得到子进程,退出值
获取导致子进程异常终止信号:
WIFSIGNALED(status)--->>为真--->>调用WTERMSIG(status)--->>得到,导致进程异常终止的信号编号.
41.waitpid()可以设置非阻塞
pid_t waitpid(pid_t pid,int *status,int optains)//一次wait/waitpid函数调用,只能回收一个进程
pid_t waitpid()
参数:
pid:指定待回收的子进程pid
>0:待回收的子进程pid
-1:任意子进程
0:同组的任一个子进程
返回值:
>0:表示成功回收的子进程pid
0:函数调用时,参3指定了WNOHANG,并且,没有子进程结束
-1:失败,errno
总结:
wait,waitpid一次调用,回收一个子进程
想回收多个,while
41.IPC进程间通信
42.管道
实现原理:内核接住环形队列机制,使用缓冲区实现.
特质:1.伪文件
2.管道中的数据只能一次读取
3.数据在管道中,只能单项流动
局限性: 1.自己写,不能自己读
2.数据不可以反复读取
3.半双工通信
4.血缘关系进程间可用
43.pipe函数: 创建,并打开管道
int pipe(int fd[2])
参数:fd[0]:0表示读端
fd[1]:1表示写端
返回值:成功 :0
失败:-1 errno
管道的读写行为:
读管道:
1.管道有数据,read返回实际督导的字节数
2.管道无数据: 1>无写端,read返回0(读到文件尾)
2>有写端,read阻塞等待
写管道:
1.无读端,异常终止.(是由于SIGPIPE)
2.有读端,
1>管道已满,阻塞等待
2.管道未满,返回写出的字节数
管道的优劣:
优点:简单,相比信号,套接字实现进程间通信,简单很多
缺点:
1.只能单向通信,双向通信需要建立两个管道
2.只能用于父子,兄弟进程(有共同祖先)间通信.该问题后来使用fifo有名管道解决,无血缘关系的进程通信使用fifo有名管道解决
44.FIFO的理论依据:就是内核空间是共享的,通过内核空间实现进程之间的通信
45.共享内存mmap
void * mmap(void *addr, size_t length, int prot , int flags, int fd, off_t offset);
参数:
addr:指定映射区的首地址.通常传NULL,表示让系统自动分配
length:共享内存映射区的大小<小于等于文件大小>
prot:共享内存映射区的读写属性,PROT_READ,PROT_WRITE,PRO_READ|PROT_WRITE
flags:标注共享内存的共享属性.MAP_SHARED(对内存的修改会反应在磁盘上),MAP_PRIVATE(对内存的修改不会反应在磁盘上)
fd:用于创建共享内存映射区的那个文件的文件描述符
offset:默认0,表示映射文件全部.偏移位置.必须是4k的整数倍
返回值:
成功:映射区的首地址
失败:MAP_FAILED(void*(-1)),errno
释放共享内存映射:int munmap(void *addr, size_t length),释放映射区;
使用命令:od -tcx testmap 以16进制查看文件内容
使用注意事项:
1.用于创建映射区的大小为0,实际指定非0大小创建映射区,会出总结错误
2.用于创建映射区的文件大小为0,实际指定0大小创建映射区,出无效参数
3.用于创建映射区的文件读写属性为只读,映射区属性为读\写.出""无效参数""
4.创建映射区,需要read权限,当访问权限指定为""共享",MAP_SHARED",因此,mmap的读写权限,应该小于等于文件的open权限,只写肯定不行
5.文件描述符fd,在mmap创建映射区完成即可关闭,后续访问文件,用地址访问.
6.offset必须是4096的整数倍(跟mmu有关,mmu映射的最小单位是4k)
7.对申请的内存,不能越界访问
8.munmap用于释放的地址,必须是mmap申请返回的地址
9.映射区访问权限为""私有""MAP_PRIVATE,对内存所做的修改,值在内存有效,不会反应到物理磁盘上.
10.映射区访问权限为""私有""MAP_PRIVATE,只需要open,有读权限,用于创建映射区即可
mmap函数保险调用方式:
1.open(""文件名"",O_RDWR)
2.mmap(NULL,有效文件大小,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0)
父子进程使用mmap进程间通信(步骤)
父进程先创建映射区.open(O_RDWR) mmap()
指定MAP_SHARED权限
fork()创建子进程
一个进程读,另外一个进程写
无血缘关系进程间mmap通信:
两个进程打开同一个文件,创建映射区
指定flags为MAP_SHARED
一个进程写入,另外一个进程读出
[注意]无血缘关系进程间通信.mmap:数据可有重复读取
fifo:数据只能读取一次,
信号共性:
简单,不能携带大量信息,满足条件才发送
信号的特质:
信号是软件层面上的""中断"".一旦信号产生,无论程序执行到什么位置,必须立即停止运行,处理信号,处理结束,再继续执行后续指令,所有信号的产生以及处理全部都是由内核完成的
产生信号:
1.按键产生:如:ctrl+c,ctrl+z,ctrl+\
2.系统调用产生,如:kill,raise,abort
3.软件条件产生,如定时器alarm
4.硬件异常产生,如:非法访问内存(段错误),除0(浮点数例外),内存对齐出差(总线错误)
5.命令产生:如:kill命令
概念:
未决:产生与递达之间
递达:产生并且送达到进程.直接被内核处理掉
信号处理方式:执行默认处理动作,忽略,捕捉(自定义)
阻塞信号集(信号屏蔽字):本质,位图,用来记录信号的屏蔽状态,一旦被屏蔽的信号,在接触屏幕时,一直处于未决态
未决信号集:本质,位图,用来记录信号的处理状态.该信号集中的信号,表示,已经产生,但尚未被处理
信号4要素:信号在使用之前,应先确定信号的4要素,然后再使用
1.编号
2.名称
3.事件
4.默认处理动作
kill命令和kill函数:
kill(pid_t pid,intsignum)
pid>0:发送指定进程
pid=0:发送跟调用kill函数的那个进程处于同一个进程租的进程
pid<-1:取绝对值,发送绝对值所对应的进程组的所有组员
返回值: 成功:0
失败-1
alarm函数:使用自然计时法.
定时发送SIGALARM当前进程.
unsigned int alarm(unsigned int seconds);
seconds:定时秒数
返回值:上次定时剩余时间
无错误现象
time 命令:查看程序执行时间.实际时间=用户时间+内核时间+等待时间----->>优化瓶颈在IO
信号集操作函数:
sigset_t set:自定义信号集
sigemptyset(sigset_t *set):清空信号集
sigfillset(sigset_t *set):全部置1
sigaddset(sigset_t *set,int signum`):将一个信号添加到集合中
sigdelset(sigset_t *set,int signum`):将一个信号从集合移除
sigismember(const sigset_t *set,int signum):判断一个信号是否在集合中.在---->1,不在---->0
设置信号屏蔽字和解除屏蔽:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how: SIG_BLOCK: 设置阻塞
SIG_UNBLOCK 取消阻塞
SIG_SETMASK:用自定义的set替换mask
set: 定义set
oldset: 旧有的mask
查看未决信号集:
int sigpending(sigset_t *set):set传出未决 信号集
信号查看:man 7 signal
信号捕捉:
signal();
sigaction()
信号捕捉特性:
1.捕捉函数执行期间,信号屏蔽字 由---->sa_mask,捕捉函数执行结束.恢复回mask
2.捕捉函数执行期间,本信号自动被屏蔽(sa_flgs=0)
3.捕捉函数执行期间,被屏蔽信号多次发送,解除屏蔽后,只处理一个,不处理排队
SIGCHILD信号:只要子进程的状态发生变化,就会产生SIGCHILD信号
产生情况:
1.子进程终止时
2.子进程接收到SIGSTOP信号停止时,
3.子进程处在停止态,接受到SIGCONT后唤醒时
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<unistd.h> #include<pthread.h> #include<signal.h> void print_set(sigset_t *set) { int i; for(i = 1; i < 32; i++) { if(sigismember(set, i)) putchar('1'); else putchar('0'); } printf("\n"); } main() { sigset_t set, oldset, pedset; int ret = 0; sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT); sigaddset(&set, SIGBUS); sigaddset(&set, SIGUSR1); sigaddset(&set, SIGKILL); ret = sigprocmask(SIG_BLOCK, &set, &oldset); if(ret == -1) { perror("sigprocmask error\n"); } while(1) { ret = sigpending(&pedset); if(ret == -1) { perror("sigpending error\n"); } print_set(&pedset); sleep(1); } return 0; }
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<unistd.h> #include <signal.h> void sig_catch(int signo)//回调函数 { printf("catch you !! %d\n",signo); return; } int main(int argc,char *argv[]) { signal(SIGINT, sig_catch); while(1){} return 0; }
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<unistd.h> #include <signal.h> void sig_catch(int signo)//回调函数 { if(signo == SIGINT) { printf("catch you !! %d\n", signo); sleep(10); } else if (signo == SIGQUIT) printf("catch you--------------- !! %d\n", signo); return; } int main(int argc, char *argv[]) { struct sigaction act, oldact; act.sa_handler = sig_catch;//设置回调函数 sigemptyset(&act.sa_mask);//清空sa_mask屏蔽字,只在sig_catch工作时有效 sigaddset(&act.sa_mask,SIGQUIT);//让在回调函数处理期间,不要处理其他到来的信号 act.sa_flags = 0;//默认值 int ret = sigaction(SIGINT, &act, &oldact);//注册信号捕捉函数 if(ret == -1) { perror("sigaction error\n"); } ret = sigaction(SIGQUIT, &act, &oldact);//注册信号捕捉函数 if(ret == -1) { perror("sigaction error\n"); } while(1) {} return 0; }
会话:多个金出组的集合
创建一个会话需要注意一下6点注意事项
1.调用进程不能是进程组组长,该进程变成新会话首进程(session header)
2.该进程称为一个欣进程组的组长进程.
3.需有root权限(ununtu不需要)
4.新会话丢弃原有的控制终端,该会话没有控制终端
5.该调用进程是组长进程,则出错返回
6.建立新会话时,先调用fork,父进程终止,子进程调用setsid
守护进程:
daemon进程,通常运行与操作系统后台,脱离控制终端.一般不与用户直接交互.周期性的等待某个事件发生或者周期性执行某一动作,不受用户登陆住校影响.通常采用一d结尾的命令方式
创建守护进程的步骤:
1.fork子进程,让父进程终止
2.子进程调用setsid()创建新会话
3.通常根据需要,改变工作目录位置chdir()
4.通常根据需要,重设umask文件掩码
5.通常根据需要,关闭/重定向文件描述符
6.守护进程业务逻辑.while()
示例代码
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<pthread.h> #include<fcntl.h> #include <sys/stat.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t pid; int fd, ret; pid = fork(); if(pid > 0) //让父进程终止 exit(0); pid = setsid(); //创建新会话 if(pid == 0) perror("setsid error"); ret = chdir("/home/"); if(ret == -1) { perror("chdir error");//改变工作目录位置 } umask(0022);//改变文件访问权限掩码 close(STDIN_FILENO);//关闭文件描述符0 fd = open("/dev/null", O_RDWR); //fd---->0 if(fd == -1) { perror("open error\n"); } dup2(fd, STDOUT_FILENO);//重定向stdout和stderr dup2(fd, STDERR_FILENO); while(1){};//模拟守护进程业务 return 0; }
线程概念:
进程:有独立的,进程地址空间,有独立的pcb进程控制块
线程:有独立的pc,没有独立的进程地址空间
linux:查看 ps -Lf 进程号,------>线程号,LWP----->cpu 执行的最小单位
gcc在编译线程的时候,需要加上参数-lpthread
示例: gcc pthreadtest.c -o pthreadtest -lpthread
线程控制原语:
pthread pthread_self();获取线程id,线程id是在进程地址空间内部,用来标识线程身份的一个号,主要给本进程或者本进程的其他线程使用.
返回值:本线程id
int pthread_create(pthread_t *tid,pthread_attr_t *attr,void *(*start_routine),void *arg)
参数1:传出参数,表新创建的子进程id
参数2:线程属性.传NULL表使用默认属性
参数3:子线程回调函数,创建成功,pthread_create函数返回时,该函数会被自动调用
参数4:参3的参数,没有的话NULL
返回值:成功 0
失败:errno
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<pthread.h> void *tfn(void *arg) { printf("I am thread!!\n"); printf("thread:pid=%d, tid=%lu\n", getpid(), pthread_self()); return NULL; } int main() { pthread_t tid; int ret = pthread_create(&tid, NULL, tfn, NULL); if(ret != 0) { perror("pthread_create error"); } printf("main:pid=%d, tid=%lu\n", getpid(), pthread_self()); sleep(2); return 0; }
循环创建线程
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<pthread.h> void *tfn(void *arg) { printf("I am thread!!\n"); printf("thread:pid=%d, tid=%lu\n", getpid(), pthread_self()); return NULL; } int main() { pthread_t tid; int i, ret; for(i = 1; i <=10; i++) { ret = pthread_create(&tid, NULL, tfn, NULL); if(ret != 0) { perror("pthread_create error"); } //printf("main:pid=%d, tid=%lu\n", getpid(), pthread_self()); } sleep(20); return 0; }
创建多个线程,并且给线程传递参数
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<pthread.h> void *tfn(void *arg) { int i = (int)arg;//强转,受参数类型限制 //printf("I am thread!!\n"); printf("I am %d thread:pid=%d, tid=%lu\n", arg, getpid(), pthread_self()); return NULL; } int main() { pthread_t tid; int i, ret; for(i = 1; i <= 10; i++) { ret = pthread_create(&tid, NULL, tfn, (void *)i);//采用的是值传递,需要借助强转(受参数类型限制) if(ret != 0) { perror("pthread_create error"); } //printf("main:pid=%d, tid=%lu\n", getpid(), pthread_self()); } printf("main:pid=%d, tid=%lu\n", getpid(), pthread_self()); sleep(20); return 0; }
void pthread_exit(void *retval)退出当前线程
retval:退出值,无退出值时,NULL
返回值:无
示例:pthread_exit((void *)0);这个函数退出主线程,但是不退出进程
比较:
exit():退出当前进程
return:返回到调用处
pthread_exit() :退出当前线程
int pthread_join(pthread_t thread, void **value_ptr);(与wait函数的功能一样)
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<pthread.h> struct thrd { int var; char str[256]; }; void *tfn(void *arg) { struct thrd *tval; tval = malloc(sizeof(tval)); memset(tval, 0, sizeof(tval)); tval->var = 100; strcpy(tval->str, "hello thread"); return (void *)tval; } int main() { pthread_t tid; struct thrd *retval; int ret = pthread_create(&tid, NULL, tfn, NULL); if(ret != 0) { perror("pthread_create error"); } ret = pthread_join(tid, (void **)&retval); if(ret != 0) { perror("pthread_join error\n"); } printf("child thread exit with var:%d ,str= %s\n", retval->var, retval->str ); free(retval); memset(retval, 0, sizeof(retval)); printf("child thread exit with var:%d ,str= %s\n", retval->var, retval->str ); pthread_exit(NULL); }
int prthread_join(pthread_t thread,void **retval):回收线程
thread:待回收的那个线程的退储值
返回值:成功:0
失败:errno
int pthread_detach(pthread_t thread):设置线程分离
thread:待分离的线程id
返回值:成功:0
失败:errno
int pthread_cancel(pthread_t thread):杀死一个线程,需要取消点(保存点)
thread:待杀死的线程id
返回值:成功0
失败:errno
如果,子线程没有到达取消点,那么pthread_cancel无效
我们可以在程序中,手动添加一个取消点,使用pthread_testcancel()
成功被pthread_cancel()杀,死的线程,返回值为-1,使用pthread_join回收
线程杀死:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<pthread.h> void *tfn(void *arg) { while(1) { printf("thread:pid=%d, tid=%lu\n", getpid(), pthread_self()); sleep(1); } return NULL; } int main() { pthread_t tid; int ret = pthread_create(&tid, NULL, tfn, NULL); if(ret != 0) { fprintf(stderr, "pthread_create error:%s\n", strerror(ret)); exit(1); } printf("main:pid=%d, tid=%lu\n", getpid(), pthread_self()); sleep(10); ret = pthread_cancel(tid); if(ret != 0) { fprintf(stderr, "pthread_create error:%s\n", strerror(ret)); exit(1); } while(1); pthread_exit((void *)0); }
线程分离
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<unistd.h> 5 #include<errno.h> 6 #include<pthread.h> 7 void *tfn(void *arg) { 8 while(1) { 9 printf("thread:pid=%d, tid=%lu\n", getpid(), pthread_self()); 10 sleep(1); 11 } 12 return NULL; 13 } 14 int main() { 15 pthread_t tid; 16 17 18 19 20 int ret = pthread_create(&tid, NULL, tfn, NULL); 21 if(ret != 0) { 22 23 printf("pthread_create error:%s\n", strerror(ret)); 24 exit(1); 25 } 26 27 ret = pthread_detach(tid);//设置线程分离,线程终止,会自动回收pcb,无需回收 28 if(ret != 0) { 29 30 printf("pthread_detach error:%s\n", strerror(ret)); 31 exit(1); 32 33 } 34 35 ret = pthread_join(tid, NULL); 36 printf("pthread_join ret=%d\n", ret); 37 if(ret != 0) { 38 39 printf("pthread_join error:%s\n", strerror(ret)); 40 exit(1); 41 42 } 43 printf("main:pid=%d, tid=%lu\n", getpid(), pthread_self()); 44 45 46 pthread_exit((void *)0); 47 }
线程控制原语 | 进程控制原语 |
pthread_create() | fork() |
pthread_self() | getpid() |
pthread_exit() | exit(,return) |
pthread_join() | waitpid() |
pthread_cancel() | kill() |
设置线程属性,分离态属性
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<unistd.h> 5 #include<errno.h> 6 #include<pthread.h> 7 void *tfn(void *arg) { 8 printf("thread:pid=%d, tid=%lu\n", getpid(), pthread_self()); 9 10 return NULL; 11 } 12 int main() { 13 pthread_t tid; 14 pthread_attr_t attr; 15 16 int ret = pthread_attr_init(&attr); 17 if(ret != 0) { 18 19 fprintf(stderr, "attr_init error:%s\n", strerror(ret)); 20 exit(1); 21 } 22 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置线程属性为分离属性 23 if(ret != 0) { 24 25 fprintf(stderr, "att_setdetachstate error:%s\n", strerror(ret)); 26 exit(1); 27 } 28 29 ret = pthread_create(&tid, &attr, tfn, NULL); //创建线程的时候,使用线程属性,对线程进行设置 30 if(ret != 0) { 31 32 perror("pthread_create error"); 33 } 34 35 ret = pthread_join(&tid, NULL); 36 37 if(ret != 0) { 38 fprintf(stderr, "pthread_join error:%s\n", strerror(ret)); 39 exit(1); 40 } 41 42 ret = pthread_attr_destroy(&attr); 43 if(ret != 0) { 44 45 fprintf(stderr, "attr_destroy error:%s\n", strerror(ret)); 46 exit(1); 47 } 48 49 pthread_exit((void *)0); 50 }
线程属性(每个进程的栈大小是8M,所有线程进行平均分配):
1.分离属性.
1>pthread_attr_t attr 创建一个线程属性结构体变量
2>pthtead_attr_init(&attr)初始化线程属性
3>pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);设置线程属性为分离态
4>pthread_create(&tid,&attr,tfn,NULL)借助修改后的设置线程属性,创建分离态的新线程
4>pthread_attr_destroy(&attr)销毁线程属性
2.修改线程栈大小
线程使用注意事项:
1.主线程退出其他线程不退出,主线程应调用pthread_exit
2.避免僵尸线程
pthread_join
pthread_detach
pthread_create:指定分离属性
将join线程可能在join函数返回前就释放自己的所有资源,所以不应该放回被回收线程栈中的值
3.malloc和mmap申请的内存可以被其他线程释放,因为线程之间共享堆区
4.应避免在多线程模型中调用fork,除非,马上exec,子进程中只有调用fork的线程存在,其他线程在子进程中均使用pthread_exit
5.信号的复杂语义很难和多线程共存,应避免在多线程中引入信号机制
线程同步:
锁的创建过程以及使用:
1.pthread_mutex_t lock:创建锁
2.pthread_mutex_init:初始化
3.pthread_mutex_lock:加锁
4:访问共享数据
5.pthread_mutex_unlock解锁
6.pthread_mutex_destroy:销毁锁
注意事项:
尽量保证锁的粒度,越小越好.(访问共享数据前,加锁.访问结束[立即]解锁).
互斥锁,本质是结构体,我们可以看成是整数,初值为1.(pthread_mutex_init()函数调用成功)
加锁:--操作,阻塞线程
解锁:++操作,唤醒阻塞在锁上的线程
try锁:尝试加锁,成功--;
失败返回,同时设置错误号是EBUSY
死锁:
是使用不恰当导致的现象;
1.对一个锁反复lock.
2.两个线程,各自持有一把锁,而去请求另外一把
restrict关键字:
用来限定指针变量.被该关键字限定的指针变量所指向的内存操作,必须由本指针来完成.
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<unistd.h> 5 #include<errno.h> 6 #include<pthread.h> 7 pthread_mutex_t mutex;//定义一个把互斥锁,可以把它想象成一个整数 8 void *tfn(void *arg) { 9 while(1) { 10 pthread_mutex_lock(&mutex);//加锁,可以想象成锁-- 11 printf("hello"); 12 sleep(rand() % 2);//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误 13 printf("world\n"); 14 pthread_mutex_unlock(&mutex);//解锁,可以想象成锁++ 15 sleep(rand() % 2); 16 17 } 18 return NULL; 19 } 20 int main() { 21 pthread_t tid; 22 int ret = pthread_mutex_init(&mutex, NULL);//初始化互斥锁,我们可以认为锁的值时1 23 if(ret != 0) { 24 fprintf(stderr, "pthread_mutex_init:%s\n", strerror(ret)); 25 } 26 pthread_create(&tid, NULL, tfn, NULL); 27 while(1) { 28 pthread_mutex_lock(&mutex);//加锁 29 printf("HELLO"); 30 sleep(rand() % 2);//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误 31 printf("WORLD\n"); 32 pthread_mutex_unlock(&mutex);//解锁 33 sleep(rand() % 2); 34 35 36 } 37 ret = pthread_mutex_destroy(&mutex);//销毁互斥锁 38 if(ret != 0) { 39 fprintf(stderr, "pthread_mutex_destroy:%s\n", strerror(ret)); 40 } 41 pthread_exit((void *)0); 42 }
条件变量:
条件变量本身不是锁,它通常结合锁来使用
主要应用函数:
pthread_cond_init函数
pthread_cond_destroy函数
pthread_cond_wait函数
pthread_cond_timedwait函数
pthread_cond_signal函数
pthread_cond_broadcast函数
以上6个函数返回值是:成功返回0,失败直接返回错误号
pthread_cond_t 类型 用于定义条件变量
1.定义一个条件变量
pthread_cond_t cond;
2.初始化条件变量:
1>pthread_cond_init(&cond,NULL);动态初始化
2>pthread_cond_t cond=PTHREAD_COND_INITIALIZER静态初始化
pthread_cond_signal:唤醒阻塞在条件变量 上的(至少)一个线程
pthread_cond_broadcast();唤醒阻塞条件变量上的所有线程
用线程锁和条件变量实现生产者消费者模型(一个生产者-----一个消费者):
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<unistd.h> 5 #include<errno.h> 6 #include<pthread.h> 7 void err_thread(int ret, char *str) { 8 if(ret != 0) { 9 fprintf(stderr, "%s:%n", str, strerror(ret)); 10 pthread_exit(NULL); 11 } 12 } 13 struct msg { 14 int num; 15 struct msg *next 16 17 }; 18 19 struct msg *head; 20 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //定义并且在初始化互斥量 21 pthread_cond_t has_data = PTHREAD_COND_INITIALIZER ;//定义并初始化一个条件变量 22 23 24 void *producer(void *arg) { 25 while(1) { 26 struct msg *mp = malloc(sizeof(struct msg)); 27 mp->num = rand() % 1000 + 1; //模拟生产一个数据 28 printf("------producer %d\n", mp->num); 29 pthread_mutex_lock(&mutex);//加锁,互斥量 30 mp->next = head; //写公共区域数据 31 head = mp; 32 pthread_mutex_unlock(&mutex); 33 pthread_cond_signal(&has_data);//唤醒阻塞在条件变量上的线程 34 sleep(rand() % 3); 35 } 36 return NULL; 37 } 38 void *consumer(void *arg) { 39 while(1) { 40 struct msg *mp; 41 pthread_mutex_lock(&mutex); 42 if(head == NULL) { 43 pthread_cond_wait(&has_data, &mutex); //阻塞等待条件变量 44 } 45 mp = head; 46 head = mp->next; 47 pthread_mutex_unlock(&mutex); 48 printf("---------------consumer:%d\n", mp->num); 49 sleep(rand() % 3); 50 free(mp); 51 } 52 return NULL; 53 } 54 55 56 57 int main() { 58 pthread_t pid, cid; 59 srand(time(NULL)); 60 int ret = pthread_create(&pid, NULL, producer, NULL); 61 if(ret != 0) { 62 err_thread(ret, "pthread_create producer error"); 63 } 64 ret = pthread_create(&cid, NULL, consumer, NULL); 65 if(ret != 0) { 66 err_thread(ret, "pthread_create consumer error"); 67 } 68 pthread_join(pid, NULL); 69 pthread_join(cid, NULL); 70 return 0; 71 }
用线程锁和条件变量实现生产者消费者模型(一个生产者-----多个消费者):
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<unistd.h> 5 #include<errno.h> 6 #include<pthread.h> 7 void err_thread(int ret, char *str) { 8 if(ret != 0) { 9 fprintf(stderr, "%s:%n", str, strerror(ret)); 10 pthread_exit(NULL); 11 } 12 } 13 struct msg { 14 int num; 15 struct msg *next 16 17 }; 18 19 struct msg *head; 20 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //定义并且在初始化互斥量 21 pthread_cond_t has_data = PTHREAD_COND_INITIALIZER ;//定义并初始化一个条件变量 22 23 24 void *producer(void *arg) { 25 while(1) { 26 struct msg *mp = malloc(sizeof(struct msg)); 27 mp->num = rand() % 1000 + 1; //模拟生产一个数据 28 printf("------producer %d\n", mp->num); 29 pthread_mutex_lock(&mutex);//加锁,互斥量 30 mp->next = head; //写公共区域数据 31 head = mp; 32 pthread_mutex_unlock(&mutex); 33 pthread_cond_signal(&has_data);//唤醒阻塞在条件变量上的线程 34 sleep(rand() % 3); 35 } 36 return NULL; 37 } 38 void *consumer(void *arg) { 39 while(1) { 40 struct msg *mp; 41 pthread_mutex_lock(&mutex); 42 if(head == NULL) { 43 pthread_cond_wait(&has_data, &mutex); //阻塞等待条件变量 44 } 45 mp = head; 46 head = mp->next; 47 pthread_mutex_unlock(&mutex); 48 printf("---------------consumer:%lu %d\n", pthread_self(), mp->num); 49 sleep(rand() % 3); 50 free(mp); 51 } 52 return NULL; 53 } 54 55 56 57 int main() { 58 pthread_t pid, cid; 59 srand(time(NULL)); 60 int ret = pthread_create(&pid, NULL, producer, NULL); 61 if(ret != 0) { 62 err_thread(ret, "pthread_create producer error"); 63 } 64 ret = pthread_create(&cid, NULL, consumer, NULL); 65 if(ret != 0) { 66 err_thread(ret, "pthread_create consumer error"); 67 } 68 ret = pthread_create(&cid, NULL, consumer, NULL); 69 if(ret != 0) { 70 err_thread(ret, "pthread_create consumer error"); 71 } 72 ret = pthread_create(&cid, NULL, consumer, NULL); 73 if(ret != 0) { 74 err_thread(ret, "pthread_create consumer error"); 75 } 76 ret = pthread_create(&cid, NULL, consumer, NULL); 77 if(ret != 0) { 78 err_thread(ret, "pthread_create consumer error"); 79 } 80 ret = pthread_create(&cid, NULL, consumer, NULL); 81 if(ret != 0) { 82 err_thread(ret, "pthread_create consumer error"); 83 } 84 pthread_join(pid, NULL); 85 pthread_join(cid, NULL); 86 return 0; 87 }
信号量:
应用于线程同步,也可以应用于进程同步.
相当于初始化值为N的互斥量,N值,表示可以同时访问共享数据区的线程数
函数:
sem_t sem:定义类型
int sem_init(sem_t *sem,int pshared,unsigned int value)
参数:
sem:信号量
pshared: 0:用于线程间同步
1:用于进程间同步
value:N值.(指定同时访问的线程数)
sem_init();
sem_destroy();
sem_wait()一次调用,做一次操作,当信号量的值为0时,再次---就会阻塞(对比pthread_mutex_lock)
sem_post();一次调用,做一次++操作,当信号量的值为N时,再次++就会阻塞(对比pthread_mutex_unlock)
网络编程:
1协议:一组规则
2.OSI七层模型:物数网传回表应
3.tcpip:网(链路层)网传应
应用层:http,fto,ssh,nfs,telnet
传输层:TCP,UDP
网络层:IP
4.网络套接字:socket
一个文件描述符指向一个套接字(该套接字内部由内核接住两个缓冲区实现)
套接字:在通信过程中,套接字必须成对出现
5.网络字节序:
小端法(pc本地存储):高位存高地址,低位存低地址
大端法(网络传输): 高位存储低地址,低位存储在高地址
htonl---->本地---->网络(ip)
htons--->本地--->网络(port)
ntohl-->网络--->本地(IP)
ntohs--->网络--->本地()port)
6.ip地址转换函数:
1.int inet_pton(int af, const char *restrict src, void *restrict dst);本地字节序转换成网络字节序
af:AF_INET(ipv4),AF_INET(ipv6)
src:ip地址(点分十进制)
dst:传出,(转换后的网络字节序的ip地址)
返回值:
成功:1
异常:0 说明src指向的不是一个有效的ip地址
失败:-1
2.const char *inet_ntop(int af, const void *src,char *dst, socklen_t cnt);网络字节序转换为本地字节序
af:AF_INET(ipv4),AF_INET(ipv6)
src:网咯字节序的ip地址
dst:转换后的本地字节序
size:dst的大小
返回值: 成功:dst
失败:null
7.sockaddr地址结构
strunt sockaddr_in addr
addr.sin_family=AF_INET/AF_INET6;
addr.sin_port=htons(9527);
int dst;
addr.sin_addr.s_addr=inet_pton(AF_INET,""192.168.22.45"",(void *)dst);
addr.sin_addr.s_addr=dst;
重点addr.sin_addr.s_addr=htonl(INADDR_ANY);取出系统中有效的ip地址,二进制类型
bind(fd,(struct sockaddr*)addr.size)
8socket函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol);创建一个套接字
参数:domain:AF_INET,AF_INET6,AF_UNIX
type:SOCK_STREAM,SOCK_DGRAM
protocol:0
返回值:成功:新套接字所对应的文件描述符
失败:-1 errno
bind函数
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);给socket绑定一个ip和 端口号,把这个称为地址结构
参数:soket函数返回值
struck sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(8888);
addr.sin_addr.s_addr=htonl(INADDR_ANY)
addr:(struct sockaddr *)&addr
addrlen:sizeof(addr):地址结构大小
返回值:
成功:0
失败-1
int listen(int sockfd, int backlog);设置同时与服务器建立连接的上限数(同时进行3次捂手的客户端数量)
参数:
sockfd:socket函数返回值
backlog:上限数值.最大128
返回值:
成功:0
失败:-1 errno
int accept4(int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags);阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符
参数:socket 函数返回值
addr:传出参数.成功与服务器建立的那个客户端的地址结构(IP+port)
socklen_t clit_addr=sizeof(addr);
addrlen:传入传出.&clit_addr_len
入:addr的大小.出:客户端addr实际大小
返回值:
成功:能与服务器数据通信的socket对应的文件描述符
失败:-1,errno
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);使用现在的socket与服务器建立连接
参数:
sockfd:socket函数返回值
addr:传入参数.服务器地址结构
addrlen:服务器的地址结构长度
返回值:
成功:0
失败:-1 errno
如果不使用bind绑定客户端地址结构,采用""隐式绑定""
9.TCP通信流程分析:
server:
1.socket() 创建socket
2.bind() 绑定服务器地址结构
3.listen() 设置监听上限
4.accept() 阻塞监听客户端连接
5.read() 读socket获取客户端连接
6.小--大写 toupper()
7.write(fd)
8.close()
client:
1.socket() 创建socket
2.connect() 与服务器建立连接
3.write() 写数据到socket
4.read() 读转换后的数据
5.显示读取结果
6.close()
#include<stdio.h> #include<arpa/inet.h> #include<stdlib.h> #include<unistd.h> #include<errno.h> #include<pthread.h> #include<ctype.h> #include<sys/socket.h> #include<string.h> #define SERV_PORT 9527 int main(int argc,char *argv[]) { int lfd=0,cfd=0; int ret; char buf[BUFSIZ],client_IP[1024],*buf2; struct sockaddr_in serv_addr,clit_addr; socklen_t clit_addr_len; serv_addr.sin_family=AF_INET; serv_addr.sin_port=htons(SERV_PORT); serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); lfd=socket(AF_INET,SOCK_STREAM,0); printf("lfd:%d\n",lfd); if(lfd==-1){ perror("socket error"); } printf("-------------------------------------------------------------\n"); if(lfd!=-1){ printf("server is started\n"); } bind(lfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); listen(lfd,128); clit_addr_len=sizeof(clit_addr); printf("clit_addr length:%d\n",clit_addr_len); cfd=accept(lfd,(struct sockaddr *)&clit_addr,&clit_addr_len); if(cfd==-1){ perror("accept error"); }else{ printf("reciving a client!!\n"); printf("client ip:%s port:%d\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),ntohs(clit_addr.sin_port)); printf("---------------------------------------------------------------\n"); } buf2="server hello:\n"; printf("buf2 length:%d\n",strlen(buf2)); while(1){ ret=read(cfd,buf,sizeof(buf)); printf("-------------------------------------------------------------\n"); write(STDOUT_FILENO,buf,ret); printf("\n"); int i; for(i=0;i<ret;i++){ buf[i]=toupper(buf[i]); } write(cfd,buf2,strlen(buf2)); write(cfd,buf,ret); } close(lfd); close(cfd); return 0; }
错误处理函数:
封装目的:
在server.c在编程过程中突出逻辑,将出错处理与逻辑分开,可以直接跳转蛮手册
[wrap.c] [wrap.h]
存放网络通信相关常用自定义函数 存放网络通信相关常用自定义函数原形(声明)
命名方式:系统调用函数首字母大写,方便查看蛮手册
如Listen(),Accept()
函数功能:调用系统调用函数,处理出错场景
在server.c和client.c中调用子定义函数
联合编译:server.c和wrap.c生成server
client.c和wrap.c生成client
1.fread和fwrite无法在socket中使用,因为这2个函数需要的是一个文件结构体指针,而在socket中只能提供文件描述符,不能提供文件的结构体指针.
2.fgets和fputs需要在提供文件结构体指针,而在socket编程中只能提供文件描述符,不能提供文件结构体指针,所以无法使用
多进程并发服务器步骤:
1.socket() 创建监听套接字lfd
2.bind() 绑定地址结构Struct sockaddr_in addr
3.listen()
4.while(1) {
cfg=accept(), 接收客户端连接请求
pid =fork();
if(pid==0) { 子进程read()-------小--->大----write
close(lfd); 关闭用于建立连接的套接字lfd
read()
小----大
write(0)}else if(pid>0){
close(cfd); 关闭用于与客户端通信的套接字cfd
while(1){
wait/waitpid(0,NULL,WNOHANG);
}
continue()
}
}
5.子进程
close(lfd)
小---大
write()
父进程:
close(cfd)
注册信号捕捉函数: SIGCHLD
在回调函数中,完成子进程回收
while(waitpid())
多线程并发服务器:server.c
1.socket(); 创建监听套接字lfd
2.bind() 绑定地址结构
3.listen()
4.while(1){
cfd=accept(lfd,);
pthread_create(&tid,NULL,tfn,NULL);
}
5.子线程:
void *tfn(void *arg){
close(lfd);
read(cfd);
小--大
write(cfd)
}
c标准库函数:
https://zh.cppreference.com/w/c/string/byte/strstr
多进程实现socket服务端,添加了信号注册,通过回调函数回收子进程,示例如下:
1 #include<stdio.h> 2 #include<arpa/inet.h> 3 #include<stdlib.h> 4 #include<unistd.h> 5 #include<errno.h> 6 #include<pthread.h> 7 #include<ctype.h> 8 #include<sys/socket.h> 9 #include<string.h> 10 #include<strings.h> 11 #include<signal.h> 12 #include<ctype.h> 13 #include <sys/wait.h> 14 #include"wrap.h" 15 16 #define SRV_PORT 9999 17 void catch_child(){ 18 while(waitpid(0, NULL, WNOHANG) > 0); 19 return ; 20 } 21 22 int main() { 23 int lfd, cfd,opt=1; 24 pid_t pid; 25 struct sockaddr_in srv_addr, clt_addr; 26 socklen_t clt_addr_len; 27 char buf[BUFSIZ]; 28 int ret, i; 29 //memset(&srv_addr,0,sizeof(srv_addr) 30 bzero(&srv_addr, sizeof(srv_addr)); 31 srv_addr.sin_family = AF_INET; 32 srv_addr.sin_port = htons(SRV_PORT); 33 srv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 34 35 lfd = Socket(AF_INET, SOCK_STREAM, 0); 36 setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//端口复用需要放在bind函数之前 37 Bind(lfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)); 38 39 Listen(lfd, 128); 40 clt_addr_len = sizeof(clt_addr); 41 printf("server has started already!welcome you connecting\n"); 42 while(1) { 43 44 cfd = Accept(lfd, (struct sockaddr *)&clt_addr, &clt_addr_len); 45 pid = fork(); 46 if(pid < 0) { 47 48 perr_exit("fork error\n"); 49 } else if(pid == 0) { 50 close(lfd); 51 break; 52 } else { 53 struct sigaction act; 54 act.sa_handler = catch_child; 55 sigemptyset(& act.sa_mask); 56 act.sa_flags = 0; 57 ret = sigaction(SIGCHLD, &act, NULL); 58 if(ret != 0) { 59 60 perr_exit("sigaction error\n"); 61 62 } 63 close(cfd); 64 continue; 65 } 66 67 68 } 69 if(pid == 0) { 70 for(;;) { 71 72 ret = Read(cfd, buf, sizeof(buf)); 73 if(ret == 0) { 74 close(cfd); 75 exit(1); 76 } 77 78 79 for(i = 0; i < ret; i++) 80 buf[i] = toupper(buf[i]); 81 82 write(cfd, buf, ret); 83 // write(cfd, buf, ret); 84 write(STDOUT_FILENO, "\n", sizeof("\n")); 85 write(STDOUT_FILENO, buf, ret); 86 } 87 88 } 89 return 0; 90 }
多线程高并发服务器
1 /* server.c */ 2 #include <stdio.h> 3 #include <string.h> 4 #include <netinet/in.h> 5 #include <arpa/inet.h> 6 #include <pthread.h> 7 8 #include "wrap.h" 9 #define MAXLINE 80 10 #define SERV_PORT 6666 11 12 struct s_info { 13 struct sockaddr_in cliaddr; 14 int connfd; 15 }; 16 void *do_work(void *arg) 17 { 18 int n,i; 19 struct s_info *ts = (struct s_info*)arg; 20 char buf[MAXLINE]; 21 char str[INET_ADDRSTRLEN]; 22 /* 可以在创建线程前设置线程创建属性,设为分离态,哪种效率高呢? */ 23 pthread_detach(pthread_self()); 24 while (1) { 25 n = Read(ts->connfd, buf, MAXLINE); 26 if (n == 0) { 27 printf("the other side has been closed.\n"); 28 break; 29 } 30 printf("received from %s at PORT %d\n", 31 inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)), 32 ntohs((*ts).cliaddr.sin_port)); 33 for (i = 0; i < n; i++) 34 buf[i] = toupper(buf[i]); 35 Write(ts->connfd, buf, n); 36 } 37 Close(ts->connfd); 38 } 39 40 int main(void) 41 { 42 struct sockaddr_in servaddr, cliaddr; 43 socklen_t cliaddr_len; 44 int listenfd, connfd; 45 int i = 0; 46 pthread_t tid; 47 struct s_info ts[256]; 48 int opt = 1; 49 listenfd = Socket(AF_INET, SOCK_STREAM, 0); 50 51 bzero(&servaddr, sizeof(servaddr)); 52 servaddr.sin_family = AF_INET; 53 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 54 servaddr.sin_port = htons(SERV_PORT); 55
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//端口复用需要放在bind函数之前
56 Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 57 Listen(listenfd, 20); 58 59 printf("Accepting connections ...\n"); 60 while (1) { 61 cliaddr_len = sizeof(cliaddr); 62 connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); 63 ts[i].cliaddr = cliaddr; 64 ts[i].connfd = connfd; 65 /* 达到线程最大数时,pthread_create出错处理, 增加服务器稳定性 */ 66 pthread_create(&tid, NULL, do_work, (void*)&ts[i]); 67 i++; 68 } 69 return 0; 70 }
增加端口复用函数:
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
多路io转接模型
select多路io转接:
原理:借助内核,select来监听,客户端连接和数据通信事件
void FD_ZERO(fd_set *set): ---清空一个文件描述符集合
fd_set rset;
FD_ZERO(&rset);
void FD_SET(int fd,fd_set *set): ---将待监听的文件描述符,添加到监听集合中
FS_SET(3,&rset);
FS_SET(5,&rset);
FD_SET(6,&rset);
void FD_CLR(int fd,fd_set *set): ---将一个文件描述符从监听集合中移除
FD_CLR(4,&rset),
int FD_ISSET(int fd,fd_set *set): ---判断一个文件描述符是否在监听集合中
返回值:在:1,不在:0
FD_ISSET(4,&rset)
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set*exceptfds,struct timeval *timeout)
nfds: 监听的所有文件描述符中,最大的文件描述符+1
readfds:读 文件描述符监听集合. 传入,传出参数
writefds:写 文件描述符监听集合 传入,传出参数 NULL
exceptfds:异常 文件描述符监听集合 传入,传出参数 NULL
timeout: >0:设置监听超时时长
NULL:阻塞监听
0:非阻塞监听,轮询
返回值:
>0:所有监听集合(3个)中,满足对应时间的总数
0:没有满足监听条件的文件描述符
-1:errno
思路分析:
1.lfd=socket();创建套接字
2.bind() 绑定地址结构
3.listen() 设定监听上限
4.1 fd_set rset,allset; 创建r监听集合
4.2FD_ZERO(&allset) 将allset监听集合清空
4.3FD_SET(lfd,&allset) 将lfd添加至r集合
while(1){}
4.4 rset=allset;
4.4ret=select(lfd+1,&rset,NULL,NULL,NULL): 监听文件描述符集合对应事件
if(ret>0){ 有监听的描述符满足对应事件
if(FD_ISSET(lfd,&rset)){ //1在,0不在
cfd = accept(); 建立连接,返回用于通信的文件描述符
FD_SET(cfd,&allset); 添加到监听通信描述集合中
}
for(i=ldf+1;i<=最大文件描述符;i++){
FD_ISSET(i,&rset) 有read,write事件发生
read();
小---大
write()
}
}
}
select优缺点:
缺点:监听上限受文件描述符限制,最大1024
监测满足条件的fd,自己添加业务逻辑提高小.提高了编码难度.
优点:跨平台.window,linux,macOS,Unix,类Unix,mips
IO多路转接select例子
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<arpa/inet.h> 6 #include<ctype.h> 7 8 #include"wrap.h" 9 10 #define SERV_PORT 6666 11 12 int main(int argc, char *argv[]) { 13 14 int i, j, n, nready; 15 16 int maxfd = 0; 17 int listenfd, connfd; 18 char buf[BUFSIZ]; 19 20 struct sockaddr_in clie_addr, serv_addr; 21 socklen_t clie_addr_len; 22 23 listenfd = Socket(AF_INET, SOCK_STREAM, 0); 24 int opt = 1; 25 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 26 bzero(&serv_addr, sizeof(serv_addr)); 27 serv_addr.sin_family = AF_INET; 28 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 29 serv_addr.sin_port = htons(SERV_PORT); 30 Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); 31 Listen(listenfd, 128); 32 33 34 //select 多路IO转接 35 fd_set rset, allset; /*rset读取事件文件描述符结合allset用来暂存*/ 36 maxfd = listenfd; 37 38 FD_ZERO(&allset); 39 FD_SET(listenfd, &allset); /*构造select监控文件描述符集*/ 40 41 while(1) { 42 rset = allset;/*每次循环时都从新设置select监听信号集*/ 43 nready = select(maxfd + 1, &rset, NULL, NULL, NULL); 44 if(nready < 0) { 45 perr_exit("select error"); 46 } 47 if(FD_ISSET(listenfd, &rset)) { /*说明有新的客户端连接请求*/ 48 clie_addr_len = sizeof(clie_addr); 49 connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len); /*Accept不会阻塞*/ 50 FD_SET(connfd, &allset); /*向监控文件描述符集合allset添加新的文件描述符connfd*/ 51 if(maxfd < connfd) { 52 maxfd = connfd; 53 } 54 if(0 == --nready) { /*只有listenfd有事件,后续的for不需执行*/ 55 continue; 56 } 57 58 } 59 for(i = listenfd + 1; i <= maxfd; i++) { 60 61 if(FD_ISSET(i, &rset)) { 62 if((n = Read(i, buf, sizeof(buf))) == 0) { /*当client关闭连接时,服务器端也关闭对应连接*/ 63 Close(i); 64 FD_CLR(i, &allset); /*解除select 对此文件描述符的监控*/ 65 } else if(n > 0) { 66 for(j = 0; j < n; j++) { 67 68 buf[j] = toupper(buf[j]); 69 70 } 71 write(STDOUT_FILENO, buf, n); 72 write(STDOUT_FILENO, "\n", sizeof("\n")); 73 write(i, buf, n); 74 } 75 76 77 } 78 79 80 81 82 } 83 84 } 85 86 Close(listenfd); 87 return 0; 88 89 90 }
多路IO转接:
select:
poll:
int poll(struct pollfd * fds,nfds_t nfds,int timeout)
fds:监听的文件描述[数组]
struct pollfd{
int fd; 待监听的文件描述符
short events:待监听的文件描述符对应的监听事件
取值:POLLIN,POLLOUT,POLLERR
short revnets;传入时,给0值.如果满足对应事件的话,返回 非0---->POLLIN,POLLOUT,POLLERR
nfds:监听数组的,实际有效监听个数
timeout: >0 超时时长.单位:毫秒
-1:阻塞等待
0:不阻塞
返回值:返回满足对应监听事件的文件描述符总个数
优点:
自带数组结构,可以将监听事件集合和返回事件集合分离
拓展 监听上限,超出1024限制
缺点:
不能跨平台.只能在linux中使用,window不支持poll
无法直接定位满足监听事件的文件描述符,编码难度较大
read函数返回值:
>0:实际读到的字节数
=0;socket中,表示对端关闭,close()
-1:如果errno==EINTR 被异常终端,需要重启
如果errno==EAGIN或EWOULDBLOCK 以非阻塞方式读数据,但是美欧数据
如果errno==ECONNECTRESET 说明连接被重置.需要close(),移除监听队列
突破1024文件描述符限制:
cat /proc/sys/fs/file-max -->当前计算机所能打开的最大文件个数,受硬件影响
ulimit -a ----->当前用户下的进行,默认打开文件描述符个数.缺省为1024
修改:
打开 vi/etc/security/limits.conf,写入
* soft nofile 65536 --->设置默认值,可以直接借助命令修改[住校用户,使其生效]
* hard nofile 100000 ---->命令修改上限
epoll:
int epoll_create(int size) 创建一个监听红黑树
size:创建的红黑树的监听节点数量(仅供参考)
返回值:指向新创建的红黑树的根节点的fd
失败:-1 errno
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)) 操作这个监听红黑树
epfd:epoll_create 函数的返回值
op:对该监听的红黑树所做的操作
EPOLL_CTL_ADD添加fd到 监听红黑树
EPOLL_CTL_WOD 修改fd在 监听红黑树上的监听事件
EPOLL_CTL_DEL 将一个fd从监听红黑树上摘下(取消监听)
fd:
带监听的fd
event:本质 struct epoll_event 结构体 地址
events:事件
EPOLLIN/EPOLLOUT/EPOLLERR
data:联合体
int fd:对应监听事件的fd
void *ptr;
uint32_t u32:不用
uint64_t u64不用
返回值:成功 0,失败-1 errno
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout) 阻塞监听
epfd:epoll_create 函数的返回值.epfd
events:传出参数,[数组],满足监听条件的哪些fd结构体
maxevents:数组元素的总个数
timeout:
-1:阻塞
0:不阻塞
>0:超时时间(毫秒)
返回值:
>0:满足监听的总个数,可以用作循环上限
0:没有fd满足监听事件
-1:失败errno
epoll实现多路IO转接思路:
1.lfd=socket()) 用于监听连接事件
2.bind()
3.listen()
4.
epoll_create(1024) spfd:监听红黑树的树根
struct epoll_event tmp,ep[1024]: tmp用来设置单个fd属性,ep是epoll_wait()传出满足监听事件的数组
tep.events=EPOLLIN;
tep.data.fd=lfd;
epoll_ctlepfd(epfd,EPOLL_CTL_ADD,lfd,&tep) 将lfd添加到监听红黑树
while(1){
ret=epoll_wait(epfd,ep,1024,-1) 实施监听
for(i=0;i<ret;i++){
if(ep[i].data.fd==lfd){//lfd满足读事件,有新的客户端发起连接请求
cfg=Accept();
tep.events=EPOLLIN;初始化cfg的监听属性
tep.data.fd=cfg;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&tep);
}else{
n=read(ep[i].data.fd,buf,sizeof(buf);
if(n==0){
close(ep[i],EPOLL_CTL_ADD,cfd,&tep);//将关闭的cfd,从监听树上摘下
}else if(n>0){
小----大
write(ep[i].data.fd.buf,n)
}
}
}
}
epoll事件模型:
ET模式:
边沿触发:
缓冲区剩余未读尽的数据不会导致epoll_wait.新的事件满足,才会触发
struct epoll_event event
event.events=EPOLLIN|EPOLLET
LT模式:
水平触发--默认采用模式
缓冲区剩余未读尽的数据才会曹植epoll_wait返回
结论:
epoll的ER模式,高效模式,但是只支持非阻塞模式
struct epoll_event event
event.events=EPOLLIN|EOLLET
epoll_ctl(epfd,EPOLL_CTL_ADD,cfg,&event)
int flg=fcntl(cfg,F_GETFL)
flg|=O_NONBLOCK
fcntl(cfg,F_SETFL,flg)
优点:
高效
缺点:
不能跨平台.Linux
epoll反应堆模型:
epoll ET模式+非阻塞+void * ptr
原来:socket,bind,listen--epoll_create 创建监听 红黑树-- 返回epfd--epoll_ctl()向树上添加一个监听fd--epoll_wait监听--while(1)--
--epoll_wait对应监听fd有事件产生 --返回 监听满足数组.-- 判断返回数组元素 --lfd满足 --Accept--cfd满足--read(--大小-->小
--wirite回去
反应堆:不但要监听cfg的读事件,还要监听cfd的写事件
socket,bind,listen--epoll_create 创建监听 红黑树-- 返回epfd--epoll_ctl()向树上添加一个监听fd--epoll_wait监听--while(1)--
--epoll_wait监听-对应监听fd有事件产生 --返回 监听满足数组.-- 判断返回数组元素 --lfd满足 --Accept--cfd满足--read--小-->大--cfg从监听红黑树上摘下--epoll_ctl(监听 cfd的写事件--EPOLLOUT
--EPOLL_CTL_ADD重新放到红黑树上监听写事件--等待epoll_wait返回--说明cfg可写--write回去---cfd从监听红黑树上摘下--EPOLLIN
--epoll_ctl()--EPOLL_CTL_ADD重新放到红黑树上监听读事件--epoll_wait监听
eventset函数:
设置回调函数:lfd--->acceptconn()
cfd--->recvdata()
eventadd函数:
将一个fd,添加到监听红黑树.
网络编程中:
read----recv()
write---send()
查看文件的二进制,八进制
find / -maxdepth 2 '*.c'
find / -type 'l'
find / -name '*.c'
find / -size +20M -size -50M
搜素文件内容:
grep -r 'copy' / -n :-n表示显示行号
项目花费哪些子系统(模块)
1.各个模块之间如何进行业务通信
2.负责哪几个模块
3.索2负责的模块中包含哪些技术点
4.开发过程中碰到什么问题,以及解决思路
5.该问题对你项目开发构成有哪些影响,对后续开发的影响
gcc编译可以执行程序4步骤:
1.预处理
hello.c
展开宏,头文件,替换条件编译,删除注释,空行,空白
gcc -E
2.编译
生成: hello.s文件
检查语法规范
gcc hello.c -S hello.s
3.汇编
生成: hello.o
将汇编指令翻译成机器指令
4.链接
链接,生成a.out
-o是指定生成名字
数据段合并,数据地址回填
gcc -I./inc hello.c -o hello:其中-I参数表示指定头文件的目录
gcc编译:
-I 指定编译头文件的目录
-c 只做预处理,编译,汇编,得到二进制文件
-g 编译时添加调试文件,主要支持gdb调试
-Wall 显示所有的警告信息
-D 向程序中动态注册宏
隐式声明:如果编译没有找到函数定义,也没有找到函数声明,那么函数会根据调用的函数名和参数,当然返回值必须是int类型,进行隐式声明,如果返回值不是int的,不能进行隐式声明的.
1.没有函数定义
2.没有函数声明
3.返回值int
编译器会根据调用的函数名和参数以及返回值进行隐式声明
经常使用的头文件
1 2 3 4 5 6 | #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> |
lseek:
1.获取文件大小
2.移动文件指针
3.拓展文件
stat的结构体
目录结构体
文件类型
select
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性