【个人笔记】网络编程与高级系统编程 学习笔记

本文深入探讨了Linux系统编程,包括命令行解析、文件操作、I/O高级特性、进程管理、进程间通信、多线程、计算机网络及网络编程。详细介绍了各种系统调用和函数的使用,如getopt、open、read、write、fork、exec、pthread等,并通过实例展示了如何实现文件传输、进程间通信和网络服务器。此外,还讨论了多进程和多线程的优缺点以及网络编程中的粘包问题。
摘要由CSDN通过智能技术生成


前言

网络编程与高级系统编程 0317 复习 0516 完成 0606

历时三个月满当当,期间两周梳理计网,阶段完结撒花~

  • 命令行解析
  • 文件与目录基本操作
  • 高级 IO P1
  • 高级 IO P2
  • 多进程
  • 多进程体系与进程管理
  • 进程间通信
  • 多线程编程
  • 计算机网络
  • 网络编程

第一章 命令行解析

getopt 函数:命令行解析函数

头文件 #include <unistd.h>

函数 int getopt(int argc, char * const argv[ ], const char * optstring);

单个字符,表示选项

选项后跟 : 表示必须跟参数,参数紧跟选项或者以空格隔开

选项后跟 :: 表示可以跟参数,也可以不跟。如果跟,必须紧跟选项

char *optarg——指向当前选项参数(如果有)的指针

int optind——再次调用 getopt() 时的下一个 argv 指针的索引

int optopt——最后一个已知选项

int opterr——这个变量非零时,向 stderr 打印错误。默认为 1

功能:实现 ls -a -l——stat->权限格式(lstat)、getpwuid(uid 转 pw_name)和 getgrgid(gid 转 gr_name)->获取终端大小->分列实现


第二章 文件与目录基本操作

open 函数:打开和可能创建一个文件

头文件 #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

函数 int open(const char *pathname, int flags); // 返回 fd
int open(const char *pathname, int flags, mode_t mode);

read 函数:从一个文件描述符读

头文件 #include <unistd.h>

函数 ssize_t read(int fd, void *buf, size_t count);

write 函数:从一个文件描述符写

头文件 #include <unistd.h>

函数 ssize_t write(int fd, const void *buf, size_t count);

close 函数:关闭一个文件描述符

头文件 #include <unistd.h>

函数 int close(int fd);

功能:fd 实现文件 cat 和 cp

#include "head.h"

int main(int argc, char **argv) {
    int fd, fd2;
    if ((fd = open(argv[1], O_RDONLY)) < 0) {
        perror("open");
        exit(1);
    }
    if (argc != 3) {
        fprintf(stderr, "Usage : %s file1 file2\n", argv[0]);
        exit(1);
    }
    if ((fd2 = open(argv[2], O_CREAT | O_RDWR, 0666)) < 0) { // cp 命令
        perror("open");
        exit(1);
    }
    while (1) { 
        char buff[512] = {0};
        ssize_t size = read(fd, buff, sizeof(buff));
        printf("%s", buff); // cat 功能
        write(fd2, buff, size); // cp 命令
        if (size <= 0) {
            break;
        }
    }
    close(fd);
    close(fd2);
    return 0;
}

fopen 函数:打开文件流

头文件 #include <stdio.h>

函数 FILE *fopen(const char *pathname, const char *mode);

fread 函数:输入二进制流

头文件 #include <stdio.h>

函数 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

fwrite 函数:输出二进制流

头文件 #include <stdio.h>

函数 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

fclose 函数:关闭流

头文件 #include <stdio.h>

int fclose(FILE *stream);

功能:文件流实现文件 cp(完成)

#include "head.h"

int main(int argc, char *argv[]) {
    int opt;
    char rfile[50] = {0}, wfile[50] = {0};
    FILE *r, *w;
    while ((opt = getopt(argc, argv, "r:w:")) != -1) {
        switch (opt) {
            case 'r': {
                strcpy(rfile, optarg);
                break;
            }
            case 'w': {
                strcpy(wfile, optarg);
                break;
            }
            default: {
                fprintf(stderr, "Usage : %s - r rfile -w wfile\n", argv[0]);
                exit(1);
            }
        }
    }
    if (strlen(rfile) == 0 || strlen(wfile) == 0) {
        fprintf(stderr, "Usage : %s -r rfile -w wfile", argv[0]);
        exit(1);
    }
    if ((r = fopen(rfile, "r")) == NULL) {
        perror(rfile);
        exit(1);
    }
    if ((w = fopen(wfile, "w")) == NULL) {
        perror(wfile);
        exit(1);
    }
    while (1) {
        char buff[512] = {0};
        size_t size = fread(buff, 1, sizeof(buff), r);
        if (size <= 0) {
            break;
        }
        fwrite(buff, 1, size, w);
    }
    fclose(r);
    fclose(w);
    return 0;
}

opendir 函数:打开一个目录

头文件 #include <sys/types.h>
#include <dirent.h>

​ DIR *opendir(const char *name);

函数 DIR *opendir(const char *name);

readdir 函数:读一个目录

头文件 #include <dirent.h>

函数 struct dirent *readdir(DIR *dirp);

closedir 函数:关闭一个目录

头文件 #include <sys/types.h>

​ #include <dirent.h>

函数 int closedir(DIR *dirp);

功能:目录操作实现 ls

#include "head.h"

int main(int argc, char **argv) { // ls 命令
    char dir_name[256] = {0};
    DIR *dir = NULL;
    if (argc == 1) {
        strcpy(dir_name, ".");
    } else {
        strcpy(dir_name, argv[1]);
    }
    if ((dir = opendir(dir_name)) == NULL) {
        perror(dir_name);
        exit(1);
    }
    while (1) {
        struct dirent *dir_ptr;
        if ((dir_ptr = readdir(dir)) == NULL) {
            break;
        }
        printf("%s\n", dir_ptr->d_name);
    }
    closedir(dir);
    return 0;
}

第三章 高级 IO P1

fcntl 函数:操作文件描述符,通过 fcntl 可以改变已打开的文件性质

头文件 #include <unistd.h>

​ #include <fcntl.h>

函数 int fcntl(int fd, int cmd, … /* arg */ );

#include "head.h"

int main() { // 标准输入设置为非阻塞
    int age = 0;
    int flag = fcntl(0, F_GETFL);
    flag |= O_NONBLOCK; // flag &= ~O_NONBLOCK; 阻塞 flag 写法
    fcntl(0, F_SETFL, flag); 
    scanf("%d", &age);
    printf("Balabala is %d years old!\n", age);
    return 0;
}

第四章 高级IO P2

select 函数:同步 I/O 多路复用

头文件 #include <sys/time.h>
​ #include <sys/types.h>
​ #include <unistd.h>
函数 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
​ void FD_ZERO(fd_set *set); // clear the set
​ void FD_SET(int fd, fd_set *set); // add a given file descriptor to the set
​ void FD_CLR(int fd, fd_set *set); // remove a given file descriptor from the set
​ int FD_ISSET(int fd, fd_set *set); // test to see if a active file descriptor is part of the set

缺点 1:遍历时间复杂度高;

缺点 2:下沉到内核态,回到用户态时存在大量无效拷贝效率低;

缺点 3:监视文件 fd 数量限制

#include "head.h"

int main() {
    int age;
    fd_set rfds;
    struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    FD_ZERO(&rfds);
    FD_SET(0, &rfds);
    int ret = select(1, &rfds, NULL, NULL, &tv);
    if (ret < 0) {
        perror("select");
        exit(1);
    }
    if (ret > 0) {
        scanf("%d", &age);
    } else {
        age = 100;
    }
    printf("Balabala is %d years old\n", age);
    return 0;
}

epoll 函数:IO 事件通知设备

头文件 #include <sys/epoll.h>

补充:边缘触发——波经过时触发;水平触发——波经过后随时可以触发(默认值,不用在事件配置)。

epoll_create1 函数:打开一个 epoll 文件描述符

头文件 #include <sys/epoll.h>

函数 int epoll_create1(int flags); // flags 为 0

epoll_ctl 函数:epoll 文件描述符的控制接口

头文件 #include <sys/epoll.h>

函数 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

EPOLL_CTL_ADD
	Add  fd to the interest list and associate the settings specified in event with the internal file linked to fd.

EPOLL_CTL_MOD
	Change the settings associated with fd in the interest list to the new settings specified in event.

EPOLL_CTL_DEL
	Remove  (deregister)  the target file descriptor fd from the interest list.  The event argument is ignored and can be NULL (but see BUGS below).
typedef union epoll_data {
	void        *ptr;
	int          fd;
	uint32_t     u32;
	uint64_t     u64;
} epoll_data_t;

struct epoll_event {
	uint32_t     events; /* Epoll events */
	epoll_data_t data;   /* User data variable */
};

epoll_wait 函数:在 epoll 文件描述符上等待一个 IO 事件

头文件 #include <sys/epoll.h>

函数 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

优点 1:底层红黑树存储,发生事件回调 O(1),故时间复杂度低;

优点 2:避免内核态与用户态大量拷贝(去、来,两个角度);

优点 3:fd 上限是最大可以打开文件的数目

// epoll example for suggested usage
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;

/* Code to set up listening socket, 'listen_sock',
   (socket(), bind(), listen()) omitted */

epollfd = epoll_create1(0);
if (epollfd == -1) {
    perror("epoll_create1");
    exit(EXIT_FAILURE);
}

ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

for (;;) {
    nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); // -1 causes epoll_wait() to block indefinitely
    if (nfds == -1) {
        perror("epoll_wait");
        exit(EXIT_FAILURE);
    }

    for (n = 0; n < nfds; ++n) {
        if (events[n].data.fd == listen_sock) { // listen_sorck events
            conn_sock = accept(listen_sock, (struct sockaddr *) &addr, &addrlen);
            if (conn_sock == -1) {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            setnonblocking(conn_sock);
            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = conn_sock;
            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) {
                perror("epoll_ctl: conn_sock");
                exit(EXIT_FAILURE);
            }
        } else {
            do_use_fd(events[n].data.fd);
        }
    }
}

第五章 多进程

fork 函数:创建一个子进程

fork 后 copy-on-write(写拷贝),读取同一份资源,修改资源内容才会发生真正拷贝,避免资源浪费

头文件 #include <sys/types.h>

​ #include <unistd.h>

函数 pid_t fork(void);

#include "head.h"

int main() { // 通过查看 echo $$,理解 shell PID 与 ./a.out 关系
    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("fork()");
        exit(1);
    }
    if (pid) { // 子进程 fork() 返回 0, 父进程 fork() 返回正的子进程 pid 
        printf("In Parent printf Process! <%d>--><%d>--><%d>\n", getppid(), getpid(), pid);
    } else {
        printf("In Child printf Process! <%d>--><%d>\n", getppid(), getpid());
    }
    return 0;
}

wait 函数:等待进程改变状态

头文件 #include <sys/types.h>

​ #include <sys/wait.h>

函数 pid_t wait(int *wstatus);

#include "head.h"

int main() {
    pid_t pid;
    int i = 1;
    for (; i <= 10; ++i) {
        if ((pid = fork()) < 0) {
            perror("fork()");
            exit(1);
        }
        if (pid == 0) {
            break;
        }
    }
    if (pid == 0) {
        printf("I'm the %dth Child Process!\n", i);
    } else {
        for (int i = 0; i < 10; ++i) {
            wait(NULL); // 阻塞直到子进程改变状态
        }
        printf("I'm Parent Process!\n");
    }
    return 0;
}

exec 函数(簇):启动另一个程序执行,用它来取代原调用进程的代码段、数据段和堆栈段

头文件 #include <unistd.h>

函数 int execl(const char *pathname, const char *arg, …, (char *) NULL);

​ int execlp(const char *file, const char *arg, …, (char *) NULL); // 从 PATH 环境变量中查找文件并执行

​ (略)

#include "head.h"

int main() {
    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("fork()");
        exit(1);
    }
    if (pid == 0) {
        execlp("ls", "ls", "/etc/hosts", NULL);
        printf("Haha\n"); // 不输出  
    } else {
        int status;
        wait(&status);
        printf("status = %d\n", status);
    }
    return 0;
}

功能:run a.c 若 a.c 存在,编译并执行;若不存在,打开 vim 创建 a.c 编辑保存,之后编译并执行


第六章 进程体系与进程管理

进程?

image:程序怎么变成进程;instance:进程具有什么东西。

内核资源:文件、用户、组、二进制 image、线程、上下文

虚拟内存:上层程序设计者不需要考虑内存已被使用的问题,联系虚拟文件系统

作业:理解虚拟内存,明确进程空间(内存分布,C 与 C++ 不同之处)

并发与并行区别——并发是两个队列交替使用一台咖啡机,并行是两个队列同时使用两台咖啡机

CPU 密集型与 IO 密集型区别

协同式调度与抢占式调度区别

完全公平调度器(completely fair scheduler)

CFS 给 N 个进程每个进程分配 1/N 的处理器时间;

然后通过优先级和权值调整分配,默认优先级为 0,权值为 1;

优先级值设置越小(优先级越高),则权值越大,分配比例也增加;

为了确定每个进程真实的执行时间:引入目标延迟,目标延迟是调度的固定周期;

为了避免因为目标延迟设置过小导致每个进程运行的时间过短:引入最小粒度;

公平性:每个进程都会得到处理器资源的“公平配额”。

功能:./a.out ins start end // ./a.out 3 0 100,参考 flock


第七章 进程间通信

共享内存

ftok 函数:转换路径 + project ID 为 IPC key

头文件 #include <sys/types.h>

​ #include <sys/ipc.h>

函数 key_t ftok(const char *pathname, int proj_id);

shmget 函数:分配一个共享内存段

头文件 #include <sys/ipc.h>

​ #include <sys/shm.h>

函数 int shmget(key_t key, size_t size, int shmflg);

shmflg 通常为 IPC_CREAT | IPC_EXCL | 0666 // In addition to the above flags, the least significant 9 bits of shmflg specify the permissions granted to the owner, group, and others.

shmat 函数:附着共享内存地址操作

头文件 #include <sys/types.h>

​ #include <sys/shm.h>

函数 void *shmat(int shmid, const void *shmaddr, int shmflg);

​ int shmdt(const void *shmaddr); // detach

shmaddr 为 NULL 则系统分配

命令 ipcs——查看 Linux IPC

命令 ipcrm -m [shmid]——删除指定 shmid IPC

#include "head.h"

int main() { // 父子进程 demo
    pid_t pid;
    int shmid;
    char *share_memory = NULL;
    key_t key = ftok("./1.shm.c", 328);
    if ((shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
    if ((share_memory = shmat(shmid, NULL, 0)) == NULL) {
        perror("shmat");
        exit(1);
    }
    if ((pid = fork()) < 0) {
        perror("fork()");
        exit(1);
    }
    if (pid) {
        while (1) {
            scanf("%[^\n]s", share_memory); // 假设输入不溢出 
            getchar();
        }
    } else {
        while (1) {
            if (strlen(share_memory)) {
            	printf("<Child> : %s\n", share_memory);
               	memset(share_memory, 0, 4096); // 如果不在 if 中,可能直接清空缓冲区,原子操作重要性
            }
        }
    } 
    return 0;
}
//Usage : ./o.out -t 1|2 -m msgg
#include "head.h"

struct SHM {
    int flag;
    int type;
    char msg[512];
};

int main(int argc, char **argv) { // 不同进程 demo 1
    int opt;
    struct SHM shm_data;
    memset(&shm_data, 0, sizeof(shm_data));
    if (argc != 5) {
        fprintf(stderr, "Usage : %s -t 1|2 -m msg.\n", argv[0]);
        exit(1);
    }
    while ((opt = getopt(argc, argv, "t:m:")) != -1) {
        switch (opt) {
            case 't':
                shm_data.type = atoi(optarg);
                break;
            case 'm':
                strcpy(shm_data.msg, optarg);
                break;
            default:
                fprintf(stderr, "Usage : %s -t 1|2 -m msg.\n", argv[0]);
                exit(1);
        }
    }
    key_t key;
    int shmid;
    struct SHM *share_memory = NULL;
    key = ftok("./2.shm_1.c", 328);
    if ((shmid = shmget(key, sizeof(struct SHM), IPC_CREAT | IPC_EXCL | 0600)) < 0) {
        if (errno == EEXIST) {
            printf("shm exist!\n");
            if ((shmid = shmget(key, sizeof(struct SHM), 0600)) < 0) {
                perror("shmget2");
                exit(1);
            }
        } else {
            perror("shmget1");
            exit(1);
        }
    } else {
        printf("Success!\n");
    }
    if ((share_memory = (struct SHM *)shmat(shmid, NULL, 0)) < 0) {
        perror("shmat");
        exit(1);
    }
    printf("addr of share_memory %p\n", share_memory); // 思考: 打印共享内存(虚拟内存)地址不同
    while (1) {
        if (!share_memory- >flag) {
            printf("<%d> : get shm data\n", shm_data.type);
            share_memory->flag = 1;
            sprintf(share_memory->msg, "<%d> : %s", shm_data.type, shm_data.msg);
            sleep(1); // 强制同步
        } else {
            printf("%s\n", share_memory->msg);
            share_memory->flag = 0;
        }
    }
    return 0;
}

互斥锁和条件变量

pthread_mutex_init 函数:初始化互斥锁对象——如初始化属性为多进程中的线程共享,默认单进程

头文件 #include <pthread.h>

函数 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

pthread_mutex_lock 函数:互斥锁加锁——若已被加锁,挂起等待

头文件 #include <pthread.h>

函数 int pthread_mutex_lock(pthread_mutex_t *mutex);

pthread_mutex_unlock 函数:互斥锁解锁

头文件 #include <pthread.h>

函数 int pthread_mutex_unlock(pthread_mutex_t *mutex);

pthread_cond_init 函数:初始化条件变量

头文件 #include <pthread.h>

函数 int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)

pthread_cond_signal 函数:重新启动那些等待条件变量的线程之一

头文件 #include <pthread.h>

函数 int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_wait 函数:原子操作——解锁互斥锁和等待条件变量信号,阻塞状态等待,等到信号后重新加锁

头文件 #include <pthread.h>

函数 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

//Usage : ./o.out -t 1|2 -m msgg
#include "head.h"

struct SHM {
    int flag;
    int type;
    char msg[512];
    pthread_mutex_t mutex;
    pthread_cond_t cond;
};

int main(int argc, char **argv) { // 不同进程 demo 2
    int opt;
    struct SHM shm_data;
    memset(&shm_data, 0, sizeof(shm_data));
    if (argc != 5) {
        fprintf(stderr, "Usage : %s -t 1|2 -m msg.\n", argv[0]);
        exit(1);
    }
    while ((opt = getopt(argc, argv, "t:m:")) != -1) {
        switch (opt) {
            case 't':
                shm_data.type =atoi(optarg);
                break;
            case 'm':
                strcpy(shm_data.msg, optarg);
                break;
            default:
                fprintf(stderr, "Usage : %s -t 1|2 -m msg.\n", argv[0]);
                exit(1);
        }
    }
    key_t key;
    int shmid;
    struct SHM *share_memory = NULL;
    key = ftok("./2.shm_1.c", 328);
    if ((shmid = shmget(key, sizeof(struct SHM), IPC_CREAT | IPC_EXCL | 0600)) < 0) {
        if (errno == EEXIST) {
            printf("shm exist!\n");
            if ((shmid = shmget(key, sizeof(struct SHM), 0600)) < 0) {
                perror("shmget2");
                exit(1);
            }
        } else {
            perror("shmget1");
            exit(1);
        }
    } else {
        printf("Success!\n");
    }
    if ((share_memory = (struct SHM *)shmat(shmid, NULL, 0)) < 0) {
        perror("shmat");
        exit(1);
    }
    if (shm_data.type == 1) {
        pthread_mutexattr_t mattr;
        pthread_mutexattr_init(&mattr);
        pthread_mutexattr_setpshared(&mattr, 1); // 默认互斥锁单进程中的线程共享

        pthread_condattr_t cattr;
        pthread_condattr_init(&cattr);
        pthread_condattr_setpshared(&cattr, 1);
        
        pthread_mutex_init(&share_memory->mutex, &mattr);
        pthread_cond_init(&share_memory->cond, &cattr);
    }
    if (shm_data.type == 1) {
        while (1) {
            char buff[512] = {0};
            printf("Input : \n");
            scanf("%[^\n]s", buff);
            getchar();
            if (strlen(buff)) {
                pthread_mutex_lock(&share_memory->mutex);
                strcpy(share_memory->msg, buff);
                pthread_cond_signal(&share_memory->cond);
                pthread_mutex_unlock(&share_memory->mutex);
            }
        }
    } else {
        while (1) {
            pthread_mutex_lock(&share_memory->mutex);
            pthread_cond_wait(&share_memory->cond, &share_memory->mutex); // 解锁->等待,两者原子
            printf("\033[32m\033[0m : %s\n", share_memory->msg);
            memset(share_memory->msg, 0, sizeof(share_memory->msg));
            pthread_mutex_unlock(&share_memory->mutex);
        }
    }
    return 0;
}

命名管道和匿名管道

mkfifo 函数:创建一个 FIFO 命名管道

头文件 #include <sys/types.h>

​ #include <sys/stat.h>

函数 int mkfifo(const char *pathname, mode_t mode);

terminal 1:mkfifo becho ./a.out > bterminal 2:cat b // 查看输出

问题:命名管道和匿名管道的区别、应用场景?

popen 函数:管道流去向一个进程或管道流来自一个进程 // 创建管道,调用 fork() 产生子进程,然后从子进程中调用 /bin/sh -c 来执行参数 command 的指令

头文件 #include <stdio.h>

函数 FILE *popen(const char *command, const char *type);

pclose 函数:等待关联的进程结束

头文件 #include <stdio.h>

函数 int pclose(FILE *stream);

#include "head.h"

int main() { 
    FILE *fp;
    char buff[512] = {0};
    if ((fp = my_popen("cat ./1.popen.c", "r")) == NULL) {
        perror("popen()");
        exit(1);
    }
    while (fgets(buff, sizeof(buff), fp) != NULL) {
        printf("%s", buff);
        memset(buff, 0, sizeof(buff));
    }
    sleep(10); // double sleep verifies zombie function my_pclose
    my_pclose(fp);
    sleep(100);
    return 0;
}

pipe 函数:创建管道

头文件 #include <unistd.h>

函数 int pipe(int pipefd[2]); // pipefd[0]——读,pipefd[1]——写

dup2 函数:复制一个文件描述符

头文件 #include <unistd.h>

函数 int dup2(int oldfd, int newfd); // 实现——改写原有 newfd 指向

fdopen 函数:通过文件描述符打开文件,返回文件流

头文件 #include <stdio.h>

函数 FILE *fdopen(int fd, const char *mode);

fileno 函数:返回文件流的文件描述符(与 fdopen 对应)

头文件 #include <stdio.h>

函数 int fileno(FILE *stream);

wait4 函数:wait3 和 wait4 函数执行与 waitpid 类似的工作

头文件 #include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>

函数 pid_t wait4(pid_t pid, int *wstatus, int options, struct rusage *rusage);

#include "head.h"

static pid_t *childpid = NULL;
static int maxsize = 0;

FILE *my_popen(const char *cmd, const char *type) { // 手写 popen,pclose 函数
    FILE *fp;
    int pipefd[2];
    pid_t pid;
    if ((type[0] != 'r' && type[0] != 'w') || type[1] != '\0') {
        errno = EINVAL;
        return NULL;
    }
    if (childpid == NULL) {
        maxsize = sysconf(_SC_OPEN_MAX);
        if ((childpid = (pid_t *)calloc(maxsize, sizeof(pid_t))) == NULL) {
            return NULL;
        }
    }
    if (pipe(pipefd) < 0) {
        return NULL;
    }
    if ((pid = fork()) < 0) {
        return NULL;
    }
    if (pid == 0) {
        if (type[0] == 'r') {
            close(pipefd[0]);
            if (pipefd[1] != STDOUT_FILENO) {
                dup2(pipefd[1], STDOUT_FILENO);
                close(pipefd[1]);
            }
        } else {
            close(pipefd[1]);
            if (pipefd[0] != STDIN_FILENO) {
                dup2(pipefd[0], STDIN_FILENO);
                close(pipefd[0]);
            }
        }
        execl("/bin/sh", "sh", "-c", cmd, NULL);
    }
    if (type[0] == 'r') {
        close(pipefd[1]);
        if ((fp = fdopen(pipefd[0], type)) == NULL) {
            return NULL;
        }
    } else {
        close(pipefd[0]);
        if ((fp = fdopen(pipefd[1], type)) == NULL) {
            return NULL;
        }
    }
    childpid[fileno(fp)] = pid;
    return fp;
}
    
int my_pclose(FILE *fp) {
    int status, pid, fd;
    fd = fileno(fp);
    pid = childpid[fd];
    if (pid == 0) {
        errno = EINVAL;
        return -1;
    }
    fflush(fp);
    close(fd);
    wait4(pid, &status, 0, NULL);
    return status;
}

内存映射

问题:内存映射与共享内存有什么区别?共享内存的特点、使用场景?

mmap 函数:映射文件到内存

头文件 #include <sys/mman.h>

函数 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

// man 手册 mmap demo
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

int main(int argc, char *argv[]) {
    char *addr;
    int fd;
    struct stat sb;
    off_t offset, pa_offset;
    size_t length;
    ssize_t s;
    if (argc < 3 || argc > 4) {
        fprintf(stderr, "%s file offset [length]\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    fd = open(argv[1], O_RDONLY);
    if (fd == -1)
        handle_error("open");
    if (fstat(fd, &sb) == -1)           /* To obtain file size */
        handle_error("fstat");
    offset = atoi(argv[2]);
    pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
        /* offset for mmap() must be page aligned */
    if (offset >= sb.st_size) {
        fprintf(stderr, "offset is past end of file\n");
        exit(EXIT_FAILURE);
    }
    if (argc == 4) {
        length = atoi(argv[3]);
        if (offset + length > sb.st_size)
            length = sb.st_size - offset;
                /* Can't display bytes past end of file */

    } else {    /* No length arg ==> display to end of file */
        length = sb.st_size - offset;
    }
    addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
                MAP_PRIVATE, fd, pa_offset);
    if (addr == MAP_FAILED)
        handle_error("mmap");
    s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
    if (s != length) {
        if (s == -1)
            handle_error("write");

        fprintf(stderr, "partial write");
        exit(EXIT_FAILURE);
    }
    munmap(addr, length + offset - pa_offset);
    close(fd);
    exit(EXIT_SUCCESS);
}

信号量

信号量实现进程间、线程间的动作同步 // 联系读者写者问题、生产者消费者问题、哲学家就餐问题,本质——多线程同步

semget 函数:得到信号量集合 ID

头文件 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数 int semget(key_t key, int nsems, int semflg); // nsems 为信号量个数,而非信号量的值

semctl 函数:信号量控制操作

头文件 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数 int semctl(int semid, int semnum, int cmd, …); // 通常 SETVAL semun 中的 val,信号量的资源数

semop 函数:信号量操作

头文件 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数 int semop(int semid, struct sembuf *sops, size_t nsops); // sembuf 中的 sem_op 等于 1 则加 1,等于 -1 则减 1

// PV 操作
#include "head.h"
#define INS 3

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 create_sem(const char *path, int project_id, int nsems) {
    int sem_id;
    key_t key = ftok(path, project_id);
    if ((sem_id = semget(key, 1, IPC_CREAT | 0666)) < 0) {
        return -1;
    }
    return sem_id;
}

int init_sem(int sem_id, int sem_num, int value) {
    union semun arg;
    arg.val = value;
    return semctl(sem_id, sem_num, SETVAL, arg);
}

int P(int sem_id, int sem_num) {
    struct sembuf sbuff;
    sbuff.sem_num = sem_num;
    sbuff.sem_op = -1;
    sbuff.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sbuff, 1) < 0) {
        return -1;
    }
    return 0;
}

int V(int sem_id, int sem_num) {
    struct sembuf sbuff;
    sbuff.sem_num = sem_num;
    sbuff.sem_op = 1;
    sbuff.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sbuff, 1) < 0) {
        return -1;
    }
    return 0;
}

int main(int argc, char **argv) {
    int sem_id;
    if ((sem_id = create_sem(".", 2021, 1)) < 0) {
        perror("creat_sem");
        exit(1);
    }
    if (argc > 1) {
        if (init_sem(sem_id, 0, INS) < 0) {
            perror("init");
            exit(1);
        }
    }
    int cnt = 0;
    while (1) {
        if (P(sem_id, 0) < 0) {
            perror("P");
            exit(1);
        }
        sleep(3);
        ++cnt;
        if (V(sem_id, 0) < 0) {
            perror("V");
            exit(1);
        }
        printf("This is %dth of mine!\n", cnt);
    }
    return 0;
}

消息队列

// 优点:解耦,异步,削峰;缺点:系统复杂度(重复、丢失、失序),一致性问题(异步影响)
msgget 函数:获取消息队列 ID

头文件 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数 int msgget(key_t key, int msgflg);

msgsnd 函数:发送消息到队列

头文件 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

msgrcv 函数:从队列收到消息

头文件 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

#include "head.h"

struct msgbuff {
    long mtype;
    char mtext[512];
};

void send_msg(int qid, int type, char *m_msg, int size) {
    struct msgbuff msg;
    msg.mtype = type;
    strcpy(msg.mtext, m_msg);
    if (msgsnd(qid, (void *)&msg, sizeof(msg.mtext), IPC_NOWAIT) == -1) {
        perror("msgsnd");
        exit(1);
    }
    return ;
}

void get_msg(int qid, int type) {
    while (1) {
        struct msgbuff msg;
        bzero(&msg, sizeof(msg));
        if (msgrcv(qid, (void *)&msg, sizeof(msg.mtext), type, MSG_NOERROR) == -1) {
            perror("msgrcv");
            exit(1);
        }
        printf("抢到了资源: <类型 %d> <内容 %s>\n", type, msg.mtext);
    }
    return ;
}

int main(int argc, char **argv) {
    int opt, mode = 2, msgtype = 1;
    int msgq_id;
    char mtext[512] = {0};
    // mode == 1 -> send
    while ((opt = getopt(argc, argv, "st:rm:")) != -1) {
        switch (opt) {
            case 's':
                mode = 1;
                break;
            case 'r':
                mode = 2;
                break;
            case 't':
                msgtype = atoi(optarg);
                break;
            case 'm':
                strcpy(mtext, optarg);
                break;
            default:
                fprintf(stderr, "Usage : %s -[s|r] -t type -m msg\n", argv[0]);
                exit(1);
        }
    }
    if ((msgq_id = msgget(2021, IPC_CREAT | 0666)) < 0) {
        perror("msgget");
        exit(1);
    }
    if (mode == 1) {
        send_msg(msgq_id, msgtype, mtext, sizeof(mtext));
    } else {
        get_msg(msgq_id, msgtype);
    }
    return 0;
}

第八章 多线程编程

高速缓存区——运算过程中的数据放在 cache 上,直接读取

线程是 CPU 调度的基本单位

共享内存(线程安全,避免 data race -> mutex 同步 -> 避免死锁)

上下文切换代价小

多线程的好处

编程抽象模块化

增加程序的并发性

提高响应能力 // 在多个 CPU 上并行

IO 阻塞可行

上下文切换代价小

内存保存,线程之间共享内存,切换无需置换内存

线程模式

一个连接对应一个线程 // Apache 的标准 fork 模式

事件驱动的线程模式

线程的竞争——竞争发生的窗口,也就是需要同步的代码段称为临界区,锁的不是临界区,锁的是资源

线程 ID

线程 ID 和进程 ID 相似,但有一个本质的区别:进程 ID 是由内核分配的,而线程 ID 是由线程库分配的

获取自己的线程 ID: pthread_t pthread_self(void);

判断两个线程是否相同:int pthread_equal(pthread_t t1, pthread_t t2); // 不能用 “==” 判断,POSIX 标准未规定 pthread_t 是一个算术类型

线程终止

线程会在下面的情况下退出,这和进程近似:

线程在启动时返回,该线程就结束
如果线程调用了 pthread_exit(),线程就会终止
如果线程被另一个线程通过 pthread_cancel() 函数取消,也会终止

在以下场景中,所有线程都会被杀死,进程被杀死:

进程从主函数中返回
进程通过 exit() 函数返回 // 包括线程中 exit()

pthread_create 函数:创建一个新线程

头文件 #include <pthread.h>

函数 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); // 默认线程属性,一般默认 NULL

pthread_detach 函数:分离一个线程 // 防止被 join

#include <pthread.h>

函数 int pthread_detach(pthread_t thread);

pthread_join 函数:与一个结束的线程结合 // 使用 pthread_create 创建线程后主进程结束,创建的线程也会结束,使用 pthread_join 可以阻塞主线程

头文件 #include <pthread.h>

函数 int pthread_join(pthread_t thread, void **retval);

#include "head.h"
#define INS 100

struct Arg {
    int age;
    char name[20];
};

void *print(void *arg) {
    struct Arg argin;
    argin = *(struct Arg *)arg;
    printf("%s is %d years old!\n", argin.name, argin.age);
}

int main() { // compile and link with -l pthread
    pthread_t *tid = (pthread_t *)calloc(INS + 5, sizeof(pthread_t));
    struct Arg *arg = (struct Arg *)calloc(INS + 5, sizeof(struct Arg));
    for (int i = 0; i < INS; ++i) {
        strcpy(arg[i].name, "Wu");
        arg[i].age = i;
        pthread_create(&tid[i], NULL, print, &arg[i]);
    }
    for (int i = 0; i < INS; ++i) {
        pthread_join(tid[i], NULL); // 阻塞主线程
    }
    return 0;
}
#include "head.h"
#define INS 5
#define MAX 100

int now;
int sum;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *work(void *arg) {
    while (1) {
        pthread_mutex_lock(&mutex);
        if (now > MAX) {
            pthread_mutex_unlock(&mutex);
            return NULL;
        }
        sum += now;
        ++now;
        pthread_mutex_unlock(&mutex);
    }   
}

int main() { // demo: 多线程 1 加到 100
    pthread_t thread[INS + 5];
    for (int i = 0; i < INS; ++i) {
        pthread_create(&thread[i], NULL, work, NULL); 
    }
    for (int i = 0; i < INS; ++i) {
        pthread_join(thread[i], NULL);
    }
    printf("sum = %d\n",sum);
    return 0;
}

线程池

// thread_pool.h
#ifndef _THREAD_POOL_H
#define _THREAD_POOL_H

struct task_queue {
    int size;
    int total;
    int head;
    int tail;
    void **data;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
};

void task_queue_init(struct task_queue *taskQueue, int size);
void task_queue_push(struct task_queue *taskQueue, char *str);
char *task_queue_pop(struct task_queue *taskQueue);

#endif
// 3.thread_pool.c
#include "head.h"

void task_queue_init(struct task_queue *taskQueue, int size) {
    taskQueue->size = size;
    taskQueue->total = taskQueue->head = taskQueue->tail = 0;
    pthread_mutex_init(&taskQueue->mutex, NULL);
    pthread_cond_init(&taskQueue->cond, NULL);
    taskQueue->data = calloc(size + 5, sizeof(void *));
    return ;
}

void task_queue_push(struct task_queue *taskQueue, char *str) {
    pthread_mutex_lock(&taskQueue->mutex);
    if (taskQueue->total == taskQueue->size) {
        printf("taskQueue is full!\n");
        pthread_mutex_unlock(&taskQueue->mutex);
        return ;
    }
    printf("<Push> : %s\n", str);
    taskQueue->data[taskQueue->tail] = str;
    ++taskQueue->total;
    if (++taskQueue->tail == taskQueue->size) {
        printf("taskQueue reached end!\n");
        taskQueue->tail = 0;
    }
    pthread_cond_signal(&taskQueue->cond);
    pthread_mutex_unlock(&taskQueue->mutex);
    return ;
}

char *task_queue_pop(struct task_queue *taskQueue) {
    pthread_mutex_lock(&taskQueue->mutex);
    if (taskQueue->total == 0) {
        printf("taskQueue is empty!\n");
        pthread_cond_wait(&taskQueue->cond, &taskQueue->mutex);
    }
    char *str = taskQueue->data[taskQueue->head];
    printf("<Pop> : %s\n", str);
    --taskQueue->total;
    if (++taskQueue->head == taskQueue->size) {
        printf("taskQueue head reached end!\n");
        taskQueue->head = 0;
    }
    pthread_mutex_unlock(&taskQueue->mutex);
    return str;   
}
// 经过大量实验,得到结论有二,如下:
// 重要结论一:wait 到 signal 后,阻塞结束,进入 lock 抢锁(也是阻塞,但非原阻塞)
// 重要结论二:主线程和子线程优先级相同,一起抢锁
// 4.thread_pool_test.c
#include "head.h"
#define INS 5
#define QUEUE_SIZE 50

void *do_work(void *arg) {
    pthread_detach(pthread_self());
    struct task_queue *taskQueue = (struct task_queue *)arg;
    while (1) {
        char *str = task_queue_pop(taskQueue);
        usleep(20);
        printf("<%ld> : %s\n", pthread_self(), str);
    }
    return NULL;
}

int main() { // 线程池 demo
    pthread_t tid[INS + 1];
    struct task_queue taskQueue;
    task_queue_init(&taskQueue, QUEUE_SIZE);
    char buff[QUEUE_SIZE][1024] = {0};
    for (int i = 0; i < INS; ++i) {
        pthread_create(&tid[i], NULL, do_work, (void *)&taskQueue);
    }
    int sub = 0;
    while (1) {
        FILE *fp = fopen("./4.thread_pool_test.c", "r");
        if (fp == NULL) {
            perror("fopen");
            exit(1);
        }
        while (fgets(buff[sub], 1024, fp) != NULL) {
            task_queue_push(&taskQueue, buff[sub]);
            if (++sub == QUEUE_SIZE) {
                sub = 0; // 假设不覆盖
            }
            if (taskQueue.total == taskQueue.size) {
                while (1) {
                    if (taskQueue.total < taskQueue.size) {
                        break;
                    }
                    printf("<%ld> : 主线程 push 已满\n", pthread_self());
                    usleep(10000);
                }
            }
        }
        fclose(fp);
    }
    return 0;
}

第九章 计算机网络

链接:传输演示站点

TCP——传输控制协议,面向连接,可靠的数据传输协议

UDP——用户数据报协议,无连接,不可靠的数据传输协议

IP——尽力而为交付服务

为什么会选择无连接运输:UDP

关于何时、发送什么数据的应用层控制更为精细

无需建立连接

无状态连接

分组开销小

RDT 3.0

序号、ACK、超时重传(网络不好)、3 次冗余 ACK(快速重传,包丢失)、序号滑动窗口

TCP 客户端-服务端(语法、语义、同步)

三次握手

SYN = 1, seq = client_isn // 第一次握手,确认客户端能发

SYN = 1, seq = server_isn, ack = client_isn + 1// 第二次握手,确认服务端能收能发,

SYN = 0, seq = client_isn + 1, ack = server_isn + 1 // 第三次握手,确定客户端能收

四次挥手

FIN

ACK,FIN

ACK,等待约 30 s(有两点——确保 ACK 被服务端收到;确保之前服务器发的包能被客户端都收到,防止在网络中游荡,因为断开后客户端的端口号会被再一次用)

TCP 拥塞控制的指导思想

一个丢失的报文段意味着拥塞,丢包就应该减小发送端的发送速率

一个确认的报文段表示网络正在向接收方交付报文段,因此,当收到对之前的报文段的 ACK 后,应该增加发送方速率

带宽探测

拥塞控制

慢启动

TCP 建立之初,cwnd 被设置为 1MSS,每收到一个确认,将 cwnd 将增加一个 MSS

在慢启动过程中,刚开始速度慢,但是会以指数级增长

结束慢启动:

timeout:阈值被设置为 cwnd/2,超时重传,重新开始慢启动

cwnd >= ssthresh:进入拥塞避免

3 次冗余 ACK:阈值被设置为 cwnd/2,快速重传,进入快速恢复

拥塞避免

在拥塞避免状态下,cwnd 每一个 RTT 增加一个 MSS,线性增长

结束拥塞避免:

timeout:阈值被设置为 cwnd/2,超时重传,重新开始慢启动

3 个冗余 ACK:阈值被设置为 cwnd/2,快速重传,进入快速恢复

快速恢复

收到三个冗余 ACK,cwnd 快速恢复到新阈值 + 3MSS

结束快速恢复:

timeout:阈值被设置为 cwnd/2,超时重传,重新开始慢启动

收到 new ACK: cwnd = ssthread,并进入拥塞避免


第十章 网络编程

套接字程序调试三种方式:netstat -alnt | grep 20001,lsof -i :20001,进程目录

socket 函数:创建套接字

头文件 #include <sys/types.h>

​ #include <sys/socket.h>

函数 int socket(int domain, int type, int protocol);

​ domain: AF_INET——IPv4 Internet protocols

​ type: SOCK_STREAM——TCP、SOCK_DGRAM——UDP

​ protocol:一般为 0

bind 函数:绑定 IP 和端口,客户端可以不写,内核分配临时端口

头文件 #include <sys/types.h>

​ #include <sys/socket.h>

函数 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

​ sockfd:是调用 socket 返回的文件描述符

​ addr:保存你的地址(即端口和 IP 地址)信息

​ addrlen:设置为 sizeof(struct sockaddr)

sockaddr_in 结构体 struct sockaddr_in {
short int sin_family; // dddress family
unsigned short int sin_port; // port number
struct in_addr sin_addr; // internet address
unsigned char sin_zero[8]; // same size as struct sockaddr
};

struct in_addr 结构体 struct in_addr {
unsigned long s_addr;
};

in_addr_t inet_addr(const char *cp); // IP 格式转换——char * --> 整型(查证确认,整型)

char *inet_ntoa(struct in_addr in); // IP 格式转换——struct in_addr --> char *

int atoi(const char *nptr); // 字符串转整数

uint16_t htons(uint16_t hostshort); // 本地字节序转换——本地 --> 网络

uint16_t ntohs(uint16_t netshort); // 本地字节序转换——网络 --> 本地

listen 函数:监听 socket

头文件 #include <sys/types.h>

​ #include <sys/socket.h>

函数 int listen(int sockfd, int backlog);

​ backlog:有多少个可以排队

accept 函数:接受连接,从 sockfd 上返回一个新的连接 sockfd

头文件 #include <sys/types.h>

​ #include <sys/socket.h>

函数 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

​ 第一个参数 sockfd 必须是经由 socked(),bind(),listen() 函数处理后的 socket

​ 第二个参数是一个地址,将保存对端地址到该地址中 // 传出参数

​ 第三个参数是地址长度的地址

connect 函数:建立连接

头文件 #include <sys/types.h>

​ #include <sys/socket.h>

函数 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

​ sockfd 是系统调用 socket() 返回的套接字文件描述符

​ addr 是保存着目的地端口和 IP 地址的数据结构 struct sockaddr

​ addrlen 设置为 sizeof(struct sockaddr)

send 函数:发送数据 // 和 write 类似

头文件 #include <sys/types.h>

​ #include <sys/socket.h>

函数 ssize_t send(int sockfd, const void *buf, size_t len, int flags);

recv 函数:收数据 // 和 read 类似

头文件 #include <sys/types.h>
#include <sys/socket.h>

函数 ssize_t recv(int sockfd, void *buf, size_t len, int flags);

// common.c 
// socket 自定义接口实现
#include "head.h"

int socket_create(int port) {
    int sockfd;
    struct sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        return -1;
    }
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        return -1;
    }
    if (listen(sockfd, 20) < 0) {
        return -1;
    }
    return sockfd;
}

int socket_connect(const char *ip, int port) {
    int sockfd;
    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        return -1;
    }
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = inet_addr(ip);
    if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) {
        return -1;
    }
    return sockfd;
}
// 1.girl.c
#include "head.h"

void Usage(const char *arg) {
    fprintf(stderr, "Usage : %s -p port\n", arg);
}

int main(int argc, char **argv) {
    if (argc != 3) {
        Usage(argv[0]);
        exit(1);
    }
    int opt, port, sockfd;
    while ((opt = getopt(argc, argv, "p:")) != -1) {
        switch(opt) {
            case 'p':
                port = atoi(optarg);
                break;
            default:
                Usage(argv[0]);
                exit(1);
        }
    }
    if ((sockfd = socket_create(port)) < 0) {
        perror("socket_create");
        exit(1);
    }
    while (1) {
        int new_fd, pid;
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        if ((new_fd = accept(sockfd, (struct sockaddr *)&client, &len)) < 0) {
            perror("accept");
            exit(1);
        }
        printf("<Accept> %s:%d!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
        if ((pid = fork()) < 0) {
            perror("fork");
            exit(1);
        }
        if (pid == 0) {
            while (1) {
                char buff[1024] = {0};
                size_t rsize = recv(new_fd, buff, sizeof(buff), 0); // 问题 1:大于 0 但是小于 1024 的造成原因?
                printf("<Recv %ld> : %s\n", rsize, buff);
                if (rsize <= 0) {  
                    close(sockfd);
                    break;
                }   
            }    
            printf("<Bye> : A boy left!\n");
            exit(0);
        } // 问题 2:多进程 socket 如何设计 wait,以避免连接退出带来的僵尸进程? 
    }
    return 0;
}
// 2.boy.c
#include "head.h"

int sockfd;

void close_sock(int signum) {
    send(sockfd, "Bye", 3, 0);
    close(sockfd);
    exit(0);
    return ;
}

int main(int argc, char **argv) {
    if (argc != 3) {
        fprintf(stderr, "Usage : %s ip port!\n", argv[0]);
        exit(1);
    }
    if ((sockfd = socket_connect(argv[1], atoi(argv[2]))) < 0) {
        perror("socket_connect");
        exit(1);
    }
    signal(SIGINT, close_sock); // 告知退出
    while (1) {
        char buff[1024] = {0};
        scanf("%[^\n]s", buff);
        getchar();
        if (strlen(buff)) {
            send(sockfd, buff, sizeof(buff), 0);
        }
    }
    return 0;
}
// socket 多进程改为多线程, 间接解决退出后僵尸进程
// client Usage: telnet 公网IP 端口
// 1.girl.c 
#include "head.h"
#define MAXUSER 100

void Usage(const char *arg) {
    fprintf(stderr, "Usage : %s -p port\n", arg);
}

void *worker(void *arg) {
    int sockfd;
    sockfd = *(int *)arg;
    while (1) {
        char buff[1024] = {0};
        size_t rsize = recv(sockfd, buff, sizeof(buff), 0);
        printf("<Recv %ld> : %s\n", rsize, buff);
        if (rsize <= 0) { 
            close(sockfd);
            break;
        } else {
            send(sockfd, buff, strlen(buff), 0); // 回声服务器
        }   
    }    
    printf("<Bye> : A boy left!\n");
}

int main(int argc, char **argv) {
    if (argc != 3) {
        Usage(argv[0]);
        exit(1);
    }
    pthread_t tid[MAXUSER + 5];
    int fd[MAXUSER + 5];
    int opt, port, sockfd;
    while ((opt = getopt(argc, argv, "p:")) != -1) {
        switch(opt) {
            case 'p':
                port = atoi(optarg);
                break;
            default:
                Usage(argv[0]);
                exit(1);
        }
    }
    if ((sockfd = socket_create(port)) < 0) {
        perror("socket_create");
        exit(1);
    }
    while (1) {
        int new_fd;
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        if ((new_fd = accept(sockfd, (struct sockaddr *)&client, &len)) < 0) {
            perror("accept");
            exit(1);
        }
        if (new_fd >= MAXUSER) { // fd 对应最小未用, 联系 pid 增大
            close(new_fd);
            printf("Too Many Users!");
            break;
        }
        fd[new_fd] = new_fd;
        printf("<Accept> %s:%d!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
        pthread_create(&tid[new_fd], NULL, worker, (void *)&fd[new_fd]);
    }
    return 0;
}
// socket 多线程回声改为 select 事件驱动
// client Usage: telnet 公网IP 端口
// 1.girl.c 
#include "head.h"
#define MAXUSER 100

void Usage(const char *arg) {
    fprintf(stderr, "Usage : %s -p port\n", arg);
}

int main(int argc, char **argv) {
    if (argc != 3) {
        Usage(argv[0]);
        exit(1);
    }
    int fd[MAXUSER + 5] = {0};
    int opt, port, server_listen, maxfd = 0;
    while ((opt = getopt(argc, argv, "p:")) != -1) {
        switch(opt) {
            case 'p':
                port = atoi(optarg);
                break;
            default:
                Usage(argv[0]);
                exit(1);
        }
    }
    if ((server_listen = socket_create(port)) < 0) {
        perror("socket_create");
        exit(1);
    }
    fd_set rfds;
    maxfd = server_listen;
    fd[server_listen] = server_listen;
    int n = 0;
    int new_fd;
    while (1) {
        FD_ZERO(&rfds);
        for (int i = 0; i < MAXUSER; ++i) {
            if (fd[i] > 0) {
                FD_SET(fd[i], &rfds);
            }
        }
        struct timeval tv;
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        int ret = select(maxfd + 1, &rfds, NULL, NULL, &tv); // 思考: maxfd + 1 原因
        if (ret < 0) {
            perror("select");
            exit(1);
        }
        if (ret == 0) {
            if (fd[new_fd]) {
                printf("select ret = 0, close client!\n");
                close(fd[new_fd]);
                fd[new_fd] = 0;
            }
            continue;
        }
        if (FD_ISSET(server_listen, &rfds)) {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            if ((new_fd = accept(server_listen, (struct sockaddr *)&client, &len)) < 0) {
                perror("accept");
                exit(1);
            }
            if (new_fd >= MAXUSER) {
                close(new_fd);
                printf("Too Many Users!\n");
                break;
            }
            if (maxfd < new_fd) maxfd = new_fd; // 再次进入循环时 select nfds 用
            fd[new_fd] = new_fd;
            printf("<Accept> %s:%d!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); }
        for (int i = 0; i < MAXUSER; ++i) {
            if (fd[i] == server_listen) continue;
            if (fd[i] > 0 && FD_ISSET(fd[i], &rfds)) { 
                char buff[1024] = {0};
                int rsize = recv(fd[i], buff, sizeof(buff), 0);
                if (rsize <= 0) {
                    close(fd[i]);
                    fd[i] = 0;
                } else {
                    send(fd[i], buff, strlen(buff), 0);
                }
            }
        }
    }
    return 0;
}
// socket 传文件——文件信息、文件文本
// 1.girl.c
#include "head.h"
#define MAXUSER 100

struct file_info {
    char name[50];
    ssize_t size;
};

void Usage(const char *arg) {
    fprintf(stderr, "Usage : %s -p port\n", arg);
}

void *worker(void *arg) {
    int sockfd;
    sockfd = *(int*)arg;
    struct file_info info;
    recv(sockfd, (void *)&info, sizeof(info), 0);
    printf("file_name = %s\n", info.name);
    while (1) {
        char buff[1024] = {0};
        size_t rsize = recv(sockfd, buff, sizeof(buff), 0);
        printf("<Recv %ld> : %s\n", rsize, buff);
        if (rsize <= 0) { // 
            close(sockfd);
            break;
        } else {
            send(sockfd, buff, strlen(buff), 0);
        }   
    }    
    printf("<Bye> : A boy left!\n");
}

int main(int argc, char **argv) {
    if (argc != 3) {
        Usage(argv[0]);
        exit(1);
    }
    pthread_t tid[MAXUSER + 5];
    int fd[MAXUSER + 5];
    int opt, port, sockfd;
    while ((opt = getopt(argc, argv, "p:")) != -1) {
        switch(opt) {
            case 'p':
                port = atoi(optarg);
                break;
            default:
                Usage(argv[0]);
                exit(1);
        }
    }
    if ((sockfd = socket_create(port)) < 0) {
        perror("socket_create");
        exit(1);
    }
    while (1) {
        int new_fd;
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        if ((new_fd = accept(sockfd, (struct sockaddr *)&client, &len)) < 0) {
            perror("accept");
            exit(1);
        }
        fd[new_fd] = new_fd;
        printf("<Accept> %s:%d!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
        pthread_create(&tid[new_fd], NULL, worker, (void *)&fd[new_fd ]);
    }
    return 0;
}
// socket 传文本文件——文件信息、文件文本
// 2.boy.c
#include "head.h"

struct file_info {
    char name[50];
    ssize_t size;
};

int sockfd;

void close_sock(int signum) {
    send(sockfd, "Bye", 3, 0);
    close(sockfd);
    exit(0);
    return ;
}

int main(int argc, char **argv) {
    struct file_info info;
    if (argc != 4) {
        fprintf(stderr, "Usage : %s ip port!\n", argv[0]);
        exit(1);
    }
    if ((sockfd = socket_connect(argv[1], atoi(argv[2]))) < 0) {
        perror("socket_connect");
        exit(1);
    }
    FILE *fp = fopen(argv[3], "r");
    fseek(fp, 0, SEEK_END);
    info.size = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    signal(SIGINT, close_sock);
    strcpy(info.name, argv[3]);
    send(sockfd, (void *)&info, sizeof(info), 0); // sizeof 结构体
    while (1) {
        char buff[1024] = {0};
        ssize_t cnt = fread(buff, 1, 1024, fp); // 注意 1, 1024 可读完
        if (cnt == 0) break;
        send(sockfd, buff, strlen(buff), 0); // strlen 文件文本
    }
    fclose(fp);
    close(sockfd);
    return 0;
}

粘包问题——C/S 文件传输软件的设计与实现

理解整包,拆包,粘包

理解拆包和粘包的产生原因,掌握处理方法

进一步了解底层协议乃至所有网络编程中对数据流的处理方法

// 头要封装一起传 (同协议); 不能不考虑粘包问题 (recv 大小多少无法确定, 则 packet 无法正确取内容大小)
// bug 传文件不结束, 解决方案 memcpy 中 dest src 均 (char*), 而不只是 offset 部分, 原因待确认
// 日期: 20210603 记录一下这苦比的一天
// 1.server.c
#include "head.h"
#define MAX 20

struct User {
    int fd;
    char ip[20];
    char name[20];
};

struct Packet {
    char filename[50];
    ssize_t size;
    char body[1024];
};

int recv_file(int sockfd, const char *datadir) {
    struct Packet packet, packet_t, packet_pre;
    size_t packet_size = sizeof(struct Packet);
    size_t offset = 0, recv_size = 0, total_size = 0, flag = 0;
    FILE *fp;
    if (mkdir(datadir, 0755) < 0) {
        if (errno == EEXIST) {
            printf("Dir Exist!\n");
        } else {
            return -1;
        }
    }
    while (1) {
        if (offset) {
            memcpy((char *)&packet, (char *)&packet_pre, offset);
        }
        while ((recv_size = recv(sockfd, (void *)&packet_t, packet_size, 0)) > 0) {
            if (offset + recv_size == packet_size) {
                printf("整包!\n");
                memcpy((char *)&packet + offset, (char *)&packet_t, recv_size);
                offset = 0;
                break;
            } else if (offset + recv_size < packet_size) {
                printf("拆包!\n");
                memcpy((char *)&packet + offset, (char *)&packet_t, recv_size);
                offset += recv_size;
            } else {
                printf("粘包!\n");
                memcpy((char *)&packet + offset, (char *)&packet_t, packet_size - offset);
                memcpy((char *)&packet_pre, (char *)&packet_t + (packet_size - offset), \
                       recv_size - (packet_size - offset));
                offset = recv_size - (packet_size - offset);
                break;
            }
        }
        if (!flag) {
            printf("Saving file <%s> to , /%s/...\n", packet.filename, datadir);
            char filepath[512] = {0};
            sprintf(filepath, "./%s/%s", datadir, packet.filename);
            if ((fp = fopen(filepath, "wb")) == NULL) {
                perror("fopen");
                return -1;
            }
            ++flag;
        }
        size_t write_size;
        if (packet.size - total_size >= packet_size) {
            printf("1\n");
            write_size = fwrite(packet.body, 1, sizeof(packet.body), fp); 
        } else {
            printf("2\n");
            write_size = fwrite(packet.body, 1, packet.size - total_size, fp);
        }
        total_size += write_size;
        if (total_size >= packet.size) {
            printf("File Finished!\n");
            break;
        } 
    }
    fclose(fp);
    return 0;
}

void *do_work(void *arg) {
    struct User *user;
    user = (struct User *)arg;
    printf("Waiting for data: <%s:%s>\n", user->ip, user->name);
    recv_file(user->fd, user->name);
    close(user->fd);
    return NULL;
}

int main(int argc, char **argv) {
    int server_listen, port, sockfd;
    pthread_t tid[MAX + 5] = {0};
    struct User users[MAX + 5];
    bzero(&users, sizeof(users));
    if (argc != 2) {
        fprintf(stderr, "Usae : %s port!\n", argv[0]); 
        exit(1);
    }
    port = atoi(argv[1]);
    if ((server_listen = socket_create(port)) < 0) {
        perror("server_listen");
        exit(1);
    }
    printf("After socket_create <%d>\n", server_listen);
    while (1) {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        if ((sockfd = accept(server_listen, (struct sockaddr *)&client, &len)) < 0) {
            perror("accept");
            exit(1);
        }
        if (recv(sockfd, &users[sockfd].name, sizeof(users[sockfd].name), 0) <= 0) {
            // do sth.
            close(sockfd);
            continue;
        } else {
            users[sockfd].fd = sockfd;
            strcpy(users[sockfd].ip, inet_ntoa(client.sin_addr));
        }
        printf("After socket_accept <%d>\n", sockfd);
        int ack = 1;
        send(sockfd, (void *)&ack, sizeof(int), 0);
        pthread_create(&tid[sockfd], NULL, do_work, (void *)&users[sockfd]);
    }
    return 0;
}
// 2.client.c
#include "head.h"

struct Packet {
    char filename[50];
    ssize_t size;
    char body[1024];
};

int send_file(int sockfd, const char *file) {
    FILE *fp;
    char *p;
    struct Packet packet;
    memset(&packet, 0, sizeof(packet));
    // strcpy(packet.filename, file); // 注释并替换, 去除路径名
    strcpy(packet.filename, (p = strrchr(file, '/')) ? p + 1 : file);
    if ((fp = fopen(file, "r")) == NULL) {
        perror("fopen");
        return -1;
    }
    fseek(fp, 0, SEEK_END);
    packet.size = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    while (1) {
        size_t nread = fread(packet.body, 1, sizeof(packet.body), fp);
        if (nread <= 0) {
            break;
        }
        // buffer
        send(sockfd, (void *)&packet, sizeof(packet), 0);
        memset(packet.body, 0, sizeof(packet.body));
    }
    fclose(fp);
    return 0;
}

int main(int argc, char **argv) {
    if (argc != 4) {
        fprintf(stderr, "Usage : %s ip port file\n", argv[0]);
        exit(1);
    }
    int sockfd, port, ack;
    char ip[20] = {0}, file[50] = {0};
    strcpy(ip, argv[1]);
    port = atoi(argv[2]);
    strcpy(file, argv[3]);
    if ((sockfd = socket_connect(ip, port)) < 0) {
        perror("socket_connect");
        exit(1);
    }
    send(sockfd, "balabala", sizeof("balabala"), 0);
    printf("After socket_connect <%d>\n", sockfd);
    int rsize = recv(sockfd, (void *)&ack, sizeof(int), 0);
    if (rsize <= 0 || ack != 1) {
        close(sockfd);
        exit(1);fhttps://learn.kaikeba.com/video/363639
    }
    printf("Waiting for data transfer!\n");
    send_file(sockfd, file);
    close(sockfd);
    return 0;
}

网络并发模型——主从 reactor 模式在线聊天室

什么是 IO 多路复用

单线程或单进程同时监测若干个文件描述符是否可以执行 IO 操作的能力

并发模型

process IO 模型

pthread IO 模型

single reactor pthread IO 模型

single reactor pthread + worker threads IO 模型

主从 reactor 模式

主从 reactor + woker threads 模式

主从 reactor + worker threads 模式流程设计

数据类型结构体:struct User、struct logRequest、struct logResponse

  • 建立一主两从反应堆,listener(监听套接字) 加入到主反应堆 epollfd
  • 注册信息——客户端连接–>服务端返回 new_fd(连接套接字)–>客户端发送用户信息–>服务端接收用户信息注册–>客户端接收服务端注册应答
  • struct User 用户信息结构体加入到从反应堆,new_fd(连接套接字)加入到从反应堆 subepollfd

手写主从 reactor 模式实现聊天室,代码见 github 链接 // 阶段完结,撒花!!!


结论

学习笔记,定期回顾,有问题留言。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

idiot5liev

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值