Linx系统编程
在 Linux 中,手册节号通常被分为以下 8 个部分:
1:用户命令和可执行文件的手册页。 2:系统调用和内核函数的手册页。 3:C 库函数的手册页。 4:特殊文件的手册页,例如设备文件和驱动程序。 5:文件格式和约定的手册页。 6:游戏的手册页。 7:杂项手册页,例如惯例、宏包和协议等。(signal) 8:系统管理命令和守护进程的手册页。 文件与IO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
int main (){
int ret ;
ret = close ( 10 );
if ( ret == - 1 ){
perror ( "close error" );
}
if ( ret == - 1 ){
fprintf ( stderr , "close error: %s \n " , strerror ( errno ));
}
printf ( "EINTR desc = %s \n " , strerror ( EINTR )); // 系统调用被中断
// man 2 close
// E2BIG 参数列表太长 EACCESS 权限不足 EAGAIN 重试 EBADF 错误的文件描述符 EBUSY 设备或资源忙 ECHILD 无子进程
// EDOM 数学参数不在函数域内 EEXIST 文件已存在 EFAULT 地址错误 EFBIG 文件太大 EINTR 系统调用被中断
return 0 ;
}
1
2
3
4
# 输出
close error: Bad file descriptor
close error: Bad file descriptor
EINTR desc = Interrupted system call
文件描述符
Linux (int)非负整数 (文件描述符) C (FILE* fp)(文件指针) 0 (STDIN_FILENO) 标准输入 stdin 1 (STDOUT_FILENO) 标准输出 stdout 2 (STDERR_FILENO) 标准错误 stderr
相互转换函数:
fileno
: 将文件指针转换为文件描述符
fdopen
: 将文件描述符转换为文件指针
1
2
3
4
5
6
7
#include <stdlib.h>
#include <stdio.h>
int main (){
printf ( "fileno(stdin) = %d \n " , fileno ( stdin ));
return 0 ;
}
1
2
# 输出
fileno( stdin) = 0
文件系统调用
open
man 2 open
打开文档
ulimit -n
一个进程能够打开的文件个数
cat /proc/sys/fs/file-max
查看当前系统中文件描述符的最大数量限制
功能:打开可能创建一个文件,得到了一个文件描述符
函数原型:
int open(const char *path, int flags);
int open(const char *path, int flags, mode_t mode);
函数参数
返回值:0:成功;-1:失败,并设置errno值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
// #define ERR_EXIT(m) (perror(m), exit(EXIT_FAILURE))
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main (){
int fd ;
fd = open ( "test.txt" , O_RDONLY );
/*
if(fd == -1){
fprintf(stderr, "open error with errno=%d %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
*/
/*
if(fd == -1){
perror("open error");
exit(EXIT_FAILURE);
}
*/
if ( fd == - 1 ){
ERR_EXIT ( "open error" );
}
close ( fd );
return 0 ;
}
1
2
# 输出
open error: No such file or directory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CXX = gcc
CXXFLAGS = -c -Wall -g
TARGETS = open
SRCS = $( wildcard *.c)
OBJS = $( patsubst %.c, %.o, $( SRCS))
all : $( TARGETS )
$(TARGETS) : %: %.o
$( CXX) -o $@ $^
%.o : %.c
$( CXX) $( CXXFLAGS) $< -o $@
.PHONY : clean
clean :
rm -f *.o $( TARGETS)
read
功能:从该文件中读取文件
函数原型:
ssize_t read(int fd, void *buffer, size_t count);
函数参数:fd :想要读的文件的文件描述符;buf :指向内存块的指针,从文件中读取来的字节放到这个内存块中;count :从该文件复制到buf中的字节数
注:读取的文件指针偏移,内核数据结构会维护
返回值:0:文件结束;-1:出现错误;复制到缓存区的字节数
write
功能:将数据写到一个文件中
函数原型:
ssize_t write(int fd, void *buffer, size_t count);
函数参数:fd :想要写入的文件的文件描述符;buf :指向内存块的指针,从这个内存块中读取数据写入到文件中;count :要写入文件的字节数
返回值:写入的字节数:写入成功;-1:出现错误
close
功能:关闭文件
函数原型:
函数参数:fd:文件描述符
返回值:0:成功;-1,失败,并设置errno值
简单版cp命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#define BUFF_SIZE 1024
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main ( int argc , char * argv []){
int infd , outfd ;
if ( argc != 3 ){
fprintf ( stderr , "Usage %s src dest \n " , argv [ 0 ]);
exit ( EXIT_FAILURE );
}
infd = open ( argv [ 1 ], O_RDONLY );
if (( infd = open ( argv [ 1 ], O_RDONLY )) == - 1 ){
ERR_EXIT ( "open src file error" );
}
if (( outfd = open ( argv [ 2 ], O_WRONLY | O_CREAT | O_TRUNC , 0644 )) == - 1 ){ // 等价creat(),creat不多见了
ERR_EXIT ( "open std file error" );
}
char buff [ BUFF_SIZE ];
int numRead ;
while (( numRead = read ( infd , buff , BUFF_SIZE )) > 0 ) {
write ( outfd , buff , numRead );
}
if ( close ( infd ) == - 1 ){
ERR_EXIT ( "close src file error" );
}
if ( close ( outfd ) == - 1 ){
ERR_EXIT ( "close dst file error" );
}
exit ( EXIT_SUCCESS );
return 0 ;
}
lseek
功能:通过指定相对于开始位置、当前位置或末尾位置的字节数来重定位curp,这取决于Iseek()函数中指定的位置
函数原型:
off_t lseek(int fd, off_t offset, int whence);
函数参数:fd:设置的文件描述符; offset:偏移量;
whence:搜索的起始位置
SEEK_SET:从文件开始处计算偏移,offset必须为负数
SEEK_CUR:从当前文件的偏移值计算偏移
SEEK_END:从文件的结束处计算偏移
返回值:新的文件偏移值:成功;-1:错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int main (){
int fd ;
fd = open ( "test.txt" , O_RDONLY );
if ( fd == - 1 ){
ERR_EXIT ( "open error" );
}
char buf [ 1024 ] = { 0 };
int ret = read ( fd , buf , 5 );
if ( ret == - 1 ){
ERR_EXIT ( "read error" );
}
printf ( "buf = %s \n " , buf );
ret = lseek ( fd , 0 , SEEK_CUR );
if ( ret == - 1 ){
ERR_EXIT ( "lseek" );
}
printf ( "current offset = %d \n " , ret );
close ( fd );
return 0 ;
}
int main (){
int fd ;
fd = open ( "test.txt" , O_WRONLY | O_CREAT | O_TRUNC , 0644 );
if ( fd == - 1 ){
ERR_EXIT ( "open error" );
}
write ( fd , "hello" , 5 );
int ret = lseek ( fd , 1024 * 1024 * 1024 , SEEK_CUR );
if ( ret == - 1 ){
ERR_EXIT ( "lseek" );
}
write ( fd , "world" , 5 );
close ( fd );
return 0 ;
}
// od -c file 查看文件空格
readdir
功能:访问指定目录下一个连接的细节
函数原型:
struct dirent* readdir(DIR *dirptr);
函数参数:dirptr:目录指针
返回值:一个指向dirent结构得指针,包含指定目录中下一个连接得细节:没有更多连接时返回0
简单版ls指令
1
2
3
4
5
6
7
8
9
10
11
12
13
int main (){
DIR * dir = opendir ( "." );
struct dirent * de ;
while (( de = readdir ( dir )) != NULL ){
if ( strncmp ( de -> d_name , "." , 1 ) != 0 ){
printf ( "%s " , de -> d_name );
}
}
printf ( " \n " );
closedir ( dir );
exit ( EXIT_SUCCESS );
return 0 ;
}
mkdir
功能:用来创建名为pathname的新目录
函数原型:
int mkdir(char *pathname, mode_t mode);
函数参数:pathname:文件路径名;mode:权限位
返回值:0:成功;-1:失败
rmdir
功能:删除一个空目录
函数原型:
int rmdir(char *pathname);
函数参数:pathname:文件路径名
返回值:0:成功;-1:失败
chmod和fchmod
fchmod
功能:改变路径名为pathname的文件的权限位
函数原型:
int chmod(char *pathname, mode_t mode);
函数参数:pathname:文件路径名;mode:权限位
返回值:0:成功;-1:失败
fchmod
功能:改变已打开文件的权限位
函数原型:
int fchmod(int fd, mode_t mode);
函数参数:fd:文件描述符;mode:权限位
返回值:0:成功;-1:失败
chown和fchown
chown
功能:用来改变文件所有者的识别号(owner id)或者它的用户组识别号(group ID)
函数原型:
int chown(char *pathname, uid_t owner, gid_t group);
函数参数:pathname:文件路径名;owner:所有者识别号;group:用户组识别号
返回值:0:成功;-1:失败
fchown
功能:用来改变文件所有者的识别号(owner id)或者它的用户组识别号(group ID)
函数原型:
int fchown(int fd, uid_t owner, gid_t group);
函数参数:fd:文件描述符;owner:所有者识别号;group:用户组识别号
返回值:0:成功;-1:失败
stat
读取文件元数据
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#define MAJOR(a) (int)((unsigned short)a >> 8)
#define MINOR(a) (int)((unsigned short)a & 0xFF)
int filetype ( struct stat * buf ); // 使用 man 2 stat查看相关定义
void fileperm ( struct stat * buf , char * perm ); // 权限转成字符
int main ( int argc , char * argv []){
if ( argc != 2 ){
fprintf ( stderr , "Usage %s file \n " , argv [ 0 ]);
exit ( EXIT_FAILURE );
}
printf ( "File Name: %s \n " , argv [ 1 ]);
struct stat sbuf ;
if ( lstat ( argv [ 1 ], & sbuf ) == - 1 ){
ERR_EXIT ( "stat error" );
}
printf ( "File number: major %d, minor %d inode %d \n " , MAJOR ( sbuf . st_dev ), MINOR ( sbuf . st_dev ), ( int ) sbuf . st_ino );
// ls -li file 查看节点号
if ( filetype ( & sbuf )){
printf ( "Device number: major %d, min %d \n " , MAJOR ( sbuf . st_rdev ), MINOR ( sbuf . st_rdev ));
}
char perm [ 11 ] = { 0 };
fileperm ( & sbuf , perm );
printf ( "File permission bits=%o %s \n " , sbuf . st_mode & 0777 , perm );
return 0 ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int filetype ( struct stat * buf ){
int flag = 0 ;
printf ( "Filetype: " );
mode_t mode ;
mode = buf -> st_mode ;
switch ( mode & S_IFMT ){
case S_IFSOCK :
printf ( "socket \n " );
break ;
case S_IFLNK :
printf ( "symbolic link \n " );
break ;
case S_IFREG :
printf ( "regular file \n " );
break ;
case S_IFBLK :
printf ( "block device \n " );
flag = 1 ;
break ;
case S_IFDIR :
printf ( "directory \n " );
break ;
case S_IFCHR :
printf ( "character device \n " );
flag = 1 ;
break ;
case S_IFIFO :
printf ( "FIFO \n " );
break ;
default :
printf ( "unknown file type \n " );
}
return flag ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void fileperm ( struct stat * buf , char * perm ) {
strcpy ( perm , "----------" ); // 初始化
mode_t mode = buf -> st_mode ;
if ( S_ISSOCK ( mode ))
perm [ 0 ] = 's' ;
else if ( S_ISLNK ( mode ))
perm [ 0 ] = 'l' ;
else if ( S_ISREG ( mode ))
perm [ 0 ] = '-' ;
else if ( S_ISBLK ( mode ))
perm [ 0 ] = 'b' ;
else if ( S_ISDIR ( mode ))
perm [ 0 ] = 'd' ;
else if ( S_ISCHR ( mode ))
perm [ 0 ] = 'c' ;
else if ( S_ISFIFO ( mode ))
perm [ 0 ] = 'p' ;
else {
printf ( "unknown file type \n " );
return ;
}
// 文件权限转换
for ( int i = 0 ; i < 9 ; i += 3 ) {
perm [ i + 1 ] = ( mode & ( S_IRUSR >> i )) ? 'r' : '-' ;
perm [ i + 2 ] = ( mode & ( S_IWUSR >> i )) ? 'w' : '-' ;
perm [ i + 3 ] = ( mode & ( S_IXUSR >> i )) ? 'x' : '-' ;
}
}
简单版 ls -l命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/*
实现ls -l功能
lstat 查看 man 2 stat
getpwuid 查看 man getpwuid 通过用户id即st_uid获得用户名pw_name
getgrgid 查看 man getgrgid 通过组id即st_gid获得组名gr_name
readlink
-rw-r--r-- 1 fengchen xxx 1090 Oct 14 11:25 copy.c
权限 连接数 用户名 组名 文件大小 时间 文件名称
st_mode st_nlink pw_name gr_name st_size st_mtime
ln -s file file1
符合链接文件名称 file1 -> file 查看 man 2 readlink
*/
void file_perm ( struct stat * buf , char * perm ); // 权限转成字符
void format_time ( struct stat * sbuf , char * buf_time , size_t buf_time_size ); // 时间转换
void link_file_name ( struct dirent * de , struct stat * sbuf , char * file_name , size_t file_name_size ); // 链接的文件名
int compare ( const void * a , const void * b ); // 文件名排序
struct ls_l_info {
char each_st_mode [ 11 ];
unsigned long int each_st_nlink ;
char each_pw_name [ 20 ];
char each_gr_name [ 20 ];
off_t each_st_size ;
char each_st_mtime [ 64 ];
char each_file_name [ 256 ];
};
int main ( int argc , char * argv []) {
const char * dir_path = "." ;
if ( argc > 1 ) {
dir_path = argv [ 1 ];
}
DIR * dir = opendir ( dir_path );
if ( dir == NULL ) {
ERR_EXIT ( "opendir error" );
}
struct dirent * de ;
struct ls_l_info * file_list = NULL ; // 存储文件名的数组
int file_count = 0 ; // 文件计数
int uname_width = 0 ;
int gname_width = 0 ;
int size_width = 0 ;
int perm_width = 0 ;
int nlink_width = 0 ;
int buf_time_width = 0 ;
while (( de = readdir ( dir )) != NULL ) {
if ( strncmp ( de -> d_name , "." , 1 ) != 0 ) {
struct ls_l_info * temp_list = ( struct ls_l_info * ) realloc ( file_list , ( file_count + 1 ) * sizeof ( struct ls_l_info ));
if ( temp_list == NULL ) {
ERR_EXIT ( "realloc error" );
}
file_list = temp_list ;
struct stat sbuf ;
char file_path [ PATH_MAX ];
snprintf ( file_path , PATH_MAX , "%s/%s" , dir_path , de -> d_name );
if ( lstat ( file_path , & sbuf ) == - 1 ) {
ERR_EXIT ( "stat error" );
}
char perm [ 11 ] = { 0 };
file_perm ( & sbuf , perm );
strcpy ( file_list [ file_count ]. each_st_mode , perm );
file_list [ file_count ]. each_st_nlink = sbuf . st_nlink ;
struct passwd * pwname ;
pwname = getpwuid ( sbuf . st_uid );
strcpy ( file_list [ file_count ]. each_pw_name , pwname -> pw_name );
struct group * grname ;
grname = getgrgid ( sbuf . st_gid );
strcpy ( file_list [ file_count ]. each_gr_name , grname -> gr_name );
file_list [ file_count ]. each_st_size = sbuf . st_size ;
char buf_time [ 64 ] = { 0 };
format_time ( & sbuf , buf_time , sizeof ( buf_time ));
strcpy ( file_list [ file_count ]. each_st_mtime , buf_time );
char file_name [ 256 ] = { 0 };
link_file_name ( de , & sbuf , file_name , sizeof ( file_name ));
strcpy ( file_list [ file_count ]. each_file_name , file_name );
uname_width = ( uname_width > strlen ( pwname -> pw_name )) ? uname_width : strlen ( pwname -> pw_name );
gname_width = ( gname_width > strlen ( grname -> gr_name )) ? gname_width : strlen ( grname -> gr_name );
char size_str [ 20 ];
snprintf ( size_str , sizeof ( size_str ), "%ld" , sbuf . st_size );
size_width = ( size_width > strlen ( size_str )) ? size_width : strlen ( size_str );
perm_width = ( perm_width > strlen ( perm )) ? perm_width : strlen ( perm );
char n_link_str [ 20 ];
snprintf ( n_link_str , sizeof ( n_link_str ), "%ld" , sbuf . st_nlink );
nlink_width = ( nlink_width > strlen ( n_link_str )) ? nlink_width : strlen ( n_link_str );
buf_time_width = ( buf_time_width > strlen ( buf_time )) ? buf_time_width : strlen ( buf_time );
file_count ++ ;
}
}
closedir ( dir );
qsort ( file_list , file_count , sizeof ( struct ls_l_info ), compare );
for ( int i = 0 ; i < file_count ; i ++ ) {
printf ( "%-*s %*ld %-*s %-*s %*ld %-*s %s \n " ,
perm_width , file_list [ i ]. each_st_mode ,
nlink_width , file_list [ i ]. each_st_nlink ,
uname_width , file_list [ i ]. each_pw_name ,
gname_width , file_list [ i ]. each_gr_name ,
size_width , file_list [ i ]. each_st_size ,
buf_time_width , file_list [ i ]. each_st_mtime ,
file_list [ i ]. each_file_name );
}
free ( file_list );
exit ( EXIT_SUCCESS );
}
int compare ( const void * a , const void * b ) {
const struct ls_l_info * info_a = ( const struct ls_l_info * ) a ;
const struct ls_l_info * info_b = ( const struct ls_l_info * ) b ;
return strcmp ( info_a -> each_file_name , info_b -> each_file_name );
}
void file_perm ( struct stat * buf , char * perm ) {
strcpy ( perm , "----------" ); // 初始化
mode_t mode = buf -> st_mode ;
if ( S_ISSOCK ( mode ))
perm [ 0 ] = 's' ;
else if ( S_ISLNK ( mode ))
perm [ 0 ] = 'l' ;
else if ( S_ISREG ( mode ))
perm [ 0 ] = '-' ;
else if ( S_ISBLK ( mode ))
perm [ 0 ] = 'b' ;
else if ( S_ISDIR ( mode ))
perm [ 0 ] = 'd' ;
else if ( S_ISCHR ( mode ))
perm [ 0 ] = 'c' ;
else if ( S_ISFIFO ( mode ))
perm [ 0 ] = 'p' ;
else {
printf ( "unknown file type \n " );
return ;
}
// 文件权限转换
for ( int i = 0 ; i < 9 ; i += 3 ) {
perm [ i + 1 ] = ( mode & ( S_IRUSR >> i )) ? 'r' : '-' ;
perm [ i + 2 ] = ( mode & ( S_IWUSR >> i )) ? 'w' : '-' ;
perm [ i + 3 ] = ( mode & ( S_IXUSR >> i )) ? 'x' : '-' ;
}
}
void format_time ( struct stat * sbuf , char * buf_time , size_t buf_time_size ){
struct tm * tm = localtime ( & ( sbuf -> st_mtime ));
strftime ( buf_time , buf_time_size , "%b %d %H:%M" , tm );
}
void link_file_name ( struct dirent * de , struct stat * sbuf , char * file_name , size_t file_name_size ) {
char * linkname ;
ssize_t r ;
linkname = malloc ( sbuf -> st_size + 1 );
if ( linkname == NULL ) {
ERR_EXIT ( "malloc error" );
}
r = readlink ( de -> d_name , linkname , sbuf -> st_size + 1 );
if ( r == - 1 ) {
strncpy ( file_name , de -> d_name , file_name_size - 1 );
}
else {
linkname [ r ] = '\0' ;
snprintf ( file_name , file_name_size , "%s -> %s" , de -> d_name , linkname );
}
free ( linkname );
}
文件共享
文件描述表(1024个文件描述符)
文件状态转移
读、写、追加、同步、非阻塞等
当前文件偏移量
refcnt = 1(引用计数)
v节点指针
v节点表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int main ( int argc , char * argv []){
int fd1 , fd2 ;
char buf1 [ BUFF_SIZE ] = { 0 };
char buf2 [ BUFF_SIZE ] = { 0 };
fd1 = open ( "test.txt" , O_RDONLY );
if ( fd1 == - 1 ){
ERR_EXIT ( "open error" );
}
read ( fd1 , buf1 , 5 );
printf ( "buf1 = %s \n " , buf1 );
fd2 = open ( "test.txt" , O_RDWR );
if ( fd2 == - 1 ){
ERR_EXIT ( "open error" );
}
read ( fd2 , buf2 , 5 );
printf ( "buf2 = %s \n " , buf2 );
write ( fd2 , "world" , 5 );
memset ( buf1 , 0 , sizeof ( buf1 ));
read ( fd1 , buf1 , 5 );
printf ( "buf1 = %s \n " , buf1 );
close ( fd1 );
close ( fd2 );
return 0 ;
}
重定向 dup
2>&1
:把标准错误(2)重定向到标准输出(1)
dup:int dup(int oldfd);
dup2:int dup2(int oldfd, int newfd);
fcntl: int fcntl(int fd, int cmd, ...);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main ( int argc , char * argv []){
int fd ;
fd = open ( "test.txt" , O_WRONLY );
if ( fd == - 1 ){
ERR_EXIT ( "open error" );
}
/*
close(1);
dup(fd); // 0,1,2(输入,输出,错误)占用,默认返回3
*/
/*
dup2(fd, 1); // 如果由 newfd 参数所指定编号的文件描述符之前已经打开,那么 dup2()会首先将其关闭
*/
close ( 1 );
if ( fcntl ( fd , F_DUPFD , 0 ) < 0 ){
ERR_EXIT ( "dup fd error" );
}
printf ( "hello \n " );
return 0 ;
}
fcntl
功能:操纵文件描述符,改变已打开的文件的属性
函数原型:
int fcntl(int fd, int cmd, ...);
函数参数:
fd:文件描述符
cmd操作
复制文件描述符
F_DUPFD(Iong)
文件描述符标志
F_GETFD(void)
F_SETFD(long)
文件状态标志
F_GETFL(void)
F_SETFL(Iong)
文件锁
F GETLK
F_SETLK, F_SETLKW
返回值:0:成功;-1:失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void set_flag ( int fd , int flags );
void clr_flag ( int fd , int flags );
int main ( int argc , char * argv []){
char buf [ BUFF_SIZE ] = { 0 };
int ret ;
/* set_flag function
int flags;
flags = fcntl(0, F_GETFL, 0);
if (flags == -1) {
ERR_EXIT("fcntl get flag error");
}
ret = fcntl(0, F_SETFL, flags | O_NONBLOCK);
if (ret == -1) {
ERR_EXIT("fcntl set flag error");
}
*/
set_flag ( 0 , O_NONBLOCK );
clr_flag ( 0 , O_NONBLOCK );
ret = read ( 0 , buf , BUFF_SIZE );
if ( ret == - 1 ) {
ERR_EXIT ( "read error" );
}
printf ( "buf = %s \n " , buf );
return 0 ;
}
void set_flag ( int fd , int flags ){
int val ;
val = fcntl ( fd , F_GETFL , 0 );
if ( val == - 1 ){
ERR_EXIT ( "fcntl get flag error" );
}
val |= flags ;
if ( fcntl ( fd , F_SETFL , val ) < 0 ){
ERR_EXIT ( "fcntl set flag error" );
}
}
void clr_flag ( int fd , int flags ){
int val ;
val = fcntl ( fd , F_GETFL , 0 );
if ( val == - 1 ){
ERR_EXIT ( "fcntl get flag error" );
}
val &= ~ flags ;
if ( fcntl ( fd , F_SETFL , val ) < 0 ){
ERR_EXIT ( "fcntl set flag error" );
}
}
文件锁结构体查看 man 2 fcntl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int main ( int argc , char * argv []){
int fd ;
fd = open ( "test.txt" , O_CREAT | O_RDWR | O_TRUNC , 0644 );
if ( fd == - 1 ){
ERR_EXIT ( "open error" );
}
struct flock lock ;
memset ( & lock , 0 , sizeof ( lock ));
lock . l_type = F_WRLCK ;
lock . l_whence = SEEK_SET ;
lock . l_start = 0 ;
lock . l_len = 0 ;
if ( fcntl ( fd , F_SETLK , & lock ) == 0 ){
printf ( "lock success \n " );
printf ( "press any key to unlock \n " );
lock . l_type = F_UNLCK ;
if ( fcntl ( fd , F_SETLK , & lock ) == 0 ){
printf ( "unlock success \n " );
}
else {
ERR_EXIT ( "unlock fail" );
}
}
else {
ERR_EXIT ( "lock fail" );
}
return 0 ;
}
进程
代码段 + 数据段 + 堆栈段 + PCB(进程控制块process control block)
进程状态变迁
进程状态变迁
进程创建
给新创建的进程分配一个内部标识,在内核中建立进程结构 复制父进程的环境 为进程分配资源,包括进程映像所需要的所有元素(程序、数据、用户栈等), 复制父进程地址空间的内容到该进程地址空间中。 置该进程的状态为就绪,插入就绪队列。 进程撤销
关闭软中断:因为进程即将终止而不再处理任何软中断信号 回收资源:释放进程分配的所有资源,如关闭所有已打开文件,释放进程相应的数据结构等 写记帐信息:将进程在运行过程中所产生的记帐数据(其中包括进程运行时的各种统计信息)记录到一个全局记帐文件中 置该进程为僵死状态:向父进程发送子进程死的软中断信号,将终止信息status送到指定的存储单元中: 转进程调度:因为此时CPU已经被释放,需要由进程调度进行CPU再分配。 终止进程
从main函数返回 调用exit 调用exit 调用abort 由信号终止 fork系统调用(写时复制)
功能:创建一个子进程。一次调用两次返回,创建一个进程副本,在各自的进程地址空间返回
函数原型:
函数参数:无参数
返回值:
如果成功创建一个子进程,对于父进程来说返回子进程ID
如果成功创建一个子进程,对于子进程来说返回值为0
如果为-1
表示创建失败
子进程和父进程的区别
父进程设置的锁,子进程不继承 各自的进程ID和父进程ID不同 子进程的未决告警被清除: 子进程的未决信号集设置为空集。 注意
fork系统调用之后,父子进程将交替执行
(孤儿进程,托孤给一号进程)如果父进程先退出,子进程还没退出,那么子进程的父进程将变为init进程。(注:任何一个进程都必须有父进程)
(僵死进程,子进程先退出,父进程尚未查询子进程的退出状态)如果子进程先退出,父进程还没退出,那么子进程要等到父进程捕获到子进程的退出状态才真正结束,否则这个时候子进程就成为僵进程。
使用信号 signal(SIGCHLD, SIG_IGN)
,避免僵死进程
调用进程的进程号:pid_t getpid(void);
检索父进程的进程号:pid_t getppid(void);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main ( int argc , char * argv []){
signal ( SIGCHLD , SIG_IGN );
printf ( "before fork pid = %d \n " , getpid ());
pid_t pid ;
pid = fork ();
if ( oid == - 1 ){
ERR_EXIT ( "fork fail" );
}
if ( pid > 0 ){
printf ( "this is parent pid = %d, child pid = %d \n " , getpid (), pid );
sleep ( 1 );
}
else if ( pid == 0 ){
printf ( "this is child pid = %d, parent pid = %d \n " , getpid (), getppid ());
}
return 0 ;
}
查看进程的树状关系:pstree
/proc/PID/status
提供的PPid字段,查看每个进程的父进程。
系统支持的最大线程数:cat /proc/sys/kernel/threads-max
系统全局的 PID 号数值的限制:cat /proc/sys/kernel/pid_max
vfork
fork + exec(替换函数)
–>创建一个进程 + 替换(新的程序)
使用vfork
,子进程必须执行_exit
或者exec
函数。_
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int gval = 100 ;
int main ( int argc , char * argv []){
signal ( SIGCHLD , SIG_IGN );
printf ( "before fork pid = %d \n " , getpid ());
pid_t pid ;
pid = vfork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork fail" );
}
if ( pid > 0 ){
printf ( "this is parent pid = %d, child pid = %d, gval = %d \n " , getpid (), pid , gval );
sleep ( 1 );
}
else if ( pid == 0 ){
gval ++ ; // copy on write
printf ( "this is child pid = %d, parent pid = %d, gval = %d \n " , getpid (), getppid (), gval );
_exit ( 0 );
}
return 0 ;
}
exit
和_exit
_exit
:系统调用
exit
:C库函数
会做缓存区清除操作:fflush(stdout); exit();
调用终止处理程序(最多注册32个)
终止处理程序需注册:int atexit(void (*function)(void));
调用和注册次序相反;即调用顺序和输出顺序相反。
int execve(const char *filename, char *const argv[], char *const rnvp[]);
exec替换进程映像
替换后,不会运行之后的代码
man execlp
1
2
3
4
5
6
7
8
int execl ( const char * path , const char * arg , ...);
int execlp ( const char * file , const char * arg , ...);
int execle ( const char * path , const char * arg ,
..., char * const envp []);
int execv ( const char * path , char * const argv []);
int execvp ( const char * file , char * const argv []);
int execvpe ( const char * file , char * const argv [],
char * const envp []);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
printf ( "pid = %d \n " , getpid ());
int ret = execlp ( "./fork_pid" , "fork_pid" , NULL );
int ret = execl ( "/bin/ls" , "ls" , "-l" , NULL ); // 指定全路径
等价于
int ret = execlp ( "ls" , "ls" , "-l" , NULL );
等价于
char * const args [] = { "ls" , "-l" , NULL };
int ret = execvp ( "ls" , args );
char * const envp [] = { "AA=11" , "BB=22" , NULL }; // 配置环境变量, 但是没输出AA和BB很奇怪
int ret = execle ( "/bin/ls" , "ls" , NULL , envp );
if ( ret == - 1 ){
perror ( "ececlp error" );
}
fcntl和exec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main ( int argc , char * argv []){
printf ( "Entering main ... \n " );
int fret = fcntl ( 1 , F_SETFD , FD_CLOEXEC );
if ( fret == - 1 ){
perror ( "fcntl error" );
}
int ret = execlp ( "./fork_pid" , "fork_pid" , NULL );
if ( ret == - 1 ){
perror ( "ececlp error" );
}
printf ( "Entering main ... \n " );
return 0 ;
}
# 输出
Entering main ...
wait和waitpid
信号:异步通知事件
当子进程退出的时候,内核会向父进程发送SIGCHLD
信号,子进程的退出是异步事件(子进程可以在父进程运行的任何时刻终止)
子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。
wait
功能:父进程查询子进程的退出状态
函数原型:
函数参数:status:该参数可以获得等待子进程的信息
返回值:如果成功,返回等待子进程的ID
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main ( int argc , char * argv []){
pid_t pid ;
printf ( "before fork pid = %d \n " , getpid ());
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork fail" );
}
if ( pid > 0 ){
printf ( "this is parent pid = %d, child pid = %d \n " , getpid (), pid );
}
if ( pid == 0 ){
sleep ( 3 );
printf ( "this is child pid = %d, parent pid = %d \n " , getpid (), getppid ());
// exit(1);
abort (); // 异常终止
}
printf ( "this is parent \n " );
int ret , status ;
ret = wait ( & status ); // 等待子进程退出
printf ( "ret = %d, pid = %d \n " , ret , pid ); // wait返回值子进程PIDS
return 0 ;
}
1
2
3
4
5
6
7
8
9
10
// 状态信息
if ( WIFEXITED ( status )){
printf ( "child exited normal, exit status = %d \n " , WEXITSTATUS ( status ));
}
else if ( WIFSIGNALED ( status )){
printf ( "child exited abnormal, signal number = %d \n " , WTERMSIG ( status )); // 通过kill -l查看信号 man 7 signal
}
else if ( WIFSTOPPED ( status )){
printf ( "child stoped , signal number = %d \n " , WTERMSIG ( status ));
}
宏定义 描述 WEXITSTATUS(status)
如果WIFEXITED
非零,返回子进程退出码 WTERMSIG(status)
如果WIFSIGNALED
非零,返回信号代码 WSTOPSIG(status)
如果WIFSTOPPED
非零,返回信号代码 WIFEXITED(status)
如果子进程正常结束,返回一个非零值 WIFSIGNALED(status)
子进程因为捕获信号而终止,返回非零值 WIFSTOPPED(status)
子进程被暂停,返回非零值
waitpid
功能:用来等待某个特定进程的结束
函数原型:
pid_t waitpid(pid_t pid, int *status, int options);
函数参数
pid 进程号
pid==-1,
等待任一子进程。wait(&status)等价于waitpid(-1, &status, 0)
pid > 0
,等待其进程ID与pid相等的子进程。
pid==0
,等待其组ID等于调用进程的组ID的任一子进程。
pid < -1
,等待其组ID等于pid的绝对值的任一子进程。
status:如果不空,则把状态信息写到它指向的位置
options:允许改变waitpid的行为,最有用的一个选项是VNOHANG,它的作用是防止waitpid把调用者的执行挂起
返回值:如果成功,返回等待子进程的ID;失败返回-1
system
功能:调用bin/sh -c command
执行特定的命令,阻塞当前进程直到command命令执行完毕
函数原型:
int system(const char *command);
函数参数:command:指令
返回值:127:无法启动shel运行命令;-1:不能执行system调用的其他错误;system能够顺利执行,返回那个命令
的退出码
system函数执行时,会调用fork、execve、waitpid等函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int main ( int argc , char * argv []){
// system("ls -l | wc -w");
my_system ( "ls -l | wc -w" );
return 0 ;
}
int my_system ( const char * command ){
pid_t pid ;
int status ;
if ( command == NULL ){
return 1 ;
}
if (( pid = fork ()) < 0 ){
status = - 1 ;
}
else if ( pid == 0 ){
execl ( "/bin/sh" , "sh" , "-c" , command , NULL );
exit ( 127 );
}
else {
while ( waitpid ( pid , & status , 0 ) < 0 ){
if ( errno == EINTR ){
continue ;
}
status = - 1 ;
break ;
}
}
return status ;
}
daemon守护进程
在后台运行不受控制端控制的进程,通常以d结尾。
功能:创建守护进程
函数原型:
int daemon(int nochdir, int noclose);
函数参数:nochdir=0,将当前目录改为根目录; noclose=0,将标准输入、标准输出、标准错误重定向到/dev/null
返回值:如果成功,返回等待子进程的ID
创建守护进程
调用fork()
,创建新进程,它会是将来的守护进程 在父进程中调用exit,
保证子进程不是进程组组长 调用setsid
创建新的会话期 将当前目录改为根目录 将标准输入、标准输出、标准错误重定向到/dev/null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int steup_daemon ( int nochdir , int noclose ){
pid_t pid ;
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork fail" );
}
if ( pid > 0 ){
exit ( EXIT_SUCCESS );
}
setsid ();
if ( nochdir == 0 ){
chdir ( "/" );
}
if ( noclose == 0 ){
for ( int i = 0 ; i < 3 ; ++ i ){
close ( i );
}
open ( "/dev/null" , O_RDWR );
dup ( 0 );
dup ( 0 );
}
return 0 ;
}
killall demo
:杀死demo进程
ps aux | grep demo
:查询demo进程信息
信号
man 7 signal
信号和中断
中断过程
中断信号 中断源 保护现场 中断处理程序 恢复现场 中断源–>中断屏蔽–> 保护现场–>中断处理程序–>恢复现场
中断向量表:保存固定个数的中断处理程序入口地址
中断分类
硬件中断(外部中断)
外部中断是指由外部设各通过硬件请求的方式产生的中断,也称为硬件中断
软件中断(内部中断)
内部中断是由CPU运行程序错误或执行内部程序调用引起的一种中断,也称为软件中断。
信号是系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。
信号是在软件层次上对中断的一种模拟,所以通常把它称为是软中断
kill -l查看信号
信号分类
可靠信号(实时信号,支持排队,SIGRT开头);
非可靠信号(非实时信号,不支持排队)
信号与中断的相似点
采用了相同的异步通信方式 当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序 都在处理完毕后返回到原来的断点 对信号或中断都可进行屏蔽。 信号与中断的区别
中断有优先级,而信号没有优先级,所有的信号都是平等的 信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行 中断响应是及时的,而信号响应通常都有较大的时间延迟 进程对信号的三种响应
忽略信号:不采取任何操作,有两个信号不能忽略,也不能捕获:SIGKILL和SIGSTOP即-9和19
捕获并处理信号:内核中断正在执行的代码,转去执行先前注册过的处理程序。 执行默认操作:默认操作通常是终止进程,这取决于被发送的信号 signal
SIGINT (crtl + c)
; SIGQUIT (crtl + \)
功能:安装信号
函数原型:
__sighandler_t signal(int signum, __sighandler_t handler);
函数参数:signum:中断号,handler:中断处理程序
准备捕捉或屏蔽的信号由参数signum
给出,接收到指定信号时将要调用的函数由handler
给出
handler
:这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void
handler也可以是两个特殊值:SIG_IGN
:屏蔽该信号;SIG_DFL
:恢复默认行为
返回值:上一次所处理的程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void handler ( int sig );
void func ( int numLoops , char ch , char * pass );
int main ( int argc , char * argv []){
if ( signal ( SIGINT , handler ) == SIG_ERR ){
ERR_EXIT ( "signal error" );
}
int numLoops = 0 ;
char ch = '\0' ;
func ( numLoops , ch , "pass" );
return 0 ;
}
void handler ( int sig ){
printf ( " \n recieve a signal = %d \n " , sig );
}
void func ( int numLoops , char ch , char * pass ){
while ( 1 ) {
printf ( "Press ENTER to test (loop %d )..." , numLoops );
numLoops ++ ;
ch = getchar ();
if ( ch == '\n' ){
printf ( "%s \n " , pass );
}
else {
break ;
}
}
}
信号发送
kill
功能:发送信号
函数原型:
int kill(pid_t pid, int sig);
函数参数:pid:进程号,sig:信号
pid > 0
: 信号sig发送给进程号等于pid
的进程pid = 0
: 信号sig被发送给调用者所在组的每一个进程pid = -1
: 信号sig被发送给调用者进程有权限发送的每一个进程,除了1号进程和自己之外pid < -1
: 信号sig被发送给进程组等于-pid
的每一个进程返回值:0: 成功。 -1 :设置 errno 表示错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void handler ( int sig );
int main ( int argc , char * argv []){
if ( signal ( SIGUSR1 , handler ) == SIG_ERR ){
ERR_EXIT ( "signal error" );
}
pid_t pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork error" );
}
if ( pid == 0 ){
// kill(getppid(), SIGUSR1);
// exit(EXIT_SUCCESS);
pid = getpgrp ();
kill ( - pid , SIGUSR1 );
exit ( EXIT_SUCCESS );
}
int n = 5 ;
do {
n = sleep ( n );
} while ( n > 0 );
return 0 ;
}
void handler ( int sig ){
printf ( "recv a sig = %d \n " , sig );
}
pause
功能:使调用者进程挂起,直到一个信号被捕获
函数原型:
返回值:0: 成功。 -1 :设置 errno 表示错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void handler ( int sig );
int main ( int argc , char * argv []){
if ( signal ( SIGINT , handler ) == SIG_ERR ){
ERR_EXIT ( "signal error" );
}
alarm ( 1 );
for (;;){
pause ();
printf ( "pause return \n " );
}
return 0 ;
}
void handler ( int sig ){
printf ( "recv a sig = %d \n " , sig );
alarm ( 1 );
}
1
kill -ALRM ` ps aux | grep demo | grep -v vi | grep -v grep | awk '{print $2}' `
raise
功能:给自己发信号。raise(sig)
等价于kill(getpid(), sig)
killpg
功能:给进程组发信号。killpg(pgrp, sig)
等价于kill(-pgrp, sig)
sigqueue
功能:给进程发送信号,支持排队,可以附带信号
函数原型:
int sigqueue(pid_t pid, int sig, const union sigval value);
参数:pid:进程号, sig:信号;value:信号传递的参数
返回值:-1:失败;0:成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 接收
void handler ( int sig , siginfo_t * info , void * ctx );
int main ( int argc , char * argv []){
struct sigaction act ;
act . sa_sigaction = handler ;
sigemptyset ( & act . sa_mask );
act . sa_flags = SA_SIGINFO ;
if ( sigaction ( SIGINT , & act , NULL ) < 0 ){
ERR_EXIT ( "sigaction error" );
}
for (;;){
pause ();
}
return 0 ;
}
void handler ( int sig , siginfo_t * info , void * ctx ){
printf ( "recv a sig = %d data = %d \n " , sig , info -> si_value . sival_int );
}
// 发送
int main ( int argc , char * argv []){
if ( argc != 2 ){
fprintf ( stderr , "Usage %s pid \n " , argv [ 0 ]);
exit ( EXIT_FAILURE );
}
pid_t pid = atoi ( argv [ 1 ]);
union sigval v ;
v . sival_int = 100 ;
sigqueue ( pid , SIGINT , v );
return 0 ;
}
// 运行
. / sigqueue_send ` ps aux | grep sigqueue_recv | grep - v vi | grep - v grep | awk ' { print $ 2 } '`
可重入函数
使用不可重入函数,进程可能会修改原来进程中不应该被修改的数据,是不安全的
多数是不可重入函数的,一般条件如下:
使用静态数据结构
函数实现时调用了malloc或free函数
实现了使用标准IO函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct {
int a ;
int b ;
} TEST ;
TEST g_data ;
int main ( int argc , char * argv []){
TEST zeros = { 0 , 0 };
TEST ones = { 1 , 1 };
if ( signal ( SIGALRM , handler ) == SIG_ERR ){
ERR_EXIT ( "signal error" );
}
g_data = zeros ;
alarm ( 1 );
for (;;){
g_data = zeros ;
g_data = ones ;
}
return 0 ;
}
void unsafe_fun (){
printf ( "%d %d \n " , g_data . a , g_data . b );
}
void handler ( int sig ){
unsafe_fun ();
alarm ( 1 );
}
信号未决(pending)
执行信号的处理动作称为信号递达,信号从产生到递达之间的状态,称为信号未决。
信号集操作函数
1
2
3
4
5
int sigemptyset ( sigset_t * set );
int sigfillset ( sigset_t * set );
int sigaddset ( sigset_t * set , int signum );
int sigdelset ( sigset_t * set , int signum );
int sigismember ( const sigset_t * set , int signum );
sigprocmask
功能:读取或更改进程的信号屏蔽字
函数原型:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
函数参数:以屏蔽子mask
为例
SIG_BLOCK
被阻塞的信号集是当前信号集和信号集参数的集合。mask = mask | set
SIG_UNBLOCK
从当前阻塞信号集中删除 set 中的信号。 允许尝试解锁未被屏蔽的信号。 mask = mask & ~set
SIG_SETMASK
阻塞信号集被设置为参数 set。mask = set
如果 oldset 是非空指针,则读取进程的当前信号屏蔽字通过oldset参数传出。
返回值:0: 成功。 -1: 出错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void handler ( int sig );
void printsigset ( sigset_t * set );
int main ( int argc , char * argv []){
sigset_t pset , bset ;
sigemptyset ( & bset );
sigaddset ( & bset , SIGINT );
if ( signal ( SIGINT , handler ) == SIG_ERR ){
ERR_EXIT ( "signal error" );
}
if ( signal ( SIGQUIT , handler ) == SIG_ERR ){
ERR_EXIT ( "signal error" );
}
sigprocmask ( SIG_BLOCK , & bset , NULL );
for (;;){
sigpending ( & pset );
printsigset ( & pset );
sleep ( 1 );
}
return 0 ;
}
void handler ( int sig ){
if ( sig == SIGINT ){
printf ( "recv a sig = %d \n " , sig );
}
else {
sigset_t uset ;
sigemptyset ( & uset );
sigaddset ( & uset , SIGINT );
sigprocmask ( SIG_UNBLOCK , & uset , NULL );
}
}
void printsigset ( sigset_t * set ){
int i ;
for ( i = 1 ; i < NSIG ; ++ i ){
if ( sigismember ( set , i )){
putchar ( '1' );
}
else {
putchar ( '0' );
}
}
}
sigaction
功能:改变进程接收到特定信号后的行为
原型:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数:signum:信号值;act:指向结构sigaction的实例指针loldact:oldact指向的对象用来保存原来相对应信号的处理。
1
2
3
4
5
6
7
struct sigaction {
void ( * sa_handler )( int );
void ( * sa_sigaction )( int , siginfo_t * , void * );
sigset_t sa_mask ;
int sa_flags ;
void ( * sa_restorer )( void );
};
返回值:-1:失败;0:成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
oid handler ( int sig );
__sighandler_t my_signal ( int sig , __sighandler_t handler );
int main ( int argc , char * argv []){
// struct sigaction act;
// act.sa_handler = handler;
// sigemptyset(&act.sa_mask);
// act.sa_flags = 0;
// if (sigaction(SIGINT, &act, NULL) < 0){
// ERR_EXIT("sigaction error");
// }
my_signal ( SIGINT , handler );
for (;;){
pause ();
}
return 0 ;
}
__sighandler_t my_signal ( int sig , __sighandler_t handler ){
struct sigaction act ;
struct sigaction oldact ;
act . sa_handler = handler ;
sigemptyset ( & act . sa_mask );
act . sa_flags = 0 ;
if ( sigaction ( SIGINT , & act , & oldact ) < 0 ){
return SIG_ERR ;
}
return oldact . sa_handler ;
}
void handler ( int sig ){
printf ( "recv a sig = %d \n " , sig );
}
时间
不同精度下的休眠
秒:unsigned int sleep(unsigned int seconds);
time_t
微秒:int usleep(useconds_t usec);
1
2
3
4
struct timeval {
long tv_sec ;
long tv_usec ;
}
纳秒:int nanosleep(const struct timespec *req, struct timespec *rem);
1
2
3
4
struct timespec {
time_t tv_sec ; /* seconds */
long tv_nsec ; /* nanoseconds */
};
setitimer
功能:将 which 指定的定时器当前值存储到 value 指向的结构体中
函数原型:
int setitimer(int which, const struct itimerval *restrict value, struct itimerval *restrict ovalue);
参数:which:指定定时器类型
IYIMER_REAL
: 经过指定时间后,内核发送SIGALARM
信号给本进程
ITIMER_VIRTUAL
: 在用户空间执行指定的时间后,内核发送 SIGVTALRM
信号给本进程。
ITIMER_PROF:
在用户空间与内核空间执行指定的时间后,内核发送 SIGPROF
信号给本进程。
返回值:-1失败,0成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void handler ( int sig );
int main ( int argc , char * argv []){
if ( signal ( SIGINT , handler ) == SIG_ERR ){
ERR_EXIT ( "signal error" );
}
struct timeval tv_interval = { 1 , 0 };
struct timeval tv_value = { 1 , 0 };
struct itimerval it ;
it . it_interval = tv_interval ;
it . it_value = tv_value ;
setitimer ( ITIMER_REAL , & it , NULL );
// for(;;);
for ( int i = 0 ; i < 10000 ; i ++ );
struct itimerval oit ;
setitimer ( ITIMER_REAL , & it , & oit );
printf ( "%d %d %d %d \n " , ( int ) oit . it_interval . tv_sec , ( int ) oit . it_interval . tv_usec , ( int ) oit . it_value . tv_sec , ( int ) oit . it_value . tv_usec );
return 0 ;
}
void handler ( int sig ){
printf ( "recv a sig = %d \n " , sig );
}
管道
管道
匿名管道
pipe
功能:创建一无名管道 (在具有共同祖先的进程间通信)
原型:int pipe(int fd[2]);
参数:fd:文件描述符数组,0:读端,1写端
返回:0:成功,错误代码:失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main ( int argc , char * argv []){
int pipefd [ 2 ];
if ( pipe ( pipefd ) == - 1 ){
ERR_EXIT ( "pipe error" );
}
pid_t pid ;
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork error" );
}
if ( pid == 0 ){
close ( pipefd [ 0 ]);
write ( pipefd [ 1 ], "hello" , 5 );
close ( pipefd [ 1 ]);
exit ( EXIT_SUCCESS );
}
close ( pipefd [ 1 ]);
char buf [ 10 ] = { 0 };
read ( pipefd [ 0 ], buf , 10 );
printf ( "buf = %s \n " , buf );
return 0 ;
}
ls | wc -w
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int main ( int argc , char * argv []){
int pipefd [ 2 ];
if ( pipe ( pipefd ) == - 1 ){
ERR_EXIT ( "pipe error" );
}
pid_t pid ;
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork error" );
}
if ( pid == 0 ){
dup2 ( pipefd [ 1 ], STDOUT_FILENO );
close ( pipefd [ 1 ]);
close ( pipefd [ 0 ]);
execlp ( "ls" , "ls" , NULL );
fprintf ( stderr , "error execute ls \n " );
exit ( EXIT_FAILURE );
}
dup2 ( pipefd [ 0 ], STDIN_FILENO );
close ( pipefd [ 0 ]);
close ( pipefd [ 1 ]);
execlp ( "wc" , "wc" , "-w" , NULL );
fprintf ( stderr , "error execute ls \n " );
exit ( EXIT_FAILURE );
return 0 ;
}
cp
1
2
3
4
5
6
7
8
int main ( int argc , char * argv []){
close ( 0 );
open ( "makefile" , O_RDONLY );
close ( 1 );
open ( "test" , O_WRONLY | O_CREAT | O_TRUNC , 0644 );
execlp ( "cat" , "cat" , NULL );
return 0 ;
}
管道读写规则
当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1
,errno值为EAGAIN
。
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE
当要写入的数据量不大于PIPE_BUF
时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF
时,linux将不再保证写入的原子性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int main ( int argc , char * argv []){
int pipefd [ 2 ];
if ( pipe ( pipefd ) == - 1 ){
ERR_EXIT ( "pipe error" );
}
pid_t pid ;
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork error" );
}
if ( pid == 0 ){
sleep ( 3 );
close ( pipefd [ 0 ]);
write ( pipefd [ 1 ], "hello" , 5 );
close ( pipefd [ 1 ]);
exit ( EXIT_SUCCESS );
}
close ( pipefd [ 1 ]);
char buf [ 10 ] = { 0 };
int flags = fcntl ( pipefd [ 0 ], F_GETFL );
fcntl ( pipefd [ 0 ], F_SETFL , flags | O_NONBLOCK );
int ret = read ( pipefd [ 0 ], buf , 10 );
if ( ret == - 1 ){
ERR_EXIT ( "read error" );
}
printf ( "buf = %s \n " , buf );
return 0 ;
}
// 输出:read error: Resource temporarily unavailable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main ( int argc , char * argv []){
int pipefd [ 2 ];
if ( pipe ( pipefd ) == - 1 ){
ERR_EXIT ( "pipe error" );
}
pid_t pid ;
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork error" );
}
if ( pid == 0 ){
close ( pipefd [ 1 ]);
exit ( EXIT_SUCCESS );
}
close ( pipefd [ 1 ]);
char buf [ 10 ] = { 0 };
int ret = read ( pipefd [ 0 ], buf , 10 );
if ( ret == - 1 ){
ERR_EXIT ( "read error" );
}
printf ( "ret = %d \n " , ret );
return 0 ;
}
// 输出: ret = 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void handler ( int sig ){
printf ( "recv a sig = %d \n " , sig );
}
int main ( int argc , char * argv []){
signal ( SIGPIPE , handler );
int pipefd [ 2 ];
if ( pipe ( pipefd ) == - 1 ){
ERR_EXIT ( "pipe error" );
}
pid_t pid ;
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork error" );
}
if ( pid == 0 ){
close ( pipefd [ 0 ]);
exit ( EXIT_SUCCESS );
}
close ( pipefd [ 0 ]);
sleep ( 1 );
int ret = write ( pipefd [ 1 ], "hello" , 5 );
if ( ret == - 1 ){
ERR_EXIT ( "write error" );
}
return 0 ;
}
// 输出:
recv a sig = 13
write error : Broken pipe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 管道大小 65536
int main ( int argc , char * argv []){
int pipefd [ 2 ];
if ( pipe ( pipefd ) == - 1 ){
ERR_EXIT ( "pipe error" );
}
int ret ;
int count = 0 ;
int flags = fcntl ( pipefd [ 1 ], F_GETFL );
fcntl ( pipefd [ 1 ], F_SETFL , flags | O_NONBLOCK );
while ( 1 ){
ret = write ( pipefd [ 1 ], "A" , 1 );
if ( ret == - 1 ){
printf ( "err = %s \n " , strerror ( errno ));
break ;
}
count ++ ;
}
printf ( "pipe size = %d \n " , count );
return 0 ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int main ( int argc , char * argv []){
char a [ TEST_SIZE ];
char b [ TEST_SIZE ];
memset ( a , 'A' , sizeof ( a ));
memset ( b , 'B' , sizeof ( b ));
int pipefd [ 2 ];
int ret = pipe ( pipefd );
if ( ret == - 1 ){
ERR_EXIT ( "pipe error" );
}
pid_t pid = fork ();
if ( pid == 0 ){
close ( pipefd [ 0 ]);
ret = write ( pipefd [ 1 ], a , sizeof ( a ));
printf ( "apid = %d write %d bytes to pipe \n " , getpid (), ret );
exit ( 0 );
}
pid = fork ();
if ( pid == 0 ){
close ( pipefd [ 0 ]);
ret = write ( pipefd [ 1 ], b , sizeof ( b ));
printf ( "bpid = %d write %d bytes to pipe \n " , getpid (), ret );
exit ( 0 );
}
close ( pipefd [ 1 ]);
sleep ( 1 );
int fd = open ( "test.txt" , O_WRONLY | O_CREAT | O_TRUNC , 0644 );
char buf [ 1024 * 4 ] = { 0 };
int n = 1 ;
while ( 1 ){
ret = read ( pipefd [ 0 ], buf , sizeof ( buf ));
if ( ret == 0 ) break ;
printf ( "n = %02d pid = %d read %d bytes from pipe buf[4095] = %c \n " , n ++ , getpid (), ret , buf [ 4095 ]);
write ( fd , buf , ret );
}
return 0 ;
}
命名管道FIFO
mkfifo
功能:创建一命名管道 ,可在不相关的进程间进行通信,命令行创建mkfifo filename
原型:int mkfifo(const char *pathname, mode_t mode);
参数:pathname:文件名;mode:文件状态模式
返回:0:成功,-1:失败
命名管道打开规则
如果当前打开操作是为读 而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开FIFO
O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写 而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int main ( int argc , char * argv []){
int fifo ;
fifo = mkfifo ( "p1" , 0644 );
if ( fifo == - 1 ){
ERR_EXIT ( "FIFO create fail" );
}
int fd ;
fd = open ( "p1" , O_RDONLY | O_NONBLOCK );
if ( fd == - 1 ){
ERR_EXIT ( "open error" );
}
printf ( "open success \n " );
return 0 ;
}
// 输出:open success
int main ( int argc , char * argv []){
int fd ;
fd = open ( "p1" , O_WRONLY | O_NONBLOCK );
if ( fd == - 1 ){
ERR_EXIT ( "open error" );
}
printf ( "open success \n " );
return 0 ;
}
// 输出open error: No such device or address
cp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int main ( int argc , char * argv []){
mkfifo ( "tp" , 0644 );
int infd ;
infd = open ( "makefile" , O_RDONLY );
if ( infd == - 1 ){
ERR_EXIT ( "open error" );
}
int outfd ;
outfd = open ( "tp" , O_WRONLY );
if ( outfd == - 1 ){
ERR_EXIT ( "open error" );
}
char buf [ 1024 ];
int n ;
while (( n = read ( infd , buf , 1024 )) > 0 ){
write ( outfd , buf , n );
}
close ( infd );
close ( outfd );
return 0 ;
}
int main ( int argc , char * argv []){
int infd ;
infd = open ( "tp" , O_RDONLY );
if ( infd == - 1 ){
ERR_EXIT ( "open error" );
}
int outfd ;
outfd = open ( "test" , O_WRONLY | O_CREAT | O_TRUNC , 0644 );
if ( outfd == - 1 ){
ERR_EXIT ( "open error" );
}
char buf [ 1024 ];
int n ;
while (( n = read ( infd , buf , 1024 )) > 0 ){
write ( outfd , buf , n );
}
close ( infd );
close ( outfd );
unlink ( "tp" );
return 0 ;
}
minishell实践
parse_command
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 单条命令解析 ls -l -a
char * cp = cmdline ;
char * avp = avline ;
int i = 0 ;
while ( * cp != '\0' ){
while ( * cp == ' ' || * cp == '\t' ){
cp ++ ; // 过滤空格
}
if ( * cp == '\0' || * cp == '\n' ){
break ; // 行尾跳出
}
cmd . args [ i ] = avp ;
while ( * cp != '\0' && * cp != ' ' && * cp != '\t' && * cp != '\n' ){
* avp ++ = * cp ++ ;
}
* avp ++ = '\0' ;
// printf("[%s]\n", cmd.args[i]);
i ++ ;
}
// 单条命令执行
pid_t pid = fork (); // 让子进程执行命令execvp(execvp是替换程序)
if ( pid == - 1 ){
ERR_EXIT ( "fork" );
}
int ret ;
if ( pid == 0 ){
ret = execvp ( cmd . args [ 0 ], cmd . args );
if ( ret == - 1 ){
ERR_EXIT ( "execvp" );
}
}
wait ( NULL );
cmd [< filename][| cmd] ... [or filename][&]
方括号可选
省略号(…)表示前面可重复0次或者多次
其中or可以是>
或者>>
1
2
3
4
5
6
7
8
/* cat < test.txt | grep -n public > test2.txt & */
/* 1. 解析第一条简单命令
2. 判定是否有输入重定向符
3. 判定是否有管道
4. 判定是否有输出重定向符
5. 判定是否后台作业
6. 判断命令结束 '\n'
*/
Linux网络编程
TCP/IP
直接看 图解网络介绍 | 小林coding (xiaolincoding.com)
TCP_IP
TCP/IP四层模型
TCP/IP四层模型
封装过程
封装
链路层
最大传输单元(MTU):链路层数据帧的最大长度, 两台通信主机路径中的最小MTU叫路径MTU。
ICMP协议:用于传递差错信息、时间、回显、网络信息等控制数据,在IP报文中
ARP地址解析协议:广播机制传播,回复ARP请求,ARP缓存区映射
RARP反向地址解析协议
传输控制层
TCP
基于字节流,面向连接,可靠传输,缓冲传输,全双工,流量控制
最长报文大小MSS
保证可靠性
差错:校验和
丢包:超时重传+确认
失序:seq
重复:seq
TCP 头格式
三次握手
TCP 三次握手
四次挥手
客户端主动关闭连接 —— TCP 四次挥手
滑动窗口协议
流量控制:窗口维护
Socket
看成是用户进程与内核网络协议栈的编程接口。
不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信。
套接口的地址结构,包括PV4和通用地址结构,以及如何在不同主机和协议之间进行通信。
man 7 ip
查看
套接口必须有地址属性来标识一个端点,TCP/IP协议用IP地址、端口号和地址家族来表达。
1
2
3
4
5
struct sockaddr_in {
sa_family_t sin_family ; /* address family: AF_INET */
in_port_t sin_port ; /* port in network byte order */
struct in_addr sin_addr ; /* internet address */
};
基础概念
网络字节序
字节序
大端字节序 (Big Endian)
最高有效位(MSB:Most Significant Bit)存储于最低内存地址处,最低有效位(LSB:Lowest Significant Bit)存储于最高内存地址处。
小端字节序(Little Endian)
最高有效位存储于最高内存地址处,最低有效位存储于最低内存地址处。
主机字节序
不同的主机有不同的字节序,如x86为小端字节序,Motorola6800为大端字节序,ARM字节序是可配置的。
网络字节序
网络字节序规定为大端字节序
字节序转换函数
h:host;n:network;s:short; l:long;
1
2
3
4
5
#include <arpa/inet.h>
uint32_t htonl ( uint32_t hostlong ); // 4字节的主机字节序转为网络字节序
uint16_t htons ( uint16_t hostshort );
uint32_t ntohl ( uint32_t netlong );
uint16_t ntohs ( uint16_t netshort );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
int main ()
{
unsigned int x = 0x12345678 ;
unsigned char * p = ( unsigned char * )( & x );
printf ( "%0x %0x %0x %0x \n " , p [ 0 ], p [ 1 ], p [ 2 ], p [ 3 ]);
unsigned int y = htonl ( x );
p = ( unsigned char * )( & y );
printf ( "%0x %0x %0x %0x \n " , p [ 0 ], p [ 1 ], p [ 2 ], p [ 3 ]);
return 0 ;
}
// 输出
// 78 56 34 12
// 12 34 56 78
地址转换函数
1
2
3
4
int inet_aton ( const char * cp , struct in_addr * inp ); // 点分十进制的IPv4地址字符串转struct in_addr结构体类型的二进制表示
in_addr_t inet_addr ( const char * cp );
in_addr_t inet_network ( const char * cp ); // 点分十进制的IPv4地址字符串转对应的网络地址的二进制表示
char * inet_ntoa ( struct in_addr in ); // 网络字节序表示的struct in_addr类型的IPv4地址转换为点分十进制的字符串表示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main () {
struct in_addr ipaddr ;
inet_aton ( "192.168.0.123" , & ipaddr );
printf ( "%u \n " , ntohl ( ipaddr . s_addr ));
printf ( "IPv4地址: %s \n " , inet_ntoa ( ipaddr ));
unsigned long addr2 = inet_addr ( "192.168.0.123" );
printf ( "%u \n " , ntohl ( addr2 ));
struct in_addr ipaddr_1 ;
ipaddr_1 . s_addr = addr2 ;
printf ( "IPv4地址: %s \n " , inet_ntoa ( ipaddr_1 ));
in_addr_t ip ;
ip = inet_network ( "192.168.0.123" );
printf ( "%u \n " , ip );
ip = ntohl ( ip );
struct in_addr ipaddr_2 ;
ipaddr_2 . s_addr = ip ;
printf ( "IPv4地址: %s \n " , inet_ntoa ( ipaddr_2 ));
return 0 ;
}
套接字类型
流式套接字(SOCK_STREAM
)
提供面向连接的、可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收。
数据报式套接字(SOCK_DGRAM
)
提供无连接服务。不提供无错保证,数据可能丢失或重复,并且接收顺序混乱
原始套接字(SOCK RAW
)
socket函数
socket
功能:创建一个套接字用于通信
函数原型:int socket(int domain, int type, int protocol);
参数:domain:通信协议族(protocol family);type:socket类型;protocol:协议类型
返回值:成功:非负整数;失败:-1.
bind
功能:绑定一个本地地址到套接字
函数原型:int bind(int socket, const struct sockaddr *address, socklen_t address_len);
参数:socket:函数返回的套接字;address:要绑定的地址;address_len:地址长度
返回值:成功:0;失败:-1.
listen
功能:将套接字用于监听进入的连接;将socket从主动套接字变为被动套接字
函数原型:int listen(int socket, int backlog);
参数:socket:函数返回的套接字;backlog:规定内核为此套接字排队的最大连接个数
返回值:成功:0;失败:-1.
accept
功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞
函数原型:int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
参数:socket:函数返回的套接字;address:将返回对等方的套接字地址;address_len:地址长度
返回值:成功:非负整数;失败:-1.
connect
功能:建立一个连接至addr所指定的套接字
函数原型:int connect(int socket, const struct sockaddr *address, socklen_t address_len);
参数:socket:未连接的套接字;address:要连接的套接字地址;address_len:地址长度
返回值:成功:0;失败:-1.
属性
getsockname
功能:获取本地地址
函数原型:int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:socket:套接字;addr:本地地址;addrlen:地址长度
返回值:成功:0;失败:-1.
1
2
3
4
5
6
struct sockaddr_in localaddr ;
socklen_t addrlen = sizeof ( localaddr );
if (( getsockname ( sock , ( struct sockaddr * ) & localaddr , & addrlen ) < 0 )){
ERR_EXIT ( "getsockname fail" );
}
printf ( "ip = %s port = %d \n " , inet_ntoa ( localaddr . sin_addr ), ntohs ( localaddr . sin_port ));
getpeername
:获取对等方地址
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
gethostname
:获取主机名
int gethostname(char *name, size_t len);
gethostbyname
:通过主机名获取IP地址
struct hostent *gethostbyname(const char *name);
gethostbyaddr
:通过IP地址获取主机的完整信息
struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
TCP客户/服务器模型
netstat -an | grep TIME_WAIT
查看等待状态的网络
TCP客户/服务器模型
回射客户/服务器
回射客户/服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int main () {
int listenfd ;
// listenfd = socket(PF_INET, SOCK_STREAM, 0);
listenfd = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); // 指定TCP
if ( listenfd < 0 ){
ERR_EXIT ( "socket fail" );
}
// init
struct sockaddr_in servaddr ;
memset ( & servaddr , 0 , sizeof ( servaddr ));
servaddr . sin_family = AF_INET ;
servaddr . sin_port = htons ( 5188 );
servaddr . sin_addr . s_addr = htonl ( INADDR_ANY );
// servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 指定地址
// inet_aton("127.0.0.1", &servaddr.sin_addr);
if ( bind ( listenfd , ( struct sockaddr * ) & servaddr , sizeof ( servaddr )) < 0 ){
ERR_EXIT ( "bind fail" );
}
if ( listen ( listenfd , SOMAXCONN ) < 0 ){
ERR_EXIT ( "listen fail" );
}
struct sockaddr_in peeraddr ;
socklen_t peerlen = sizeof ( peeraddr );
int conn ;
conn = accept ( listenfd , ( struct sockaddr * ) & peeraddr , & peerlen );
if ( conn < 0 ){
ERR_EXIT ( "accept fail" );
}
char recvbuf [ 1024 ];
while ( 1 ){
memset ( recvbuf , 0 , sizeof ( recvbuf ));
int ret = read ( conn , recvbuf , sizeof ( recvbuf ));
fputs ( recvbuf , stdout );
write ( conn , recvbuf , ret );
memset ( recvbuf , 0 , sizeof ( recvbuf ));
}
close ( conn );
close ( listenfd );
return 0 ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int main () {
int sock ;
// listenfd = socket(PF_INET, SOCK_STREAM, 0);
sock = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); // 指定TCP
if ( sock < 0 ){
ERR_EXIT ( "socket fail" );
}
// init
struct sockaddr_in servaddr ;
memset ( & servaddr , 0 , sizeof ( servaddr ));
servaddr . sin_family = AF_INET ;
servaddr . sin_port = htons ( 5188 );
servaddr . sin_addr . s_addr = inet_addr ( "127.0.0.1" ); // 指定地址
// inet_aton("127.0.0.1", &servaddr.sin_addr);
int ret ;
ret = connect ( sock , ( struct sockaddr * ) & servaddr , sizeof ( servaddr ));
if ( ret < 0 ){
ERR_EXIT ( "connect fail" );
}
char sendbuf [ 1024 ] = { 0 };
char recvbuf [ 1024 ] = { 0 };
while ( fgets ( sendbuf , sizeof ( sendbuf ), stdin ) != NULL )
{
write ( sock , sendbuf , strlen ( sendbuf ));
read ( sock , recvbuf , sizeof ( recvbuf ));
fputs ( recvbuf , stdout );
memset ( sendbuf , 0 , sizeof ( sendbuf ));
memset ( recvbuf , 0 , sizeof ( recvbuf ));
}
close ( sock );
return 0 ;
}
处理多客户连接 (process-per-connection)
一个连接一个进程来处理并发。
父进程接受客户端连接,子进程用来处理和客户端的通信细节。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
void do_service ( int conn , struct sockaddr_in peeraddr ){
char recvbuf [ 1024 ];
while ( 1 ){
memset ( recvbuf , 0 , sizeof ( recvbuf ));
int ret = read ( conn , recvbuf , sizeof ( recvbuf ));
if ( ret == 0 ){
printf ( "client ip = %s port = %d close \n " , inet_ntoa ( peeraddr . sin_addr ), ntohs ( peeraddr . sin_port ));
break ;
}
else if ( ret == - 1 ){
ERR_EXIT ( "read fail" );
}
fputs ( recvbuf , stdout );
write ( conn , recvbuf , ret );
memset ( recvbuf , 0 , sizeof ( recvbuf ));
}
}
int main () {
int listenfd ;
// listenfd = socket(PF_INET, SOCK_STREAM, 0);
listenfd = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); // 指定TCP
if ( listenfd < 0 ){
ERR_EXIT ( "socket fail" );
}
// init
struct sockaddr_in servaddr ;
memset ( & servaddr , 0 , sizeof ( servaddr ));
servaddr . sin_family = AF_INET ;
servaddr . sin_port = htons ( 5188 );
servaddr . sin_addr . s_addr = htonl ( INADDR_ANY );
int on = 1 ; // 在TIME_WAIT还没消失的情况,允许服务器重启
if ( setsockopt ( listenfd , SOL_SOCKET , SO_REUSEADDR , & on , sizeof ( on )) < 0 ){
ERR_EXIT ( "setsocketopt" );
}
if ( bind ( listenfd , ( struct sockaddr * ) & servaddr , sizeof ( servaddr )) < 0 ){
ERR_EXIT ( "bind fail" );
}
if ( listen ( listenfd , SOMAXCONN ) < 0 ){
ERR_EXIT ( "listen fail" );
}
struct sockaddr_in peeraddr ;
socklen_t peerlen = sizeof ( peeraddr );
int conn ;
pid_t pid ;
while ( 1 ){
conn = accept ( listenfd , ( struct sockaddr * ) & peeraddr , & peerlen );
if ( conn < 0 ){
ERR_EXIT ( "accept fail" );
}
printf ( "ip = %s port = %d \n " , inet_ntoa ( peeraddr . sin_addr ), ntohs ( peeraddr . sin_port ));
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork fail" );
}
if ( pid == 0 ){
close ( listenfd );
do_service ( conn , peeraddr );
exit ( EXIT_SUCCESS );
}
else {
close ( conn );
}
}
return 0 ;
}
点对点聊天
双方维护一个套接字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void handler ( int sig )
{
printf ( "recv a sig = %d \n " , sig );
exit ( EXIT_SUCCESS );
}
int main () {
int listenfd ;
// listenfd = socket(PF_INET, SOCK_STREAM, 0);
listenfd = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); // 指定TCP
if ( listenfd < 0 ){
ERR_EXIT ( "socket fail" );
}
// init
struct sockaddr_in servaddr ;
memset ( & servaddr , 0 , sizeof ( servaddr ));
servaddr . sin_family = AF_INET ;
servaddr . sin_port = htons ( 5188 );
servaddr . sin_addr . s_addr = htonl ( INADDR_ANY );
// servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 指定地址
// inet_aton("127.0.0.1", &servaddr.sin_addr);
int on = 1 ; // 在TIME_WAIT还没消失的情况,允许服务器重启
if ( setsockopt ( listenfd , SOL_SOCKET , SO_REUSEADDR , & on , sizeof ( on )) < 0 ){
ERR_EXIT ( "setsocketopt" );
}
if ( bind ( listenfd , ( struct sockaddr * ) & servaddr , sizeof ( servaddr )) < 0 ){
ERR_EXIT ( "bind fail" );
}
if ( listen ( listenfd , SOMAXCONN ) < 0 ){
ERR_EXIT ( "listen fail" );
}
struct sockaddr_in peeraddr ;
socklen_t peerlen = sizeof ( peeraddr );
int conn ;
conn = accept ( listenfd , ( struct sockaddr * ) & peeraddr , & peerlen );
if ( conn < 0 ){
ERR_EXIT ( "accept fail" );
}
printf ( "ip = %s port = %d \n " , inet_ntoa ( peeraddr . sin_addr ), ntohs ( peeraddr . sin_port ));
pid_t pid ;
pid = fork ();
if ( pid == 0 ){ // 子进程发送数据
signal ( SIGUSR1 , handler );
char sendbuf [ 1024 ];
while ( fgets ( sendbuf , sizeof ( sendbuf ), stdin ) != NULL ){
write ( conn , sendbuf , strlen ( sendbuf ));
memset ( sendbuf , 0 , sizeof ( sendbuf ));
}
printf ( "child close \n " );
exit ( EXIT_FAILURE );
}
else { // 父进程接收数据
char recvbuf [ 1024 ];
while ( 1 ){
memset ( recvbuf , 0 , sizeof ( recvbuf ));
int ret = read ( conn , recvbuf , sizeof ( recvbuf ));
if ( ret == - 1 ){
ERR_EXIT ( "read fail" );
}
else if ( ret == 0 ){
printf ( "peer close \n " );
break ;
}
fputs ( recvbuf , stdout );
}
printf ( "parent close \n " );
kill ( pid , SIGUSR1 ); // 通知子进程也退出
exit ( EXIT_FAILURE );
}
return 0 ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void handler ( int sig )
{
printf ( "recv a sig = %d \n " , sig );
exit ( EXIT_SUCCESS );
}
int main () {
int sock ;
// listenfd = socket(PF_INET, SOCK_STREAM, 0);
sock = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); // 指定TCP
if ( sock < 0 ){
ERR_EXIT ( "socket fail" );
}
// init
struct sockaddr_in servaddr ;
memset ( & servaddr , 0 , sizeof ( servaddr ));
servaddr . sin_family = AF_INET ;
servaddr . sin_port = htons ( 5188 );
servaddr . sin_addr . s_addr = inet_addr ( "127.0.0.1" ); // 指定地址
// inet_aton("127.0.0.1", &servaddr.sin_addr);
int ret ;
ret = connect ( sock , ( struct sockaddr * ) & servaddr , sizeof ( servaddr ));
if ( ret < 0 ){
ERR_EXIT ( "connect fail" );
}
pid_t pid ;
pid = fork ();
if ( pid == 0 ){ // 子进程接收数据
char recvbuf [ 1024 ] = { 0 };
while ( 1 )
{
memset ( recvbuf , 0 , sizeof ( recvbuf ));
int ret = read ( sock , recvbuf , sizeof ( recvbuf ));
if ( ret == - 1 ){
ERR_EXIT ( "read fail" );
}
else if ( ret == 0 ){
printf ( "peer close \n " );
break ;
}
fputs ( recvbuf , stdout );
}
close ( sock );
kill ( getppid (), SIGUSR1 );
}
else { // 父进程发射数据
signal ( SIGUSR1 , handler );
char sendbuf [ 1024 ] = { 0 };
while ( fgets ( sendbuf , sizeof ( sendbuf ), stdin ) != NULL )
{
write ( sock , sendbuf , strlen ( sendbuf ));
memset ( sendbuf , 0 , sizeof ( sendbuf ));
}
close ( sock );
}
return 0 ;
}
TCP粘包问题
TCP粘包原因及解决办法_tcp粘包产生原因-CSDN博客
多个数据包被连续存储于连续的缓存中,在对数据包进行读取时 由于无法确定发生方的发送边界 ,而采用某一估测值大小 来进行数据读出,若双方的size不一致 时就会使指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
解决办法:本质上是要在应用层维护消息与消息的边界
定长包
包尾加\r\n
(ftp)
包头加上包体长度
更复杂的应用层协议
readn函数封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct packet {
int len ; // 包头,包体实际长度
char buf [ 1024 ]; // 包体缓冲区
};
ssize_t readn ( int fd , void * buf , size_t count )
{
size_t nleft = count ; // 剩余的字节数
ssize_t nread ; // 已接收的字节数
char * bufp = ( char * ) buf ;
while ( nleft > 0 ){
if (( nread = read ( fd , bufp , nleft )) < 0 ){
if ( errno == EINTR ){
continue ;
}
return - 1 ;
}
else if ( nread == 0 ){
return count - nleft ;
}
bufp += nread ;
nleft -= nread ;
}
return count ;
}
writen函数封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ssize_t writen ( int fd , void * buf , size_t count )
{
size_t nleft = count ; // 剩余的要发送字节数
ssize_t nwritten ; // 已发送的字节数
char * bufp = ( char * ) buf ;
while ( nleft > 0 ){
if (( nwritten = write ( fd , bufp , nleft )) < 0 ){
if ( errno == EINTR ){
continue ;
}
return - 1 ;
}
else if ( nwritten == 0 ){
continue ;
}
bufp += nwritten ;
nleft -= nwritten ;
}
return count ;
}
readline:recv和send
recv只能用于socket IO;read都可以;
recv函数参数多了flags选项,可以指定接收行为。常用选项:MSG_OOB
和MSG_PEEK
MSG_OOB
:指定接收带外数据,通过紧急指针发送的数据
MSG_PEEK
:接收socket缓冲区数据,但不清除
TCP客户/服务模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
ssize_t recv_peek ( int sockfd , void * buf , size_t len ){
while ( 1 ){
int ret = recv ( sockfd , buf , len , MSG_PEEK ); // 偷看缓冲区数据实现readline
if ( ret == - 1 && errno == EINTR ){
continue ;
}
return ret ;
}
}
ssize_t readline ( int sockfd , void * buf , size_t maxline ){
int ret ;
int nread ;
char * bufp = buf ;
int nleft = maxline ;
while ( 1 ){
ret = recv_peek ( sockfd , bufp , nleft );
if ( ret < 0 ){
return ret ;
}
else if ( ret == 0 ){
return ret ;
}
nread = ret ;
for ( int i = 0 ; i < nread ; i ++ ){
if ( bufp [ i ] == '\n' ){
ret = readn ( sockfd , bufp , i + 1 ); // 包含回车
if ( ret != i + 1 ){
exit ( EXIT_FAILURE );
}
return ret ;
}
}
if ( nread > nleft ){
exit ( EXIT_FAILURE );
}
nleft -= nread ;
ret = readn ( sockfd , bufp , nread );
if ( ret != nread ){
exit ( EXIT_FAILURE );
}
bufp += nread ;
}
return - 1 ;
}
处理僵死进程
注:之前的程序在系统运行后,使用ps -ef | grep sigchld_echo_per_serve
看不到僵尸进程所以此处的处理方法无法验证
signal(SIGCHLD, SIG_IGN)
忽略
signal(SIGCHLD, handle_sigchld);
捕获
TCP状态
三次握手 和四次挥手 11种状态
启动服务器观察状态:netstat -an | grep tcp | grep 5188
Select
五种I/O模型
阻塞I/O
非阻塞I/O
I/O复用 (select
和 poll
)
select管理多个文件描述符
信号驱动I/O
异步I/O
select函数
功能:检测多个文件描述符中是否有可读、可写或异常事件
函数原型:int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数
nfds
:表示所有文件描述符的范围,即最大的文件描述符加1。(后面集合最大值加1)readfds
、writefds
、exceptfds
:分别是指向可读、可写和异常等事件的文件描述符集合的指针。timeout
:表示超时时间。返回值:失败:1;成功:超时没检测到为0,检测到的事件个数
1
2
3
4
void FD_CLR ( int fd , fd_set * set ); // 移除
int FD_ISSET ( int fd , fd_set * set ); // fd是否在集合中
void FD_SET ( int fd , fd_set * set ); // 添加集合
void FD_ZERO ( fd_set * set ); // 清空集合
可读事件发生条件
套接口缓冲区有数据可读 连接的读一半关闭,即接收到FIN
段,读操作将返回O 如果是监听套接口,已完成连接队列不为空时 套接口上发生了一个错误待处理,错误可以通过getsockopt
指定SO_ERROR
选项来获取。 可写事件发生条件
套接口发送缓冲区有空间容纳数据。 连接的写一半关闭。即收到RST
段之后,再次调用write
操作。 套接口上发生了一个错误待处理,错误可以通过getsockopt
指定SO_ERROR
选项来获取。 异常事件发生条件
套接口存在带外数据
select改进回射客户/服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void echo_client ( int sock ){
fd_set rset ;
FD_ZERO ( & rset );
int nready ;
int maxfd ;
int fd_stdin = fileno ( stdin );
maxfd = ( fd_stdin > sock ) ? fd_stdin : sock ;
char sendbuf [ 1024 ] = { 0 };
char recvbuf [ 1024 ] = { 0 };
while ( 1 ){
FD_SET ( fd_stdin , & rset );
FD_SET ( sock , & rset );
nready = select ( maxfd + 1 , & rset , NULL , NULL , NULL );
if ( nready == - 1 ){
ERR_EXIT ( "select fail" );
}
if ( nready == 0 ){
continue ;
}
if ( FD_ISSET ( sock , & rset )){
int ret = readline ( sock , recvbuf , sizeof ( recvbuf ));
if ( ret == - 1 ){
ERR_EXIT ( "readline fail" );
}
else if ( ret == 0 ){
printf ( "server close \n " );
break ;
}
fputs ( recvbuf , stdout );
memset ( recvbuf , 0 , sizeof ( recvbuf ));
}
if ( FD_ISSET ( fd_stdin , & rset )){
if ( fgets ( sendbuf , sizeof ( sendbuf ), stdin ) == NULL ){
break ;
}
writen ( sock , sendbuf , strlen ( sendbuf ));
memset ( sendbuf , 0 , sizeof ( sendbuf ));
}
}
close ( sock );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// 服务器端
int client [ FD_SETSIZE ];
int maxi = - 1 ; // 遍历整个FD_SETSIZE太费时间,记录最大得fd位置,遍历到那个位置即可
int i ;
for ( i = 0 ; i < FD_SETSIZE ; i ++ ){
client [ i ] = - 1 ;
}
int nready ;
int maxfd = listenfd ;
fd_set rset ;
fd_set allset ;
FD_ZERO ( & rset );
FD_ZERO ( & allset );
FD_SET ( listenfd , & allset );
while ( 1 ){
rset = allset ; // select会修改fd_set,所以每次需要重新赋值一份
nready = select ( maxfd + 1 , & rset , NULL , NULL , NULL );
if ( nready == - 1 ){
if ( errno == EINTR ){ // select被信号中断需要重新执行
continue ;
}
ERR_EXIT ( "select fail" );
}
if ( nready == 0 ){
continue ;
}
if ( FD_ISSET ( listenfd , & rset )){
peerlen = sizeof ( peeraddr );
conn = accept ( listenfd , ( struct sockaddr * ) & peeraddr , & peerlen );
if ( conn < 0 ){
ERR_EXIT ( "accept fail" );
}
for ( i = 0 ; i < FD_SETSIZE ; i ++ ){
if ( client [ i ] < 0 ){
client [ i ] = conn ;
if ( i > maxi ){
maxi = i ;
}
break ;
}
}
if ( i == FD_SETSIZE ){
fprintf ( stderr , "too many clients \n " );
exit ( EXIT_FAILURE );
}
printf ( "ip = %s port = %d \n " , inet_ntoa ( peeraddr . sin_addr ), ntohs ( peeraddr . sin_port ));
FD_SET ( conn , & allset );
if ( conn > maxfd ){
maxfd = conn ;
}
if ( -- nready <= 0 ){
continue ;
}
}
for ( i = 0 ; i <= maxi ; i ++ ){
conn = client [ i ];
if ( conn == - 1 ){
continue ;
}
if ( FD_ISSET ( conn , & rset )){
char recvbuf [ 1024 ];
int ret = readline ( conn , recvbuf , sizeof ( recvbuf ));
if ( ret == - 1 ){
ERR_EXIT ( "readline fail" );
}
if ( ret == 0 ){
struct sockaddr_in peer_addr ;
socklen_t peer_len = sizeof ( peer_addr );
getpeername ( conn , ( struct sockaddr * ) & peer_addr , & peer_len );
printf ( "client ip = %s port = %d close \n " , inet_ntoa ( peer_addr . sin_addr ), ntohs ( peer_addr . sin_port ));
FD_CLR ( conn , & allset );
client [ i ] = - 1 ;
if ( i == maxi ){ // 可能删除得i是当前得maxi,要优化到第二大的位置
for ( int j = maxi - 1 ; i >= 0 ; j -- ){
if ( client [ j ] != - 1 ){
maxi = j ;
break ;
}
}
}
}
fputs ( recvbuf , stdout );
writen ( conn , recvbuf , strlen ( recvbuf ));
memset ( & recvbuf , 0 , sizeof ( recvbuf ));
if ( -- nready <= 0 ){
break ;
}
}
}
}
close和shutdown
close
终止了数据传送的两个方向
shutdown
可以有选择的终止某个方向的数据传送或者终止数据传送的两个方向
int shutdown(int sockfd, int how);
shutdown how=1
可以保证对等方接收到一个E0F
字符,而不管其他进程是否已经打开了套接字。而close
不能保证,直到套接字引用计数减为0时才发送。即直到所有的进程都关闭了套接字。
I/O超时
alarm
套接字选项
SO_SNDTIMEO
SO_RCVTIMEO
select
1
2
3
4
5
6
7
8
9
10
11
12
13
// 闹钟冲突,一般不用
void handler ( int sig ){
return ;
}
signal ( SIGALARM , handler );
alarm ( 5 );
int ret = read ( fd , buf , sizeof ( buf ));
if ( ret == - 1 && errno == EINTR ){
errno = ETIMEDOUT ;
}
else if ( ret >= 0 ){
aralm ( 0 );
}
1
2
3
4
5
setsockopt ( sock , SOL_SOCKET , SO_RCVTIMEO , 5 );
int ret = read ( sock , buf , sizeof ( buf ));
if ( ret == - 1 && errno = EWOULDBLOCK ){
errno = ETIMEDOUT ;
}
read_timeout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* read_timeout - 读超时检测函数,不含读操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int read_timeout ( int fd , unsigned int wait_seconds )
{
int ret ;
if ( wait_seconds > 0 ){
fd_set read_fdset ;
struct timeval timeout ;
FD_ZERO ( & read_fdset );
FD_SET ( fd , & read_fdset );
timeout . tv_sec = wait_seconds ;
timeout . tv_usec = 0 ;
do {
ret = select ( fd + 1 , & read_fdset , NULL , NULL , & timeout );
} while ( ret < 0 && errno == EINTR );
if ( ret == 0 ){
ret = - 1 ;
errno = ETIMEDOUT ;
}
else if ( ret == - 1 ){
ret = 0 ;
}
}
return ret ;
}
write_timeout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* write_timeout - 写超时检测函数,不含写操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int write_timeout ( int fd , unsigned int wait_seconds )
{
int ret ;
if ( wait_seconds > 0 ){
fd_set write_fdset ;
struct timeval timeout ;
FD_ZERO ( & write_fdset );
FD_SET ( fd , & write_fdset );
timeout . tv_sec = wait_seconds ;
timeout . tv_usec = 0 ;
do {
ret = select ( fd + 1 , NULL , & write_fdset , NULL , & timeout ); // 为什么放在异常集合
} while ( ret < 0 && errno == EINTR );
if ( ret == 0 ){
ret = - 1 ;
errno = ETIMEDOUT ;
}
else if ( ret == - 1 ){
ret = 0 ;
}
}
return ret ;
}
accept_timeout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* accept_timeout - 带超时的accept
* @fd: 套接字
* @addr: 输出参数,返回对方地址
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT
*/
int accept_timeout ( int fd , struct sockaddr_in * addr , unsigned int wait_seconds )
{
int ret ;
socklen_t addrlen = sizeof ( struct sockaddr_in );
if ( wait_seconds > 0 ){
fd_set accept_fdset ;
struct timeval timeout ;
FD_ZERO ( & accept_fdset );
FD_SET ( fd , & accept_fdset );
timeout . tv_sec = wait_seconds ;
timeout . tv_usec = 0 ;
do {
ret = select ( fd + 1 , & accept_fdset , NULL , NULL , & timeout ); // 等待一个客户端连接到来,意味着该套接字必须处于可读状态
} while ( ret < 0 && errno == EINTR );
if ( ret == - 1 ){
return - 1 ;
}
else if ( ret == 0 ){
ret = - 1 ;
errno = ETIMEDOUT ;
}
}
if ( addr != NULL ){
ret = accept ( fd , ( struct sockaddr * ) addr , & addrlen );
}
else {
ret = accept ( fd , NULL , NULL );
}
if ( ret == - 1 ){
ERR_EXIT ( "accept fail" );
}
return ret ;
}
connect_timeout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
*activate_nonblock -设置I/O为非阻塞模式
*@fd: 文件描述符
*/
void activate_nonblock ( int fd )
{
int ret ;
int flags = fcntl ( fd , F_GETFL );
if ( flags == - 1 ){
ERR_EXIT ( "fcntl fail" );
}
flags |= O_NONBLOCK ;
ret = fcntl ( fd , F_SETFL , flags );
if ( ret == - 1 ){
ERR_EXIT ( "fcntl fail" );
}
}
/**
*deactivate_nonblock -设置I/O为阻塞模式
*@fd: 文件描述符
*/
void deactivate_nonblock ( int fd )
{
int ret ;
int flags = fcntl ( fd , F_GETFL );
if ( flags == - 1 ){
ERR_EXIT ( "fcntl fail" );
}
flags &= ~ O_NONBLOCK ;
ret = fcntl ( fd , F_SETFL , flags );
if ( ret == - 1 ){
ERR_EXIT ( "fcntl fail" );
}
}
/**
* connect_timeout - connect
* @fd: 套接字
* @addr: 要连接得对方的地址
* @wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int connect_timeout ( int fd , struct sockaddr_in * addr , unsigned int wait_seconds )
{
int ret ;
socklen_t addrlen = sizeof ( struct sockaddr_in );
printf ( "%u \n " , addrlen );
if ( wait_seconds > 0 ){
activate_nonblock ( fd );
}
ret = connect ( fd , ( struct sockaddr * ) addr , addrlen );
if ( ret < 0 && errno == EINPROGRESS ){
fd_set connect_fdset ;
struct timeval timeout ;
FD_ZERO ( & connect_fdset );
FD_SET ( fd , & connect_fdset );
timeout . tv_sec = wait_seconds ;
timeout . tv_usec = 0 ;
do {
ret = select ( fd + 1 , NULL , & connect_fdset , NULL , & timeout ); // 等待连接操作完成,意味着该套接字必须处于可写状态
} while ( ret < 0 && errno == EINTR );
if ( ret == 0 ){
ret = - 1 ;
errno = ETIMEDOUT ;
}
else if ( ret < 0 ){
ret = - 1 ;
}
else if ( ret == 1 ){
// ret返回1.一种情况是连接建立成功;一种是套接字产生错误
// 此时错误信息不会保存至errno变量中,因此需要getsockopt来获取。
int err ;
socklen_t socklen = sizeof ( err );
int sockoptret = getsockopt ( fd , SOL_SOCKET , SO_ERROR , & err , & socklen );
if ( sockoptret == - 1 ){
return - 1 ;
}
if ( err == 0 ){
ret = 0 ;
}
else {
errno = err ;
ret = - 1 ;
}
}
}
if ( wait_seconds > 0 ){
deactivate_nonblock ( fd );
}
return ret ;
}
poll
select
的限制
用select
实现的并发服务器,能达到的并发数,受两方面限制
一个进程能打开的最大文件描述符限制。这可以通过调整内核参数。 ulimit -n 1024
调整
只能修改当前进程以及子进程
select
中的fd_set
集合容量的限制(FD SETSIZE
),这需要重新编译内核。
1
2
3
int getrlimit ( int resource , struct rlimit * rlim );
int setrlimit ( int resource , const struct rlimit * rlim );
resource 设置 RLIMIT_NOFILE
select
和poll
共同点:内核要遍历所有文件描述符,直到找到发生事件的文件描述符
poll
一个进程能打开的最大文件描述符限制。系统所有打开的最大文件描述个数也是有限的,跟内存大小有关
poll函数
功能:检测多个文件描述符中是否有可读、可写或异常事件
函数原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数
fds
:指向一个struct pollfd
结构体数组的指针,每个结构体描述一个待检测的文件描述符及其关注的事件。nfds
:表示fds
数组中结构体的数量。timeout
:表示超时时间。返回值:成功:发生事件的文件描述符数,如果超时返回0,如果出错返回-1,并将errno
设置为相应的错误码
poll
函数支持的文件描述符数目更大(nfds
参数没有上限),并且不需要像select
那样使用位图处理多个文件描述符的状态。
不用维护maxfd
不用使用FD、ZERO
、FD_SET
、FD_CLR
、FD_ISSET
函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// serve
struct pollfd client [ CLIENT_SIZE ];
int maxi = 0 ; // 遍历整个FD_SETSIZE太费时间,记录最大得fd位置,遍历到那个位置即可
int i ;
for ( i = 0 ; i < CLIENT_SIZE ; i ++ ){
client [ i ]. fd = - 1 ;
}
int nready ;
client [ 0 ]. fd = listenfd ;
client [ 0 ]. events = POLLIN ; // 对监听套接口的可读事件感兴趣
while ( 1 ){
nready = poll ( client , maxi + 1 , - 1 );
if ( nready == - 1 ){
if ( errno == EINTR ){
continue ;
}
ERR_EXIT ( "poll fail" );
}
if ( nready == 0 ){
continue ;
}
if ( client [ 0 ]. revents & POLLIN ){ // 如果产生了可读事件
peerlen = sizeof ( peeraddr );
conn = accept ( listenfd , ( struct sockaddr * ) & peeraddr , & peerlen );
if ( conn < 0 ){
ERR_EXIT ( "accept fail" );
}
for ( i = 0 ; i < CLIENT_SIZE ; i ++ ){
if ( client [ i ]. fd < 0 ){
client [ i ]. fd = conn ;
if ( i > maxi ){
maxi = i ;
}
break ;
}
}
if ( i == CLIENT_SIZE ){
fprintf ( stderr , "too many clients \n " );
exit ( EXIT_FAILURE );
}
printf ( "ip = %s port = %d \n " , inet_ntoa ( peeraddr . sin_addr ), ntohs ( peeraddr . sin_port ));
client [ i ]. events = POLLIN ;
if ( -- nready <= 0 ){
continue ;
}
}
for ( i = 1 ; i <= maxi ; i ++ ){
conn = client [ i ]. fd ;
if ( conn == - 1 ){
continue ;
}
if ( client [ i ]. events & POLLIN ){
char recvbuf [ 1024 ];
int ret = readline ( conn , recvbuf , sizeof ( recvbuf ));
if ( ret == - 1 ){
ERR_EXIT ( "readline fail" );
}
if ( ret == 0 ){
struct sockaddr_in peer_addr ;
socklen_t peer_len = sizeof ( peer_addr );
getpeername ( conn , ( struct sockaddr * ) & peer_addr , & peer_len );
printf ( "client ip = %s port = %d close \n " , inet_ntoa ( peer_addr . sin_addr ), ntohs ( peer_addr . sin_port ));
client [ i ]. fd = - 1 ;
if ( i == maxi ){ // 可能删除得i是当前得maxi,要优化到第二大的位置
for ( int j = maxi - 1 ; i >= 0 ; j -- ){
if ( client [ j ]. fd != - 1 ){
maxi = j ;
break ;
}
}
}
}
fputs ( recvbuf , stdout );
writen ( conn , recvbuf , strlen ( recvbuf ));
memset ( & recvbuf , 0 , sizeof ( recvbuf ));
if ( -- nready <= 0 ){
break ;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// client
void echo_client ( int sock ){
struct pollfd client_fd [ 2 ];
int nready ;
int fd_stdin = fileno ( stdin );
char sendbuf [ 1024 ] = { 0 };
char recvbuf [ 1024 ] = { 0 };
while ( 1 ){
client_fd [ 0 ]. fd = fd_stdin ;
client_fd [ 0 ]. events = POLLIN ;
client_fd [ 1 ]. fd = sock ;
client_fd [ 1 ]. events = POLLIN ;
nready = poll ( client_fd , 2 , - 1 );
if ( nready == - 1 ){
ERR_EXIT ( "poll fail" );
}
if ( nready == 0 ){
continue ;
}
if ( client_fd [ 1 ]. revents & POLLIN ){
int ret = readline ( sock , recvbuf , sizeof ( recvbuf ));
if ( ret == - 1 ){
ERR_EXIT ( "readline fail" );
}
else if ( ret == 0 ){
printf ( "server close \n " );
break ;
}
fputs ( recvbuf , stdout );
memset ( recvbuf , 0 , sizeof ( recvbuf ));
}
if ( client_fd [ 0 ]. revents & POLLIN ){
if ( fgets ( sendbuf , sizeof ( sendbuf ), stdin ) == NULL ){
break ;
}
writen ( sock , sendbuf , strlen ( sendbuf ));
memset ( sendbuf , 0 , sizeof ( sendbuf ));
}
}
close ( sock );
}
epoll函数
epoll的优点
相比于select
与poll
,epoll
最大的好处在于它不会随着监听fd
数目的增长而降低效率。
内核中的select
与poll
的实现是采用轮询 来处理的,轮询的fd
数目越多,耗时越多。
epoll
的实现是基于回调 的,如果fd
有期望的事件发生就通过回调函数将其加入epoll
就绪队列中。(只关心“活跃”的fd
,与fd
数目无关)
内核把fd消息通知给用户空间呢?``select/
poll采取
内存拷贝方法。而
epoll采用
共享内存`的方式。
epoll
能直接定位事件,而不必遍历整个fd
集合。因为epoll
不仅会告诉应用程序有I/O
事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的。
1
2
3
4
int epoll_create ( int size ); // 创建epoll实例 哈希表
int epoll_create1 ( int flags ); // 红黑树
int epoll_ctl ( int epfd , int op , int fd , struct epoll_event * event ); // 将I/O 添加到epoll管理
int epoll_wait ( int epfd , struct epoll_event * events , int maxevents , int timeout ); // 等待事件
epoll模式
EPOLLLT
:电平
完全靠kernel epoll
驱动,应用程序只需要处理从epoll_wait
返回的fds
。(这些fds
认为处于就绪状态)
EPOLLET
:边沿
仅仅通知应用程序哪些fds
变成了就绪状态,一旦fd
变成就绪状态,epoll
将不再关注这个fd
的在何状态信息,(从epo队列移除)直到应用程序通过读写操作触发EAGAIN
状态epoll
认为这个fd
又变为空闲状态,那么epoll
又重新关注这个fd
的状态变化(重新加入epoll
队列)
随着epoll_wait
的返回,队列中的fds
是在减少的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// serve
typedef std :: vector < struct epoll_event > EventList ;
std :: vector < int > clients ;
int epoll_fd ;
epoll_fd = epoll_create1 ( EPOLL_CLOEXEC );
struct epoll_event event ;
event . data . fd = listenfd ;
event . events = EPOLLIN | EPOLLET ;
epoll_ctl ( epoll_fd , EPOLL_CTL_ADD , listenfd , & event );
EventList events ( 16 );
struct sockaddr_in peeraddr ;
socklen_t peerlen ;
int conn ;
int nready ;
while ( 1 ){
nready = epoll_wait ( epoll_fd , &* events . begin (), static_cast < int > ( events . size ()), - 1 ); // &*迭代器 --> 指针
if ( nready == - 1 ){
if ( errno == EINTR ){
continue ;
}
ERR_EXIT ( "epoll_wait fail" );
}
if ( nready == 0 ){
continue ;
}
if (( size_t ) nready == events . size ()){
events . resize ( events . size () * 2 );
}
for ( int i = 0 ; i < nready ; i ++ ){
if ( events [ i ]. data . fd == listenfd ){
peerlen = sizeof ( peeraddr );
conn = accept ( listenfd , ( struct sockaddr * ) & peeraddr , & peerlen );
if ( conn == - 1 ){
ERR_EXIT ( "accept fail" );
}
printf ( "ip = %s port = %d \n " , inet_ntoa ( peeraddr . sin_addr ), ntohs ( peeraddr . sin_port ));
clients . push_back ( conn );
activate_nonblock ( conn );
event . data . fd = conn ;
event . events = EPOLLIN | EPOLLET ;
epoll_ctl ( epoll_fd , EPOLL_CTL_ADD , conn , & event );
}
else if ( events [ i ]. events & EPOLLIN ){
conn = events [ i ]. data . fd ;
if ( conn < 0 ){
continue ;
}
char recvbuf [ 1024 ] = { 0 };
int ret = readline ( conn , recvbuf , sizeof ( recvbuf ));
if ( ret == - 1 ){
ERR_EXIT ( "readline fail" );
}
if ( ret == 0 ){
struct sockaddr_in peer_addr ;
socklen_t peer_len = sizeof ( peer_addr );
getpeername ( conn , ( struct sockaddr * ) & peer_addr , & peer_len );
printf ( "client ip = %s port = %d close \n " , inet_ntoa ( peer_addr . sin_addr ), ntohs ( peer_addr . sin_port ));
close ( conn );
event = events [ i ];
epoll_ctl ( epoll_fd , EPOLL_CTL_DEL , conn , & event );
clients . erase ( std :: remove ( clients . begin (), clients . end (), conn ), clients . end ());
}
fputs ( recvbuf , stdout );
writen ( conn , recvbuf , strlen ( recvbuf ));
memset ( & recvbuf , 0 , sizeof ( recvbuf ));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void echo_client ( int sock ){
int epoll_fd ;
epoll_fd = epoll_create1 ( EPOLL_CLOEXEC );
struct epoll_event event , event_list [ 2 ];
int nready ;
int fd_stdin = fileno ( stdin );
char sendbuf [ 1024 ] = { 0 };
char recvbuf [ 1024 ] = { 0 };
while ( 1 ){
event . data . fd = fd_stdin ;
event . events = EPOLLIN | EPOLLET ;
epoll_ctl ( epoll_fd , EPOLL_CTL_ADD , fd_stdin , & event );
event . data . fd = sock ;
event . events = EPOLLIN | EPOLLET ;
epoll_ctl ( epoll_fd , EPOLL_CTL_ADD , sock , & event );
nready = epoll_wait ( epoll_fd , event_list , 2 , - 1 );
if ( nready == - 1 ){
ERR_EXIT ( "epoll_wait fail" );
}
if ( nready == 0 ){
continue ;
}
for ( int i = 0 ; i < nready ; i ++ ){
if ( event_list [ i ]. data . fd == sock && event_list [ i ]. events & EPOLLIN ){
int ret = readline ( sock , recvbuf , sizeof ( recvbuf ));
if ( ret == - 1 ){
ERR_EXIT ( "readline fail" );
}
else if ( ret == 0 ){
printf ( "server close \n " );
break ;
}
fputs ( recvbuf , stdout );
memset ( recvbuf , 0 , sizeof ( recvbuf ));
}
else if ( event_list [ i ]. data . fd == fd_stdin && event_list [ i ]. events & EPOLLIN ){
if ( fgets ( sendbuf , sizeof ( sendbuf ), stdin ) == NULL ){
break ;
}
writen ( sock , sendbuf , strlen ( sendbuf ));
memset ( sendbuf , 0 , sizeof ( sendbuf ));
}
}
}
close ( sock );
}
UDP
UDP特点
无连接
基于消息的数据传输服务
不可靠
一般情况下UDP
更加高效
UDP客户/服务器模型
UDP客户/服务器模型
回射客户/服务器模型
回射客户/服务器模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void echo_service ( int sock ){
char recvbuf [ 1024 ] = { 0 };
struct sockaddr_in peeraddr ;
socklen_t peerlen ;
while ( 1 ){
peerlen = sizeof ( peeraddr );
memset ( recvbuf , 0 , sizeof ( recvbuf ));
int ret = recvfrom ( sock , recvbuf , sizeof ( recvbuf ), 0 , ( struct sockaddr * ) & peeraddr , & peerlen );
if ( ret == - 1 ){
if ( errno == EINTR ){
continue ;
}
ERR_EXIT ( "recvfrom fail" );
}
else if ( ret > 0 ){
fputs ( recvbuf , stdout );
sendto ( sock , recvbuf , ret , 0 , ( struct sockaddr * ) & peeraddr , peerlen );
memset ( recvbuf , 0 , sizeof ( recvbuf ));
}
}
close ( sock );
}
int main () {
int sock ;
sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); // 指定UDP
if ( sock < 0 ){
ERR_EXIT ( "socket fail" );
}
// init
struct sockaddr_in servaddr ;
memset ( & servaddr , 0 , sizeof ( servaddr ));
servaddr . sin_family = AF_INET ;
servaddr . sin_port = htons ( 5188 );
servaddr . sin_addr . s_addr = htonl ( INADDR_ANY );
if ( bind ( sock , ( struct sockaddr * ) & servaddr , sizeof ( servaddr )) < 0 ){
ERR_EXIT ( "bind fail" );
}
echo_service ( sock );
return 0 ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void echo_client ( int sock , struct sockaddr_in servaddr ){
char sendbuf [ 1024 ] = { 0 };
char recvbuf [ 1024 ] = { 0 };
while ( fgets ( sendbuf , sizeof ( sendbuf ), stdin ) != NULL )
{
sendto ( sock , sendbuf , strlen ( sendbuf ), 0 , ( struct sockaddr * ) & servaddr , sizeof ( servaddr ));
recvfrom ( sock , recvbuf , sizeof ( recvbuf ), 0 , NULL , NULL );
fputs ( recvbuf , stdout );
memset ( sendbuf , 0 , sizeof ( sendbuf ));
memset ( recvbuf , 0 , sizeof ( recvbuf ));
}
close ( sock );
}
int main () {
int sock ;
sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); // UDP
if ( sock < 0 ){
ERR_EXIT ( "socket fail" );
}
// init
struct sockaddr_in servaddr ;
memset ( & servaddr , 0 , sizeof ( servaddr ));
servaddr . sin_family = AF_INET ;
servaddr . sin_port = htons ( 5188 );
servaddr . sin_addr . s_addr = inet_addr ( "127.0.0.1" ); // 指定地址
echo_client ( sock , servaddr );
return 0 ;
}
UDP注意点
UDP报文可能会丢失、重复
UDP报文可能会乱序
UDP缺乏流量控制
UDP协议数据报文截断
recvfrom
返回0,不代表连接关闭,因为udp是无连接的。
ICMP异步错误
UDP connect
UDP外出接口的确定
UDP聊天室
UDP聊天室
UNIX域
UNIX域特点
在同一台主机的传输速度是TCP的两倍
可以在同一台主机上各进程之间传递描述符。
UNX域套接字与传统套接字的区别是用路径名 来表示协议族的描述。
UNIX域地址结构
man 7 UNIX
1
2
3
4
5
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family ; /* AF_UNIX */
char sun_path [ UNIX_PATH_MAX ]; /* pathname */
};
回射客户/服务器模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void echo_srver ( int conn ){
char recvbuf [ 1024 ];
while ( 1 ){
memset ( recvbuf , 0 , sizeof ( recvbuf ));
int ret = read ( conn , recvbuf , sizeof ( recvbuf ));
if ( ret == 0 ){
printf ( "client close \n " );
break ;
}
else if ( ret == - 1 ){
ERR_EXIT ( "read fail" );
}
fputs ( recvbuf , stdout );
write ( conn , recvbuf , ret );
memset ( recvbuf , 0 , sizeof ( recvbuf ));
}
}
int main () {
int listenfd ;
listenfd = socket ( PF_UNIX , SOCK_STREAM , 0 ); // UNIUX
if ( listenfd < 0 ){
ERR_EXIT ( "socket fail" );
}
// init
unlink ( "/tmp/test_socket" );
struct sockaddr_un servaddr ;
memset ( & servaddr , 0 , sizeof ( servaddr ));
servaddr . sun_family = AF_UNIX ;
strcpy ( servaddr . sun_path , "/tmp/test_socket" );
if ( bind ( listenfd , ( struct sockaddr * ) & servaddr , sizeof ( servaddr )) < 0 ){
ERR_EXIT ( "bind fail" );
}
if ( listen ( listenfd , SOMAXCONN ) < 0 ){
ERR_EXIT ( "listen fail" );
}
int conn ;
pid_t pid ;
while ( 1 ){
conn = accept ( listenfd , NULL , NULL );
if ( conn == - 1 ){
ERR_EXIT ( "accept fail" );
}
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork fail" );
}
if ( pid == 0 ){
close ( listenfd );
echo_srver ( conn );
exit ( EXIT_SUCCESS );
}
else {
close ( conn );
}
}
return 0 ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void echo_client ( int sock ){
char sendbuf [ 1024 ] = { 0 };
char recvbuf [ 1024 ] = { 0 };
while ( fgets ( sendbuf , sizeof ( sendbuf ), stdin ) != NULL )
{
write ( sock , sendbuf , strlen ( sendbuf ));
read ( sock , recvbuf , sizeof ( recvbuf ));
fputs ( recvbuf , stdout );
memset ( sendbuf , 0 , sizeof ( sendbuf ));
memset ( recvbuf , 0 , sizeof ( recvbuf ));
}
close ( sock );
}
int main () {
int sock ;
sock = socket ( PF_UNIX , SOCK_STREAM , 0 );
if ( sock < 0 ){
ERR_EXIT ( "socket fail" );
}
// init
struct sockaddr_un servaddr ;
memset ( & servaddr , 0 , sizeof ( servaddr ));
servaddr . sun_family = AF_UNIX ;
strcpy ( servaddr . sun_path , "/tmp/test_socket" );
int ret ;
ret = connect ( sock , ( struct sockaddr * ) & servaddr , sizeof ( servaddr ));
if ( ret < 0 ){
ERR_EXIT ( "connect fail" );
}
echo_client ( sock );
return 0 ;
}
UNIX注意点
bind
成功将会创建一个文件,权限为0777&~umask
sun path
最好用一个绝对路径:一般放在/tmp/
路径下
UNIX域协议支持流式套接口(粘包问题)与报式套接口
UNIX域流式套接字connect
发现监听队列满时,会立刻返回一个ECONNREFUSED
。
socketpair
功能:创建一个全双工的流管道
原型:int socketpair(int domain, int type, int protocol, int sv[2]);
参数:domain
:协议家族;type
:套接字类型;protocol
:协议类型;sv
:返回套接字对
返回值:成功:0;失败:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int main (){
int sockfds [ 2 ];
if ( socketpair ( PF_UNIX , SOCK_STREAM , 0 , sockfds ) < 0 ){
ERR_EXIT ( "socketpair" );
}
pid_t pid ;
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork fail" );
}
if ( pid > 0 ){ // 父进程
int val = 0 ;
close ( sockfds [ 1 ]);
while ( 1 ){
++ val ;
printf ( "parent process sending data : %d \n " , val );
write ( sockfds [ 0 ], & val , sizeof ( val )); // 本机通信,不转网络字节序
read ( sockfds [ 0 ], & val , sizeof ( val ));
printf ( "parent process received data : %d \n " , val );
sleep ( 1 );
}
}
else if ( pid == 0 ){
int val = 0 ;
close ( sockfds [ 0 ]);
while ( 1 ){
read ( sockfds [ 1 ], & val , sizeof ( val ));
//printf("subprocess received data : %d\n", val);
++ val ;
write ( sockfds [ 1 ], & val , sizeof ( val ));
//printf("subprocess sending data : %d\n", val);
}
}
return 0 ;
}
sendmsg和recvmsg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct iovec { /* Scatter/gather array items */
void * iov_base ; /* Starting address */
size_t iov_len ; /* Number of bytes to transfer */
};
struct msghdr {
void * msg_name ; /* optional address */
socklen_t msg_namelen ; /* size of address */
struct iovec * msg_iov ; /* scatter/gather array */
size_t msg_iovlen ; /* # elements in msg_iov */
void * msg_control ; /* ancillary data, see below */
size_t msg_controllen ; /* ancillary data buffer len */
int msg_flags ; /* flags on received message */
};
// msg_control
struct cmsghdr {
size_t cmsg_len ; /* Data byte count, including header
(type is socklen_t in POSIX) */
int cmsg_level ; /* Originating protocol */
int cmsg_type ; /* Protocol-specific type */
/* followed by
unsigned char cmsg_data[]; */
};
sendmsg
功能:通过socket发送消息的系统调用
原型:ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
参数:sockfd
:socket文件描述符;mag
:需要发送的消息内容和相关元数据信息;flags
:标志位参数,用于控制消息发送的行为
返回值:成功:发送的字节数;失败:-1
recvmsg
功能:通过socket接收消息的系统调
原型:ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
参数:sockfd
:socket文件描述符;mag
:需要接收的消息内容和相关元数据信息;flags
:标志位参数,用于控制消息接收的行为
返回值:成功:接收的字节数;失败:-1
进程间通信
顺序和并发
顺序程序:顺序性、封闭性(运行环境)、确定性、可再现性
并发程序:共享性、并发性、随机性
互斥和同步 :信号量实现
进程互斥:矛盾
进程同步:协作
进程间通信目的
数据传输
资源共享
通知事件
进程控制
进程间通信分类
文件、文件锁、管道pipe和命名管道FIFO、信号 signal、消息队列、共享内存、信号量、互斥量、条件变量、读写锁、套接字socket
死锁
死锁产生的必要条件
互斥条件 :进程对资源进行排他性使用,即在一段时间内某资源仅为一个进程所占用。请求和保持条件 :当进程因请求资源而阻塞时,对已获得的资源保持不放。不可剥夺条件 :进程已获得的资源在未使用之前不能被剥夺,只能在使用完时由自己释放。环路等待条件 :各个进程组成封闭的环形链,每个进程都等待下一个进程所占用的资源防止死锁办法
资源一次性分配:破坏请求和保持条件
可剥夺资源:破坏不可剥夺条件
资源有序分配:破坏环路等待条件
死锁避免
银行家算法
信号量
互斥:P、V
在同一个进程中
同步:P、V
在不同进程中
信号量值S
S > 0
:S表示可用资源个数
S = 0
:表示无可用资源,无等待进程
S < 0
:|S|表示等待队列中进程个数
less /usr/include/sys/sem.h
查看semaphore
1
2
3
4
struct semaphore {
int value ;
pointer_PCB queue ;
}
PV原语
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
P ( s ){
s . value = s . value -- ;
if ( s . value < 0 ){
该进程状态置为等待状态,
该进程的 PCB插入相应的等待队列s . queue末尾
}
}
V ( s ){
s . value = s . value ++ ;
if ( s . value <= 0 ){
唤醒相应等待队列 s . queue中等待的一个进程 ,
改变其状态为就绪态,
并将其插入就绪队列
}
}
System V
System V
消息队列
消息队列
每个消息的最大长度有上限(MSGMAX
),每个消息队列的总字节数是有上限的(MSGMNB
),系统上消息队列的总数也有上限(MSGMNI
)。
cat /proc/sys/kernel/msgmax
消息队列数据结构
ipc_perm : IPC对象数据结构
man 2 msgctl
查看
1
2
3
4
5
6
7
8
9
struct ipc_perm {
key_t __key ; /* Key supplied to msgget(2) */
uid_t uid ; /* Effective UID of owner */
gid_t gid ; /* Effective GID of owner */
uid_t cuid ; /* Effective UID of creator */
gid_t cgid ; /* Effective GID of creator */
unsigned short mode ; /* Permissions */
unsigned short __seq ; /* Sequence number */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct msqid_ds {
struct ipc_perm msg_perm ; /* Ownership and permissions */
time_t msg_stime ; /* Time of last msgsnd(2) */
time_t msg_rtime ; /* Time of last msgrcv(2) */
time_t msg_ctime ; /* Time of last change */
unsigned long __msg_cbytes ; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum ; /* Current number of messages
in queue */
msglen_t msg_qbytes ; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid ; /* PID of last msgsnd(2) */
pid_t msg_lrpid ; /* PID of last msgrcv(2) */
};
消息队列函数
ipcrm -q msqid
:删除消息队列
ipcs
查看
msgget
功能:用来创建和访问一个消息队列
函数原型:int msgget(key_t key, int msgflg);
参数:key:某个消息队列的名字;msgflg:由9个权限标志构成,和mode一样
返回值:成功:消息队列的标识码;失败:-1.
1
2
3
4
5
6
7
8
9
10
11
12
int main ( int argc , char * args []){
int msgid ;
msgid = msgget ( 1234 , 0666 | IPC_CREAT );
// msgid = msgget(1234, 0666 | IPC_CREAT | IPC_EXCL);
// msgid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT | IPC_EXCL);
// msgid = msgget(IPC_PRIVATE, 0666);
// msgid = msgget(IPC_PRIVATE, 0);
if ( msgid == - 1 ){
ERR_EXIT ( "msg_get error" );
}
printf ( "msgget success \n " );
}
msgctl
功能:消息队列的控制函数
函数原型:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:msqid:由msgget函数返回的消息队列标识码;cmd:采取的动作(IPC_STAT、IPC_SET、IPC_RMID
);buf:动作所需要传递的参数
返回值:成功:0;失败:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int main ( int argc , char * args []){
int msgid ;
msgid = msgget ( 1234 , 0666 | IPC_CREAT );
if ( msgid == - 1 ){
ERR_EXIT ( "msg_get error" );
}
printf ( "msgget success \n " );
printf ( "msgid = %d \n " , msgid );
// msgctl(msgid, IPC_RMID, NULL); // 删除消息队列
/*
struct msqid_ds buf;
msgctl(msgid, IPC_STAT, &buf); // 获取消息队列状态
printf("mode = %o, bytes = %ld, number = %d, msgmnb = %d\n",
buf.msg_perm.mode, buf.__msg_cbytes, (int)buf.msg_qnum, (int)buf.msg_qbytes);
*/
struct msqid_ds buf ;
msgctl ( msgid , IPC_STAT , & buf );
printf ( "original msg_perm.mode: %o \n " , buf . msg_perm . mode );
sscanf ( "600" , "%ho" , & buf . msg_perm . mode );
msgctl ( msgid , IPC_SET , & buf ); // 修改消息队列状态
printf ( "new msg_perm.mode: %o \n " , buf . msg_perm . mode );
return 0 ;
}
msgsnd
功能:把一条消息添加到消息队列中
函数原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:
msqid:由msgget函数返回的消息队列标识码;
msgp:指针,指针指向准备发送的信息;
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的long int长整型;
msgflg:控制当前消息队列满或系统上限时将要发生的事
IPC_NOWAIT
表示队列满不等待,返回EAGAIN
错误。
返回值:成功:0;失败:-1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main ( int argc , char * argv []){
if ( argc != 3 ){
fprintf ( stderr , "Usage: %s <bytes> <type> \n " , argv [ 0 ]);
}
int len = atoi ( argv [ 1 ]);
int type = atoi ( argv [ 2 ]);
int msgid = msgget ( 1234 , 0 );
if ( msgid == - 1 ){
ERR_EXIT ( "msgget error" );
}
struct msgbuf * ptr ;
ptr = ( struct msgbuf * ) malloc ( sizeof ( long ) + len );
ptr -> mtype = type ;
if ( msgsnd ( msgid , ptr , len , 0 ) < 0 ){
ERR_EXIT ( "msgsnd error" );
}
return 0 ;
}
msgrcv
功能:从一个消息队列接收消息
函数原型:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数:
msqid:由msgget函数返回的消息队列标识码;
msgp:指针,指针指向准备接收的信息;
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的long int长整型;
msgtype:实现接收优先级的简单形式
msgtype=0
:返回队列第一条信息
msgtype>0
:返回队列第一条类型等于msgtype的消息
msgtype< 0
:返回队列第一条类型小于等于msgtype绝对值的消息
msgtype>0
且msgflg=MSC_EXCEPT,
接收类型不等于msgtype的第一条消息。
msgflg:控制当队列中没有相应类型的消息可供接收时要发生的事
msgflg=IPC_NOWAIT,
队列没有可读消息不等待,返回ENOMSG
错误
msgflg=MSG_NOERROR,
消息大小超过msgszl时被截断
返回值:成功:接收缓冲区的字符个数;失败:-1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct msgbuf {
long mtype ; /* message type, must be > 0 */
char mtext [ 1 ]; /* message data */
};
#define MSGMAX 8192
int main ( int argc , char * argv []){
int flag = 0 ;
int type = 0 ;
int opt ;
while ( 1 ){
opt = getopt ( argc , argv , "nt:" );
if ( opt == '?' ){
exit ( EXIT_FAILURE );
}
if ( opt == - 1 ){
break ;
}
switch ( opt ){
case 'n' :
flag |= IPC_NOWAIT ;
break ;
case 't' :
type = atoi ( optarg );
}
}
int msgid = msgget ( 1234 , 0 );
if ( msgid == - 1 ){
ERR_EXIT ( "msgget error" );
}
struct msgbuf * ptr ;
ptr = ( struct msgbuf * ) malloc ( sizeof ( long ) + MSGMAX );
ptr -> mtype = type ;
int n = 0 ;
if (( n = msgrcv ( msgid , ptr , MSGMAX , type , flag )) < 0 ){
ERR_EXIT ( "msgsnd error" );
}
printf ( "read %d bytes type = %ld \n " , n , ptr -> mtype );
return 0 ;
}
实现回射客户/服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#define MSGMAX 8192
struct msgbuf {
long mtype ; /* message type, must be > 0 */
char mtext [ MSGMAX ]; /* message data */
};
void echo_cli ( int msgid ){
int pid ;
int n ;
pid = getpid ();
struct msgbuf msg ;
memset ( & msg , 0 , sizeof ( msg ));
* (( int * ) msg . mtext ) = pid ;
msg . mtype = 1 ;
while ( fgets ( msg . mtext + 4 , MSGMAX , stdin ) != NULL ){
if ( msgsnd ( msgid , & msg , 4 + strlen ( msg . mtext + 4 ), 0 ) < 0 ){
ERR_EXIT ( "msgsnd error" );
}
memset ( msg . mtext + 4 , 0 , MSGMAX - 4 );
if (( n = msgrcv ( msgid , & msg , MSGMAX , pid , 0 )) < 0 ){
ERR_EXIT ( "msgrcv error" );
}
fputs ( msg . mtext + 4 , stdout );
memset ( msg . mtext + 4 , 0 , MSGMAX - 4 );
}
}
int main ( int argc , char * argv []){
int msgid = msgget ( 1234 , 0 );
if ( msgid == - 1 ){
ERR_EXIT ( "msgget error" );
}
echo_cli ( msgid );
return 0 ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define MSGMAX 8192
struct msgbuf {
long mtype ; /* message type, must be > 0 */
char mtext [ MSGMAX ]; /* message data */
};
void echo_srv ( int msgid ){
int n ;
struct msgbuf msg ;
memset ( & msg , 0 , sizeof ( msg ));
while ( 1 ){
if (( n = msgrcv ( msgid , & msg , MSGMAX , 1 , 0 )) < 0 ){
ERR_EXIT ( "msgrcv error" );
}
int pid ;
pid = * (( int * ) msg . mtext );
fputs ( msg . mtext + 4 , stdout );
msg . mtype = pid ;
msgsnd ( msgid , & msg , n , 0 );
}
}
int main ( int argc , char * argv []){
int msgid = msgget ( 1234 , IPC_CREAT | 0666 );
if ( msgid == - 1 ){
ERR_EXIT ( "msgget error" );
}
echo_srv ( msgid );
return 0 ;
}
共享内存
共享内存
共享内存
映射函数
映射函数
mmap
功能:将文件或者设备空间映射到共享内存区。
原型:void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数
addr
:要映射的起始地址,通常指定为NULL,让内核自动选择
length
:映射到进程地址空间的字节数
prot
:映射区保护方式
flags
:标志
fd
:文件描述符
offset
:从文件头开始的偏移量
返回值:成功:映射到的内存区的起始地址;失败:-1
munmap
功能:取消mmap函数建立的映射
原型:int munmap(void *addr, size_t length);
参数:addr
:映射的内存起始地址;length
:映射到进程地址空间的字节数
返回值:成功:0,失败:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
typedef struct stu {
char name [ 4 ];
int age ;
} STU ;
int main ( int argc , char * argv []){
if ( argc != 2 ){
fprintf ( stderr , "Usage: %s <file> \n " , argv [ 0 ]);
}
int fd ;
fd = open ( argv [ 1 ], O_CREAT | O_RDWR | O_TRUNC , 0666 );
if ( fd == - 1 ){
ERR_EXIT ( "open" );
}
lseek ( fd , sizeof ( STU ) * 5 - 1 , SEEK_SET );
write ( fd , "" , 1 );
STU * p ;
p = ( STU * ) mmap ( NULL , sizeof ( STU ) * 5 , PROT_READ | PROT_WRITE , MAP_SHARED , fd , 0 );
if ( p == NULL ){
ERR_EXIT ( "mmap" );
}
char ch = 'a' ;
int i ;
for ( i = 0 ; i < 5 ; i ++ ){
memcpy (( p + i ) -> name , & ch , 1 );
( p + i ) -> age = 20 + i ;
ch ++ ;
}
printf ( "initialize over \n " );
munmap ( p , sizeof ( STU ) * 5 );
printf ( "exit ... \n " );
}
int main ( int argc , char * argv []){
if ( argc != 2 ){
fprintf ( stderr , "Usage: %s <file> \n " , argv [ 0 ]);
}
int fd ;
fd = open ( argv [ 1 ], O_RDWR );
if ( fd == - 1 ){
ERR_EXIT ( "open" );
}
STU * p ;
p = ( STU * ) mmap ( NULL , sizeof ( STU ) * 5 , PROT_READ | PROT_WRITE , MAP_SHARED , fd , 0 );
if ( p == NULL ){
ERR_EXIT ( "mmap" );
}
int i ;
for ( i = 0 ; i < 5 ; i ++ ){
printf ( "name = %s, age = %d \n " , ( p + i ) -> name , ( p + i ) -> age );
}
munmap ( p , sizeof ( STU ) * 5 );
printf ( "exit ... \n " );
}
msync
功能:对映射的共享内存执行同步操作
原型:int msync(void *addr, size_t length, int flags);
参数:addr
:内存起始地址;length
:长度;flags
:选项
返回值:成功:0,失败:-1
共享内存数据结构
ipc_perm : IPC对象数据结构
man 2 shmctl
查看
1
2
3
4
5
6
7
8
9
10
11
struct shmid_ds {
struct ipc_perm shm_perm ; /* Ownership and permissions */
size_t shm_segsz ; /* Size of segment (bytes) */
time_t shm_atime ; /* Last attach time */
time_t shm_dtime ; /* Last detach time */
time_t shm_ctime ; /* Last change time */
pid_t shm_cpid ; /* PID of creator */
pid_t shm_lpid ; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch ; /* No. of current attaches */
...
};
共享内存函数
shmget
功能:用来创建共享内存
原型:int shmget(key_t key, size_t size, int shmflg);
参数:key:这个共享内存段名字;size:共享内存大小;shmflg:由九个权限标志构成,和mode模式标志是一样的
返回值:成功:个非负整数,即该共享内存段的标识码,失败:-1
shmat
功能:将共享内存段连接到进程地址空间
原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:shmid
:共享内存标识;shmaddr
:指定连接的地址;shmflg
:它的两个可能取值是SHM_RND
和SHM_RDONLY
shmaddr
为NULL
,核心自动选择一个地址
shmaddr
不为NULL
且shmflg
无SHM_RND
标记,则以shmaddr
为连接地址。
shmaddr
不为NULL
且shmflg
设置了SHM_RND
标记,则连接的地址会自动向下调整为SHMLBA
的整数倍。公式:shmaddr-(shmaddr%SHMLBA)
shmflg=SHM_RDONLY
,表示连接操作用来只读共享内存
返回值:成功:一个指针,指向共享内存第一个字节,失败:-1
shmdt
功能:将共享内存段与当前进程脱离(不等于删除共享内存段)
原型:int shmdt(const void *shmaddr);
参数:shmaddr:由shmat返回的指针
返回值:成功:0,失败:-1
shmctl
功能:用来创建和访问一个共享内存
原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:shmid
:由shmget
返回的共享内存标识码;cmd
:将要采取的动作(有三个可取值);buf
:指向一个保存着共享内存的模式状态和访问权限的数据结构
IPC_STAT
:把shmid_ds
结构中的数据设置为共享内存的当前关联值
IPC_SET
:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds
数据结构中给出的值
IPC_RMID
:删除共享内存段
返回值:成功:0,失败:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// write
typedef struct stu {
char name [ 32 ];
int age ;
} STU ;
int main ( int argc , char * argv []){
int shmid ;
shmid = shmget ( 1234 , sizeof ( STU ), IPC_CREAT | 0666 );
if ( shmid == - 1 ){
ERR_EXIT ( "shmget" );
}
STU * p ;
p = shmat ( shmid , NULL , 0 );
if ( p == ( void * ) - 1 ){
ERR_EXIT ( "shmat" );
}
strcpy ( p -> name , "zhangsan" );
p -> age = 20 ;
// sleep(10);
while ( 1 ){ // 读完,指针前4字节置为quit;比较的是内存
if ( memcmp ( p , "quit" , 4 ) == 0 ){
break ;
}
}
shmdt ( p );
shmctl ( shmid , IPC_RMID , NULL ); // 删除共享内存段
return 0 ;
}
// read
int main ( int argc , char * argv []){
int shmid ;
shmid = shmget ( 1234 , 0 , 0 );
if ( shmid == - 1 ){
ERR_EXIT ( "shmget" );
}
STU * p ;
p = shmat ( shmid , NULL , 0 );
if ( p == ( void * ) - 1 ){
ERR_EXIT ( "shmat" );
}
printf ( "name = %s, age = %d \n " , p -> name , p -> age );
memcpy ( p , "quit" , 4 ); // 读完,指针前4字节置为quit
shmdt ( p );
return 0 ;
}
信号量
信号量集数据结构
ipc_perm : IPC对象数据结构
man 2 semctl
查看
1
2
3
4
5
6
struct semid_ds {
struct ipc_perm sem_perm ; /* Ownership and permissions */
time_t sem_otime ; /* Last semop time */
time_t sem_ctime ; /* Last change time */
unsigned long sem_nsems ; /* No. of semaphores in set */
};
信号量函数
ipcrm -s semid
或ipcrm -S key
删除信号量集
semget
功能:用于创建和访问一个消息队列
原型:int semget(key_t key, int nsems, int semflg);
参数:key:信号集的名字;nsems:信号集中信号量的个数;semflg:九个权限标志构成,和mode一样
返回值:成功:信号集的标识码(非负整数);失败:-1
semctl
功能:用于控制信号量集
原型:int semctl(int semid, int semnum, int cmd, ...);
参数:semid:由semget返回的信号集标识码;semnum:信号集中信号量的序号;
cmd:将要采取的动作
SETVAL
:设置信号量集中的信号量的计数值
GETVA
L:获取信号量集中的信号量的计数值
IPC_STAT
:把shmid_ds
结构中的数据设置为共享内存的当前关联值
IPC_SET
:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds
数据结构中给出的值
IPC_RMID
:删除共享内存段
返回值:成功:0;失败:-1
semop
功能:用来创建和访问一个信号量集
原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
参数:semid
:是该信号量的标识码,也就是semget
函数的返回值;sops
:是个指向一个结构数值的指针;nsops:
信号量的个数
1
2
3
4
5
struct sembuf {
unsigned short sem_num ; /* 信号量编号 */
short sem_op ; /* P(-1); V(+1) */
short sem_flg ; /* IPC_NOWAIT(不阻塞)或SEM_UNDO(撤销)*/
}
返回值:成功:0;失败:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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) */
};
int sem_create ( key_t key ){
int semid ;
semid = semget ( key , 1 , IPC_CREAT | IPC_EXCL | 0666 );
if ( semid == - 1 ){
ERR_EXIT ( "semget" );
}
return semid ;
}
int sem_open ( key_t key ){
int semid ;
semid = semget ( key , 0 , 0 );
if ( semid == - 1 ){
ERR_EXIT ( "semget" );
}
return semid ;
}
int sem_setval ( int semid , int val ){
union semun su ;
su . val = val ;
int ret ;
ret = semctl ( semid , 0 , SETVAL , su );
if ( ret == - 1 ){
ERR_EXIT ( "sem_setval" );
}
printf ( "value updated ... \n " );
return 0 ;
}
int sem_getval ( int semid ){
int ret ;
ret = semctl ( semid , 0 , GETVAL , 0 );
if ( ret == - 1 ){
ERR_EXIT ( "sem_getval" );
}
printf ( "current val is %d \n " , ret );
return ret ;
}
int sem_d ( int semid ){
int ret ;
ret = semctl ( semid , 0 , IPC_RMID , 0 );
if ( ret == - 1 ){
ERR_EXIT ( "semctl" );
}
return 0 ;
}
int sem_p ( int semid ){
struct sembuf sb = { 0 , - 1 , 0 };
int ret ;
ret = semop ( semid , & sb , 1 );
if ( ret == - 1 ){
ERR_EXIT ( "semop" );
}
return 0 ;
}
int sem_v ( int semid ){
struct sembuf sb = { 0 , 1 , 0 };
int ret ;
ret = semop ( semid , & sb , 1 );
if ( ret == - 1 ){
ERR_EXIT ( "semop" );
}
return 0 ;
}
int sem_getmode ( int semid ){
union semun su ;
struct semid_ds sem ;
su . buf = & sem ;
int ret = semctl ( semid , 0 , IPC_STAT , su );
if ( ret == - 1 ){
ERR_EXIT ( "semvtl" );
}
printf ( "current permissions is %o \n " , su . buf -> sem_perm . mode );
return ret ;
}
int sem_setmode ( int semid , char * mode ){
union semun su ;
struct semid_ds sem ;
su . buf = & sem ;
int ret = semctl ( semid , 0 , IPC_STAT , su );
if ( ret == - 1 ){
ERR_EXIT ( "semvtl" );
}
printf ( "current permissions is %o \n " , su . buf -> sem_perm . mode );
sscanf ( mode , "%o" , ( unsigned int * ) & su . buf -> sem_perm . mode );
ret = semctl ( semid , 0 , IPC_SET , su );
if ( ret == - 1 ){
ERR_EXIT ( "semvtl" );
}
printf ( "permissions updated ... \n " );
return ret ;
}
void usage ( void ){
fprintf ( stderr , "usage: \n " );
fprintf ( stderr , "semtool -c \n " );
fprintf ( stderr , "semtool -d \n " );
fprintf ( stderr , "semtool -p \n " );
fprintf ( stderr , "semtool -v \n " );
fprintf ( stderr , "semtool -s <val> \n " );
fprintf ( stderr , "semtool -g \n " );
fprintf ( stderr , "semtool -f \n " );
fprintf ( stderr , "semtool -m <mode> \n " );
}
int main ( int argc , char * argv []){
int opt ;
opt = getopt ( argc , argv , "cpvds:gfm:" );
if ( opt == '?' ){
exit ( EXIT_FAILURE );
}
if ( opt == - 1 ){
usage ();
exit ( EXIT_FAILURE );
}
key_t key = ftok ( "." , 's' ); // (路径+字符产生一个key)
int semid ;
switch ( opt ){
case 'c' :
sem_create ( key );
break ;
case 'p' :
semid = sem_open ( key );
sem_p ( semid );
sem_getval ( semid );
break ;
case 'v' :
semid = sem_open ( key );
sem_v ( semid );
sem_getval ( semid );
break ;
case 'd' :
semid = sem_open ( key );
sem_d ( semid );
break ;
case 's' :
semid = sem_open ( key );
sem_setval ( semid , atoi ( optarg ));
break ;
case 'g' :
semid = sem_open ( key );
sem_getval ( semid );
break ;
case 'f' :
semid = sem_open ( key );
sem_getmode ( semid );
break ;
case 'm' :
semid = sem_open ( key );
sem_setmode ( semid , argv [ 2 ]);
break ;
}
return 0 ;
}
进程互斥示例
父进程打印O
,子进程打印X
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int semid ;
void print ( char op_char ){
int pause_time ;
srand ( getpid ());
int i ;
for ( i = 0 ; i < 10 ; i ++ ){
sem_p ( semid );
printf ( "%c" , op_char );
fflush ( stdout );
pause_time = rand () % 3 ;
sleep ( pause_time );
printf ( "%c" , op_char );
fflush ( stdout );
sem_v ( semid );
pause_time = rand () % 2 ;
sleep ( pause_time );
}
}
int main ( int argc , char * argv []){
semid = sem_create ( IPC_PRIVATE );
sem_setval ( semid , 0 ); // 初始值为0
pid_t pid ;
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork" );
}
if ( pid > 0 ){
sem_setval ( semid , 1 );
print ( 'O' );
wait ( NULL );
sem_d ( semid );
}
else {
print ( 'X' );
}
return 0 ;
}
哲学家就餐问题模拟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#define DELAY (rand() % 5 + 1)
int semid ;
// 获取刀叉
void wait_for_2fork ( int no ){
int left = no ;
int right = ( no + 1 ) % 5 ;
struct sembuf buf [ 2 ] = {
{ left , - 1 , 0 },
{ right , - 1 , 0 }
};
semop ( semid , buf , 2 );
}
// 放下刀叉
void free_2fork ( int no ){
int left = no ;
int right = ( no + 1 ) % 5 ;
struct sembuf buf [ 2 ] = {
{ left , 1 , 0 },
{ right , 1 , 0 }
};
semop ( semid , buf , 2 );
}
void philosophere ( int no ){
srand ( getpid ());
for (;;){
printf ( "%d is thinking \n " , no );
sleep ( DELAY );
printf ( "%d is hungry \n " , no );
wait_for_2fork ( no );
printf ( "%d is eating \n " , no );
sleep ( DELAY );
free_2fork ( no );
}
}
int main ( int argc , char * argv []){
semid = semget ( IPC_PRIVATE , 5 , IPC_CREAT | 0666 );
if ( semid == - 1 ){
ERR_EXIT ( "semget" );
}
union semun su ;
su . val = 1 ; // 设置初始值为1
int i ;
for ( i = 0 ; i < 5 ; i ++ ){
semctl ( semid , i , SETVAL , su );
}
int no = 0 ;
pid_t pid ;
for ( i = 1 ; i < 5 ; i ++ ){
pid = fork ();
if ( pid == - 1 ){
ERR_EXIT ( "fork fail" );
}
if ( pid == 0 ){
no = i ;
break ;
}
}
philosophere ( no );
return 0 ;
}
共享内存和信号量综合
实现shmfifo
生产者和消费者问题
生产者和消费者模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct shmfifo shmfifo_t ;
typedef struct shmhead shmhead_t ;
struct shmhead {
unsigned int blksize ; // 块大小
unsigned int blocks ; // 总块数
unsigned int rd_index ; // 读索引
unsigned int wr_index ; // 写索引
};
struct shmfifo {
shmhead_t * p_shm ; // 共享内存头部指针
char * p_payload ; // 有效负载的起始地址
int shmid ; // 共享内存id
int sem_mutex ; // 用来互斥用的信号量
int sem_full ; // 用来控制共享内存是否满的信号量
int sem_empty ; // 用来控制共享内存是否空的信号量
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
shmfifo_t * shmfifo_init ( int key , int blksize , int blocks )
{
shmfifo_t * fifo = ( shmfifo_t * ) malloc ( sizeof ( shmfifo_t ));
assert ( fifo != NULL );
memset ( fifo , 0 , sizeof ( shmfifo_t ));
int shmid ;
shmid = shmget ( key , 0 , 0 );
int size = sizeof ( shmhead_t ) + blksize * blocks ;
if ( shmid == - 1 ){
fifo -> shmid = shmget ( key , size , IPC_CREAT | 0666 );
if ( fifo -> shmid == - 1 ){
ERR_EXIT ( "shmget" );
}
fifo -> p_shm = ( shmhead_t * ) shmat ( fifo -> shmid , NULL , 0 );
if ( fifo -> p_shm == ( shmhead_t * ) - 1 ){
printf ( "*********" );
ERR_EXIT ( "shmat" );
}
fifo -> p_payload = ( char * )( fifo -> p_shm + 1 );
fifo -> p_shm -> blksize = blksize ;
fifo -> p_shm -> blocks = blocks ;
fifo -> p_shm -> rd_index = 0 ;
fifo -> p_shm -> wr_index = 0 ;
fifo -> sem_mutex = sem_create ( key );
fifo -> sem_full = sem_create ( key + 1 );
fifo -> sem_empty = sem_create ( key + 2 );
sem_setval ( fifo -> sem_mutex , 1 );
sem_setval ( fifo -> sem_full , blocks );
sem_setval ( fifo -> sem_empty , 0 );
}
else {
fifo -> shmid = shmid ;
fifo -> p_shm = ( shmhead_t * ) shmat ( fifo -> shmid , NULL , 0 );
if ( fifo -> p_shm == ( shmhead_t * ) - 1 ){
ERR_EXIT ( "shmat" );
}
fifo -> p_payload = ( char * )( fifo -> p_shm + 1 );
fifo -> sem_mutex = sem_open ( key );
fifo -> sem_full = sem_open ( key + 1 );
fifo -> sem_empty = sem_open ( key + 2 );
}
return fifo ;
}
void shmfifo_put ( shmfifo_t * fifo , const void * buf )
{
sem_p ( fifo -> sem_full );
sem_p ( fifo -> sem_mutex );
memcpy ( fifo -> p_payload + fifo -> p_shm -> blksize * fifo -> p_shm -> wr_index , buf , fifo -> p_shm -> blksize );
fifo -> p_shm -> wr_index = ( fifo -> p_shm -> wr_index + 1 ) % fifo -> p_shm -> blocks ; // 更新
sem_v ( fifo -> sem_mutex );
sem_v ( fifo -> sem_empty );
}
void shmfifo_get ( shmfifo_t * fifo , void * buf ){
sem_p ( fifo -> sem_empty );
sem_p ( fifo -> sem_mutex );
memcpy ( buf , fifo -> p_payload + fifo -> p_shm -> blksize * fifo -> p_shm -> rd_index , fifo -> p_shm -> blksize );
fifo -> p_shm -> rd_index = ( fifo -> p_shm -> rd_index + 1 ) % fifo -> p_shm -> blocks ; // 更新
sem_v ( fifo -> sem_mutex );
sem_v ( fifo -> sem_full );
}
void shmfifo_destory ( shmfifo_t * fifo ){
sem_d ( fifo -> sem_mutex );
sem_d ( fifo -> sem_empty );
sem_d ( fifo -> sem_full );
shmdt ( fifo -> p_shm );
shmctl ( fifo -> shmid , IPC_RMID , 0 );
free ( fifo );
}
POSIX
消息队列,共享内存,信号量,互斥锁,条件变量,读写锁,自旋锁,文件锁
消息队列
需要链接-lrt
使用查看 man 7 mq_overview
,查看消息队列
1
2
mkdir /dev/mqueue
mount -t mqueue none /dev/mqueue
mq_open
功能:用来创建和访问一个消息队列
原型:
1
2
mqd_t mq_open ( const char * name , int oflag );
mqd_t mq_open ( const char * name , int oflag , mode_t mode , struct mq_attr * attr );
参数:
name:某个消息队列的名字
oflag:和open函数类似O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_EXCL、O_NONBLOCK
mode:如果指定了O_CREAT
,需要设置mode。
attr:指定消息队列属性
返回值:成功:消息队列文件描述符;失败:-1
mq_close
功能:关闭消息队列
原型:int mq_close(mqd_t mqdes);
参数:mqdes:消息队列描述符
返回值:成功:0;失败:-1
1
2
3
4
5
6
7
8
9
10
11
int main ( int argc , char * argv [])
{
mqd_t mqid ;
mqid = mq_open ( "/abc" , O_CREAT | O_RDWR , 0666 , NULL );
if ( mqid == ( mqd_t ) - 1 ){
ERR_EXIT ( "mq_open" );
}
printf ( "mq_open success \n " );
mq_close ( mqid );
return 0 ;
}
mq_unlink
功能:删除消息队列
原型:int mq_unlink(const char *name);
参数:name:消息队列的名字
返回值:成功:0;失败:-1
mq_getattr/mq_setattr
功能:获取/设置消息队列属性
原型:
1
2
3
4
5
6
7
8
int mq_getattr ( mqd_t mqdes , struct mq_attr * attr );
int mq_setattr ( mqd_t mqdes , struct mq_attr * newattr , struct mq_attr * oldattr );
struct mq_attr {
long mq_flags ; /* Flags: 0 or O_NONBLOCK */
long mq_maxmsg ; /* Max. # of messages on queue */
long mq_msgsize ; /* Max. message size (bytes) */
long mq_curmsgs ; /* # of messages currently in queue */
};
返回值:成功:0;失败:-1
mq_send
功能:发送消息
原型:int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);
参数:mqdes:消息队列描述符;msg_ptr:指向消息的指针;msg_len:消息长度;msg_prio:消息优先级
返回值:成功:0;失败:-1
mq_receive
功能:接收消息
原型:ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);
参数:mqdes:消息队列描述符;msg_ptr:返回接收到的消息;msg_len:消息长度;msg_prio:消息优先级
返回值:成功:接收的消息字节数;失败:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main ( int argc , char * argv [])
{
mqd_t mqid ;
mqid = mq_open ( "/abc" , O_RDONLY );
if ( mqid == ( mqd_t ) - 1 ){
ERR_EXIT ( "mq_open" );
}
STU stu ;
unsigned prio ;
ssize_t result ;
struct mq_attr attr ;
mq_getattr ( mqid , & attr );
size_t size = attr . mq_msgsize ; // 每条消息的最大长度值
result = mq_receive ( mqid , ( char * ) & stu , size , & prio );
if ( result == - 1 ){
ERR_EXIT ( "mq_receive" );
}
printf ( "receive bytes %ld \n " , result );
printf ( "name = %s age = %d prio = %u \n " , stu . name , stu . age , prio );
mq_close ( mqid );
return 0 ;
}
mq_notify
功能:建立或者删除消息达到通知事件
原型:int mq_notify(mqd_t mqdes, const struct sigevent *sevp);
参数:mqdes:消息队列描述符;sevp:非空表示当消息到达且消息队列先前为空,将得到通知;NULL表示撤销已注册的通知
返回值:成功:0;失败:-1
通知方式
产生一个信号
创建一个线程执行一个指定的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
mqd_t mqid ;
size_t size ;
struct sigevent sigev ;
void handle_signusr1 ( int sig ){
mq_notify ( mqid , & sigev );
STU stu ;
unsigned prio ;
ssize_t result ;
result = mq_receive ( mqid , ( char * ) & stu , size , & prio );
if ( result == - 1 ){
ERR_EXIT ( "mq_receive" );
}
printf ( "name = %s age = %d prio = %u \n " , stu . name , stu . age , prio );
}
int main ( int argc , char * argv [])
{
mqid = mq_open ( "/abc" , O_RDONLY );
if ( mqid == ( mqd_t ) - 1 ){
ERR_EXIT ( "mq_open" );
}
struct mq_attr attr ;
if ( mq_getattr ( mqid , & attr ) == - 1 ) {
ERR_EXIT ( "mq_getattr" );
}
size = attr . mq_msgsize ; // 每条消息的最大长度值
signal ( SIGUSR1 , handle_signusr1 );
sigev . sigev_notify = SIGEV_SIGNAL ;
sigev . sigev_signo = SIGUSR1 ;
mq_notify ( mqid , & sigev );
for (;;){
pause ();
}
mq_close ( mqid );
return 0 ;
}
共享内存
查看 /dev/shm
shm_open
功能:用来创建和打开一个共享内存对象
原型:int shm_open(const char *name, int oflag, mode_t mode);
参数:
name:共享内存对象的名字
oflag:和open函数类似O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_EXCL、O_NONBLOCK
mode:如果没有指定了O_CREAT
,可以指定为0
返回值:成功:消息队列文件描述符;失败:-1
ftruncate
功能:修改共享内存对象大小
原型:int ftruncate(int fd, off_t length);
参数:fd:文件描述符;length:长度
返回值:成功:0;失败:-1
fstat
功能:获取共享内存对象信息
原型:int fstat(int fd, struct stat *buf);
参数:fd:文件描述符;buf:返回共享内存状态
返回值:成功:0;失败:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main ( int argc , char * argv [])
{
int shmid ;
shmid = shm_open ( "/xyz" , O_CREAT | O_RDWR , 0666 );
if ( shmid == - 1 ){
ERR_EXIT ( "shm_open" );
}
printf ( "shm_open success \n " );
if ( ftruncate ( shmid , sizeof ( STU )) == - 1 ){
ERR_EXIT ( "ftruncate" );
}
struct stat buf ;
if ( fstat ( shmid , & buf ) == - 1 ){
ERR_EXIT ( "fstat" );
}
printf ( "size = %ld, mode = %o \n " , buf . st_size , buf . st_mode & 0777 ); // umask
close ( shmid );
return 0 ;
}
shm_unlink
功能:删除共享内存对象
原型:int shm_unlink(const char *name);
参数:name:共享内存对象的名字
返回值:成功:0;失败:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// write
int main ( int argc , char * argv [])
{
int shmid ;
shmid = shm_open ( "/xyz" , O_RDWR , 0 );
if ( shmid == - 1 ){
ERR_EXIT ( "shm_open" );
}
printf ( "shm_open success \n " );
struct stat buf ;
if ( fstat ( shmid , & buf ) == - 1 ){
ERR_EXIT ( "fstat" );
}
STU * p ;
p = mmap ( NULL , buf . st_size , PROT_WRITE , MAP_SHARED , shmid , 0 );
// int prot, int flags指定了,文件打开模式要设置O_RDWR,否则会报错
if ( p == MAP_FAILED ){
ERR_EXIT ( "mmap" );
}
strcpy ( p -> name , "test" );
p -> age = 20 ;
close ( shmid );
return 0 ;
}
// read 也可od -c查看
int main ( int argc , char * argv [])
{
int shmid ;
shmid = shm_open ( "/xyz" , O_RDONLY , 0 );
if ( shmid == - 1 ){
ERR_EXIT ( "shm_open" );
}
printf ( "shm_open success \n " );
struct stat buf ;
if ( fstat ( shmid , & buf ) == - 1 ){
ERR_EXIT ( "fstat" );
}
STU * p ;
p = mmap ( NULL , buf . st_size , PROT_READ , MAP_SHARED , shmid , 0 );
if ( p == MAP_FAILED ){
ERR_EXIT ( "mmap" );
}
printf ( "name = %s, age = %d \n " , p -> name , p -> age ); // umask
close ( shmid );
return 0 ;
}
线程
进程是资源竞争的基本单位;线程是程序运行的最小单位
线程共享进程数据,但也拥有自己的一部分数据
线程ID;一组寄存器;栈;errno;信号状态;优先级
线程优点:代价小;占用资源少;可充分利用多处理器并行数量;可同时等待不同的I/O操作。
线程优点:性能损失(增加额外的同步和调度开销而可用资源不变);健壮性降低(线程之间缺乏保护);缺乏访问控制;
线程模型
N:1用户线程模型
1:1核心线程模型
N:Mh混合线程模型
POSIX线程
链接 -lpthread
pthread_create
功能:创建一个新的线程
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数:
thread
:返回线程ID;
attr
:设置线程的属性,attr为NULL表示使用默认属性;
start_routine
:是个函数地址,线程启动后要执行的函数;
arg
:传给线程启动函数的参数
返回值:成功:0;失败:错误码
pthread_exit
功能:线程终止
原型:void pthread_exit(void *retval)
参数:retval
:不要指向一个局部变量
pthread_join
功能:等待线程结束
原型:int pthread_join(pthread_t thread, void **retval);
参数:thread
:线程ID;retval
:指向一个指针
返回值:成功:0;失败:错误码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void * thread_routine ( void * arg ){
for ( int i = 0 ; i < 20 ; i ++ ){
printf ( "B" );
fflush ( stdout );
usleep ( 20 );
if ( i == 3 ){
pthread_exit ( "ABC" );
}
}
return 0 ;
}
int main (){
pthread_t tid ;
int ret ;
ret = pthread_create ( & tid , NULL , thread_routine , NULL );
if ( ret != 0 ){
fprintf ( stderr , "pthread_create: %s \n " , strerror ( ret ));
exit ( EXIT_FAILURE );
}
for ( int i = 0 ; i < 20 ; i ++ ){
printf ( "A" );
fflush ( stdout );
usleep ( 20 );
}
void * value ;
if ( pthread_join ( tid , & value ) != 0 ){
fprintf ( stderr , "pthread_join: %s \n " , strerror ( ret ));
exit ( EXIT_FAILURE );
}
printf ( " \n " );
printf ( "return msg = %s \n " , ( char * ) value );
return 0 ;
}
pthread_self
功能:返回线程id
原型:pthread_t pthread_self(void);
返回值:成功:0
pthread_cancel
功能:取消一个执行中的线程
原型:int pthread_cancel(pthread_t thread);
返回值:成功:0;失败:错误码
回射客户/服务器
进程改线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// server
void * thread_routine ( void * arg ){
pthread_detach ( pthread_self ());
int conn = * (( int * ) arg );
free ( arg );
echo_service ( conn );
printf ( "exiting thread ... \n " );
return NULL ;
}
int main () {
int listenfd ;
// listenfd = socket(PF_INET, SOCK_STREAM, 0);
listenfd = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); // 指定TCP
if ( listenfd < 0 ){
ERR_EXIT ( "socket fail" );
}
// init
struct sockaddr_in servaddr ;
memset ( & servaddr , 0 , sizeof ( servaddr ));
servaddr . sin_family = AF_INET ;
servaddr . sin_port = htons ( 5188 );
servaddr . sin_addr . s_addr = htonl ( INADDR_ANY );
int on = 1 ; // 在TIME_WAIT还没消失的情况,允许服务器重启
if ( setsockopt ( listenfd , SOL_SOCKET , SO_REUSEADDR , & on , sizeof ( on )) < 0 ){
ERR_EXIT ( "setsocketopt" );
}
if ( bind ( listenfd , ( struct sockaddr * ) & servaddr , sizeof ( servaddr )) < 0 ){
ERR_EXIT ( "bind fail" );
}
if ( listen ( listenfd , SOMAXCONN ) < 0 ){
ERR_EXIT ( "listen fail" );
}
struct sockaddr_in peeraddr ;
socklen_t peerlen = sizeof ( peeraddr );
int conn ;
// pid_t pid;
while ( 1 ){
conn = accept ( listenfd , ( struct sockaddr * ) & peeraddr , & peerlen );
if ( conn < 0 ){
ERR_EXIT ( "accept fail" );
}
printf ( "ip = %s port = %d \n " , inet_ntoa ( peeraddr . sin_addr ), ntohs ( peeraddr . sin_port ));
int ret ;
pthread_t tid ;
int * p = malloc ( sizeof ( int ));
* p = conn ;
// ret = pthread_create(&tid, NULL, thread_routine, (void*)conn);
ret = pthread_create ( & tid , NULL , thread_routine , p ); // 可移植
if ( ret != 0 ){
fprintf ( stderr , "pthread_create:%s \n " , strerror ( ret ));
exit ( EXIT_FAILURE );
}
}
return 0 ;
}
线程属性
初始化与销毁
1
2
int pthread_attr_init ( pthread_attr_t * attr );
int pthread_attr_destroy ( pthread_attr_t * attr );
获取与设置分离
1
2
int pthread_attr_setdetachstate ( pthread_attr_t * attr , int detachstate );
int pthread_attr_getdetachstate ( pthread_attr_t * attr , int * detachstate );
获取与设置栈大小
1
2
int pthread_attr_setstacksize ( pthread_attr_t * attr , size_t stacksize );
int pthread_attr_getstacksize ( pthread_attr_t * attr , size_t * stacksize );
获取与设置栈溢出保护区大小
1
2
int pthread_attr_setguardsize ( pthread_attr_t * attr , size_t guardsize );
int pthread_attr_getguardsize ( pthread_attr_t * attr , size_t * guardsize );
获取与设置线程竞争范围
1
2
int pthread_attr_setscope ( pthread_attr_t * attr , int scope );
int pthread_attr_getscope ( pthread_attr_t * attr , int * scope );
获取与设置调度策略
1
2
int pthread_attr_setschedpolicy ( pthread_attr_t * attr , int policy );
int pthread_attr_getschedpolicy ( pthread_attr_t * attr , int * policy );
获取与设置继承的调度策略
1
2
int pthread_attr_setinheritsched ( pthread_attr_t * attr , int inheritsched );
int pthread_attr_getinheritsched ( pthread_attr_t * attr , int * inheritsched );
获取与设置调度参数
1
2
int pthread_attr_setschedparam ( pthread_attr_t * attr , const struct sched_param * param );
int pthread_attr_getschedparam ( pthread_attr_t * attr , struct sched_param * param ); 并发级别
并发级别:获取与设置并发级别
1
2
int pthread_setconcurrency ( int new_level );
int pthread_getconcurrency ( void );
线程特定数据(TSD)
1
2
3
4
5
6
int pthread_key_create ( pthread_key_t * key , void ( * destructor )( void * ));
int pthread_key_delete ( pthread_key_t key );
void * pthread_getspecific ( pthread_key_t key );
int pthread_setspecific ( pthread_key_t key , const void * value );
int pthread_once ( pthread_once_t * once_control , void ( * init_routine )( void )); // 只在第一个线程时执行一次
pthread_once_t once_control = PTHREAD_ONCE_INIT ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
typedef struct tsd {
pthread_t tid ;
char * str ;
} tsd_t ;
pthread_key_t key_tsd ;
pthread_once_t once_control = PTHREAD_ONCE_INIT ;
void destory_rountine ( void * value ){
printf ( "destory ... \n " );
free ( value );
}
void once_routine ( void ){
pthread_key_create ( & key_tsd , destory_rountine );
printf ( "key init ... \n " );
}
void * thread_routine ( void * arg ){
pthread_once ( & once_control , once_routine );
tsd_t * value = ( tsd_t * ) malloc ( sizeof ( tsd_t ));
value -> tid = pthread_self ();
value -> str = ( char * ) arg ;
pthread_setspecific ( key_tsd , value );
printf ( "%s setspecific %p \n " , ( char * ) arg , value );
value = pthread_getspecific ( key_tsd );
printf ( "tid = 0x%x str = %s \n " , ( int ) value -> tid , value -> str );
sleep ( 2 );
value = pthread_getspecific ( key_tsd );
printf ( "tid = 0x%x str = %s \n " , ( int ) value -> tid , value -> str );
return NULL ;
}
int main (){
pthread_t tid1 ;
pthread_t tid2 ;
pthread_create ( & tid1 , NULL , thread_routine , "thread1" );
pthread_create ( & tid2 , NULL , thread_routine , "thread2" );
pthread_join ( tid1 , NULL );
pthread_join ( tid2 , NULL );
pthread_key_delete ( key_tsd );
return 0 ;
}
POSIX信号量
1
2
3
4
5
6
7
sem_open
sem_close
sem_unlink
sem_init
sem_destroy
sem_wait
sem_post
POSIX锁
互斥锁
1
2
3
4
pthread_mutex_init
pthread_mutex_lock
pthread_mutex_unlock
pthread_mutex_destroy
自旋锁
自旋锁与互斥锁很重要的一个区别在于,线程在申请自旋锁的时候,线程不会被挂起,它处于忙等待的状态。
1
2
3
4
pthread_spin_init
pthread_spin_lock
pthread_spin_unlock
pthread_spin_destroy
读写锁
只要没有线程持有给定的读写锁用于写,那么任意数目的线程可以持有读写锁用于读
仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配读写锁用于写
读写锁用于读称为共享锁,读写锁用于写称为排它锁
1
2
3
4
5
pthread_rwlock_init
pthread_rwlock_destroy
int pthread_rwlock_rdlock
int pthread_rwlock_wrlock
int pthread_rwlock_unlock
生产者消费者模型实践
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#define CONSUMERS_COUNT 1 // 消费者
#define PRODUCERS_COUNT 5 // 生产者
#define BUFFSIZE 10
int g_buffer [ BUFFSIZE ]; // 缓冲区
unsigned short in = 0 ; // 生产位置
unsigned short out = 0 ; // 消费位置
unsigned short produce_id = 0 ; // 当前生产的产品位置
unsigned short consume_id = 0 ; // 当前消费的产品位置
sem_t g_sem_full ;
sem_t g_sem_empty ;
pthread_mutex_t g_mutex ;
pthread_t g_thread [ CONSUMERS_COUNT + PRODUCERS_COUNT ];
void * consume ( void * arg ){
int num = ( int ) arg ;
while ( 1 ){
printf ( "%d wait buffer not empty \n " , num );
sem_wait ( & g_sem_empty );
pthread_mutex_lock ( & g_mutex );
// 打印信息
for ( int i = 0 ; i < BUFFSIZE ; i ++ ){
printf ( "%02d " , i );
if ( g_buffer [ i ] == - 1 ){
printf ( "%s" , "null" );
}
else {
printf ( "%d" , g_buffer [ i ]);
}
if ( i == out ){
printf ( " \t <--consume" );
}
printf ( " \n " );
}
consume_id = g_buffer [ out ];
printf ( "%d begin consume product %d \n " , num , consume_id );
g_buffer [ out ] = - 1 ;
out = ( out + 1 ) % BUFFSIZE ;
printf ( "%d end consume product %d \n " , num , consume_id );
pthread_mutex_unlock ( & g_mutex );
sem_post ( & g_sem_full );
sleep ( 5 );
}
return NULL ;
}
void * produce ( void * arg ){
int num = ( int ) arg ;
while ( 1 ){
printf ( "%d wait buffer not full \n " , num );
sem_wait ( & g_sem_full );
pthread_mutex_lock ( & g_mutex );
for ( int i = 0 ; i < BUFFSIZE ; i ++ ){
printf ( "%02d " , i );
if ( g_buffer [ i ] == - 1 ){
printf ( "%s" , "null" );
}
else {
printf ( "%d" , g_buffer [ i ]);
}
if ( i == in ){
printf ( " \t <--produce" );
}
printf ( " \n " );
}
printf ( "%d begin produce product %d \n " , num , produce_id );
g_buffer [ in ] = produce_id ;
in = ( in + 1 ) % BUFFSIZE ;
printf ( "%d end produce product %d \n " , num , produce_id ++ );
pthread_mutex_unlock ( & g_mutex );
sem_post ( & g_sem_empty );
sleep ( 1 );
}
return NULL ;
}
int main (){
sem_init ( & g_sem_full , 0 , BUFFSIZE );
sem_init ( & g_sem_empty , 0 , 0 );
pthread_mutex_init ( & g_mutex , NULL );
int i ;
for ( i = 0 ; i < BUFFSIZE ; i ++ ){
g_buffer [ i ] = - 1 ;
}
for ( i = 0 ; i < CONSUMERS_COUNT ; i ++ ){
pthread_create ( & g_thread [ i ], NULL , consume , ( void * ) i );
}
for ( i = 0 ; i < PRODUCERS_COUNT ; i ++ ){
pthread_create ( & g_thread [ CONSUMERS_COUNT + i ], NULL , produce , ( void * ) i );
}
for ( i = 0 ; i < CONSUMERS_COUNT + PRODUCERS_COUNT ; i ++ ){
pthread_join ( g_thread [ i ], NULL );
}
sem_destroy ( & g_sem_full );
sem_destroy ( & g_sem_empty );
pthread_mutex_destroy ( & g_mutex );
return 0 ;
}
POSIX条件变量
1
2
3
4
5
6
int pthread_cond_init ( pthread_cond_t * cond , pthread_condattr_t * cond_attr );
int pthread_cond_wait ( pthread_cond_t * cond , pthread_mutex_t * mutex );
int pthread_cond_timewait ( pthread_cond_t * cond , pthread_mutex * mutex , const timespec * abstime );
int pthread_cond_destroy ( pthread_cond_t * cond );
int pthread_cond_signal ( pthread_cond_t * cond );
int pthread_cond_broadcast ( pthread_cond_t * cond ); //向所有等待线程发起通知
使用规范
等待条件变量代码
1
2
3
4
5
pthread_mutex_lock ( & mutex );
while ( 条件为假 )
pthread_cond_wait ( & cond , & mutex );
修改条件
pthread_mutex_unlock ( & mutex );
pthread_cond_wait(cond, mutex)
对mutex
进行解锁; 等待条件,直到有线程向他发起通知 重新对mutex
进行加锁操作 为什么用while?
pthread_cond_wait
会产生信号,有两种情况,
一种是pthread_cond_wait
会自动重启,好像这个信号没有发生一样;
第二种pthread_cond_wait
可能会被虚假唤醒,因此还需要重新判断。
给条件信号发送信号代码
1
2
3
4
5
pthread_mutex_lock ( & mutex );
while ( 条件为真 );
pthread_cond_signal ( & cond );
修改条件
pthread_mutex_unlock ( & mutex );
pthread_cond_signal(&cond)
向第一个等待条件的线程发起通知,如果没有任何一个线程处于等待条件的状态,这个通知将被忽略。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#define CONSUMERS_COUNT 1 // 消费者
#define PRODUCERS_COUNT 4 // 生产者
pthread_cond_t g_cond ;
pthread_mutex_t g_mutex ;
pthread_t g_thread [ CONSUMERS_COUNT + PRODUCERS_COUNT ];
int nready = 0 ; // 当前缓冲区产品个数
void * consume ( void * arg )
{
int num = ( int ) arg ;
while ( 1 )
{
pthread_mutex_lock ( & g_mutex );
while ( nready == 0 )
{
printf ( "%d begin wait a contition ... \n " , num );
pthread_cond_wait ( & g_cond , & g_mutex );
}
printf ( "%d end wait a condtion... \n " , num );
printf ( "%d begin consume product \n " , num );
-- nready ;
printf ( "%d end consume product \n " , num );
pthread_mutex_unlock ( & g_mutex );
sleep ( 1 );
}
return NULL ;
}
void * produce ( void * arg )
{
int num = ( int ) arg ;
while ( 1 )
{
pthread_mutex_lock ( & g_mutex );
printf ( "%d begin produce product \n " , num );
++ nready ;
printf ( "%d end produce product \n " , num );
printf ( "%d signal .... \n " , num );
pthread_cond_signal ( & g_cond );
pthread_mutex_unlock ( & g_mutex );
sleep ( 1 );
}
return NULL ;
}
int main (){
pthread_cond_init ( & g_cond , NULL );
pthread_mutex_init ( & g_mutex , NULL );
int i ;
for ( i = 0 ; i < CONSUMERS_COUNT ; i ++ ){
pthread_create ( & g_thread [ i ], NULL , consume , ( void * ) i );
}
sleep ( 1 );
for ( i = 0 ; i < PRODUCERS_COUNT ; i ++ ){
pthread_create ( & g_thread [ CONSUMERS_COUNT + i ], NULL , produce , ( void * ) i );
}
for ( i = 0 ; i < CONSUMERS_COUNT + PRODUCERS_COUNT ; i ++ ){
pthread_join ( g_thread [ i ], NULL );
}
pthread_mutex_destroy ( & g_mutex );
pthread_cond_destroy ( & g_cond );
return 0 ;
}
简单线程池
用于执行大量相对短暂的任务
当任务增加的时候能够动态的增加线程池中线程的数量直到达到一个阈值。
当任务执行完毕的时候,能够动态的销毁线程池中的线程
该线程池的实现本质上也是生产者与消费模型的应用。生产者线程向任务队列中添加任务,一旦队列有任务到来,如果有等待线程就唤醒来执行任务,如果没有等待线程并且线程数没有达到阈值,就创建新线程来执行任务。
计算密集型任务:线程个数 = CPU个数
I/O密集型任务: 线程个数 > CPU个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//任务结构体,将任务放入队列,由线程池中的线程来执行
typedef struct task
{
void * ( * run )( void * arg ); // 任务回调函数
void * arg ; // 回调函数参数
struct task * next ;
} task_t ;
// 线程池结构体
typedef struct threadpool
{
condition_t ready ; // 任务准备就绪或者线程池销毁通知
task_t * first ; // 任务队列头指针
task_t * last ; // 任务队列尾指针
int counter ; // 线程池中当前线程数
int idle ; // 线程池中当前正在等待任务的线程数
int max_threads ; // 线程池中最大允许的线程数
int quit ; // 销毁线程池的时候置1
} threadpool_t ;
// 初始化线程池
void threadpool_init ( threadpool_t * pool , int threads );
// 往线程池中添加任务
void threadpool_add_task ( threadpool_t * pool , void * ( * run )( void * arg ), void * arg );
// 销毁线程池
void threadpool_destroy ( threadpool_t * pool );
miniftpd实践
参考阅读
Linux-UNIX系统编程手册(上、下册) (Michael Kerrisk)
Linux系统编程-bilibili
up对应课件
[Linux系统编程/网络编程] 笔记目录
关于Linux的系统编程总结
linux系统编程 CSDN
linux网络编程_chmy1992的博客-CSDN博客