Linx系统编程

目录

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

函数参数

  • path:文件的名称,可以包含(绝对和相对)路径

  • flags:文件打开模式

    必选项:以下三个常数中必须指定一个,且仅允许指定一个。通过#include <fcntl.h>访问

    • O_RDONLY 只读打开
    • O_WRONLY 只写打开
    • O_RDWR 可读可写打开

    以下可选项可同时指定一个或多个,和必选项按位或起来作为flag参数,以下是几个常用选项:

    • O_APPEND 表示追加,所写数据附加到文件末尾。

    • O_CREAT 若文件不存在则创建它,使用此选项需要提供第三个参数mode,表示该文件的访问权限。

    注:文件最终权限:newmode = mode&~umask

    • O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。

    • O_TRUNC 如果文件已存在,清空文件内容,长度置为0。

    • O_NONBLOCK 对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O(NonblockI/O)。

  • mode:用来规定对该文件的所有者,文件的用户组及其他用户的访问权限(除了使用数字,也可以用相关宏)

    比如 0600 和 S_IRUSR | S_IWUSR

    此时的0600需要使用 newmode = mode&~umask` –>0600 &~0022=0600

返回值: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

功能:关闭文件

函数原型:

  • int close(int fd);

函数参数: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再分配。

终止进程

  1. 从main函数返回
  2. 调用exit
  3. 调用exit
  4. 调用abort
  5. 由信号终止

fork系统调用(写时复制)

功能:创建一个子进程。一次调用两次返回,创建一个进程副本,在各自的进程地址空间返回

函数原型:

  • pid_t fork(void);

函数参数:无参数

返回值:

如果成功创建一个子进程,对于父进程来说返回子进程ID 如果成功创建一个子进程,对于子进程来说返回值为0 如果为-1表示创建失败

子进程和父进程的区别

  1. 父进程设置的锁,子进程不继承
  2. 各自的进程ID和父进程ID不同
  3. 子进程的未决告警被清除:
  4. 子进程的未决信号集设置为空集。

注意

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

功能:父进程查询子进程的退出状态

函数原型:

  • pid_t wait(int *status);

函数参数: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

创建守护进程

  1. 调用fork(),创建新进程,它会是将来的守护进程
  2. 在父进程中调用exit,保证子进程不是进程组组长
  3. 调用setsid创建新的会话期
  4. 将当前目录改为根目录
  5. 将标准输入、标准输出、标准错误重定向到/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

信号和中断

中断过程

  1. 中断信号
  2. 中断源
  3. 保护现场
  4. 中断处理程序
  5. 恢复现场

中断源–>中断屏蔽–> 保护现场–>中断处理程序–>恢复现场

中断向量表:保存固定个数的中断处理程序入口地址

中断分类

硬件中断(外部中断)

外部中断是指由外部设各通过硬件请求的方式产生的中断,也称为硬件中断

软件中断(内部中断)

内部中断是由CPU运行程序错误或执行内部程序调用引起的一种中断,也称为软件中断。

信号是系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。

信号是在软件层次上对中断的一种模拟,所以通常把它称为是软中断

kill -l查看信号

信号分类

可靠信号(实时信号,支持排队,SIGRT开头);

非可靠信号(非实时信号,不支持排队)

信号与中断的相似点

  • 采用了相同的异步通信方式
  • 当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序
  • 都在处理完毕后返回到原来的断点
  • 对信号或中断都可进行屏蔽。

信号与中断的区别

  • 中断有优先级,而信号没有优先级,所有的信号都是平等的
  • 信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行
  • 中断响应是及时的,而信号响应通常都有较大的时间延迟

进程对信号的三种响应

  1. 忽略信号:不采取任何操作,有两个信号不能忽略,也不能捕获:SIGKILL和SIGSTOP即-9和19
  2. 捕获并处理信号:内核中断正在执行的代码,转去执行先前注册过的处理程序。
  3. 执行默认操作:默认操作通常是终止进程,这取决于被发送的信号

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("\nrecieve 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

功能:使调用者进程挂起,直到一个信号被捕获

函数原型:

  • int pause(void);

返回值: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_OOBMSG_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复用selectpoll

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)
  • readfdswritefdsexceptfds:分别是指向可读、可写和异常等事件的文件描述符集合的指针。
  • 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);  // 清空集合

可读事件发生条件

  1. 套接口缓冲区有数据可读
  2. 连接的读一半关闭,即接收到FIN段,读操作将返回O
  3. 如果是监听套接口,已完成连接队列不为空时
  4. 套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取。

可写事件发生条件

  1. 套接口发送缓冲区有空间容纳数据。
  2. 连接的写一半关闭。即收到RST段之后,再次调用write操作。
  3. 套接口上发生了一个错误待处理,错误可以通过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超时

  1. alarm

  2. 套接字选项

    SO_SNDTIMEO

    SO_RCVTIMEO

  3. 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实现的并发服务器,能达到的并发数,受两方面限制

  1. 一个进程能打开的最大文件描述符限制。这可以通过调整内核参数。 ulimit -n 1024调整

    只能修改当前进程以及子进程

  2. 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

selectpoll共同点:内核要遍历所有文件描述符,直到找到发生事件的文件描述符

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、ZEROFD_SETFD_CLRFD_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的优点

  1. 相比于selectpoll,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。

    内核中的selectpoll的实现是采用轮询来处理的,轮询的fd数目越多,耗时越多。

    epoll的实现是基于回调的,如果fd有期望的事件发生就通过回调函数将其加入epoll就绪队列中。(只关心“活跃”的fd,与fd数目无关)

  2. 内核把fd消息通知给用户空间呢?``select/poll采取内存拷贝方法。而epoll采用共享内存`的方式。

  3. 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

死锁

死锁产生的必要条件

  1. 互斥条件:进程对资源进行排他性使用,即在一段时间内某资源仅为一个进程所占用。
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不可剥夺条件:进程已获得的资源在未使用之前不能被剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:各个进程组成封闭的环形链,每个进程都等待下一个进程所占用的资源

防止死锁办法

资源一次性分配:破坏请求和保持条件

可剥夺资源:破坏不可剥夺条件

资源有序分配:破坏环路等待条件

死锁避免

银行家算法

信号量

互斥: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>0msgflg=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_RNDSHM_RDONLY

shmaddrNULL,核心自动选择一个地址 shmaddr不为NULLshmflgSHM_RND标记,则以shmaddr为连接地址。 shmaddr不为NULLshmflg设置了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 semidipcrm -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:设置信号量集中的信号量的计数值 GETVAL:获取信号量集中的信号量的计数值 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;
}

功能:删除消息队列

原型: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;
}

功能:删除共享内存对象

原型:int shm_unlink(const char *name);

参数:name:共享内存对象的名字

返回值:成功:0;失败:-1

mmap
 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)

  1. mutex进行解锁;
  2. 等待条件,直到有线程向他发起通知
  3. 重新对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博客

0%