目录&索引
前言
网络编程与高级系统编程 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 链接 // 阶段完结,撒花!!!
结论
学习笔记,定期回顾,有问题留言。