├── code ├── README.md ├── return.c ├── exit.c ├── _exit.c ├── named_w_pipe.c ├── fork_init.c ├── fork_zombie.c ├── fork_wait.c ├── fork_data.c ├── pthread.cpp ├── pthread_class.cpp ├── binary_search.go ├── fork_waitpid.c ├── pthread_para.cpp ├── pthread_self.cpp ├── unnamed_pipe.c ├── fork_wexitstatus.c ├── nameed_r_pipe.c ├── pthread_predetach.cpp ├── pthread_detach.cpp ├── pthread_struct.cpp ├── pthread_mutex.c ├── sem_w.c ├── RBTree │ ├── RBtree.h │ ├── RBtree_priv.h │ ├── main.c │ └── RBtree.c ├── keyboard_select.cpp ├── pthread_cond.c ├── sem_r.c ├── share_mem_r.c ├── share_mem_w.c ├── keyboard_select_timeout.cpp ├── client_select.cpp ├── pthread_mutex_cond.c ├── client_poll.c ├── vector_iterator.cpp ├── client_epoll.2.c ├── server_poll.c ├── server_select.cpp ├── server_epoll.c └── my_vector.cpp ├── source ├── dig命令.png ├── ldd命令.png ├── top命令.png ├── 腾讯1.pdf ├── 腾讯2.pdf ├── 腾讯3.pdf ├── 腾讯4.pdf ├── 腾讯5.pdf ├── 腾讯6.pdf ├── 腾讯7.pdf ├── 腾讯8.pdf ├── 腾讯9.pdf ├── github.jpg └── README.md ├── 数据结构及算法.md ├── 学习计划.md ├── README.md ├── 海量数据处理.md ├── 网络编程.md ├── 计算机网络.md ├── Linux工具.md ├── 操作系统.md ├── 编程语言C++.md └── 真题摘录.md /code/README.md: -------------------------------------------------------------------------------- 1 | ![图片](/source/github.jpg) 2 | 3 | 基础代码实现。 4 | -------------------------------------------------------------------------------- /source/dig命令.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/dig命令.png -------------------------------------------------------------------------------- /source/ldd命令.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/ldd命令.png -------------------------------------------------------------------------------- /source/top命令.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/top命令.png -------------------------------------------------------------------------------- /source/腾讯1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/腾讯1.pdf -------------------------------------------------------------------------------- /source/腾讯2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/腾讯2.pdf -------------------------------------------------------------------------------- /source/腾讯3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/腾讯3.pdf -------------------------------------------------------------------------------- /source/腾讯4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/腾讯4.pdf -------------------------------------------------------------------------------- /source/腾讯5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/腾讯5.pdf -------------------------------------------------------------------------------- /source/腾讯6.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/腾讯6.pdf -------------------------------------------------------------------------------- /source/腾讯7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/腾讯7.pdf -------------------------------------------------------------------------------- /source/腾讯8.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/腾讯8.pdf -------------------------------------------------------------------------------- /source/腾讯9.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/腾讯9.pdf -------------------------------------------------------------------------------- /source/github.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-interview/Skill-Tree/master/source/github.jpg -------------------------------------------------------------------------------- /source/README.md: -------------------------------------------------------------------------------- 1 | ![图片](./github.jpg) 2 | 3 | 本仓库全部图片来自网络及本地截图,如有侵权请联系我。[Mail Me](linw7@mail2.sysu.edu.cn) -------------------------------------------------------------------------------- /code/return.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void fun(){ 5 | printf("using exit \n"); 6 | printf("this is the content in buff \n"); 7 | return; 8 | } 9 | 10 | int main(){ 11 | fun(); 12 | printf("already return \n"); 13 | } -------------------------------------------------------------------------------- /code/exit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void fun(){ 5 | printf("using exit \n"); 6 | printf("this is the content in buff "); 7 | exit(0); 8 | } 9 | 10 | int main(){ 11 | fun(); 12 | printf("already return \n"); 13 | } 14 | -------------------------------------------------------------------------------- /code/_exit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void fun(){ 5 | printf("using _exit \n"); 6 | printf("this is the content in buff "); 7 | _exit(0); 8 | } 9 | 10 | int main(){ 11 | fun(); 12 | printf("already return \n"); 13 | } 14 | -------------------------------------------------------------------------------- /code/named_w_pipe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define P_FIFO "/tmp/p_fifo" 6 | 7 | int main(int argc, char **argv){ 8 | int fd; 9 | if(argc < 2){ 10 | printf("please input the write data \n"); 11 | } 12 | fd = open(P_FIFO, O_WRONLY | O_NONBLOCK); 13 | write(fd, argv[1], 100); 14 | close(fd); 15 | return 0; 16 | } -------------------------------------------------------------------------------- /数据结构及算法.md: -------------------------------------------------------------------------------- 1 | # 数据结构及算法 2 | 3 | > 概念大家都清楚,多写代码是关键。 4 | 5 | --- 6 | 7 | # 目录 8 | 9 | | Chapter 1 | Chapter 2 | Chapter 3| 10 | | :---------: | :---------: | :---------: | 11 | |[纸上代码](https://github.com/linw7/Paper-Code)|[动态规划专项](#dp)|[海量数据专项](https://github.com/linw7/Skill-Tree/blob/master/%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86.md)| 12 | 13 | --- 14 | 15 | -------------------------------------------------------------------------------- /学习计划.md: -------------------------------------------------------------------------------- 1 | # 学习计划 2 | 3 | > *Learning by doing.* 4 | 5 | # 攻坚目标 6 | 7 | 1. 知识点覆盖训练 8 | 9 | - 牛客网以往错题梳理(7.10前) 10 | 11 | - 牛客网新题专项训练(全程) 12 | 13 | 2. 零散知识点整合 14 | 15 | - 单项知识点总结提炼(全程) 16 | 17 | 3. 针对性训练 18 | 19 | - 针对性刷往年笔试题(7.5开始,每日3套) 20 | 21 | 4. 编程、项目能力 22 | 23 | - LeetCode、剑指offer(7.6开始,每日3题) 24 | 25 | - 暑期练手项目(7月15后) 26 | 27 | -------------------------------------------------------------------------------- /code/fork_init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(){ 6 | pid_t pid; 7 | pid = fork(); 8 | if(pid < 0){ 9 | perror("fail to fork"); 10 | exit(-1); 11 | } 12 | else if(pid == 0){ 13 | printf("sub-process, pid : %u, ppid : %u\n", getpid(), getppid()); 14 | } 15 | else{ 16 | printf("parent, pid : %u, sub-process pid : %u\n", getpid(), pid); 17 | } 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /code/fork_zombie.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(){ 6 | pid_t pid; 7 | pid = fork(); 8 | if(pid < 0){ 9 | perror("fail to fork"); 10 | exit(-1); 11 | } 12 | else if(pid == 0){ 13 | printf("sub-process, pid : %u, ppid : %u\n", getpid(), getppid()); 14 | } 15 | else{ 16 | sleep(2); 17 | printf("parent, pid : %u, sub-process pid : %u\n", getpid(), pid); 18 | } 19 | return 0; 20 | } -------------------------------------------------------------------------------- /code/fork_wait.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main(){ 8 | pid_t pid = fork(); 9 | if(pid < 0){ 10 | perror("fork error\n"); 11 | return 0; 12 | } 13 | else if(pid > 0){ 14 | printf("parent process, i will block in wait \n"); 15 | pid_t pr = wait(NULL); 16 | printf("parent process, i catch a child process with pid : %u \n", pr); 17 | } 18 | else if(pid == 0){ 19 | printf("sub-process, pid : %u, ppid : %u \n", getpid(), getppid()); 20 | sleep(10); 21 | exit(0); 22 | } 23 | return 0; 24 | } -------------------------------------------------------------------------------- /code/fork_data.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int global = 1; 6 | 7 | int main(){ 8 | pid_t pid; 9 | int stack = 2; 10 | int *heap = (int *)malloc(sizeof(int)); 11 | *heap = 3; 12 | pid = fork(); 13 | if(pid < 0){ 14 | perror("fail to fork"); 15 | exit(-1); 16 | } 17 | else if(pid == 0){ 18 | global++; 19 | stack++; 20 | (*heap)++; 21 | printf("in sub-process, global = %d, stack = %d, heap = %d \n", global, stack, *heap); 22 | } 23 | else{ 24 | sleep(2); 25 | printf("in parent-process, global = %d, stack = %d, heap = %d \n", global, stack, *heap); 26 | } 27 | return 0; 28 | } -------------------------------------------------------------------------------- /code/pthread.cpp: -------------------------------------------------------------------------------- 1 | // g++ -o pthread pthread.cpp -lpthread 2 | #include 3 | #include 4 | 5 | void *say_hello(void *args){ 6 | printf("hello from thread. \n"); 7 | pthread_exit((void *)1); 8 | } 9 | 10 | int main() { 11 | pthread_t tid; 12 | int iRet = pthread_create(&tid, NULL, say_hello, NULL); 13 | if(iRet){ 14 | printf("pthread create error : iRet = %d\n", iRet); 15 | return iRet; 16 | } 17 | void *retval; 18 | iRet = pthread_join(tid, &retval); 19 | if(iRet){ 20 | printf("pthread join error : iRet = %d\n", iRet); 21 | return iRet; 22 | } 23 | printf("retval = %ld\n", (long)retval); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /code/pthread_class.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class Hello { 5 | public: 6 | static void *say_hello(void *args){ 7 | printf("hello from thread. \n"); 8 | pthread_exit((void *)1); 9 | } 10 | }; 11 | 12 | int main() { 13 | pthread_t tid; 14 | int iRet = pthread_create(&tid, NULL, Hello::say_hello, NULL); 15 | if(iRet){ 16 | printf("pthread create error : iRet = %d\n", iRet); 17 | return iRet; 18 | } 19 | void *retval; 20 | iRet = pthread_join(tid, &retval); 21 | if(iRet){ 22 | printf("pthread join error : iRet = %d\n", iRet); 23 | return iRet; 24 | } 25 | printf("retval = %ld\n", (long)retval); 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /code/binary_search.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | "fmt" 6 | ) 7 | 8 | type DataStruct struct { 9 | Data []int 10 | } 11 | 12 | func (d *DataStruct) Find(k int) int { 13 | left, right, mid := 1, len(d.Data), 0 14 | for { 15 | mid = int(math.Floor(float64((left + right) / 2))) 16 | if d.Data[mid] > k { 17 | right = mid - 1 18 | } else if d.Data[mid] < k { 19 | left = mid + 1 20 | } else { 21 | break 22 | } 23 | if left > right { 24 | mid = -1 25 | break 26 | } 27 | } 28 | return mid 29 | } 30 | 31 | func main() { 32 | a1 := DataStruct{[]int{1, 2, 5, 7, 15, 25, 30, 36, 39, 51, 67, 78, 80, 82, 85, 91, 92, 97}} 33 | fmt.Println(a1.Find(30)) 34 | } -------------------------------------------------------------------------------- /code/fork_waitpid.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main(){ 8 | pid_t pid, pr; 9 | pid = fork(); 10 | if(pid < 0) 11 | printf("fork error \n"); 12 | else if(pid == 0){ 13 | printf("sub-process will sleep for 10 seconds \n"); 14 | sleep(10); 15 | exit(0); 16 | } 17 | else if(pid > 0){ 18 | do{ 19 | pr = waitpid(pid, NULL, WNOHANG); 20 | if(pr == 0){ 21 | printf("no child exited \n"); 22 | sleep(1); 23 | } 24 | }while(pr == 0); 25 | if(pr == pid) 26 | printf("successful get child %d \n", pr); 27 | else 28 | printf("some error occured \n"); 29 | } 30 | return 0; 31 | } -------------------------------------------------------------------------------- /code/pthread_para.cpp: -------------------------------------------------------------------------------- 1 | // g++ -o pthread pthread_para.cpp -lpthread 2 | #include 3 | #include 4 | 5 | void *say_hello(void *args){ 6 | int i = *((int *)args); 7 | printf("hello from thread. i = %d\n", i); 8 | pthread_exit((void *)1); 9 | } 10 | 11 | int main() { 12 | pthread_t tid; 13 | int para = 10; 14 | int iRet = pthread_create(&tid, NULL, say_hello, ¶); 15 | if(iRet){ 16 | printf("pthread create error : iRet = %d\n", iRet); 17 | return iRet; 18 | } 19 | void *retval; 20 | iRet = pthread_join(tid, &retval); 21 | if(iRet){ 22 | printf("pthread join error : iRet = %d\n", iRet); 23 | return iRet; 24 | } 25 | printf("retval = %ld\n", (long)retval); 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /code/pthread_self.cpp: -------------------------------------------------------------------------------- 1 | // g++ -o pthread pthreadself.cpp -lpthread 2 | #include 3 | #include 4 | 5 | void *say_hello(void *args){ 6 | printf("hello from thread. i = %d, tid = %ld\n", *((int *)args), pthread_self()); 7 | pthread_exit((void *)1); 8 | } 9 | 10 | int main() { 11 | pthread_t tid; 12 | int para = 10; 13 | int iRet = pthread_create(&tid, NULL, say_hello, ¶); 14 | if(iRet){ 15 | printf("pthread create error : iRet = %d\n", iRet); 16 | return iRet; 17 | } 18 | void *retval; 19 | iRet = pthread_join(tid, &retval); 20 | if(iRet){ 21 | printf("pthread join error : iRet = %d\n", iRet); 22 | return iRet; 23 | } 24 | printf("retval = %ld\n", (long)retval); 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /code/unnamed_pipe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define INPUT 0 7 | #define OUTPUT 1 8 | 9 | int main() { 10 | int fd[2]; 11 | char buff[256]; 12 | pipe(fd); 13 | pid_t pid = fork(); 14 | if(pid < 0){ 15 | printf("error fork \n"); 16 | exit(1); 17 | } 18 | else if(pid == 0){ 19 | printf("in child process ... \n"); 20 | close(fd[INPUT]); 21 | write(fd[OUTPUT], "hello world", strlen("hello world")); 22 | exit(0); 23 | } 24 | else{ 25 | printf("in parent process ... \n"); 26 | close(fd[OUTPUT]); 27 | int count = read(fd[INPUT], buff, sizeof(buff)); 28 | printf("%d bytes of data received from child process : %s \n", count, buff); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /code/fork_wexitstatus.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main(){ 8 | pid_t pid = fork(); 9 | if(pid < 0){ 10 | perror("fork error\n"); 11 | return 0; 12 | } 13 | else if(pid > 0){ 14 | printf("parent process \n"); 15 | int status = -1; 16 | pid_t pr = wait(&status); 17 | if(WIFEXITED(status)){ 18 | printf("the child process %d exit normallly \n", pr); 19 | printf("the return code is %d \n", WEXITSTATUS(status)); 20 | } 21 | else { 22 | printf("the child process %d exit abnormally \n", pr); 23 | } 24 | } 25 | else if(pid == 0){ 26 | printf("sub-process, pid : %u, ppid : %u \n", getpid(), getppid()); 27 | exit(3); 28 | } 29 | return 0; 30 | } -------------------------------------------------------------------------------- /code/nameed_r_pipe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define P_FIFO "/tmp/p_fifo" 9 | 10 | int main(){ 11 | char cache[100]; 12 | int fd; 13 | memset(cache, 0, sizeof(cache)); 14 | if(access(P_FIFO, F_OK) == 0){ 15 | execlp("rm", "-f", P_FIFO, NULL); 16 | printf("access \n"); 17 | } 18 | if(mkfifo(P_FIFO, 0777) < 0){ 19 | printf("make named fifo failed \n"); 20 | } 21 | fd = open(P_FIFO, O_RDONLY | O_NONBLOCK); 22 | while(1){ 23 | memset(cache, 0, sizeof(cache)); 24 | if(read(fd, cache, 100) == 0){ 25 | printf("no data \n"); 26 | } 27 | else{ 28 | printf("get data : %s \n", cache); 29 | } 30 | sleep(1); 31 | } 32 | close(fd); 33 | return 0; 34 | } -------------------------------------------------------------------------------- /code/pthread_predetach.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void *tfn1(void *args){ 7 | printf("the thread. \n"); 8 | return NULL; 9 | } 10 | 11 | int main() { 12 | int iRet; 13 | pthread_t tid; 14 | pthread_attr_t attr; 15 | iRet = pthread_attr_init(&attr); 16 | if(iRet){ 17 | printf("cant't init attr %s \n", strerror(iRet)); 18 | return iRet; 19 | } 20 | iRet = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 21 | if(iRet){ 22 | printf("can't set attr %s \n", strerror(iRet)); 23 | return iRet; 24 | } 25 | iRet = pthread_create(&tid, &attr, tfn1, NULL); 26 | if(iRet){ 27 | printf("can't create thread %s \n", strerror(iRet)); 28 | return iRet; 29 | } 30 | iRet = pthread_join(tid, NULL); 31 | if(iRet){ 32 | printf("pthread has been detached \n"); 33 | return iRet; 34 | } 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /code/pthread_detach.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void *tfn1(void *args){ 8 | printf("the sub thread sleeping 5 seconds \n"); 9 | sleep(5); 10 | printf("the thread done \n"); 11 | return NULL; 12 | } 13 | 14 | int main() { 15 | int iRet; 16 | pthread_t tid; 17 | pthread_attr_t attr; 18 | iRet = pthread_create(&tid, NULL, tfn1, NULL); 19 | if(iRet){ 20 | printf("can't create thread %s \n", strerror(iRet)); 21 | return iRet; 22 | } 23 | iRet = pthread_detach(tid); 24 | if(iRet){ 25 | printf("can't detach thread %s \n", strerror(iRet)); 26 | return iRet; 27 | } 28 | printf("hello\n"); 29 | iRet = pthread_join(tid, NULL); 30 | if(iRet){ 31 | printf("pthread has been detached \n"); 32 | return iRet; 33 | } 34 | printf("the main thread sleeping 8 seconds \n"); 35 | sleep(8); 36 | printf("the main thread done \n"); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /code/pthread_struct.cpp: -------------------------------------------------------------------------------- 1 | // g++ -o pthread pthread_struct.cpp -lpthread 2 | #include 3 | #include 4 | #include 5 | 6 | struct arg_type{ 7 | int a; 8 | char b[100]; 9 | }; 10 | void *say_hello(void *args){ 11 | struct arg_type temp = *((struct arg_type *)args); 12 | printf("hello from thread. a = %d, b = %s\n", temp.a, temp.b ); 13 | pthread_exit((void *)1); 14 | } 15 | 16 | int main() { 17 | pthread_t tid; 18 | struct arg_type args; 19 | args.a = 10; 20 | char temp[100] = "say_hello"; 21 | strncpy(args.b, temp, sizeof(temp)); 22 | int iRet = pthread_create(&tid, NULL, say_hello, &args); 23 | if(iRet){ 24 | printf("pthread create error : iRet = %d\n", iRet); 25 | return iRet; 26 | } 27 | void *retval; 28 | iRet = pthread_join(tid, &retval); 29 | if(iRet){ 30 | printf("pthread join error : iRet = %d\n", iRet); 31 | return iRet; 32 | } 33 | printf("retval = %ld\n", (long)retval); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /code/pthread_mutex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | pthread_mutex_t mutex_x = PTHREAD_MUTEX_INITIALIZER; 8 | 9 | int total = 20; 10 | 11 | void* self_ticket(void *argv){ 12 | for(int i = 0; i < 20; ++i){ 13 | pthread_mutex_lock(&mutex_x); 14 | if(total > 0){ 15 | sleep(1); 16 | printf("sell the %dth ticket \n", 20 - total + 1); 17 | --total; 18 | } 19 | pthread_mutex_unlock(&mutex_x); 20 | } 21 | return 0; 22 | } 23 | 24 | int main(){ 25 | int iRet; 26 | pthread_t tids[4]; 27 | int i = 0; 28 | for(int i = 0; i < 4; i++){ 29 | iRet = pthread_create(&tids[i], NULL, &self_ticket, NULL); 30 | if(iRet){ 31 | printf("thread create error\n"); 32 | } 33 | } 34 | sleep(30); 35 | void *retval; 36 | for(int i = 0; i < 4; ++i){ 37 | iRet = pthread_join(tids[i], &retval); 38 | if(iRet){ 39 | printf("pthread join error\n"); 40 | } 41 | } 42 | return 0; 43 | } -------------------------------------------------------------------------------- /code/sem_w.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define SEM_KRY 4001 12 | #define SHM_KRY 5678 13 | 14 | union semun{ 15 | int val; 16 | }; 17 | 18 | int main(void){ 19 | int shmid = shmget(SHM_KRY, sizeof(int), IPC_CREAT | 0666); 20 | if(shmid < 0){ 21 | printf("create shm error \n"); 22 | return -1; 23 | } 24 | void *shmptr = shmat(shmid, NULL, 0); 25 | if(shmptr == (void *)-1){ 26 | printf("shmat error : %s \n", strerror(errno)); 27 | return -1; 28 | } 29 | int *data = (int *)shmptr; 30 | int semid = semget(SEM_KRY, 2, 0666); 31 | union semun semuni; 32 | struct sembuf sembuf; 33 | while(1){ 34 | sembuf.sem_num = 1; 35 | sembuf.sem_op = -1; 36 | sembuf.sem_flg = SEM_UNDO; 37 | semop(semid, &sembuf, 1); 38 | scanf("%d", data); 39 | sembuf.sem_num = 0; 40 | sembuf.sem_op = 1; 41 | sembuf.sem_flg = SEM_UNDO; 42 | semop(semid, &sembuf, 1); 43 | } 44 | return 0; 45 | } -------------------------------------------------------------------------------- /code/RBTree/RBtree.h: -------------------------------------------------------------------------------- 1 | #ifndef RBTREE_H 2 | #define RBTREE_H 3 | 4 | typedef struct rb_tree *rb_tree; 5 | 6 | /* Creates an empty Red-Black tree. */ 7 | rb_tree RBcreate(); 8 | /* Frees an entire tree. */ 9 | void RBfree(rb_tree tree); 10 | /* Cleans up. Call this when you won't be using any more Red-Black trees. */ 11 | void RBcleanup(); 12 | 13 | /* Inserts an element with specified key into tree. */ 14 | int RBinsert(rb_tree tree, int key); 15 | 16 | /* Deletes an element with a particular key. */ 17 | int RBdelete(rb_tree tree, int key); 18 | 19 | /* Writes a tree to stdout in preorder format. 20 | * Outputs everything on the same line. */ 21 | void RBwrite(rb_tree tree); 22 | /* Reads a tree in preorder format from file. 23 | * Warning: does NOT check to see if the resulting tree violates Red-Black 24 | * properties, and BOLDLY ASSUMES that the input file is well-formatted. Will 25 | * return once it sees something it doesn't understand. */ 26 | rb_tree RBread(char *fname); 27 | 28 | /* Draws an SVG picture of the tree in the specified file. */ 29 | void RBdraw(rb_tree tree, char *fname); 30 | 31 | #endif /* RBTREE_H */ 32 | -------------------------------------------------------------------------------- /code/keyboard_select.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int main(int argc, char *argv[]) { 17 | int keyboard; 18 | int ret, i; 19 | char c; 20 | fd_set readfd; 21 | struct timeval timeout; 22 | // 打开键盘文件返回键盘文件描述符 23 | keyboard = open("/dev/tty", O_RDONLY | O_NONBLOCK); 24 | assert(keyboard > 0); 25 | 26 | while(1) { 27 | // 设置超时信息 28 | timeout.tv_sec = 1; 29 | timeout.tv_usec = 1; 30 | // 对readdf清零 31 | FD_ZERO(&readfd); 32 | // 对resdfd标记keyboard的位置为1 33 | FD_SET(keyboard, &readfd); 34 | // 设置select函数参数并调用 35 | ret = select(keyboard + 1, &readfd, NULL, NULL, &timeout); 36 | // 判断fd是否在readfd集合中 37 | if(FD_ISSET(keyboard, &readfd)) { 38 | // 开始读取键盘输入 39 | i = read(keyboard, &c, 1); 40 | if('\n' == c) 41 | continue; 42 | printf("The time is %c\n", c); 43 | if('q' == c) 44 | break; 45 | } 46 | } 47 | return 0; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /code/pthread_cond.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 8 | pthread_cond_t ready = PTHREAD_COND_INITIALIZER; 9 | 10 | int x = 10; 11 | int y = 20; 12 | 13 | void *func1(void *arg){ 14 | printf("func1 start\n"); 15 | pthread_mutex_lock(&lock); 16 | while(x < y){ 17 | pthread_cond_wait(&ready, &lock); 18 | } 19 | pthread_mutex_unlock(&lock); 20 | sleep(3); 21 | printf("func1 end\n"); 22 | } 23 | 24 | void *func2(void *arg){ 25 | printf("func2 start\n"); 26 | pthread_mutex_lock(&lock); 27 | x = 20; 28 | y = 10; 29 | printf("has change x and y\n"); 30 | pthread_mutex_unlock(&lock); 31 | if(x > y){ 32 | pthread_cond_signal(&ready); 33 | } 34 | printf("func2 end\n"); 35 | } 36 | 37 | int main(){ 38 | pthread_t tid1, tid2; 39 | int iRet; 40 | iRet = pthread_create(&tid1, NULL, func1, NULL); 41 | if(iRet){ 42 | printf("pthread 1 create error\n"); 43 | return iRet; 44 | } 45 | sleep(2); 46 | iRet = pthread_create(&tid2, NULL, func2, NULL); 47 | if(iRet){ 48 | printf("pthread 2 create error"); 49 | return iRet; 50 | } 51 | sleep(5); 52 | return 0; 53 | } -------------------------------------------------------------------------------- /code/sem_r.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define SEM_KRY 4001 12 | #define SHM_KRY 5678 13 | 14 | union semun{ 15 | int val; 16 | }; 17 | 18 | int main(void){ 19 | int shmid = shmget(SHM_KRY, sizeof(int), IPC_CREAT | 0666); 20 | if(shmid < 0){ 21 | printf("create shm error \n"); 22 | return -1; 23 | } 24 | void *shmptr = shmat(shmid, NULL, 0); 25 | if(shmptr == (void *)-1){ 26 | printf("shmat error : %s \n", strerror(errno)); 27 | return -1; 28 | } 29 | int *data = (int *)shmptr; 30 | 31 | int semid = semget(SEM_KRY, 2, IPC_CREAT | 0666); 32 | union semun semuni; 33 | semuni.val = 0; 34 | semctl(semid, 0, SETVAL, semuni); 35 | semuni.val = 1; 36 | semctl(semid, 1, SETVAL, semuni); 37 | 38 | struct sembuf sembuf; 39 | 40 | while(1){ 41 | sembuf.sem_num = 0; 42 | sembuf.sem_op = -1; 43 | sembuf.sem_flg = SEM_UNDO; 44 | semop(semid, &sembuf, 1); 45 | printf("the num : %d \n", *data); 46 | sembuf.sem_num = 1; 47 | sembuf.sem_op = 1; 48 | sembuf.sem_flg = SEM_UNDO; 49 | semop(semid, &sembuf, 1); 50 | } 51 | return 0; 52 | } -------------------------------------------------------------------------------- /code/share_mem_r.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #define TEXT_SZ 2048 7 | struct shared_use_st{ 8 | int written; 9 | char text[TEXT_SZ]; 10 | }; 11 | 12 | int main(){ 13 | int shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT); 14 | if(shmid == -1){ 15 | fprintf(stderr, "shmget failed \n"); 16 | exit(EXIT_FAILURE); 17 | } 18 | 19 | void *shared_memory = shmat(shmid, (void *)0, 0); 20 | if(shared_memory == (void *)-1){ 21 | fprintf(stderr, "shmat failed \n"); 22 | exit(EXIT_FAILURE); 23 | } 24 | printf("memory attached at : % ld \n", (long)shared_memory); 25 | 26 | struct shared_use_st *shared_stuff = (struct shared_use_st *)shared_memory; 27 | shared_stuff->written = 0; 28 | int running = 1; 29 | while(running){ 30 | if(shared_stuff->written){ 31 | printf("you wrote : %s \n", shared_stuff->text); 32 | sleep(4); 33 | shared_stuff->written = 0; 34 | if(strncmp(shared_stuff->text, "end", 3) == 0){ 35 | running = 0; 36 | } 37 | } 38 | } 39 | 40 | if(shmdt(shared_memory) == -1){ 41 | fprintf(stderr, "shmdt failed \n"); 42 | exit(EXIT_FAILURE); 43 | } 44 | 45 | if(shmctl(shmid, IPC_RMID, 0) == -1){ 46 | fprintf(stderr, "shmctl failed \n"); 47 | exit(EXIT_FAILURE); 48 | } 49 | return 0; 50 | } -------------------------------------------------------------------------------- /code/share_mem_w.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #define TEXT_SZ 2048 7 | struct shared_use_st{ 8 | int written; 9 | char text[TEXT_SZ]; 10 | }; 11 | 12 | int main(){ 13 | int shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT); 14 | if(shmid == -1){ 15 | fprintf(stderr, "shmget failed \n"); 16 | exit(EXIT_FAILURE); 17 | } 18 | 19 | void *shared_memory = shmat(shmid, (void *)0, 0); 20 | if(shared_memory == (void *)-1){ 21 | fprintf(stderr, "shmat failed \n"); 22 | exit(EXIT_FAILURE); 23 | } 24 | printf("memory attached at : % ld \n", (long)shared_memory); 25 | 26 | struct shared_use_st *shared_stuff = (struct shared_use_st *)shared_memory; 27 | shared_stuff->written = 0; 28 | char buffer[TEXT_SZ]; 29 | int running = 1; 30 | while(running){ 31 | while(shared_stuff->written == 1){ 32 | sleep(1); 33 | printf("waiting for client \n"); 34 | } 35 | printf("enter some text \n"); 36 | fgets(buffer, TEXT_SZ, stdin); 37 | strncpy(shared_stuff->text, buffer, TEXT_SZ); 38 | shared_stuff->written = 1; 39 | if(strncmp(buffer, "end", 3) == 0){ 40 | running = 0; 41 | } 42 | } 43 | 44 | if(shmdt(shared_memory) == -1){ 45 | fprintf(stderr, "shmdt failed \n"); 46 | exit(EXIT_FAILURE); 47 | } 48 | return 0; 49 | } -------------------------------------------------------------------------------- /code/keyboard_select_timeout.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int main(int argc, char *argv[]) { 17 | int keyboard; 18 | int ret, i; 19 | char c; 20 | fd_set readfd; 21 | struct timeval timeout; 22 | // 打开键盘文件返回键盘文件描述符 23 | keyboard = open("/dev/tty", O_RDONLY | O_NONBLOCK); 24 | assert(keyboard > 0); 25 | 26 | while(1) { 27 | // 设置超时信息 28 | timeout.tv_sec = 5; 29 | timeout.tv_usec = 0; 30 | // 对readdf清零 31 | FD_ZERO(&readfd); 32 | // 对resdfd标记keyboard的位置为1 33 | FD_SET(keyboard, &readfd); 34 | // 设置select函数参数并调用 35 | ret = select(keyboard + 1, &readfd, NULL, NULL, &timeout); 36 | if(ret == -1) 37 | perror("select error"); 38 | else if(ret) { 39 | // 判断fd是否在readfd集合中 40 | if(FD_ISSET(keyboard, &readfd)) { 41 | // 开始读取键盘输入 42 | i = read(keyboard, &c, 1); 43 | if('\n' == c) 44 | continue; 45 | printf("The time is %c\n", c); 46 | if('q' == c) 47 | break; 48 | } 49 | // 超过5秒没有输入显示超时 50 | else if(ret == 0) 51 | printf("time out\n"); 52 | } 53 | } 54 | return 0; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /code/client_select.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define DEFAULT_PORT 6666 17 | 18 | int main() { 19 | int connfd = 0; 20 | int cLen = 0; 21 | struct sockaddr_in client; 22 | if(argc < 2){ 23 | printf("Usage : client [server ip address] \n"); 24 | return -1; 25 | } 26 | client.sin_family = AF_INET; 27 | client.sin_port = htons(DEFAULT_PORT); 28 | client.sin_addr.a_addr = inet_addr[argv[1]]; 29 | connfd = socket(AF_INET, SOCK_STREAM, 0); 30 | if(connfd < 0){ 31 | perror("socket"); 32 | return -1; 33 | } 34 | if(connect(connfd, (struct sockaddr *)&client, sizeof(client)) < 0){ 35 | perror("connect"); 36 | return -1; 37 | } 38 | char buff[1024]; 39 | bzero(buff, sizeof(buff)); 40 | recv(connfd, buff, 1024, 0); 41 | printf("recv : %s \n", buff); 42 | bzero(buff, sizeof(buff)); 43 | strcpy(buff, "this is client \n"); 44 | send(connfd, buff, 1024, 0); 45 | while(1){ 46 | bzero(buff, sizeof(buff)); 47 | scanf("%s", buff); 48 | int p = strlen(buff); 49 | buff[p] = '\0'; 50 | send(connfd, buff, 1024, 0); 51 | printf("i have send buff \n"); 52 | } 53 | close(fd); 54 | return 0; 55 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![图片](./source/github.jpg) 2 | 3 | # 后台开发核心知识 4 | 5 | > 准备秋招,欢迎来树上取果实。 6 | 7 | 看过很多书,但总是忘得很快。知识广度越大越容易接纳新东西,但从考察角度来说,自然是对某个方面了解越深越好。那些大而全的著作虽然每本都是经典中的经典,但实际工作中可能只用到其中的一小部分。之前实习经历使我对后台开发有了更深刻的认知和了解,现在距离秋招只有两个月了,这里将以最短的篇幅,最清晰的层级结构去总结那些对C++后台开发最为核心的内容。 8 | 9 | 我现在越发觉得少即是多,看再多东西没有理解透彻都是白搭,把最常用的每天过一遍才是最有效的。开发中我们经常用缓存来提高吞吐率,学习知识何不也给自己加个Cache呢? 10 | 11 | 最后,希望大家秋招都能找到满意的工作。 12 | 13 | # 快速索引 14 | 15 | 上面我们提到了Cache来缩小知识范围,但是即使是被压缩过的知识依旧很多,我们怎么能够在脑海中快速检索它们呢?结合查找算法,Hash无疑是最快的,但又有多少人能够给一个"key"立马对应上"value"呢?所以,最适合人类认知的方式是通过索引 + 树状结构,在整理这份笔记时,我划分了很多级索引用来将各部分知识点划分到相应的模块中,检索任意一个知识点最多5级深度,不仅检索速度上去了还可以对整个知识体系有宏观认识。 16 | 17 | | Chapter 1 | Chapter 2 | Chapter 3| Chapter 4 | Chapter 5 | Chapter 6 | Chapter 7| 18 | | :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | 19 | | [错题精解](https://github.com/linw7/Skill-Tree/blob/master/错题精解.md) | [真题摘录](https://github.com/linw7/Skill-Tree/blob/master/%E7%9C%9F%E9%A2%98%E6%91%98%E5%BD%95.md)| [Linux工具](https://github.com/linw7/Skill-Tree/blob/master/Linux工具.md)| [编程语言(C++)](https://github.com/linw7/Skill-Tree/blob/master/编程语言C++.md) | [数据结构与算法](https://github.com/linw7/Skill-Tree/blob/master/数据结构及算法.md) | [计算机网络](https://github.com/linw7/Skill-Tree/blob/master/计算机网络.md) | [操作系统](https://github.com/linw7/Skill-Tree/blob/master/操作系统.md) | 20 | 21 | 22 | # 独立专题 23 | 24 | 哲学中,整体与个体的关系是物质世界普遍存在的规律。上面各部分知识相对独立,既要有零又要有整才能收获更多,实战无疑是最好的。 25 | 26 | - [纸上代码](https://github.com/linw7/Paper-Code) 27 | 28 | - [练手项目](https://github.com/linw7/TKeed) 29 | 30 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://opensource.org/licenses/MIT) 31 | -------------------------------------------------------------------------------- /code/pthread_mutex_cond.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 8 | pthread_cond_t ready = PTHREAD_COND_INITIALIZER; 9 | 10 | int traveler_cond = 0; 11 | 12 | void *traveler_arrive(void *name){ 13 | printf("traveler %s need a taxi now\n", (char *)name); 14 | pthread_mutex_lock(&lock); 15 | traveler_cond++; 16 | pthread_cond_wait(&ready, &lock); 17 | pthread_mutex_unlock(&lock); 18 | printf("traveler %s now got a taxi\n", (char *)name); 19 | pthread_exit((void *)0); 20 | } 21 | 22 | void *taxi_arrived(void *name){ 23 | printf("taxi %s arrives\n", (char *)name); 24 | while(1){ 25 | pthread_mutex_lock(&lock); 26 | if(traveler_cond > 0){ 27 | pthread_cond_signal(&ready); 28 | pthread_mutex_unlock(&lock); 29 | break; 30 | } 31 | pthread_mutex_unlock(&lock); 32 | } 33 | pthread_exit((void *)0); 34 | } 35 | 36 | int main(){ 37 | pthread_t tids[3]; 38 | int iRet = pthread_create(&tids[0], NULL, taxi_arrived, (void *)("Jack")); 39 | if(iRet){ 40 | printf("pthread_create error : iRet = %d\n", iRet); 41 | return iRet; 42 | } 43 | printf("time passing by\n"); 44 | sleep(1); 45 | iRet = pthread_create(&tids[1], NULL, traveler_arrive, (void *)("Susan")); 46 | if(iRet){ 47 | printf("pthread_create error : iRet = %d\n", iRet); 48 | return iRet; 49 | } 50 | printf("time passing by\n"); 51 | sleep(1); 52 | iRet = pthread_create(&tids[0], NULL, taxi_arrived, (void *)("Mike")); 53 | if(iRet){ 54 | printf("pthread_create error : iRet = %d\n", iRet); 55 | return iRet; 56 | } 57 | printf("time passing by\n"); 58 | sleep(1); 59 | void *retval; 60 | for(int i = 0; i < 3; i++){ 61 | iRet = pthread_join(tids[i], &retval); 62 | if(iRet){ 63 | printf("pthread_join error : iRet = %d\n", iRet); 64 | return iRet; 65 | } 66 | printf("retval = %ld\n", (long)retval); 67 | } 68 | return 0; 69 | } -------------------------------------------------------------------------------- /code/client_poll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define MAXLINE 1024 19 | #define DEFAULT_PORT 6666 20 | #define max(a, b) (a > b) ? a : b 21 | 22 | static void handle_connection(int sockfd); 23 | 24 | int main(int argc, char const *argv[]) 25 | { 26 | int connfd = 0; 27 | int cLen = 0; 28 | struct sockaddr_in client; 29 | if(argc < 2){ 30 | printf("Usage : Server IP address \n"); 31 | return -1; 32 | } 33 | connfd = socket(AF_INET, SOCK_STREAM, 0); 34 | client.sin_family = AF_INET; 35 | client.sin_port = htons(DEFAULT_PORT); 36 | client.sin_addr.s_addr = inet_addr(argv[1]); 37 | if(connfd < 0){ 38 | perror("socket"); 39 | return -1; 40 | } 41 | if(connect(connfd, (struct sockaddr *)&client, sizeof(client)) < 0){ 42 | perror("connect"); 43 | return -2; 44 | } 45 | handle_connection(connfd); 46 | return 0; 47 | } 48 | 49 | static void handle_connection(int sockfd){ 50 | char sendline[MAXLINE], recvline[MAXLINE]; 51 | int maxfd, stdineof; 52 | struct pollfd pfds[2]; 53 | int n; 54 | pfds[0].fd = sockfd; 55 | pfds[0].events = POLLIN; 56 | pfds[1].fd = STDIN_FILENO; 57 | pfds[1].events = POLLIN; 58 | while(1){ 59 | poll(pfds, 2 , -1); 60 | if(pfds[0].revents & POLLIN){ 61 | n = read(sockfd, recvline, MAXLINE); 62 | if(n == 0){ 63 | fprintf(stderr, "client : server is closed \n"); 64 | close(sockfd); 65 | } 66 | write(STDOUT_FILENO, recvline, n); 67 | } 68 | if(pfds[1].revents & POLLIN){ 69 | n = read(STDIN_FILENO, sendline, MAXLINE); 70 | if(n == 0){ 71 | shutdown(sockfd, SHUT_WR); 72 | continue; 73 | } 74 | write(sockfd, sendline, n); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /海量数据处理.md: -------------------------------------------------------------------------------- 1 | # 海量数据处理 2 | 3 | 4 | ## TOP N问题 5 | 6 | 1. 如何在海量数据中找出重复最多一个。 7 | 8 | - 通过hash映射为小文件 9 | 10 | - 通过hash_map统计各个小文件重读最多的并记录次数 11 | 12 | - 对每个小文件重复最多的进行建立大根堆 13 | 14 | 2. 上亿有重数据,统计最多前N个。 15 | 16 | - 内存存不下 17 | 18 | - 通过hash映射为小文件 19 | 20 | - 通过hash_map统计各个小文件重读最多的并记录次数 21 | 22 | - 对每个小文件重复最多的进行建立大根堆并重复N次取走堆顶并重建堆操作 23 | 24 | - 内存存得下 25 | 26 | - 直接内存通过hash_map统计并建大根堆 27 | 28 | - 重复N次取走堆顶并重建堆操作 29 | 30 | 3. 海量日志数据,提取出某日访问百度次数最多的那个IP(同1)。 31 | 32 | - 将IP % 1000映射到1000个小文件中 33 | 34 | - 相同IP会被映射到同一个文件 35 | 36 | - 不会出现累加和更大情况 37 | 38 | - 分1000次在内存处理小文件,得到频率最大IP(使用map统计) 39 | 40 | - 对这1000个IP建立大根堆 41 | 42 | 4. 1000w查询串统计最热门10个(同2)。 43 | 44 | - 同上 45 | 46 | 47 | 5. 1G的文件,里面1行1个不超过16字节的词。内存限制1M,返回频数最高前100(同2)。 48 | 49 | - 将单词 % 5000存入5000小文件 50 | 51 | - 平均各文件约200K 52 | 53 | - 对超过1M的文件继续分割直到小于200K 54 | 55 | - 使用map统计各个词出现的频率 56 | 57 | - 对5000词使用堆排序或归并排序 58 | 59 | ## 分布式TOP N问题 60 | 61 | 6. 分布在100台电脑的海量数据,统计前十。 62 | 63 | - 各数据只出现在一台机器中 64 | 65 | - 先在独立机器得到前十 66 | 67 | - 若可以放入内存直接堆排序 68 | 69 | - 若不可全放入内存:哈希分块 -> map统计 -> 归总堆排 70 | 71 | - 再将100台计算机的TOP10组合起来堆排序 72 | 73 | - 同一元素可同时出现在不同机器中 74 | 75 | - 遍历所有数据,重新hash取模,使同一个元素只出现在单独的一台电脑中,然后采用上面方法先统计每台电脑TOP10再汇总起来 76 | 77 | ## 快速外排序问题 78 | 79 | 7. 有10个1G文件,每行都是一个可重复用户query,按query频度排序。 80 | 81 | - 顺序读取十个文件并采取哈希,将query写入10个文件中 82 | 83 | - 通过hash_map(query, count)统计每个query出现次数,至少2G内存 84 | 85 | - 通过得到的hash_map中query和query_count,对query_count排序并将重新输出到文件中,得到已排序好的文件 86 | 87 | - 对十个文件进行归并排序(外排序) 88 | 89 | ## 公共数据问题 90 | 91 | 8. A,B两个文件各存放50亿url,每个为64Byte,限制内存4G找出公共url。 92 | 93 | - 对A和B两个大文件,先通过url % 1000将数据映射到1000个文件中,单个文件大小约320M(我们只需要检查对应小文件A1 V B1......,不对应小文件不会有相同url) 94 | 95 | - 通过hash_set统计,把A1的url存储到hash_set中,再遍历对应的B1小文件,检查是否在hash_set中,若存在则写入外存。重复循环处理对应的1000个对。 96 | 97 | 9. 1000w有重字符串,对字符串去重。 98 | 99 | - 先hash分为多个文件 100 | 101 | - 逐个文件检查并插入set中 102 | 103 | - 多个set取交集 104 | 105 | ## 内存内TOP N问题 106 | 107 | 10. 100w个数字找出最大100个。 108 | 109 | - 堆排序法 110 | 111 | - 建大根堆,取走堆顶并重建堆,重复100次 112 | 113 | - 快排法 114 | 115 | - 使用快速排序划分,若某次枢纽元在后10000时(具体情况具体分析),对后10000数据排序后取前100 116 | 117 | ## 位图法 118 | 119 | 11. 在2.5亿数字中找出不重复的整数。 120 | 121 | - 使用2-Bit位图法,00表示不存在,01表示出现一次,10表示出现多次,11无意义。这样只需要1G内存。 122 | 123 | - 或者hash划分小文件,小文件使用hash_set检查各个元素,得到的。 124 | 125 | 12. 如何在40亿数字中快速判断是否有某个数? 126 | 127 | - 位图法标记某个数字是否存在,check标记数组。 128 | -------------------------------------------------------------------------------- /code/vector_iterator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | void is_same(vector::iterator it, vector::iterator it_1) { 7 | 8 | } 9 | 10 | int main() { 11 | vector vec; 12 | for(int i = 1; i <= 7; ++i) 13 | vec.push_back(i); 14 | cout << "The capacity = " << vec.capacity() << endl << endl; 15 | 16 | // size <= capacity,不需要重新分配 17 | auto itbegin = vec.begin(); 18 | auto itend = vec.end(); 19 | vec.push_back(8); 20 | auto itbegin_1 = vec.begin(); 21 | auto itend_1 = vec.end(); 22 | if(itbegin == itbegin_1) 23 | cout << "Begin same." << endl; 24 | else 25 | cout << "Begin different." << endl; 26 | if(itend == itend_1) 27 | cout << "End same." << endl; 28 | else 29 | cout << "End different." << endl; 30 | cout << endl; 31 | 32 | // size > capacity,重新分配内存 33 | vec.push_back(9); 34 | auto itbegin_2 = vec.begin(); 35 | auto itend_2 = vec.end(); 36 | if(itbegin_2 == itbegin_1) 37 | cout << "Begin same." << endl; 38 | else 39 | cout << "Begin different." << endl; 40 | if(itend_2 == itend_1) 41 | cout << "End same" < 6 | 7 | typedef struct rb_node { 8 | int key; 9 | struct rb_node *parent; 10 | struct rb_node *lchild, 11 | *rchild; 12 | char color; 13 | } *rb_node; 14 | struct rb_tree { 15 | rb_node root; 16 | rb_node nil; 17 | }; 18 | 19 | /* Our pool of nodes for faster allocation */ 20 | static rb_node rb_mem_pool = NULL; 21 | 22 | 23 | /* Section 1: Creating and freeing trees and nodes */ 24 | /* Helper routine: frees a subtree rooted at specified node. */ 25 | static void rb_free_subtree(rb_tree tree, rb_node node); 26 | /* Creates a new node, taking from the memory pool if available. */ 27 | static rb_node rb_new_node(rb_tree tree, int data); 28 | /* Frees a node to the memory pool. */ 29 | static void rb_free_node(rb_node node); 30 | 31 | /* Section 2: Insertion */ 32 | /* Corrects for properties violated on an insertion. */ 33 | static void rb_insert_fix(rb_tree tree, rb_node n); 34 | /* Helper routine: returns the uncle of a given node. */ 35 | static rb_node rb_get_uncle(rb_tree tree, rb_node n); 36 | 37 | /* Section 3: Deletion */ 38 | /* Helper routine: transplants node `from' into node `to's position. */ 39 | static void rb_transplant(rb_tree tree, rb_node to, rb_node from); 40 | /* Corrects for properties violated on a deletion. */ 41 | static void rb_delete_fix(rb_tree tree, rb_node n); 42 | 43 | /* Section 4: I/O */ 44 | /* Helper routine: write an entire subtree to stdout. */ 45 | static void rb_preorder_write(rb_tree tree, rb_node n); 46 | /* Reads a tree in preorder format, limited by the maximum value of max. */ 47 | static rb_node rb_read_subtree(rb_tree tree, rb_node *next, int max, FILE *fp); 48 | /* Helper routine: read a single node from file fp. */ 49 | static rb_node rb_read_node(rb_tree tree, FILE *fp); 50 | 51 | /* Section 5: General helper routines */ 52 | /* Returns a node with the given key. */ 53 | static rb_node rb_get_node_by_key(rb_tree haystack, int needle); 54 | /* Rotates a tree around the given root. */ 55 | static void rb_rotate(rb_tree tree, rb_node root, int go_left); 56 | /* Returns minimum node in the given subtree. */ 57 | static rb_node rb_min(rb_tree tree, rb_node node); 58 | /* Computes height of the tree rooted at node n. */ 59 | static int rb_height(rb_tree tree, rb_node n); 60 | 61 | /* Section 6: SVG */ 62 | #define RADIUS 15.0 /* Radius of each node */ 63 | #define PADDING 10.0 /* Padding between nodes */ 64 | #define MAXWIDTH 1000 /* Maximum width of an image in px */ 65 | #define IMGBORDER 5 /* Blank space around image */ 66 | /* Draws a subtree rooted at a node n */ 67 | static void rb_draw_subtree(FILE *fp, rb_tree tree, rb_node n, double x, 68 | double y, int h, int rowpos, double factor); 69 | /* Calculates x position of circle exp rows from the top, at position rowpos in 70 | * its row. factor corrects for an image which would be greater than MAXWIDTH. */ 71 | static double calcpos(int exp, int rowpos, double factor); 72 | 73 | #endif /* RBTREE_PRIV_H */ 74 | -------------------------------------------------------------------------------- /code/RBTree/main.c: -------------------------------------------------------------------------------- 1 | #include "RBtree.h" 2 | #include 3 | #include 4 | 5 | #define READFILE "RBinput.txt" 6 | #define DRAWFILE "RBdrawing.svg" 7 | 8 | void help() { 9 | printf( 10 | "Commands:\n" 11 | "\tC - Create empty tree\n" 12 | "\tR - Read tree from %s\n" 13 | "\tW - Write tree to screen in preorder format\n" 14 | "\tI n - Insert node with key `n' into tree\n" 15 | "\tD n - Delete node with key `n' from tree\n" 16 | "\tP - draw Picture in %s\n" 17 | "\tH - Help\n" 18 | "\tS - Stop\n", 19 | READFILE, DRAWFILE); 20 | } 21 | 22 | int main(int argc, char *argv[]) { 23 | rb_tree tree = NULL, 24 | tmp = NULL; 25 | int cmd = 0; 26 | 27 | printf("This is a demo for RBTree\n"); 28 | help(); 29 | while (cmd != EOF) { 30 | int arg; 31 | printf("$ "); 32 | fflush(stdout); 33 | /* Find the first non-whitespace character */ 34 | while (isspace((cmd = getchar()))) { 35 | if (cmd == '\n') { 36 | /* We delete up to end of line after the 37 | * switch, so put the \n back on. */ 38 | ungetc(cmd, stdin); 39 | break; 40 | } 41 | } 42 | switch(cmd) { 43 | case 'C': 44 | case 'c': 45 | /* If we already had a tree, we need to free up 46 | * the memory. */ 47 | if (tree != NULL) { 48 | printf("Tree already exists - clearing.\n"); 49 | RBfree(tree); 50 | } 51 | tree = RBcreate(); 52 | break; 53 | case 'R': 54 | case 'r': 55 | tmp = RBread(READFILE); 56 | /* If there was an error, just keep the tree we 57 | * have. */ 58 | if (tmp != NULL) { 59 | if (tree != NULL) { 60 | printf("Non-empty tree - overwriting.\n"); 61 | RBfree(tree); 62 | } 63 | tree = tmp; 64 | } 65 | break; 66 | case 'W': 67 | case 'w': 68 | if (tree == NULL) { 69 | fprintf(stderr, "Error: no tree loaded, cannot write.\n"); 70 | } else { 71 | RBwrite(tree); 72 | } 73 | break; 74 | case 'I': 75 | case 'i': 76 | if (tree == NULL) { 77 | printf("No tree loaded - creating empty one.\n"); 78 | tree = RBcreate(); 79 | } 80 | if (scanf("%d", &arg) != 1) { 81 | fprintf(stderr, "Error: must specify integer key to insert.\n"); 82 | } else { 83 | RBinsert(tree, arg); 84 | } 85 | break; 86 | case 'D': 87 | case 'd': 88 | if (tree == NULL) { 89 | fprintf(stderr, "Error: no tree loaded, cannot delete.\n"); 90 | } else { 91 | if (scanf("%d", &arg) != 1) { 92 | fprintf(stderr, "Error: must specify integer key to delete.\n"); 93 | } else { 94 | RBdelete(tree, arg); 95 | } 96 | } 97 | break; 98 | case 'P': 99 | case 'p': 100 | if (tree == NULL) { 101 | fprintf(stderr, "Error: no tree loaded, cannot draw.\n"); 102 | } else { 103 | RBdraw(tree, DRAWFILE); 104 | } 105 | break; 106 | case 'H': 107 | case 'h': 108 | help(); 109 | break; 110 | case EOF: 111 | /* Make the shell not return on the same line */ 112 | putchar('\n'); 113 | case 'S': 114 | case 's': 115 | cmd = EOF; 116 | break; 117 | /* Corresponds to an empty command */ 118 | case '\n': 119 | break; 120 | default: 121 | fprintf(stderr, "Error: unknown command `%c'.\n", cmd); 122 | help(); 123 | break; 124 | } 125 | if (cmd != EOF) { 126 | /* Delete the rest of the line. */ 127 | while ((cmd = getchar()) != '\n'); 128 | } 129 | } 130 | 131 | /* We need to free the tree. */ 132 | if (tree != NULL) { 133 | RBfree(tree); 134 | } 135 | RBcleanup(); 136 | 137 | return 0; 138 | } 139 | -------------------------------------------------------------------------------- /code/client_epoll.2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define IPADDRESS "127.0.0.1" 13 | #define SERV_PORT 6666 14 | #define MAXSIZE 1024 15 | #define FDSIZE 1024 16 | #define EPOLLEVENTS 20 17 | 18 | int create_sock(){ 19 | int sockfd; 20 | struct sockaddr_in servaddr; 21 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 22 | bzero(&servaddr, sizeof(servaddr)); 23 | servaddr.sin_family = AF_INET; 24 | servaddr.sin_port = htons(SERV_PORT); 25 | return sockfd; 26 | } 27 | 28 | void add_event(int epollfd, int fd, int state){ 29 | struct epoll_event ev; 30 | ev.events = state; 31 | ev.data.fd = fd; 32 | epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev); 33 | } 34 | 35 | void delete_event(int epollfd, int fd, int state){ 36 | struct epoll_event ev; 37 | ev.events = state; 38 | ev.data.fd = fd; 39 | epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev); 40 | } 41 | 42 | void mod_event(int epollfd, int fd, int state){ 43 | struct epoll_event ev; 44 | ev.events = state; 45 | ev.data.fd = fd; 46 | epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev); 47 | } 48 | 49 | void do_read(int epollfd, int fd, int sockfd, char *buff){ 50 | int nread = read(fd, buff, MAXSIZE); 51 | if(nread == -1){ 52 | perror("read error"); 53 | close(fd); 54 | } 55 | else if(nread == 0){ 56 | fprintf(stderr, "server close"); 57 | close(fd); 58 | } 59 | else{ 60 | if(fd == STDIN_FILENO) 61 | add_event(epollfd, sockfd, EPOLLOUT); 62 | else{ 63 | delete_event(epollfd, sockfd, EPOLLIN); 64 | add_event(epollfd, STDOUT_FILENO, EPOLLOUT); 65 | } 66 | } 67 | } 68 | 69 | void do_write(int epollfd, int fd, int sockfd, char *buff){ 70 | char temp[100]; 71 | int count = 0; 72 | buff[strlen(buff) - 1] = '\0'; 73 | snprintf(temp, sizeof(temp), "%s_%02d\n", buff, count++); 74 | int nwrite = write(fd, temp, strlen(temp)); 75 | if(nwrite == -1){ 76 | perror("write error"); 77 | close(fd); 78 | } 79 | else{ 80 | if(fd == STDOUT_FILENO) 81 | delete_event(epollfd, fd, EPOLLOUT); 82 | else 83 | mod_event(epollfd, fd, EPOLLIN); 84 | } 85 | memset(buff, 0, MAXSIZE); 86 | } 87 | 88 | void handle_events(int epollfd, struct epoll_event *events, int num, int sockfd, char *buff){ 89 | for(int i = 0; i < num; i++){ 90 | // fd为有事件产生的描述符 91 | int fd = events[i].data.fd; 92 | if(events[i].events & EPOLLIN) 93 | do_read(epollfd, fd, sockfd, buff); 94 | else if(events[i].events & EPOLLOUT) 95 | do_write(epollfd, fd, sockfd, buff); 96 | } 97 | } 98 | 99 | void handle_connection(int sockfd){ 100 | struct epoll_event events[EPOLLEVENTS]; 101 | char buff[MAXSIZE]; 102 | int epollfd = epoll_create(FDSIZE); 103 | add_event(epollfd, STDIN_FILENO, EPOLLIN); 104 | while(1){ 105 | int ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1); 106 | handle_events(epollfd, events, ret, sockfd, buff); 107 | } 108 | close(epollfd); 109 | } 110 | 111 | int main(){ 112 | struct sockaddr_in servaddr; 113 | bzero(&servaddr, sizeof(servaddr)); 114 | servaddr.sin_family = AF_INET; 115 | servaddr.sin_port = htons(SERV_PORT); 116 | inet_pton(AF_INET, IPADDRESS, &servaddr.sin_addr); 117 | int sockfd = socket(AF_INET, SOCK_STREAM, 0); 118 | connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 119 | handle_connection(sockfd); 120 | close(sockfd); 121 | return 0; 122 | } 123 | 124 | -------------------------------------------------------------------------------- /code/server_poll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define IPADDRESS "127.0.0.1" 19 | #define PORT 6666 20 | #define MAXLINE 1024 21 | #define LISTENQ 5 22 | #define OPEN_MAX 1000 23 | #define INFTIM -1 24 | 25 | int bind_and_listen() { 26 | int servfd; 27 | struct sockaddr_in my_addr; 28 | unsigned int sin_size; 29 | if((servfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { 30 | perror("socket"); 31 | return -1; 32 | } 33 | printf("socket ok \n"); 34 | 35 | my_addr.sin_family = AF_INET; 36 | my_addr.sin_port = htons(PORT); 37 | my_addr.sin_addr.s_addr = INADDR_ANY; 38 | bzero(&(my_addr.sin_zero), 0); 39 | if(bind(servfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1){ 40 | perror("bind"); 41 | return -2; 42 | } 43 | printf("bind ok \n"); 44 | 45 | if(listen(servfd, LISTENQ) == -1){ 46 | perror("listen"); 47 | return -3; 48 | } 49 | printf("listen ok \n"); 50 | return servfd; 51 | } 52 | 53 | void do_poll(int listenfd){ 54 | int connfd, sockfd; 55 | struct sockaddr_in client_addr; 56 | socklen_t client_addr_len; 57 | struct pollfd client_fds[OPEN_MAX]; 58 | int max = 0; 59 | int nready; 60 | 61 | client_fds[0].fd = listenfd; 62 | client_fds[0].events = POLLIN; 63 | 64 | for(int i = 1; i < OPEN_MAX; ++i) 65 | client_fds[i].fd = -1; 66 | while(1){ 67 | // 有新client接入时先判断是否在比较大的下标位置,是的话修改max,否则不用更新 68 | nready = poll(client_fds, max + 1, INFTIM); 69 | if(nready == -1){ 70 | perror("poll error"); 71 | exit(1); 72 | } 73 | // 有新连接时必须接受,获得新fd,并将新fd放到数组中 74 | if(client_fds[0].revents & POLLIN){ 75 | client_addr_len = sizeof(client_addr); 76 | // 建立新连接 77 | if((connfd = accept(listenfd, (struct sockaddr *)&client_addr, &client_addr_len)) == -1){ 78 | if(errno == EINTR) 79 | continue; 80 | else{ 81 | perror("accept error"); 82 | exit(1); 83 | } 84 | } 85 | // 打印新连接的IP + port 86 | fprintf(stdout, "accept a new client : %s:%d \n", inet_ntoa(client_addr.sin_addr), client_addr.sin_port); 87 | // 将新的描述符添加到数组中(选取client_fds[0]之外数组中第一个未使用槽) 88 | for(int i = 1; i < OPEN_MAX; i++){ 89 | if(client_fds[i].fd < 0){ 90 | max = (i > max ? i : max); 91 | // 判断是否到了最大连接数 92 | if(max == OPEN_MAX){ 93 | fprintf(stderr, "too many clients. \n"); 94 | exit(1); 95 | } 96 | printf("current connection num = %d\n", max); 97 | // 添加至读描述符集合 98 | client_fds[i].fd = connfd; 99 | client_fds[i].events = POLLIN; 100 | printf("already add !\n"); 101 | break; 102 | } 103 | } 104 | // 记录客户连接套接字个数 105 | if(--nready <= 0) 106 | continue; 107 | } 108 | char buff[MAXLINE]; 109 | memset(buff, 0, MAXLINE); 110 | int readlen = 0; 111 | for(int i = 1; i <= max; i++){ 112 | // 寻找集合中已存在描述符并测试客户端描述符是否准备好 113 | if(client_fds[i].fd < 0) 114 | continue; 115 | if(client_fds[i].revents & POLLIN){ 116 | // 客户端准备好则接受客户端发送的信息 117 | readlen = read(client_fds[i].fd, buff, MAXLINE); 118 | // 返回0表示EOF,结束读取,清除该描述符 119 | if(readlen == 0){ 120 | close(client_fds[i].fd); 121 | client_fds[i].fd = -1; 122 | continue; 123 | } 124 | // 向客户端发送buff 125 | write(STDOUT_FILENO, buff, readlen); 126 | write(client_fds[i].fd, buff, readlen); 127 | } 128 | } 129 | } 130 | } 131 | int main(int argc, char *argv[]){ 132 | int listenfd = bind_and_listen(); 133 | if(listenfd < 0) 134 | return 0; 135 | do_poll(listenfd); 136 | return 0; 137 | } 138 | -------------------------------------------------------------------------------- /code/server_select.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | 19 | int main() { 20 | // servfd监听描述符,acceptfd连接描述符 21 | int servfd, acceptfd; 22 | struct sockaddr_in my_addr; 23 | // 创建套接字(IPv4 + TCP) 24 | if((servfd == socket(AF_INET, SOCK_STREAM, 0) == -1)) { 25 | perror("socket"); 26 | return -1; 27 | } 28 | printf("socket ok \n"); 29 | // 设置sockaddr_in结构的类型,端口号,IP地址 30 | my_addr.sin_family = AF_INET; 31 | my_addr.sin_port = htons(6666); 32 | my_addr.sin_addr.s_addr = htonl(INADDR_ANY); 33 | // 对my_addr清零 34 | bzero(&(my_addr.sin_zero), 0); 35 | // 将套接字描述符和sockaddr_in结构绑定 36 | if(bind(servfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1){ 37 | perror("bind"); 38 | return -2; 39 | } 40 | printf("bind ok\n"); 41 | 42 | int maxsock = servfd; 43 | fd_set client_fdset; 44 | struct timeval tv; 45 | int client_sockfd[5]; 46 | bzero((void *)client_sockfd, sizeof(client_sockfd)); 47 | int conn_amount = 0; 48 | char buffer[1024]; 49 | int ret = 0; 50 | 51 | while(1){ 52 | // 将fd_set集合置零并重设fd位,设置超时时间 53 | FD_ZERO(&client_fdset); 54 | FD_SET(servfd, &client_fdset); 55 | tv.tv_sec = 30; 56 | tv.tv_usec = 0; 57 | // 把已连上的client描述符加入集合中 58 | for(int i = 0; i < 5; ++i){ 59 | if(client_sockfd[i] != 0) 60 | FD_SET(client_sockfd[i], &client_fdset); 61 | } 62 | // 调用select函数并检查是否正确 63 | ret = select(maxsock + 1, &client_fdset, NULL, NULL, &tv); 64 | if(ret < 0){ 65 | perror("select error \n"); 66 | break; 67 | } 68 | else if(ret == 0){ 69 | printf("timeout \n"); 70 | continue; 71 | } 72 | // 遍历每一个已连上的client的fd,查看是否有数据,conn_amount初始为0 73 | for(int i = 0; i < conn_amount; ++i) { 74 | if(FD_ISSET(client_sockfd[i], &client_fdset)){ 75 | printf("start recv from client[%d] : \n", i); 76 | ret = recv(client_sockfd[i], buffer, 1024, 0); 77 | // 无数据可读则关闭连接且在集合中清理掉 78 | if(ret <= 0){ 79 | printf("client[%d] close\n", i); 80 | close(client_sockfd[i]); 81 | FD_CLR(client_sockfd[i], &client_fdset); 82 | client_sockfd[i] = 0; 83 | } 84 | // 有数据可读 85 | else 86 | printf("recv from client[%d] : %s\n", i, buffer); 87 | } 88 | } 89 | // 检查是否有新连接 90 | if(FD_ISSET(servfd, &client_fdset)){ 91 | struct sockaddr_in client_addr; 92 | size_t size = sizeof(struct sockaddr_in); 93 | // 服务器接收新连接并返回连接描述符sock_client 94 | int sock_client = accept(servfd, (struct sockaddr *)(&client_addr), (unsigned int *)(&size)); 95 | if(sock_client < 0){ 96 | perror("accept error \n"); 97 | continue; 98 | } 99 | // 已连接数小于5接受新连接 100 | if(conn_amount < 5){ 101 | client_sockfd[conn_amount++] = sock_client; 102 | bzero(buffer, 1024); 103 | // 建立起连接向客户机发送消息 104 | strcpy(buffer, "this is server ! welcome \n "); 105 | send(sock_client, buffer, 1024, 0); 106 | // 打印客户机信息,IP + port 107 | printf("new connection client[%d] %s : %d \n", conn_amount, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); 108 | bzero(buffer, sizeof(buffer)); 109 | // 开始调用recv接收用户输入数据 110 | ret = recv(sock_client, buffer, 1024, 0); 111 | if(ret < 0){ 112 | perror("recv error \n"); 113 | close(servfd); 114 | return -1; 115 | } 116 | printf("recv : %s \n", buffer); 117 | // 检查新建立连接的描述符是否比当前最大的更大并更新(select第一个参数) 118 | if(sock_client > maxsock) { 119 | maxsock = sock_client; 120 | } 121 | } 122 | // 超过最大连接 123 | else{ 124 | printf("max connections, quit \n"); 125 | break; 126 | } 127 | } 128 | } 129 | // 关掉已连上的fd和自身fd 130 | for(int i = 0; i < 5; ++i){ 131 | if(client_sockfd[i] != 0) 132 | close(client_sockfd[i]); 133 | } 134 | close(servfd); 135 | return 0; 136 | } 137 | -------------------------------------------------------------------------------- /code/server_epoll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define IPADDRESS "127.0.0.1" 13 | #define PORT 6666 14 | #define MAXSIZE 1024 15 | #define LISTENQ 5 16 | #define FDSIZE 1000 17 | #define EPOLLEVENTS 100 18 | 19 | int sock_bind(const char *ip, int port){ 20 | struct sockaddr_in servaddr; 21 | int listenfd = socket(AF_INET, SOCK_STREAM, 0); 22 | if(listenfd == -1){ 23 | perror("socket error"); 24 | exit(1); 25 | } 26 | bzero(&servaddr, sizeof(servaddr)); 27 | servaddr.sin_family = AF_INET; 28 | inet_pton(AF_INET, ip, &servaddr.sin_addr); 29 | servaddr.sin_port = htons(port); 30 | if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1){ 31 | perror("bind error"); 32 | exit(1); 33 | } 34 | return listenfd; 35 | } 36 | 37 | void delete_event(int epollfd, int fd, int state){ 38 | struct epoll_event ev; 39 | ev.events = state; 40 | ev.data.fd = fd; 41 | epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev); 42 | } 43 | 44 | void modify_event(int epollfd, int fd, int state){ 45 | struct epoll_event ev; 46 | ev.events = state; 47 | ev.data.fd = fd; 48 | epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev); 49 | } 50 | 51 | void do_read(int epollfd, int fd, char *buff){ 52 | // 读数据 53 | int nread = read(fd, buff, MAXSIZE); 54 | if(nread == -1){ 55 | perror("read error"); 56 | close(fd); 57 | // 发生错误,从注册的fd中删除此客户fd 58 | delete_event(epollfd, fd, EPOLLIN); 59 | } 60 | else if(nread == 0){ 61 | fprintf(stderr, "client close \n"); 62 | close(fd); 63 | // 完成读操作,删除客户fd 64 | delete_event(epollfd, fd, EPOLLIN); 65 | } 66 | else{ 67 | printf("read message is : %s \n", buff); 68 | // 事件由读改为写 69 | modify_event(epollfd, fd, EPOLLOUT); 70 | } 71 | } 72 | 73 | void do_write(int epollfd, int fd, char *buff){ 74 | int nwrite = write(fd, buff, strlen(buff)); 75 | if(nwrite == -1){ 76 | perror("write error \n"); 77 | close(fd); 78 | // 发生错误从注册中删除 79 | delete_event(epollfd, fd, EPOLLIN); 80 | } 81 | else 82 | modify_event(epollfd, fd, EPOLLIN); 83 | // 写完刷新buff 84 | memset(buff, 0, MAXSIZE); 85 | } 86 | 87 | void add_event(int epollfd, int fd, int state){ 88 | // ev用注册事件,state表示监听的事件,fd是被注册的fd 89 | struct epoll_event ev; 90 | ev.events = state; 91 | ev.data.fd = fd; 92 | // 调用epoll_ctl注册,函数类型通过EPOLL_CTL_XX来区别 93 | // 将新的fd注册到epollfd 94 | epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev); 95 | } 96 | 97 | 98 | void handle_accept(int epollfd, int listenfd){ 99 | struct sockaddr_in cliaddr; 100 | socklen_t cliaddrlen = 0; 101 | memset(&cliaddr, 0, sizeof(struct sockaddr_in)); 102 | // 接受客户请求并返回客户fd 103 | int clifd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen); 104 | if(clifd == -1) 105 | perror("accept error"); 106 | else{ 107 | printf("accept a new client : %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port); 108 | // 将客户fd同样注册到epollfd中 109 | add_event(epollfd, clifd, EPOLLIN); 110 | } 111 | } 112 | 113 | void handle_events(int epollfd, struct epoll_event *events, int num, int listenfd, char *buff){ 114 | // 遍历events数组,获得有事件产生的fd及产生的事件 -> 对应操作 115 | for(int i = 0; i < num; i++){ 116 | int fd = events[i].data.fd; 117 | // 监听描述符监听到事件产生,执行gaccept操作 118 | if((fd == listenfd) && (events[i].events & EPOLLIN)) 119 | handle_accept(epollfd, listenfd); 120 | // 监听到有读事件,执行读操作 121 | else if(events[i].events & EPOLLIN) 122 | do_read(epollfd, fd, buff); 123 | // 执行写操作 124 | else if(events[i].events & EPOLLOUT) 125 | do_write(epollfd, fd, buff); 126 | } 127 | } 128 | 129 | void do_epoll(int listenfd){ 130 | // 用户态buff缓冲 131 | char buff[MAXSIZE]; 132 | struct epoll_event events[EPOLLEVENTS]; 133 | // 创建一个epoll句柄,监听数为PDSIZE 134 | int epollfd = epoll_create(FDSIZE); 135 | // 在epollfd中注册监听listenfd 136 | add_event(epollfd, listenfd, EPOLLIN); 137 | while(1){ 138 | // 等待事件产生,类似于select() 139 | // events用于返回事件集合 140 | // ret需要处理的事件数目,0表示已超时 141 | int ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1); 142 | // 根据返回的事件数和事件类型,派发到具体具体流程 143 | handle_events(epollfd, events, ret, listenfd, buff); 144 | } 145 | close(epollfd); 146 | } 147 | 148 | int main(){ 149 | // 创建套接字并绑定地址,返回监听描述符 150 | int listenfd = sock_bind(IPADDRESS, PORT); 151 | // 开始监听 152 | listen(listenfd, LISTENQ); 153 | // 注册监听描述符并响应用户请求 154 | do_epoll(listenfd); 155 | return 0; 156 | } 157 | 158 | /* 159 | * 创建epoll,返回epollfd 160 | * 向epoll中注册listenfd 161 | * 调用epoll_wait阻塞掉 162 | * 等待回调函数填充events数组 163 | * 遍历event数组检查fd和请求 164 | * 分发到对应操作 165 | * 操作执行后要根据执行结果对fd重新注册、删除或修改 166 | */ 167 | -------------------------------------------------------------------------------- /code/my_vector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define DOUBLE_SIZE 4 | using namespace std; 5 | 6 | template 7 | class Vector { 8 | private: 9 | const int ADD_SIZE = 64; 10 | T *array; 11 | unsigned int vsize; 12 | unsigned int vcapacity; 13 | T *allocator(unsigned int size); 14 | void destory(T *array); 15 | public: 16 | Vector():array(0), vsize(0), vcapacity(0){} 17 | Vector(const T& t, unsigned int n); 18 | Vector(const Vector& other); 19 | Vector& operator =(Vector& other); 20 | T &operator[](unsigned int pos); 21 | unsigned int size(); 22 | unsigned int capacity(); 23 | bool empty(); 24 | void clear(); 25 | void push_back(const T& t); 26 | void insert_after(int pos, const T& t); 27 | void push_front(const T& t); 28 | void insert_before(int pos, const T& t); 29 | void erase(unsigned int pos); 30 | void print(); 31 | ~Vector() { 32 | clear(); 33 | } 34 | }; 35 | template Vector::Vector(const T& t, unsigned int n):array(0), vsize(0), vcapacity(0) { 36 | while(n--) { 37 | push_back(t); 38 | } 39 | } 40 | template Vector::Vector(const Vector& other) { 41 | array = other.array; 42 | vsize = other.vsize; 43 | vcapacity = other.vcapacity; 44 | } 45 | template unsigned int Vector::size() { 46 | return vsize; 47 | } 48 | template unsigned int Vector::capacity() { 49 | return vcapacity; 50 | } 51 | template bool Vector::empty() { 52 | return (vsize == 0); 53 | } 54 | template void Vector::clear() { 55 | destory(array); 56 | array = 0; 57 | vsize = 0; 58 | vcapacity = 0; 59 | } 60 | template void Vector::push_back(const T& t) { 61 | insert_after(vsize - 1, t); 62 | } 63 | template void Vector::insert_after(int pos, const T& t) { 64 | insert_before(pos + 1, t); 65 | } 66 | template void Vector::push_front(const T& t) { 67 | insert_before(0, t); 68 | } 69 | template void Vector::insert_before(int pos, const T& t) { 70 | if(vsize == vcapacity) { 71 | T *old_array = array; 72 | #ifdef DOUBLE_SIZE 73 | if(vcapacity == 0) 74 | vcapacity = 1; 75 | vcapacity = vcapacity << 1; 76 | #else 77 | vcapacity += ADD_SIZE; 78 | #endif 79 | array = allocator(vcapacity); 80 | for (unsigned int i = 0; i < vsize; ++i) 81 | array[i] = old_array[i]; 82 | destory(old_array); 83 | } 84 | for(int i = (int)vsize++; i > pos; --i) 85 | array[i] = array[i - 1]; 86 | array[pos] = t; 87 | } 88 | template void Vector::erase(unsigned int pos) { 89 | if(pos < vsize) { 90 | --vsize; 91 | for(unsigned int i = pos; i < vsize; ++i) 92 | array[i] = array[i + 1]; 93 | } 94 | } 95 | template void Vector::print() { 96 | for(unsigned int i = 0; i < size(); ++i) { 97 | cout << array[i] << " "; 98 | } 99 | cout << endl; 100 | cout << "Real time size = " << size() << endl; 101 | cout << "The capacity = " << capacity() << endl << endl; 102 | } 103 | template T* Vector::allocator(unsigned int size){ 104 | return new T[size]; 105 | } 106 | template void Vector::destory(T *array) { 107 | if(array) 108 | delete[] array; 109 | } 110 | template Vector& Vector::operator =(Vector& other) { 111 | /* 1. 判断是否为同一个对象 112 | * 2. 清空本身变量、释放申请的内存 113 | * 3. 重新申请内存 114 | * 4. 逐字节复制(深拷贝) 115 | */ 116 | if(this == &other) 117 | return *this; 118 | clear(); 119 | vsize = other.size(); 120 | vcapacity = other.capacity(); 121 | array = new T[vcapacity]; 122 | for(unsigned int i = 0; i < vsize; ++i) { 123 | array[i] = other[i]; 124 | } 125 | return *this; 126 | } 127 | template T& Vector::operator[](unsigned int pos) { 128 | assert(pos < vsize); 129 | return array[pos]; 130 | } 131 | 132 | int main() { 133 | Vector my_vector; 134 | // 插入元素 135 | my_vector.push_back(1); 136 | my_vector.push_back(2); 137 | my_vector.push_back(3); 138 | my_vector.push_front(0); 139 | if(my_vector.size() == 4) 140 | my_vector.print(); 141 | 142 | // 删除元素(i为下标) 143 | my_vector.erase(1); 144 | if((!my_vector.empty()) && my_vector.capacity() == 64) 145 | my_vector.print(); 146 | 147 | // 清空 148 | my_vector.clear(); 149 | if(my_vector.empty()) 150 | cout << "Vector is empty !" << endl << endl; 151 | 152 | // 测试动态扩展 153 | for(int i = 0; i < 100; i++) 154 | my_vector.push_back(i); 155 | my_vector.print(); 156 | 157 | // 测试拷贝构造函数(浅拷贝) 158 | Vector my_vector_2(my_vector); 159 | /* 160 | * my_vector.clear(); 161 | * 调用此句会出错,my_vector指向的内存被释放 162 | * my_vector_2中array指针为野指针 163 | */ 164 | my_vector_2.print(); 165 | 166 | // 测试=重载运算符 167 | Vector my_vector_1(0, 10); 168 | my_vector_1 = my_vector; 169 | my_vector_1.print(); 170 | 171 | // 测试[]重载运算符 172 | int n; 173 | cin >> n; 174 | cout << "The " << n << "th element = " << my_vector[n] << endl << endl; 175 | 176 | return 0; 177 | } -------------------------------------------------------------------------------- /网络编程.md: -------------------------------------------------------------------------------- 1 | # 网络编程基础 2 | 3 | ## 常见问题 4 | 5 | ### Socket API 6 | 7 | 1. 网络编程一般步骤? 8 | 9 | - TCP: 10 | 11 | - 服务端:socket -> bind -> listen -> accept -> recv/send -> close。 12 | 13 | - 客户端:socket -> connect -> send/recv -> close。 14 | 15 | - UDP: 16 | 17 | - 服务端:socket -> bind -> recvfrom/sendto -> close。 18 | 19 | - 客户端:socket -> sendto/recvfrom -> close。 20 | 21 | 2. send、sendto区别,recv、recvfrom区别? 22 | 23 | 24 | ### TCP/UDP 25 | 26 | 1. TCP和UDP区别? 27 | 28 | - TCP面向连接(三次握手),通信前需要先建立连接;UDP面向无连接,通信前不需要连接。 29 | 30 | - TCP通过序号、重传、流量控制、拥塞控制实现可靠传输;UDP不保障可靠传输,尽最大努力交付。 31 | 32 | - TCP面向字节流传输,因此可以被分割并在接收端重组;UDP面向数据报传输。 33 | 34 | 2. TCP为什么不是两次握手而是三次? 35 | 36 | - 如果仅两次连接可能出现一种情况:客户端发送完连接报文(第一次握手)后由于网络不好,延时很久后报文到达服务端,服务端接收到报文后向客户端发起连接(第二次握手)。此时客户端会认定此报文为失效报文,但在两次握手情况下服务端会认为已经建立起了连接,服务端会一直等待客户端发送数据,但因为客户端会认为服务端第二次握手的回复是对失效请求的回复,不会去处理。这就造成了服务端一直等待客户端数据的情况,浪费资源。 37 | 38 | 3. TCP为什么挥手是四次而不是三次? 39 | 40 | - TCP是全双工的,它允许两个方向的数据传输被独立关闭。当主动发起关闭的一方关闭连接之后,TCP进入半关闭状态,此时主动方可以只关闭输出流。 41 | 42 | - 之所以不是三次而是四次主要是因为被动关闭方将"对主动关闭报文的确认"和"关闭连接"两个操作分两次进行。 43 | 44 | - "对主动关闭报文的确认"是为了快速告知主动关闭方,此关闭连接报文已经收到。此时被动方不立即关闭连接是为了将缓冲中剩下的数据从输出流发回主动关闭方(主动方接收到数据后同样要进行确认),因此要把"确认关闭"和"关闭连接"分两次进行。 45 | 46 | - **Linux的close实际上是同时关闭输入流和输出流,并不是我们常说的四次握手。半关闭函数为shutdown,它可以用来断开某个具体描述符的TCP输入流或输出流。** 47 | 48 | 4. 为什么要有TIME_WAIT状态,TIME_WAIT状态过多怎么解决? 49 | 50 | - 主动关闭连接一方在发送对被动关闭方关闭连接的确认报文时,有可能因为网络状况不佳,被动关闭方超时未能收到此报文而重发断开连接(FIN)报文,此时如果主动方不等待而是直接进入CLOSED状态,则接收到被动关闭方重发的断开连接的报文会触发RST分组而非ACK分组,当被动关闭一方接收到RST后会认为出错了。所以说处于TIME_WAIT状态就是为了在重新收到断开连接分组情况下进行确认。 51 | 52 | - 解决方法: 53 | 54 | - 可以通过修改sysctl中TIME_WAIT时间来减少此情况(HTTP 1.1也可以减少此状态)。 55 | 56 | - 利用SO_LINGER选项的强制关闭方式,发RST而不是FIN,来越过TIMEWAIT状态,直接进入CLOSED状态。 57 | 58 | 5. TCP建立连接及断开连接是状态转换? 59 | 60 | - 客户端:SYN_SENT -> ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT。 61 | 62 | - 服务端:LISTEN -> SYN_RCVD -> ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED。 63 | 64 | 6. TCP流量控制和拥塞控制的实现? 65 | 66 | - 流量控制:TCP采用大小可变的滑动窗口进行流量控制。窗口大小的单位是字节,在TCP报文段首部的窗口字段写入的数值就是当前给对方设置的发送窗口数值的上限,发送窗口在连接建立时由双方商定。但在通信的过程中,接收端可根据自己的资源情况,随时动态地调整对方的发送窗口上限值。 67 | 68 | - 拥塞控制:网络拥塞现象是指到达通信子网中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象。严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象。拥塞控制是处理网络拥塞现象的一种机制。 69 | 70 | 7. TCP重传机制? 71 | 72 | - 滑动窗口机制,确立收发的边界,能让发送方知道已经发送了多少、尚未确认的字节数、尚待发送的字节数;让接收方知道已经确认收到的字节数。 73 | 74 | - 选择重传,用于对传输出错的序列进行重传。 75 | 76 | 8. 三次握手过程? 77 | 78 | - 主动建立连接方A的TCP向主机B发出连接请求报文段,其首部中的SYN(同步)标志位应置为1,表示想与目标主机B进行通信,并发送一个同步序列号x进行同步,表明在后面传送数据时的第一个数据字节的序号是x + 1。SYN同步报文会指明客户端使用的端口以及TCP连接的初始序号。 79 | 80 | - 接收连接方B的TCP收到连接请求报文段后,如同意则发回确认。在确认报中应将ACK位和SYN位置1,表示客户端的请求被接受。确认号应为x + 1,同时也为自己选择一个序号y。 81 | 82 | - 主动方A的TCP收到目标主机B的确认后要向目标主机B给出确认,其ACK置1,确认号为y + 1,而自己的序号为x + 1。 83 | 84 | 9. 四次挥手过程? 85 | 86 | - 主动关闭主机A的应用进程先向其TCP发出连接释放请求,并且不再发送数据。TCP通知对方要释放从A到B这个方向的连接,将发往主机B的TCP报文段首部的终止比特FIN置1,其序号x等于前面已传送过的数据的最后一个字节的序号加1。 87 | 88 | - 被动关闭主机B的TCP收到释放连接通知后即发出确认,其序号为y,确认号为x + 1,同时通知高层应用进程,这样,从A到B的连接就释放了,连接处于半关闭状态。但若主机B还有一些数据要发送主机A,则可以继续发送。主机A只要正确收到数据,仍应向主机B发送确认。 89 | 90 | - 若主机B不再向主机A发送数据,其应用进程就通知TCP释放连接。主机B发出的连接释放报文段必须将终止比特FIN和确认比特ACK置1,并使其序号仍为y,但还必须重复上次已发送过的ACK = x + 1。 91 | 92 | - 主机A必须对此发出确认,将ACK置1,ACK = y + 1,而自己的序号是x + 1。这样才把从B到A的反方向的连接释放掉。主机A的TCP再向其应用进程报告,整个连接已经全部释放。 93 | 94 | ### I/O模型 95 | 96 | 1. 阻塞和非阻塞I/O区别? 97 | 98 | - 如果内核缓冲没有数据可读时,read()系统调用会一直等待有数据到来后才从阻塞态中返回,这就是阻塞I/O。 99 | 100 | - 非阻塞I/O在遇到上述情况时会立即返回给用户态进程一个返回值,并设置errno为EAGAIN。 101 | 102 | - 对于往缓冲区写的操作同理。 103 | 104 | 2. 同步和异步区别? 105 | 106 | - 同步I/O指处理I/O操作的进程和处理I/O操作的进程是同一个。 107 | 108 | - 异步I/O中I/O操作由操作系统完成,并不由产生I/O的用户进程执行。 109 | 110 | 3. Reactor和Proactor区别? 111 | 112 | - Reactor模式已经是同步I/O,处理I/O操作的依旧是产生I/O的程序;Proactor是异步I/O,产生I/O调用的用户进程不会等待I/O发生,具体I/O操作由操作系统完成。 113 | 114 | - 异步I/O需要操作系统支持,Linux异步I/O为AIO,Windows为IOCP。 115 | 116 | 4. epoll和select及poll区别? 117 | 118 | - 文件描述符数量限制:select文件描述符数量受到限制,最大为2048(FD_SETSIZE),可重编内核修改但治标不治本;poll没有最大文件描述符数量限制;epoll没有最大文件描述符数量限制。 119 | 120 | - 检查机制:select和poll会以遍历方式(轮询机制)检查每一个文件描述符以确定是否有I/O就绪,每次执行时间会随着连接数量的增加而线性增长;epoll则每次返回后只对活跃的文件描述符队列进行操作(每个描述符都通过回调函数实现,只有活跃的描述符会调用回调函数并添加至队列中)。**当大量连接是非活跃连接时epoll相对于select和poll优势比较大,若大多为活跃连接则效率未必高(设计队列维护及红黑树创建)** 121 | 122 | - 数据传递方式:select和poll需要将FD_SET在内核空间和用户空间来回拷贝;epoll则避免了不必要的数据拷贝。 123 | 124 | 5. epoll中ET和LT模式的区别与实现原理? 125 | 126 | - LT:默认工作方式,同时支持阻塞I/O和非阻塞I/O,LT模式下,内核告知某一文件描述符读、写是否就绪了,然后你可以对这个就绪的文件描述符进行I/O操作。如果不作任何操作,内核还是会继续通知。这种模式编程出错误可能性较小但由于重复提醒,效率相对较低。传统的select、poll都是这种模型的代表。 127 | 128 | - ET:高速工作方式(因为减少了epoll_wait触发次数),适合高并发,只支持非阻塞I/O,ET模式下,内核告知某一文件描述符读、写是否就绪了,然后他假设已经知道该文件描述符是否已经就绪,内核不会再为这个文件描述符发更多的就绪通知(epoll_wait不会返回),直到某些操作导致文件描述符状态不再就绪。 129 | 130 | 6. ET模式下要注意什么(如何使用ET模式)? 131 | 132 | - 对于读操作,如果read没有一次读完buff数据,下一次将得不到就绪通知(ET特性),造成buff中数据无法读出,除非有新数据到达。 133 | 134 | - 解决方法:将套接字设置为非阻塞,用while循环包住read,只要buff中有数据,就一直读。一直读到产生EAGIN错误。 135 | 136 | - 对于写操作主要因为ET模式下非阻塞需要我们考虑如何将用户要求写的数据写完。 137 | 138 | - 解决方法:只要buff还有空间且用户请求写的数据还未写完,就一直写。 139 | 140 | 141 | ### 操作系统 142 | 143 | 1. Linux下进程间通信方式? 144 | 145 | - 管道: 146 | 147 | - 无名管道(内存文件):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程之间使用。进程的亲缘关系通常是指父子进程关系。 148 | 149 | - 有名管道(FIFO文件,借助文件系统):有名管道也是半双工的通信方式,但是允许在没有亲缘关系的进程之间使用,管道是先进先出的通信方式。 150 | 151 | - 共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与信号量,配合使用来实现进程间的同步和通信。 152 | 153 | - 消息队列:消息队列是有消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 154 | 155 | - 套接字:适用于不同机器间进程通信,在本地也可作为两个进程通信的方式。 156 | 157 | - 信号:用于通知接收进程某个事件已经发生,比如按下ctrl + C就是信号。 158 | 159 | - 信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,实现进程、线程的对临界区的同步及互斥访问。 160 | 161 | 2. Linux下同步机制? 162 | 163 | - POSIX信号量:可用于进程同步,也可用于线程同步。 164 | 165 | - POSIX互斥锁 + 条件变量:只能用于线程同步。 166 | 167 | 3. 线程和进程的区别? 168 | 169 | - 调度:线程是调度的基本单位(PC,状态码,通用寄存器,线程栈及栈指针);进程是拥有资源的基本单位(打开文件,堆,静态区,代码段等)。 170 | 171 | - 并发性:一个进程内多个线程可以并发(最好和CPU核数相等);多个进程可以并发。 172 | 173 | - 拥有资源:线程不拥有系统资源,但一个进程的多个线程可以共享隶属进程的资源;进程是拥有资源的独立单位。 174 | 175 | - 系统开销:线程创建销毁只需要处理PC值,状态码,通用寄存器值,线程栈及栈指针即可;进程创建和销毁需要重新分配及销毁task_struct结构。 176 | 177 | 4. 介绍虚拟内存? 178 | 179 | 5. 内存分配及碎片管理? 180 | 181 | 6. 有很多小的碎片文件怎么处理? 182 | 183 | ## Linux 184 | 185 | 1. fork系统调用? 186 | 187 | 2. 什么场景用共享内存,什么场景用匿名管道? 188 | 189 | 3. 有没有用过开源的cgi框架? 190 | 191 | 4. epoll和select比有什么优势有什么劣势,epoll有什么局限性? 192 | 193 | - epoll优势:1. 没有描述符数量限制;2. 通过回调代替轮询;3. 内存映射代替数据在用户和内核空间来回拷贝。 194 | 195 | - epoll劣势(局限性):select可以跨平台,epoll只能在Linux上使用。 196 | 197 | 5. 线程(POSIX)锁有哪些? 198 | 199 | - 互斥锁(mutex) 200 | 201 | - 互斥锁属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程A和B,它们分别运行在core 0和core 1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞,此时会通过上下文切换将线程A置于等待队列中,此时core 0就可以运行其他的任务(如线程C)。 202 | 203 | - 条件变量(cond) 204 | 205 | - 自旋锁(spin) 206 | 207 | - 自旋锁属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,如果自旋锁已经被线程B所持有,那么线程A就会一直在core 0上进行忙等待并不停的进行锁请求,检查该自旋锁是否已经被线程B释放,直到得到这个锁为止。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。 208 | 209 | - 虽然它的效率比互斥锁高,但是它也有些不足之处: 210 | 211 | - 自旋锁一直占用CPU,在未获得锁的情况下,一直进行自旋,所以占用着CPU,如果不能在很短的时间内获得锁,无疑会使CPU效率降低。 212 | 213 | - 在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁。 214 | 215 | - 自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。 216 | 217 | - 读写锁(rwlock) 218 | 219 | ## TKeed 220 | 221 | 1. 项目整体架构是什么?请求怎么进来?处理完怎么出去? 222 | 223 | - 整体架构为:I/O多路复用 + 非阻塞I/O + 线程池,即Reactor反应堆模型。 224 | 225 | - 处理流程: 226 | 227 | - 创建监听描述符并在epoll中注册。 228 | 229 | - 监听到新请求,epoll从阻塞中返回并建立新连接。 230 | 231 | - 将新建的连接描述符在epoll中注册。 232 | 233 | - 当某个连接接收到用户请求数据时,将任务投放到线程池任务队列中。 234 | 235 | - 工作线程被条件变量(任务队列不为空)唤醒,并互斥访问线程池。 236 | 237 | - 得到任务的线程完成解析及响应。 238 | 239 | - 工作线程执行函数为do_request,参数即为task结构。 240 | 241 | - 每个task结构在建立连接是被初始化,包含描述符、缓冲区等信息是,并在do_request执行时记录解析结果及状态。 242 | 243 | 2. 在做压测时,机器配置是什么样的?数据如何? 244 | 245 | - 本地测试。 246 | 247 | - 四核i5处理器 + 128G固态硬盘。 248 | 249 | 3. 为了QPS(Query per second, 1秒内完成的请求数量)更高可以做哪些改进? 250 | 251 | - 对请求结果做缓存。 252 | 253 | - 多次搜索请求采用异步I/O,改串行为并行。 254 | 255 | - 调整并发线程数量(通常和CPU核心数相同)。 256 | 257 | 4. 有没有注意到压测时内存,CPU,I/O指标? 258 | 259 | - 压测同时打开top -H -p pid查看CPU,I/O,内存信息。 260 | 261 | 5. 压测时有没有见过TIME_WAIT?怎么样会见到?怎么解决? 262 | 263 | - 当服务端关闭连接时会产生TIME_WAIT。 264 | 265 | - 解决方案: 266 | 267 | - HTTP 1.1在同一个TCP连接上尽量传输更多数据。 268 | 269 | - 通过修改sysctl配置减小TIME_WAIT时间。 270 | 271 | 6. 是会主动关闭还是会等待客户端关闭连接? 272 | 273 | - 服务端会在完成请求之后关闭连接。 274 | 275 | 7. 写一个Server需要注意哪些问题? 276 | 277 | - 只支持request/response,除此之外是否需要支持cgi。 278 | 279 | - 并发量,QPS,资源占用(内存,CPU,I/O,网络流量等)。 280 | 281 | - CPU占用是否过高。 282 | 283 | - 内存是否泄露。 284 | 285 | 8. 项目中遇到什么困难,你是如何解决的? 286 | 287 | - CPU占用过高。 288 | 289 | - 压测时,每次最后会挂掉。 290 | 291 | 9. 做这个项目的目的是什么? 292 | 293 | 10. 定时器是如何实现的?里面放了有多少个连接(怎么确定大小)?谁去取超时的连接?检查超时之后还会继续检查吗,还是检查完之后就断了? 294 | 295 | 11. 如果发生超时,在关闭连接时同时又收到了新的数据怎么办? 296 | 297 | 12. 用什么数据结构存放url,怎么解析的? 298 | 299 | - 使用tk_request_t结构中buff读取用户请求,buff为循环缓冲(8192 Bytes)。 300 | 301 | - 每次进入while循环时读取用户请求到buff中循环队列尾位置(plast),之后解析用户请求并响应。 302 | 303 | - 支持HTTP 1.1,只要有数据就读取 -> 解析 -> 响应。 304 | 305 | 306 | ## 实习经历 307 | 308 | 1. 介绍一下上网行为管理这个系统? 309 | 310 | 2. 介绍一下格林威治云平台做哪些任务? 311 | 312 | 3. 改变数据获取方式及校验数据一致性? 313 | 314 | 4. WTGGroup模块做什么用的? 315 | 316 | ## 数据结构 317 | 318 | 1. 层序遍历二叉树? 319 | 320 | 2. map和hashmap的区别是什么? 321 | 322 | 3. Hash发生冲突时怎么处理? 323 | 324 | 4. hashmap的时间复杂度是多少?map的时间复杂度? 325 | 326 | 5. 优先队列时间复杂度? 327 | -------------------------------------------------------------------------------- /计算机网络.md: -------------------------------------------------------------------------------- 1 | # 计算机网络 2 | 3 | > 重点在TCP/IP协议和HTTP协议。 4 | 5 | --- 6 | 7 | # 目录 8 | 9 | | Chapter 1 | Chapter 2 | Chapter 3| 10 | | :---------: | :---------: | :---------: | 11 | |[网络层(IP)](#net)|[传输层(TCP/UDP)](#trans)|[应用层(HTTP)](#app)| 12 | 13 | --- 14 | 15 | # 内容 16 | 17 | ### 网络层(IP) 18 | 19 | > 待补充 20 | 21 | --- 22 | 23 | ### 传输层(TCP/UDP) 24 | 25 | 1. ISO七层模型中表示层和会话层功能是什么? 26 | - 表示层:图像、视频编码解,数据加密。 27 | 28 | - 会话层:建立会话,如session认证、断点续传。 29 | 30 | 2. 描述TCP头部? 31 | - 序号(32bit):传输方向上字节流的字节编号。初始时序号会被设置一个随机的初始值(ISN),之后每次发送数据时,序号值 = ISN + 数据在整个字节流中的偏移。假设A -> B且ISN = 1024,第一段数据512字节已经到B,则第二段数据发送时序号为1024 + 512。用于解决网络包乱序问题。 32 | 33 | - 确认号(32bit):接收方对发送方TCP报文段的响应,其值是收到的序号值 + 1。 34 | 35 | - 首部长(4bit):标识首部有多少个4字节 * 首部长,最大为15,即60字节。 36 | 37 | - 标志位(6bit): 38 | - URG:标志紧急指针是否有效。 39 | 40 | - ACK:标志确认号是否有效(确认报文段)。用于解决丢包问题。 41 | 42 | - PSH:提示接收端立即从缓冲读走数据。 43 | 44 | - RST:表示要求对方重新建立连接(复位报文段)。 45 | 46 | - SYN:表示请求建立一个连接(连接报文段)。 47 | 48 | - FIN:表示关闭连接(断开报文段)。 49 | 50 | - 窗口(16bit):接收窗口。用于告知对方(发送方)本方的缓冲还能接收多少字节数据。用于解决流控。 51 | 52 | - 校验和(16bit):接收端用CRC检验整个报文段有无损坏。 53 | 54 | 3. 三次握手过程? 55 | - 第一次:客户端发含SYN位,SEQ_NUM = S的包到服务器。(客 -> SYN_SEND) 56 | 57 | - 第二次:服务器发含ACK,SYN位且ACK_NUM = S + 1,SEQ_NUM = P的包到客户机。(服 -> SYN_RECV) 58 | 59 | - 第三次:客户机发送含ACK位,ACK_NUM = P + 1的包到服务器。(客 -> ESTABLISH,服 -> ESTABLISH) 60 | 61 | 4. 四次挥手过程? 62 | - 第一次:客户机发含FIN位,SEQ = Q的包到服务器。(客 -> FIN_WAIT_1) 63 | 64 | - 第二次:服务器发送含ACK且ACK_NUM = Q + 1的包到服务器。(服 -> CLOSE_WAIT,客 -> FIN_WAIT_2) 65 | - 此处有等待 66 | 67 | - 第三次:服务器发送含FIN且SEQ_NUM = R的包到客户机。(服 -> LAST_ACK,客 -> TIME_WAIT) 68 | - 此处有等待 69 | 70 | - 第四次:客户机发送最后一个含有ACK位且ACK_NUM = R + 1的包到客户机。(服 -> CLOSED) 71 | 72 | 5. 为什么握手是三次,挥手是四次? 73 | - 对于握手:握手只需要确认双方通信时的初始化序号,保证通信不会乱序。(第三次握手必要性:假设服务端的确认丢失,连接并未断开,客户机超时重发连接请求,这样服务器会对同一个客户机保持多个连接,造成资源浪费。) 74 | 75 | - 对于挥手:TCP是双工的,所以发送方和接收方都需要FIN和ACK。只不过有一方是被动的,所以看上去就成了4次挥手。 76 | 77 | 6. TCP连接状态? 78 | - CLOSED:初始状态。 79 | 80 | - LISTEN:服务器处于监听状态。 81 | 82 | - SYN_SEND:客户端socket执行CONNECT连接,发送SYN包,进入此状态。 83 | 84 | - SYN_RECV:服务端收到SYN包并发送服务端SYN包,进入此状态。 85 | 86 | - ESTABLISH:表示连接建立。客户端发送了最后一个ACK包后进入此状态,服务端接收到ACK包后进入此状态。 87 | 88 | - FIN_WAIT_1:终止连接的一方(通常是客户机)发送了FIN报文后进入。等待对方FIN。 89 | 90 | - CLOSE_WAIT:(假设服务器)接收到客户机FIN包之后等待关闭的阶段。在接收到对方的FIN包之后,自然是需要立即回复ACK包的,表示已经知道断开请求。但是本方是否立即断开连接(发送FIN包)取决于是否还有数据需要发送给客户端,若有,则在发送FIN包之前均为此状态。 91 | 92 | - FIN_WAIT_2:此时是半连接状态,即有一方要求关闭连接,等待另一方关闭。客户端接收到服务器的ACK包,但并没有立即接收到服务端的FIN包,进入FIN_WAIT_2状态。 93 | 94 | - LAST_ACK:服务端发动最后的FIN包,等待最后的客户端ACK响应,进入此状态。 95 | 96 | - TIME_WAIT:客户端收到服务端的FIN包,并立即发出ACK包做最后的确认,在此之后的2MSL时间称为TIME_WAIT状态。 97 | 98 | 7. 解释FIN_WAIT_2,CLOSE_WAIT状态和TIME_WAIT状态? 99 | - FIN_WAIT_2: 100 | - 半关闭状态。 101 | 102 | - 发送断开请求一方还有接收数据能力,但已经没有发送数据能力。 103 | 104 | - CLOSE_WAIT状态: 105 | - 被动关闭连接一方接收到FIN包会立即回应ACK包表示已接收到断开请求。 106 | 107 | - 被动关闭连接一方如果还有剩余数据要发送就会进入CLOSED_WAIT状态。 108 | 109 | - TIME_WAIT状态: 110 | - 又叫2MSL等待状态。 111 | 112 | - 如果客户端直接进入CLOSED状态,如果服务端没有接收到最后一次ACK包会在超时之后重新再发FIN包,此时因为客户端已经CLOSED,所以服务端就不会收到ACK而是收到RST。所以TIME_WAIT状态目的是防止最后一次握手数据没有到达对方而触发重传FIN准备的。 113 | 114 | - 在2MSL时间内,同一个socket不能再被使用,否则有可能会和旧连接数据混淆(如果新连接和旧连接的socket相同的话)。 115 | 116 | 8. 解释RTO,RTT和超时重传? 117 | - 超时重传:发送端发送报文后若长时间未收到确认的报文则需要重发该报文。可能有以下几种情况: 118 | 119 | - 发送的数据没能到达接收端,所以对方没有响应。 120 | 121 | - 接收端接收到数据,但是ACK报文在返回过程中丢失。 122 | 123 | - 接收端拒绝或丢弃数据。 124 | 125 | - RTO:从上一次发送数据,因为长期没有收到ACK响应,到下一次重发之间的时间。就是重传间隔。 126 | - 通常每次重传RTO是前一次重传间隔的两倍,计量单位通常是RTT。例:1RTT,2RTT,4RTT,8RTT...... 127 | 128 | - 重传次数到达上限之后停止重传。 129 | 130 | - RTT:数据从发送到接收到对方响应之间的时间间隔,即数据报在网络中一个往返用时。大小不稳定。 131 | 132 | 9. 流量控制原理? 133 | - 目的是接收方通过TCP头窗口字段告知发送方本方可接收的最大数据量,用以解决发送速率过快导致接收方不能接收的问题。所以流量控制是点对点控制。 134 | 135 | - TCP是双工协议,双方可以同时通信,所以发送方接收方各自维护一个发送窗和接收窗。 136 | 137 | - 发送窗:用来限制发送方可以发送的数据大小,其中发送窗口的大小由接收端返回的TCP报文段中窗口字段来控制,接收方通过此字段告知发送方自己的缓冲(受系统、硬件等限制)大小。 138 | 139 | - 接收窗:用来标记可以接收的数据大小。 140 | 141 | - TCP是流数据,发送出去的数据流可以被分为以下四部分:已发送且被确认部分 | 已发送未被确认部分 | 未发送但可发送部分 | 不可发送部分,其中发送窗 = 已发送未确认部分 + 未发但可发送部分。接收到的数据流可分为:已接收 | 未接收但准备接收 | 未接收不准备接收。接收窗 = 未接收但准备接收部分。 142 | 143 | - 发送窗内数据只有当接收到接收端某段发送数据的ACK响应时才移动发送窗,左边缘紧贴刚被确认的数据。接收窗也只有接收到数据且最左侧连续时才移动接收窗口。 144 | 145 | 10. 拥塞控制原理? 146 | - 拥塞控制目的是防止数据被过多注网络中导致网络资源(路由器、交换机等)过载。因为拥塞控制涉及网络链路全局,所以属于全局控制。控制拥塞使用拥塞窗口。 147 | 148 | - TCP拥塞控制算法: 149 | - 慢开始 & 拥塞避免:先试探网络拥塞程度再逐渐增大拥塞窗口。每次收到确认后拥塞窗口翻倍,直到达到阀值ssthresh,这部分是慢开始过程。达到阀值后每次以一个MSS为单位增长拥塞窗口大小,当发生拥塞(超时未收到确认),将阀值减为原先一半,继续执行线性增加,这个过程为拥塞避免。 150 | 151 | - 快速重传 & 快速恢复:略。 152 | 153 | - 最终拥塞窗口会收敛于稳定值。 154 | 155 | 11. 如何区分流量控制和拥塞控制? 156 | - 流量控制属于通信双方协商;拥塞控制涉及通信链路全局。 157 | 158 | - 流量控制需要通信双方各维护一个发送窗、一个接收窗,对任意一方,接收窗大小由自身决定,发送窗大小由接收方响应的TCP报文段中窗口值确定;拥塞控制的拥塞窗口大小变化由试探性发送一定数据量数据探查网络状况后而自适应调整。 159 | 160 | - 实际最终发送窗口 = min{流控发送窗口,拥塞窗口}。 161 | 162 | 12. TCP如何提供可靠数据传输的? 163 | - 建立连接(标志位):通信前确认通信实体存在。 164 | 165 | - 序号机制(序号、确认号):确保了数据是按序、完整到达。 166 | 167 | - 数据校验(校验和):CRC校验全部数据。 168 | 169 | - 超时重传(定时器):保证因链路故障未能到达数据能够被多次重发。 170 | 171 | - 窗口机制(窗口):提供流量控制,避免过量发送。 172 | 173 | - 拥塞控制:同上。 174 | 175 | 13. TCP soctet交互流程? 176 | - 服务器: 177 | - 创建socket -> int socket(int domain, int type, int protocol); 178 | - domain:协议域,决定了socket的地址类型,IPv4为AF_INET。 179 | 180 | - type:指定socket类型,SOCK_STREAM为TCP连接。 181 | 182 | - protocol:指定协议。IPPROTO_TCP表示TCP协议,为0时自动选择type默认协议。 183 | 184 | - 绑定socket和端口号 -> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 185 | - sockfd:socket返回的套接字描述符,类似于文件描述符fd。 186 | 187 | - addr:有个sockaddr类型数据的指针,指向的是被绑定结构变量。 188 | ```C++ 189 | // IPv4的sockaddr地址结构 190 | struct sockaddr_in { 191 | sa_family_t sin_family; // 协议类型,AF_INET 192 | in_port_t sin_port; // 端口号 193 | struct in_addr sin_addr; // IP地址 194 | }; 195 | struct in_addr { 196 | uint32_t s_addr; 197 | } 198 | ``` 199 | 200 | - addrlen:地址长度。 201 | 202 | - 监听端口号 -> int listen(int sockfd, int backlog); 203 | - sockfd:要监听的sock描述字。 204 | 205 | - backlog:socket可以排队的最大连接数。 206 | 207 | - 接收用户请求 -> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 208 | - sockfd:服务器socket描述字。 209 | 210 | - addr:指向地址结构指针。 211 | 212 | - addrlen:协议地址长度。 213 | 214 | - 注:一旦accept某个客户机请求成功将返回一个全新的描述符用于标识具体客户的TCP连接。 215 | 216 | - 从socket中读取字符 -> ssize_t read(int fd, void *buf, size_t count); 217 | - fd:连接描述字。 218 | 219 | - buf:缓冲区buf。 220 | 221 | - count:缓冲区长度。 222 | 223 | - 注:大于0表示读取的字节数,返回0表示文件读取结束,小于0表示发生错误。 224 | 225 | - 关闭socket -> int close(int fd); 226 | 227 | - fd:accept返回的连接描述字,每个连接有一个,生命周期为连接周期。 228 | 229 | - 注:sockfd是监听描述字,一个服务器只有一个,用于监听是否有连接;fd是连接描述字,用于每个连接的操作。 230 | 231 | - 客户机: 232 | - 创建socket -> int socket(int domain, int type, int protocol); 233 | 234 | - 连接指定计算机 -> int connect(int sockfd, struct sockaddr* addr, socklen_t addrlen); 235 | - sockfd客户端的sock描述字。 236 | 237 | - addr:服务器的地址。 238 | 239 | - addrlen:socket地址长度。 240 | 241 | - 向socket写入信息 -> ssize_t write(int fd, const void *buf, size_t count); 242 | - fd、buf、count:同read中意义。 243 | 244 | - 大于0表示写了部分或全部数据,小于0表示出错。 245 | 246 | - 关闭oscket -> int close(int fd); 247 | - fd:同服务器端fd。 248 | 249 | --- 250 | 251 | ### 应用层(HTTP) 252 | 253 | HTTP协议工作在应用层,端口号是80。HTTP协议被用于网络中两台计算机间的通信,相比于TCP/IP这些底层协议,HTTP协议更像是高层标记型语言,浏览器根据从服务器得到的HTTP响应体中分别得到报文头,响应头和信息体(HTML正文等),之后将HTML文件解析并呈现在浏览器上。同样,我们在浏览器地址栏输入网址之后,浏览器相当于用户代理帮助我们组织好报文头,请求头和信息体(可选),之后通过网络发送到服务器,,服务器根据请求的内容准备数据。所以如果想要完全弄明白HTTP协议,你需要写一个浏览器 + 一个Web服务器,一侧来生成请求信息,一侧生成响应信息。 254 | 255 | 从网络分层模型来看,HTTP工作在应用层,其在传输层由TCP协议为其提供服务。所以可以猜到,HTTP请求前,客户机和服务器之间一定已经通过三次握手建立起连接,其中套接字中服务器一侧的端口号为HTTP周知端口80。在请求和传输数据时也是有讲究的,通常一个页面上不只有文本数据,有时会内嵌很多图片,这时候有两种选择可以考虑。一种是对每一个文件都建立一个TCP连接,传送完数据后立马断开,通过多次这样的操作获取引用的所有数据,但是这样一个页面的打开需要建立多次连接,效率会低很多。另一种是对于有多个资源的页面,传送完一个数据后不立即断开连接,在同一次连接下多次传输数据直至传完,但这种情况有可能会长时间占用服务器资源,降低吞吐率。上述两种模式分别是HTTP 1.0和HTTP 1.1版本的默认方式,具体是什么含义会在后面详细解释。 256 | 257 | **HTTP工作流程** 258 | 259 | 一次完整的HTTP请求事务包含以下四个环节: 260 | 261 | - 建立起客户机和服务器连接。 262 | 263 | - 建立连接后,客户机发送一个请求给服务器。 264 | 265 | - 服务器收到请求给予响应信息。 266 | 267 | - 客户端浏览器将返回的内容解析并呈现,断开连接。 268 | 269 | **HTTP协议结构** 270 | 271 | 请求报文 272 | 273 | 对于HTTP请求报文我们可以通过以下两种方式比较直观的看到:一是在浏览器调试模式下(F12)看请求响应信息,二是通过wireshark或者tcpdump抓包实现。通过前者看到的数据更加清晰直观,通过后者抓到的数据更真实。但无论是用哪种方式查看,得到的请求报文主题体信息都是相同的,对于请求报文,主要包含以下四个部分,每一行数据必须通过"\r\n"分割,这里可以理解为行末标识符。 274 | 275 | - 报文头(只有一行) 276 | 277 | 结构:method uri version 278 | 279 | - method 280 | 281 | HTTP的请求方法,一共有9中,但GET和POST占了99%以上的使用频次。GET表示向特定资源发起请求,当然也能提交部分数据,不过提交的数据以明文方式出现在URL中。POST通常用于向指定资源提交数据进行处理,提交的数据被包含在请求体中,相对而言比较安全些。 282 | 283 | - uri 284 | 285 | 用来指代请求的文件,≠URL。 286 | 287 | - version 288 | 289 | HTTP协议的版本,该字段有HTTP/1.0和HTTP/1.1两种。 290 | 291 | - 请求头(多行) 292 | 293 | 在HTTP/1.1中,请求头除了Host都是可选的。包含的头五花八门,这里只介绍部分。 294 | 295 | - Host:指定请求资源的主机和端口号。端口号默认80。 296 | 297 | - Connection:值为keep-alive和close。keep-alive使客户端到服务器的连接持续有效,不需要每次重连,此功能为HTTP/1.1预设功能。 298 | 299 | - Accept:浏览器可接收的MIME类型。假设为text/html表示接收服务器回发的数据类型为text/html,如果服务器无法返回这种类型,返回406错误。 300 | 301 | - Cache-control:缓存控制,Public内容可以被任何缓存所缓存,Private内容只能被缓存到私有缓存,non-cache指所有内容都不会被缓存。 302 | 303 | - Cookie:将存储在本地的Cookie值发送给服务器,实现无状态的HTTP协议的会话跟踪。 304 | 305 | - Content-Length:请求消息正文长度。 306 | 307 | 另有User-Agent、Accept-Encoding、Accept-Language、Accept-Charset、Content-Type等请求头这里不一一罗列。由此可见,请求报文是告知服务器请求的内容,而请求头是为了提供服务器一些关于客户机浏览器的基本信息,包括编码、是否缓存等。 308 | 309 | 310 | - 空行(一行) 311 | 312 | - 可选消息体(多行) 313 | 314 | 响应报文 315 | 316 | 响应报文是服务器对请求资源的响应,通过上面提到的方式同样可以看到,同样地,数据也是以"\r\n"来分割。 317 | 318 | - 报文头(一行) 319 | 320 | 结构:version status_code status_message 321 | 322 | - version 323 | 324 | 描述所遵循的HTTP版本。 325 | 326 | - status_code 327 | 328 | 状态码,指明对请求处理的状态,常见的如下。 329 | 330 | - 200:成功。 331 | 332 | - 301:内容已经移动。 333 | 334 | - 400:请求不能被服务器理解。 335 | 336 | - 403:无权访问该文件。 337 | 338 | - 404:不能找到请求文件。 339 | 340 | - 500:服务器内部错误。 341 | 342 | - 501:服务器不支持请求的方法。 343 | 344 | - 505:服务器不支持请求的版本。 345 | 346 | - status_message 347 | 348 | 显示和状态码等价英文描述。 349 | 350 | - 响应头(多行) 351 | 352 | 这里只罗列部分。 353 | 354 | - Date:表示信息发送的时间。 355 | 356 | - Server:Web服务器用来处理请求的软件信息。 357 | 358 | - Content-Encoding:Web服务器表明了自己用什么压缩方法压缩对象。 359 | 360 | - Content-Length:服务器告知浏览器自己响应的对象长度。 361 | 362 | - Content-Type:告知浏览器响应对象类型。 363 | 364 | - 空行(一行) 365 | 366 | - 信息体(多行) 367 | 368 | 实际有效数据,通常是HTML格式的文件,该文件被浏览器获取到之后解析呈现在浏览器中。 369 | 370 | **CGI与环境变量** 371 | 372 | - CGI程序 373 | 374 | 服务器为客户端提供动态服务首先需要解决的是得到用户提供的参数再根据参数信息返回。为了和客户端进行交互,服务器需要先创建子进程,之后子进程执行相应的程序去为客户服务。CGI正是帮助我们解决参数获取、输出结果的。 375 | 376 | 动态内容获取其实请求报文的头部和请求静态数据时完全相同,但请求的资源从静态的HTML文件变成了后台程序。服务器收到请求后fork()一个子进程,子进程执行请求的程序,这样的程序称为CGI程序(Python、Perl、C++等均可)。通常在服务器中我们会预留一个单独的目录(cgi-bin)用来存放所有的CGI程序,请求报文头部中请求资源的前缀都是/cgi-bin,之后加上所请求调用的CGI程序即可。 377 | 378 | 所以上述流程就是:客户端请求程序 -> 服务器fork()子进程 -> 执行被请求程序。接下来需要解决的问题就是如何获取客户端发送过来的参数和输出信息怎么传递回客户端。 379 | 380 | - 环境变量 381 | 382 | 对CGI程序来说,CGI环境变量在创建时被初始化,结束时被销毁。当CGI程序被HTTP服务器调用时,因为是被服务器fork()出来的子进程,所以其继承了其父进程的环境变量,这些环境变量包含了很多基本信息,请求头中和响应头中列出的内容(比如用户Cookie、客户机主机名、客户机IP地址、浏览器信息等),CGI程序所需要的参数也在其中。 383 | 384 | - GET方法下参数获取 385 | 386 | 服务器把接收到的参数数据编码到环境变量QUERY_STRING中,在请求时只需要直接把参数写到URL最后即可,比如"http:127.0.0.1:80/cgi-bin/test?a=1&b=2&c=3",表示请求cgi-bin目录下test程序,'?'之后部分为参数,多个参数用'&'分割开。服务器接收到请求后环境变量QUERY_STRING的值即为a=1&b=2&c=3。 387 | 388 | 在CGI程序中获取环境变量值的方法是:getenv(),比如我们需要得到上述QUERY_STRING的值,只需要下面这行语句就可以了。 389 | 390 | char *value = getenv("QUERY_STRING"); 391 | 392 | 之后对获得的字符串处理一下提取出每个参数信息即可。 393 | 394 | - POST方法下参数获取 395 | 396 | POST方法下,CGI可以直接从服务器标准输入获取数据,不过要先从CONTENT_LENGTH这个环境变量中得到POST参数长度,再获取对应长度内容。 397 | 398 | **会话机制** 399 | 400 | HTTP作为无状态协议,必然需要在某种方式保持连接状态。这里简要介绍一下Cookie和Session。 401 | 402 | - Cookie 403 | 404 | Cookie是客户端保持状态的方法。 405 | 406 | Cookie简单的理解就是存储由服务器发至客户端并由客户端保存的一段字符串。为了保持会话,服务器可以在响应客户端请求时将Cookie字符串放在Set-Cookie下,客户机收到Cookie之后保存这段字符串,之后再请求时候带上Cookie就可以被识别。 407 | 408 | 除了上面提到的这些,Cookie在客户端的保存形式可以有两种,一种是会话Cookie一种是持久Cookie,会话Cookie就是将服务器返回的Cookie字符串保持在内存中,关闭浏览器之后自动销毁,持久Cookie则是存储在客户端磁盘上,其有效时间在服务器响应头中被指定,在有效期内,客户端再次请求服务器时都可以直接从本地取出。需要说明的是,存储在磁盘中的Cookie是可以被多个浏览器代理所共享的。 409 | 410 | - Session 411 | 412 | Session是服务器保持状态的方法。 413 | 414 | 首先需要明确的是,Session保存在服务器上,可以保存在数据库、文件或内存中,每个用户有独立的Session用户在客户端上记录用户的操作。我们可以理解为每个用户有一个独一无二的Session ID作为Session文件的Hash键,通过这个值可以锁定具体的Session结构的数据,这个Session结构中存储了用户操作行为。 415 | 416 | 当服务器需要识别客户端时就需要结合Cookie了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用Cookie来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在Cookie里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。如果客户端的浏览器禁用了Cookie,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如sid=xxxxx这样的参数,服务端据此来识别用户,这样就可以帮用户完成诸如用户名等信息自动填入的操作了。 417 | -------------------------------------------------------------------------------- /Linux工具.md: -------------------------------------------------------------------------------- 1 | # [Linux工具](http://man.linuxde.net/par/2) 2 | 3 | > Linux下还是有很多超棒的开发工具的。 4 | 5 | 在Linux日常使用中,最常用的命令自然是sudo, ls, cp, mv, cat等,但作为后台开发者,上述命令远远不够。从我的理解来看,合格的C/C++开发者至少需要从开发及调试工具、文件处理、性能分析、网络工具四个方面针对性使用一些开发工具。这里我罗列了一些,大部分都是开发中经常需要使用的命令,有些功能比较简单的命令我会给出一些基本用法,有些本身自带体系(比如vim, gdb等)的命令只能附上链接了。 6 | 7 | 开发及调试工具介绍了从“编辑 -> 编译 -> 分析目标文件 -> 追踪调用过程”的全套命令,文件处理部分介绍了查找、统计、替换等基本文本操作命令,性能分析介绍了查看进程信息、CPU负载、I/O负载、内存使用情况等基本命令,网络工具介绍了可以查看“链路层 -> 网络层 -> 传输层 -> 应用层”信息的工具。除此以外,其他命令中也列出了开发者经常会用到的一些命令,基本可以满足日常开发需要。 8 | 9 | --- 10 | 11 | # 目录 12 | 13 | | Chapter 1 | Chapter 2 | Chapter 3| Chapter 4 |Chapter 5| 14 | | :---------: | :---------: | :---------: | :--------: |:--------:| 15 | |[开发及调试](#pro)|[文件处理](#file)|[性能分析](#sysinfo)|[网络工具](#net)|[其他](#other)| 16 | 17 | - 开发及调试 18 | 19 | - 编辑器:vim 20 | - 编译器:gcc/g++ 21 | - 调试工具:gdb 22 | - 查看依赖库:ldd 23 | - 二进制文件分析:objdump 24 | - ELF文件格式分析:readelf 25 | - 跟踪进程中系统调用:strace 26 | - 跟踪进程栈:pstack 27 | - 进程内存映射:pmap 28 | 29 | - 文件处理 30 | - 文件查找:find 31 | - 文本搜索:grep 32 | - 排序:sort 33 | - 转换:tr 34 | - 按列切分文本:cut 35 | - 按列拼接文本:paste 36 | - 统计行和字符:wc 37 | - 文本替换:sed 38 | - 数据流处理:awk 39 | 40 | - 性能分析 41 | - 进程查询:ps 42 | - 进程监控:top 43 | - 打开文件查询:lsof 44 | - 内存使用量:free 45 | - 监控性能指标:sar 46 | 47 | - 网络工具 48 | - 网卡配置:ifconfig 49 | - 查看当前网络连接:netstat 50 | - 查看路由表:route 51 | - 检查网络连通性:ping 52 | - 转发路径:traceroute 53 | - 网络Debug分析:nc 54 | - 命令行抓包:tcpdump 55 | - 域名解析工具:dig 56 | - 网络请求:curl 57 | 58 | - 其他 59 | - 终止进程:kill 60 | - 修改文件权限:chmod 61 | - 创建链接:ln 62 | - 显示文件尾:tail 63 | - 版本控制:git 64 | - 设置别名:alias 65 | 66 | --- 67 | 68 | # 内容 69 | 70 | ### 开发及调试 71 | 72 | > 开发工具大部分都提供了完善的功能,所以这里不一一列举用法。从技术层面来说,调试工具比开发工具更考验一个人的工程能力。 73 | 74 | 1. 编辑器:vim 75 | - 服务器端开发必知必会,功能强大,这里不一一列举,但基本的打开文件、保存退出要会。 76 | - [详见](http://coolshell.cn/articles/5426.html) 77 | 78 | 2. 编译器:gcc/g++ 79 | - C/C++编译器,必知必会,除此以外需要了解预处理-> 编译 -> 汇编 -> 链接等一系列流程。 80 | - [详见](http://www.cnblogs.com/lidan/archive/2011/05/25/2239517.html) 81 | 82 | 3. 调试工具:gdb 83 | - 服务器端调试必备。 84 | - [详见](http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/gdb.html) 85 | 86 | 4. 查看依赖库:ldd 87 | - 程序依赖库查询 88 | ```shell 89 | # ldd后接可执行文件 90 | # 第一列为程序依赖什么库,第二列为系统提供的与程序需要的库所对应的库,第三列为库加载的开始地址 91 | # 前两列可以判断系统提供的库和需要的库是否匹配,第三列可以知道当前库在进程地址空间中对应的开始位置 92 | 93 | ldd a.out 94 | ``` 95 | 5. 二进制文件分析:objdump 96 | - 反汇编,需要理解汇编语言 97 | - [详见](http://man.linuxde.net/objdump) 98 | 99 | 7. ELF文件格式分析:readelf 100 | - 可以得到ELF文件各段内容,分析链接、符号表等需要用到 101 | - [详见](http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/readelf.html) 102 | 103 | 8. 跟踪进程中系统调用:strace 104 | - [详见](http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/strace.html) 105 | 106 | 9. 跟踪进程栈:pstack 107 | - [详见](http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/pstack.html#) 108 | 109 | 10. 进程内存映射:pmap 110 | - 显示进程内存映射 111 | ```shell 112 | # -x显示扩展信息,后接进程pid 113 | # Address: 内存开始地址 114 | # 显示信息: 115 | Kbytes: 占用内存的字节数 116 | RSS: 保留内存的字节数 117 | Dirty: 脏页的字节数(包括共享和私有的) 118 | Mode: 内存的权限:read、write、execute、shared、private 119 | Mapping: 占用内存的文件、或[anon](分配的内存)、或[stack](堆栈) 120 | Device: 设备名 (major:minor) 121 | 122 | pmap -x 12345 123 | ``` 124 | 125 | --- 126 | 127 | ### 文件处理 128 | 129 | > Everything is file. 在Linux环境下,对文本处理相当频繁,所以有些命令的参数还是需要记忆的。另外其他很多命令的输出信息都需要通过文件处理命令来筛选有用信息。 130 | 131 | 1. 文件查找:find 132 | 133 | 按名查找: 134 | 135 | - 查找具体文件(一般方式) 136 | ```shell 137 | find . -name *.cpp 138 | ``` 139 | 140 | - 查找具体文件(正则方式) 141 | ```shell 142 | # -regex为正则查找,-iregex为忽略大小写的正则查找 143 | 144 | find -regex ".*.cpp$" 145 | ``` 146 | 147 | 定制查找: 148 | - 按类型查找 149 | ```shell 150 | # f(file)为文件,d(dictionary)为目录,l(link)为链接 151 | 152 | find . -type f 153 | ``` 154 | 155 | - 按时间查找 156 | ```shell 157 | # atime为访问时间,x天内加参数"-atime -x",超过x天加"-atime -x" 158 | # mtime为修改时间 159 | 160 | find . -type f -atime -7 161 | ``` 162 | 163 | - 按大小查找 164 | ```shell 165 | # -size后接文件大小,单位可以为k(kb),m(MB),g(GB) 166 | 167 | find . -type f -size -1k 168 | ``` 169 | 170 | - 按权限查询 171 | ```shell 172 | # -perm后接权限 173 | 174 | find . -type -perm 644 175 | ``` 176 | 177 | 2. 文本搜索:grep 178 | - 模式匹配 179 | ```shell 180 | # 匹配test.cpp文件中含有"iostream"串的内容 181 | 182 | grep "iostream" test.cpp 183 | ``` 184 | 185 | - 多个模式匹配 186 | ```shell 187 | # 匹配test.cpp文件中含有"iostream"和"using"串的内容 188 | 189 | grep -e "using" -e "iostream" test.cpp 190 | ``` 191 | 192 | - 输出信息 193 | ```shell 194 | # -n为打印匹配的行号;-i搜索时忽略大小写;-c统计包含文本次数 195 | 196 | grep -n "iostream" test.cpp 197 | ``` 198 | 3. 排序:sort 199 | - 文件内容行排序 200 | ```shell 201 | # 排序在内存进行,不改变文件 202 | # -n(number)表示按数字排序,-d(dictionary)表示按字典序 203 | # -k N表示按各行第N列进行排序 204 | # -r(reverse)为逆序排序 205 | 206 | sort -n -k 1 test 207 | ``` 208 | 209 | 4. 转换:tr 210 | - 字符替换 211 | ```shell 212 | # 转换在内存进行,不改变文件 213 | # 将打开文件中所有目标字符替换 214 | 215 | cat test | tr '1' '2' 216 | ``` 217 | 218 | - 字符删除 219 | ```shell 220 | # 转换在内存进行,不改变文件 221 | # -d删除(delete) 222 | 223 | cat test | tr -d '1' 224 | ``` 225 | 226 | - 字符压缩 227 | ```shell 228 | # 转换在内存进行,不改变文件 229 | # -s位于后部 230 | 231 | cat test | tr ' ' -s 232 | ``` 233 | 5. 按列切分文本:cut 234 | - 截取特定列 235 | ```shell 236 | # 截取的内存进行,不改变文件 237 | # -b(byte)以字节为单位,-c(character)以字符为单位,-f以字段为单位 238 | # 数字为具体列范围 239 | 240 | cut -f 1,2 test 241 | ``` 242 | 243 | - 指定界定符 244 | ```shell 245 | # 截取的内存进行,不改变文件 246 | # -d后接界定符 247 | 248 | cut -f 2 -d ',' new 249 | ``` 250 | 251 | 6. 按列拼接文本:paste 252 | - 按列拼接 253 | ```shell 254 | # 在内存中拼接,不改变文件 255 | # 将两个文件按对应列拼接 256 | # 最后加上-d "x"会将x作为指定分隔符(paste test1 test2 -d ",") 257 | # 两文件列数可以不同 258 | 259 | paste test1 test2 260 | ``` 261 | - 指定界定符拼接 262 | ```shell 263 | # 在内存中拼接,不改变文件 264 | # 按照-d之后给出的界定符拼接 265 | 266 | paste test1 test2 -d "," 267 | ``` 268 | 269 | 7. 统计行和字符:wc 270 | - 基本统计 271 | ```shell 272 | # -l统计行数(line),-w统计单词数(word),-c统计字符数(character) 273 | 274 | wc -l test 275 | ``` 276 | 8. 文本替换:sed 277 | - 区别于上面的命令,sed是可以直接改变被编辑文件内容的。 278 | - [详见](http://coolshell.cn/articles/9104.html) 279 | 280 | 9. 数据流处理:awk 281 | - 区别于上面的命令,awk是可以直接改变被编辑文件内容的。 282 | - [详见](http://coolshell.cn/articles/9070.html) 283 | 284 | --- 285 | 286 | ### 系统信息 287 | 288 | > 性能监视工具对于程序员的作用就像是听诊器对于医生的作用一样。系统信息主要针对于服务器性能较低时的排查工作,主要包括CPU信息,文件I/O和内存使用情况,通过进程为纽带得到系统运行的瓶颈。 289 | 290 | 1. 进程查询:ps 291 | - 查看正在运行进程 292 | ```shell 293 | # 常结合grep筛选信息(e.g, ps -ef | grep xxx) 294 | 295 | ps -ef 296 | ``` 297 | 298 | - 以完整格式显示所有进程 299 | ```shell 300 | # 常结合grep筛选信息 301 | 302 | ps -ajx 303 | ``` 304 | 305 | 2. 进程监控:top 306 | - 显示实时进程信息 307 | ```shell 308 | # 这是个大招,都不带参数的,具体信息通过grep筛选 309 | # 交互模式下键入M进程列表按内存使用大小降序排列,键入P进程列表按CPU使用大小降序排列 310 | # %id表示CPU空闲率,过低表示可能存在CPU存在瓶颈 311 | # %wa表示等待I/O的CPU时间百分比,过高则I/O存在瓶颈 > 用iostat进一步分析 312 | 313 | top 314 | ``` 315 | 316 | 3. 打开文件查询:lsof 317 | - 查看占用某端口的进程 318 | ```shell 319 | # 最常见的就是mysql端口被占用使用(lsof i:3307) 320 | # 周知端口(ftp:20/21, ssh:22, telnet:23, smtp:25, dns:53, http:80, pop3:110, https:443) 321 | 322 | lsof -i:53 323 | ``` 324 | 325 | - 查看某用户打开的文件 326 | ```shell 327 | # -u(user)为用户,后接用户名 328 | 329 | lsof -u inx 330 | ``` 331 | 332 | - 查看指定进程打开的文件 333 | ```shell 334 | # -p(process)为进程,后接进程PID 335 | 336 | lsof -p 12345 337 | ``` 338 | 339 | - 查看指定目录下被进程打开的文件 340 | ```shell 341 | # 这里是"+d",需要注意,使用"+D"递归目录 342 | 343 | lsof +d /test 344 | ``` 345 | 346 | 4. 内存使用量:free 347 | - 内存使用量 348 | ```shell 349 | # 可获得内存及交换区的总量,已使用量,空闲量等信息 350 | 351 | free 352 | ``` 353 | 354 | 5. 监控性能指标:sar 355 | 356 | 监控CPU 357 | - 监控CPU负载 358 | ```shell 359 | # 加上-q可以查看运行队列中进程数,系统上进程大小,平均负载等 360 | # 这里"1"表示采样时间间隔是1秒,这里"2"表示采样次数为2 361 | 362 | sar -q 1 2 363 | ``` 364 | 365 | - 监控CPU使用率 366 | ```shell 367 | # 可以显示CPU使用情况 368 | # 参数意义同上 369 | 370 | sar -u 1 2 371 | ``` 372 | 373 | 监控内存 374 | - 查询内存 375 | ```shell 376 | # 可以显示内存使用情况 377 | # 参数意义同上 378 | 379 | sar -r 1 2 380 | ``` 381 | 382 | - 页面交换查询 383 | ```shell 384 | # 可以查看是否发生大量页面交换,吞吐率大幅下降时可用 385 | # 参数意义同上 386 | 387 | sar -W 1 2 388 | ``` 389 | 390 | --- 391 | 392 | ### 网络工具 393 | > 网络工具部分只介绍基本功能,参数部分一笔带过。这部分重点不在于工具的使用而是对反馈的数据进行解读,并且这部分命令功能的重合度还是比较高的。 394 | 395 | 1. 网卡配置(链路层):ifconfig 396 | - 显示设备信息 397 | ```shell 398 | # 可以显示已激活的网络设备信息 399 | 400 | ifconfig 401 | ``` 402 | - 启动关闭指定网卡 403 | ```shell 404 | # 前一个参数为具体网卡,后一个为开关信息 405 | # up为打开,down为关闭 406 | 407 | ifconfig eth0 up 408 | ``` 409 | 410 | - 配置IP地址 411 | ```shell 412 | # 前一个参数为具体网卡,后一个为配置的IP地址 413 | 414 | ifconfig eth0 192.168.1.1 415 | ``` 416 | 417 | - 设置最大传输单元 418 | ```shell 419 | 前一个参数为具体网卡,后面为MTU的大小 420 | # 设置链路层MTU值,通常为1500 421 | 422 | ifconfig eth0 mtu 1500 423 | ``` 424 | 425 | - 启用和关闭ARP协议 426 | ``` 427 | # 开启arp如下,若关闭则-arp 428 | 429 | ifconfig eth0 arp 430 | ``` 431 | 432 | 2. 查看当前网络连接(链路层/网络层/传输层):netstat 433 | - 网络接口信息 434 | ```shell 435 | # 显示网卡信息,可结合ifconfig学习 436 | 437 | netstat -i 438 | ``` 439 | 440 | - 列出端口 441 | ```shell 442 | # -a(all)表示所有端口,-t(tcp)表示所有使用中的TCP端口 443 | # -l(listening)表示正在监听的端口 444 | 445 | netstat -at 446 | ``` 447 | 448 | - 显示端口统计信息 449 | ```shell 450 | # -s(status)显示各协议信息 451 | # -加上-t(tcp)显示tcp协议信息,加上-u(udp)显示udp协议信息 452 | 453 | netstat -s 454 | ``` 455 | 456 | - 显示使用某协议的应用名 457 | ```shell 458 | # -p(progress)表示程序,可以显示使用tcp/udp协议的应用的名称 459 | 460 | netstat -pt 461 | ``` 462 | 463 | - 查找指定进程、端口 464 | ```shell 465 | # 互逆操作第一个显示某程序使用的端口号,第二个显示某端口号的使用进程 466 | # 第二个操作可以用lsof替代 467 | 468 | netstat -ap | grep ssh 469 | netstat -an | grep ':80' 470 | ``` 471 | 472 | 3. 查看路由表(网络层IP协议):route 473 | - 查看路由信息 474 | ```shell 475 | # 得到路由表信息,具体分析路由表工作需要网络知识 476 | # 可以通过netstat -r(route)得到同样的路由表 477 | 478 | route 479 | ``` 480 | 481 | 4. 检查网络连通性(网络层ICMP协议):ping 482 | - 检查是否连通 483 | ```shell 484 | # 主要功能是检测网络连通性 485 | # 可以额外得到网站的ip地址和连接最大/最小/平均耗时。 486 | 487 | ping baidu.com 488 | ``` 489 | 490 | 5. 转发路径(网络层ICMP协议):traceroute 491 | - 文件包途径的IP 492 | ```shell 493 | # 494 | # 可以打印从沿途经过的路由器IP地址 495 | 496 | traceroute baidu.com 497 | ``` 498 | 499 | 6. 网络Debug分析(网络层/传输层):nc 500 | - 端口扫描 501 | ```shell 502 | # 黑客很喜欢 503 | # 扫描某服务器端口使用情况 504 | # -v(view)显示指令执行过程,-w(wait)设置超时时长 505 | # -z使用输入输出模式(只在端口扫描时使用) 506 | # 数字为扫描的端口范围 507 | 508 | nc -v -w 1 baidu.com -z 75-1000 509 | ``` 510 | 511 | - [其他详见](https://www.oschina.net/translate/linux-netcat-command) 512 | 513 | 7. 命令行抓包(网络层/传输层):tcpdump 514 | - 抓包利器,没有什么比数据更值得信赖。可以跟踪整个传输过程。 515 | - [详见](http://www.cnblogs.com/ggjucheng/archive/2012/01/14/2322659.html) 516 | 517 | 8. 域名解析工具(应用层DNS协议):dig 518 | ```shell 519 | # 应用层,DNS 520 | # 打印域名解析结果 521 | # 打印域名解析过程中涉及的各级DNS服务器地址 522 | 523 | dig baidu.com 524 | ``` 525 | 526 | 9. 网络请求(应用层):curl 527 | - [详见](http://www.cnblogs.com/gbyukg/p/3326825.html) 528 | 529 | --- 530 | 531 | ### 其他 532 | 533 | > 这里都是日常开发中高频命令。 534 | 535 | 1. 终止进程:kill 536 | - 杀死具体进程 537 | ```shell 538 | # 加具体进程PID 539 | 540 | kill 12345 541 | ``` 542 | 543 | - 杀死某进程相关进程 544 | ```shell 545 | # 加上"-9"杀死某进程相关进程 546 | 547 | kill -9 12345 548 | ``` 549 | 550 | 2. 修改文件权限:chmod 551 | - 更改文件权限 552 | ```shell 553 | # 可以对三种使用者设置权限,u(user, owner),g(group),o(other) 554 | # 文件可以有三种权限,r(read),w(write),x(execute) 555 | # 这里u+r表示文件所有者在原有基础上增加文件读取权限 556 | # 这里777分别对应,u=7,g=7,o=7,具体数字含义自行google 557 | 558 | chmod u+r file 559 | chmod 777 file 560 | ``` 561 | 562 | 3. 创建链接:ln 563 | - 创建硬链接 564 | ```shell 565 | # 文件inode中链接数会增加,只有链接数减为0时文件才真正被删除 566 | 567 | ln file1 file2 568 | ``` 569 | 570 | - 创建软(符号链接)链接 571 | ```shell 572 | # -s(symbol)为符号链接,仅仅是引用路径 573 | # 相比于硬链接最大特点是可以跨文件系统 574 | # 类似于Windows创建快捷方式,实际文件删除则链接失效 575 | 576 | ln -s file1 file2 577 | ``` 578 | 579 | 4. 显示文件尾:tail 580 | - 查看文件尾部 581 | ```shell 582 | # -f参数可以不立即回传结束信号,当文件有新写入数据时会及时更新 583 | # 查看日志时常用 584 | 585 | tail -f test 586 | ``` 587 | 588 | 5. 版本控制:git 589 | - 版本控制最好用的软件,没有之一。至少要知道"git init","git add","git commit","git pull","git push"几个命令。 590 | - [详见](http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/) 591 | 592 | 6. 设置别名:alias 593 | - 常用命令添加别名 594 | ```shell 595 | # ".bashrc"文件中配置常用命令别名,生效后在命令行只需要使用别名即可代替原先很长的命令 596 | 597 | alias rm='rm -i' 598 | ``` 599 | 600 | --- 601 | 602 | ### 实战 603 | 604 | 假设已经通过vim编辑,gcc编译得到可执行文件server,这时就可以使用一些开发者常用的工具来进行后期调试。这里都是给出最简单的用法,意在快速掌握一些基本开发工具。 605 | 606 | 先clone这个项目,然后使用src_code下代码编译通过后通过下面命令调试。[代码](https://github.com/linw7/TKeed) 607 | 608 | 1. 单步调试:gdb 609 | 610 | - 运行得不到正确结果可以通过gdb设置断点来查看每个中间变量值,以此来确定哪里出了问题。因为gdb调试内容较多,这里不详细说明。另外,gdb出了可以单步查看变量值,还可以分析coredump文件来排查错误。 611 | 612 | 2. 动态库依赖:ldd 613 | 614 | - 命令:ldd ./server 615 | 616 | - 可以查看可执行文件server所需的所有动态库,动态库所在目录及其被映射到的虚拟地址空间。 617 | 618 | 3. 性能分析:top 619 | 620 | - top可以查看当前系统很多信息,比如1,5,15分钟内负载,运行、休眠、僵尸进程数,用户、内核程序占CPU百分比,存储信息等。top可以定位具体哪个进程CPU占用率高和内存使用率高。我们可以以此定位性能问题出在什么程序上(比如你后台执行TKeed server之后,可以看到CPU占用率为99%,这时候我们就需要从这个程序入手了)。 621 | 622 | 4. 系统调用:strace 623 | 624 | - 命令:strace ./server 625 | 626 | - 上面已经提到TKeed server的CPU占用率为99%,那么问题通常一定是出在了死循环上。我们接下来在代码中找到死循环位置。因为程序中epoll_wait需要阻塞进程,我们怀疑是不是这里没有阻塞,这时就可以通过上面的方式运行server程序。此时可以打印出没次系统调用及其参数等,我们也可以加-o filename将系统调用信息保存下来。 627 | 628 | 5. 打印进程:ps 629 | 630 | - 命令:ps -ejH 631 | 632 | - 我们在命令行下打开的程序的父进程是shell程序,之前用strace打开server程序,strace也是server的父进程。我们有时候需要知道进程间的层级关系就需要打印进程树,上面的ps命令可以做到。当出现僵尸进程时就可以通过进程树定位具体是哪个进程出了问题。另外当想要知道进程pid时,ps -el | grep XXX也是很常用的。 633 | 634 | 6. 打开文件:lsof 635 | 636 | - lsof -i:3000 637 | 638 | - 比如在运行server时发现端口被占用了,可以通过lsof -i:port来查看对应端口号正在被哪个进程所占用。端口占用是非常常见的问题,比如3306被占用我遇到过好几次,要么是某个程序正好占用了要么是之前没能结束进程,这些都可以借助lsof帮助查看端口。 639 | 640 | 7. 修改权限:chmod 641 | 642 | - chmod 000 ./index.html 643 | 644 | - 可以修改文件权限,这里设为000,这样任何人都无法访问,重新在浏览器请求127.0.0.1:3000/index.html就会因为文件权限不够而无法展示,服务器返回状态码为403,符合我们预期。修改权限后再请求一次可得到状态码200。 645 | 646 | 8. 网卡信息:ifconfig 647 | 648 | - ifconfig 649 | 650 | - 如果想看一下整个传输过程,可以使用tcpdump来抓包,但是抓包时参数需要加上网卡信息,这时候可以通过ifconfig来获得网卡信息。 651 | 652 | 9. 抓包分析:tcpdump 653 | 654 | - tcpdump -i eth0 port 3000 655 | 656 | - 可以用tcpdump来抓包分析三次握手及数据传输过程,-i之后加上上一步得到的网卡地址,port可以指定监听的端口号。 -------------------------------------------------------------------------------- /操作系统.md: -------------------------------------------------------------------------------- 1 | # 操作系统 2 | 3 | > 面向进程和线程学习操作系统。 4 | 5 | # 目录 6 | 7 | | Chapter 1 | Chapter 2 | Chapter 3| Chapter 4 | Chapter 5| 8 | | :---------: | :---------: | :---------: | :---------: | :---------: | 9 | |[进程线程模型](#thread)|[进程间通信](#con)|[同步互斥机制](#mutex)|[存储管理](#mem)|[网络I/O模型](#netio)| 10 | 11 | --- 12 | 13 | # 内容 14 | 15 | ### 进程线程模型 16 | 17 | 线程和进程的概念已经在操作系统书中被翻来覆去讲了很多遍。很多概念虽然都是套话,但没能理解透其中深意会导致很多内容理解不清晰。对于进程和线程的理解和把握可以说基本奠定了对系统的认知和把控能力。其核心意义绝不仅仅是“线程是调度的基本单位,进程是资源分配的基本单位”这么简单。 18 | 19 | **多线程** 20 | 21 | 我们这里讨论的是用户态的多线程模型,同一个进程内部有多个线程,所有的线程共享同一个进程的内存空间,进程中定义的全局变量会被所有的线程共享,比如有全局变量int i = 10,这一进程中所有并发运行的线程都可以读取和修改这个i的值,而多个线程被CPU调度的顺序又是不可控的,所以对临界资源的访问尤其需要注意安全。我们必须知道,做一次简单的i = i + 1在计算机中并不是原子操作,涉及内存取数,计算和写入内存几个环节,而线程的切换有可能发生在上述任何一个环节中间,所以不同的操作顺序很有可能带来意想不到的结果。 22 | 23 | 但是,虽然线程在安全性方面会引入许多新挑战,但是线程带来的好处也是有目共睹的。首先,原先顺序执行的程序(暂时不考虑多进程)可以被拆分成几个独立的逻辑流,这些逻辑流可以独立完成一些任务(最好这些任务是不相关的)。比如QQ可以一个线程处理聊天一个线程处理上传文件,两个线程互不干涉,在用户看来是同步在执行两个任务,试想如果线性完成这个任务的话,在数据传输完成之前用户聊天被一直阻塞会是多么尴尬的情况。 24 | 25 | 对于线程,我认为弄清以下两点非常重要: 26 | 27 | - 线程之间有无先后访问顺序(线程依赖关系) 28 | 29 | - 多个线程共享访问同一变量(同步互斥问题) 30 | 31 | 另外,我们通常只会去说同一进程的多个线程共享进程的资源,但是每个线程特有的部分却很少提及,除了标识线程的tid,每个线程还有自己独立的栈空间,线程彼此之间是无法访问其他线程栈上内容的。而作为处理机调度的最小单位,线程调度只需要保存线程栈、寄存器数据和PC即可,相比进程切换开销要小很多。 32 | 33 | 线程相关接口不少,主要需要了解各个参数意义和返回值意义。 34 | 35 | 1. 线程创建和结束 36 | 37 | - 背景知识: 38 | 39 | 在一个文件内的多个函数通常都是按照main函数中出现的顺序来执行,但是在分时系统下,我们可以让每个函数都作为一个逻辑流并发执行,最简单的方式就是采用多线程策略。在main函数中调用多线程接口创建线程,每个线程对应特定的函数(操作),这样就可以不按照main函数中各个函数出现的顺序来执行,避免了忙等的情况。线程基本操作的接口如下。 40 | 41 | - 相关接口: 42 | 43 | - 创建线程:int pthread_create(pthread_t *pthread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *agr); 44 | 45 | 创建一个新线程,pthread和start_routine不可或缺,分别用于标识线程和执行体入口,其他可以填NULL。 46 | 47 | - pthread:用来返回线程的tid,*pthread值即为tid,类型pthread_t == unsigned long int。 48 | 49 | - attr:指向线程属性结构体的指针,用于改变所创线程的属性,填NULL使用默认值。 50 | 51 | - start_routine:线程执行函数的首地址,传入函数指针。 52 | 53 | - arg:通过地址传递来传递函数参数,这里是无符号类型指针,可以传任意类型变量的地址,在被传入函数中先强制类型转换成所需类型即可。 54 | 55 | - 获得线程ID:pthread_t pthread_self(); 56 | 57 | 调用时,会打印线程ID。 58 | 59 | - 等待线程结束:int pthread_join(pthread_t tid, void** retval); 60 | 61 | 主线程调用,等待子线程退出并回收其资源,类似于进程中wait/waitpid回收僵尸进程,调用pthread_join的线程会被阻塞。 62 | 63 | - tid:创建线程时通过指针得到tid值。 64 | 65 | - retval:指向返回值的指针。 66 | 67 | - 结束线程:pthread_exit(void *retval); 68 | 69 | 子线程执行,用来结束当前线程并通过retval传递返回值,该返回值可通过pthread_join获得。 70 | 71 | - retval:同上。 72 | 73 | - 分离线程:int pthread_detach(pthread_t tid); 74 | 75 | 主线程、子线程均可调用。主线程中pthread_detach(tid),子线程中pthread_detach(pthread_self()),调用后和主线程分离,子线程结束时自己立即回收资源。 76 | 77 | - tid:同上。 78 | 79 | 2. 线程属性值修改 80 | 81 | - 背景知识: 82 | 83 | 线程属性对象类型为pthread_attr_t,结构体定义如下: 84 | ```C++ 85 | typedef struct{ 86 | int etachstate; // 线程分离的状态 87 | int schedpolicy; // 线程调度策略 88 | struct sched_param schedparam; // 线程的调度参数 89 | int inheritsched; // 线程的继承性 90 | int scope; // 线程的作用域 91 | // 以下为线程栈的设置 92 | size_t guardsize; // 线程栈末尾警戒缓冲大小 93 | int stackaddr_set; // 线程的栈设置 94 | void * stackaddr; // 线程栈的位置 95 | size_t stacksize; // 线程栈大小 96 | }pthread_arrt_t; 97 | ``` 98 | 99 | - 相关接口: 100 | 101 | 对上述结构体中各参数大多有:pthread_attr_get***()和pthread_attr_set***()系统调用函数来设置和获取。这里不一一罗列。 102 | 103 | 3. 线程同步 104 | 105 | - [详见同步互斥专题](#mutex) 106 | 107 | **多进程** 108 | 109 | 每一个进程是资源分配的基本单位。进程结构由以下几个部分组成:代码段、堆栈段、数据段。代码段是静态的二进制代码,多个程序可以共享。实际上在父进程创建子进程之后,父、子进程除了pid外,几乎所有的部分几乎一样,子进程创建时拷贝父进程PCB中大部分内容,而PCB的内容实际上是各种数据、代码的地址或索引表地址,所以复制了PCB中这些指针实际就等于获取了全部父进程可访问数据。所以简单来说,创建新进程需要复制整个PCB,之后操作系统将PCB添加到进程核心堆栈底部,这样就可以被操作系统感知和调度了。 110 | 111 | 父、子进程共享全部数据,但并不是说他们就是对同一块数据进行操作,子进程在读写数据时会通过写时复制机制将公共的数据重新拷贝一份,之后在拷贝出的数据上进行操作。如果子进程想要运行自己的代码段,还可以通过调用execv()函数重新加载新的代码段,之后就和父进程独立开了。我们在shell中执行程序就是通过shell进程先fork()一个子进程再通过execv()重新加载新的代码段的过程。 112 | 113 | 1. 进程创建与结束 114 | 115 | - 背景知识: 116 | 117 | 进程有两种创建方式,一种是操作系统创建的一种是父进程创建的。从计算机启动到终端执行程序的过程为:0号进程 -> 1号内核进程 -> 1号用户进程(init进程) -> getty进程 -> shell进程 -> 命令行执行进程。所以我们在命令行中通过 ./program执行可执行文件时,所有创建的进程都是shell进程的子进程,这也就是为什么shell一关闭,在shell中执行的进程都自动被关闭的原因。从shell进程到创建其他子进程需要通过以下接口。 118 | 119 | - 相关接口: 120 | 121 | - 创建进程:pid_t fork(void); 122 | 123 | 返回值:出错返回-1;父进程中返回pid > 0;子进程中pid == 0 124 | 125 | - 结束进程:void exit(int status); 126 | 127 | - status是退出状态,保存在全局变量中S?,通常0表示正常退出。 128 | 129 | - 获得PID:pid_t getpid(void); 130 | 131 | 返回调用者pid。 132 | 133 | - 获得父进程PID:pid_t getppid(void); 134 | 135 | 返回父进程pid。 136 | 137 | - 其他补充: 138 | 139 | - 正常退出方式:exit()、_exit()、return(在main中)。 140 | 141 | exit()和_exit()区别:exit()是对_exit()的封装,都会终止进程并做相关收尾工作,最主要的区别是_exit()函数关闭全部描述符和清理函数后不会刷新流,但是exit()会在调用_exit()函数前刷新数据流。 142 | 143 | return和exit()区别:exit()是函数,但有参数,执行完之后控制权交给系统。return若是在调用函数中,执行完之后控制权交给调用进程,若是在main函数中,控制权交给系统。 144 | 145 | - 异常退出方式:abort()、终止信号。 146 | 147 | 2. 僵尸进程、孤儿进程 148 | 149 | - 背景知识: 150 | 151 | 父进程在调用fork接口之后和子进程已经可以独立开,之后父进程和子进程就以未知的顺序向下执行(异步过程)。所以父进程和子进程都有可能先执行完。当父进程先结束,子进程此时就会变成孤儿进程,不过这种情况问题不大,孤儿进程会自动向上被init进程收养,init进程完成对状态收集工作。而且这种过继的方式也是守护进程能够实现的因素。如果子进程先结束,父进程并未调用wait或者waitpid获取进程状态信息,那么子进程描述符就会一直保存在系统中,这种进程称为僵尸进程。 152 | 153 | - 相关接口: 154 | 155 | - 回收进程(1):pid_t wait(int *status); 156 | 157 | 一旦调用wait(),就会立即阻塞自己,wait()自动分析某个子进程是否已经退出,如果找到僵尸进程就会负责收集和销毁,如果没有找到就一直阻塞在这里。 158 | 159 | - status:指向子进程结束状态值。 160 | 161 | - 回收进程(2):pid_t waitpid(pid_t pid, int *status, int options); 162 | 163 | 返回值:返回pid:返回收集的子进程id。返回-1:出错。返回0:没有被手机的子进程。 164 | 165 | - pid:子进程识别码,控制等待哪些子进程。 166 | 1. pid < -1,等待进程组识别码为pid绝对值的任何进程。 167 | 168 | 2. pid = -1,等待任何子进程。 169 | 170 | 3. pid = 0,等待进程组识别码与目前进程相同的任何子进程。 171 | 172 | 4. pid > 0,等待任何子进程识别码为pid的子进程。 173 | 174 | - status:指向返回码的指针。 175 | 176 | - options:选项决定父进程调用waitpid后的状态。 177 | 178 | 1. options = WNOHANG,即使没有子进程退出也会立即返回。 179 | 180 | 2. options = WUNYRACED,子进程进入暂停马上返回,但结束状态不予理会。 181 | 182 | 3. 守护进程 183 | 184 | - 背景知识: 185 | 186 | 守护进程是脱离终端并在后台运行的进程,执行过程中信息不会显示在终端上并且也不会被终端发出的信号打断。 187 | 188 | - 操作步骤: 189 | 190 | - 创建子进程,父进程退出:fork() + if(pid > 0){exit(0);},使子进程称为孤儿进程被init进程收养。 191 | 192 | - 在子进程中创建新会话:setsid()。 193 | 194 | - 改变当前目录结构为根:chdir("/")。 195 | 196 | - 重设文件掩码:umask(0)。 197 | 198 | - 关闭文件描述符:for(int i = 0; i < 65535; ++i){close(i);}。 199 | 200 | 4. Linux进程控制 201 | 202 | - 进程地址空间(地址空间) 203 | 204 | 虚拟存储器为每个进程提供了独占系统地址空间的假象。尽管每个进程地址空间内容不尽相同,但是他们的都有相似的结构。X86 Linux进程的地址空间底部是保留给用户程序的,包括文本、数据、堆、栈等,其中文本区和数据区是通过存储器映射方式将磁盘中可执行文件的相应段映射至虚拟存储器地址空间中。有一些"敏感"的地址需要注意下,对于32位进程来说,代码段从0x08048000开始。从0xC0000000开始到0xFFFFFFFF是内核地址空间,通常情况下代码运行在用户态(使用0x00000000 ~ 0xC00000000的用户地址空间),当发生系统调用、进程切换等操作时CPU控制寄存器设置模式位,进入内和模式,在该状态(超级用户模式)下进程可以访问全部存储器位置和执行全部指令。也就说32位进程的地址空间都是4G,但用户态下只能访问低3G的地址空间,若要访问3G ~ 4G的地址空间则只有进入内核态才行。 205 | 206 | - 进程控制块(处理机) 207 | 208 | 进程的调度实际就是内核选择相应的进程控制块,被选择的进程控制块中包含了一个进程基本的信息。 209 | 210 | - 上下文切换 211 | 212 | 内核管理所有进程控制块,而进程控制块记录了进程全部状态信息。每一次进程调度就是一次上下文切换,所谓的上下文本质上就是当前运行状态,主要包括通用寄存器、浮点寄存器、状态寄存器、程序计数器、用户栈和内核数据结构(页表、进程表、文件表)等。进程执行时刻,内核可以决定抢占当前进程并开始新的进程,这个过程由内核调度器完成,当调度器选择了某个进程时称为该进程被调度,该过程通过上下文切换来改变当前状态。一次完整的上下文切换通常是进程原先运行于用户态,之后因系统调用或时间片到切换到内核态执行内核指令,完成上下文切换后回到用户态,此时已经切换到进程B。 213 | 214 | **线程、进程比较** 215 | 216 | 关于进程和线程的区别这里就不一一罗列了,主要对比下线程和进程操作中主要的接口。 217 | 218 | - fork()和pthread_create() 219 | 220 | 负责创建。调用fork()后返回两次,一次标识主进程一次标识子进程;调用pthread_create()后得到一个可以独立执行的线程。 221 | 222 | - wait()和pthread_join() 223 | 224 | 负责回收。调用wait()后父进程阻塞;调用pthread_join()后主线程阻塞。 225 | 226 | - exit()和pthread_exit() 227 | 228 | 负责退出。调用exit()后调用进程退出,控制权交给系统;调用pthread_exit()后线程退出,控制权交给主线程。 229 | 230 | --- 231 | 232 | ### 进程间通信 233 | 234 | Linux几乎支持全部UNIX进程间通信方法,包括管道(有名管道和无名管道)、消息队列、共享内存、信号量和套接字。其中前四个属于同一台机器下进程间的通信,套接字则是用于网络通信。 235 | 236 | **管道** 237 | 238 | - 无名管道 239 | 240 | - 无名管道特点: 241 | 242 | - 无名管道是一种特殊的文件,这种文件只存在于内存中。 243 | 244 | - 无名管道只能用于父子进程或兄弟进程之间,必须用于具有亲缘关系的进程间的通信。 245 | 246 | - 无名管道只能由一端向另一端发送数据,是半双工方式,如果双方需要同时收发数据需要两个管道。 247 | 248 | - 相关接口: 249 | 250 | - int pipe(int fd[2]); 251 | 252 | - fd[2]:管道两端用fd[0]和fd[1]来描述,读的一端用fd[0]表示,写的一端用fd[1]表示。通信双方的进程中写数据的一方需要把fd[0]先close掉,读的一方需要先把fd[1]给close掉。 253 | 254 | - 有名管道: 255 | 256 | - 有名管道特点: 257 | 258 | - 有名管道是FIFO文件,存在于文件系统中,可以通过文件路径名来指出。 259 | 260 | - 无名管道可以在不具有亲缘关系的进程间进行通信。 261 | 262 | - 相关接口: 263 | 264 | - int mkfifo(const char *pathname, mode_t mode); 265 | 266 | - pathname:即将创建的FIFO文件路径,如果文件存在需要先删除。 267 | 268 | - mode:和open()中的参数相同。 269 | 270 | 271 | **消息队列** 272 | 273 | **共享内存** 274 | 275 | 进程可以将同一段共享内存连接到它们自己的地址空间,所有进程都可以访问共享内存中的地址,如果某个进程向共享内存内写入数据,所做的改动将立即影响到可以访问该共享内存的其他所有进程。 276 | 277 | - 相关接口 278 | 279 | - 创建共享内存:int shmget(key_t key, int size, int flag); 280 | 281 | 成功时返回一个和key相关的共享内存标识符,失败范湖范围-1。 282 | 283 | - key:为共享内存段命名,多个共享同一片内存的进程使用同一个key。 284 | 285 | - size:共享内存容量。 286 | 287 | - flag:权限标志位,和open的mode参数一样。 288 | 289 | - 连接到共享内存地址空间:void *shmat(int shmid, void *addr, int flag); 290 | 291 | 返回值即共享内存实际地址。 292 | 293 | - shmid:shmget()返回的标识。 294 | 295 | - addr:决定以什么方式连接地址。 296 | 297 | - flag:访问模式。 298 | 299 | - 从共享内存分离:int shmdt(const void *shmaddr); 300 | 301 | 调用成功返回0,失败返回-1。 302 | 303 | - shmaddr:是shmat()返回的地址指针。 304 | 305 | - 其他补充 306 | 307 | 共享内存的方式像极了多线程中线程对全局变量的访问,大家都对等地有权去修改这块内存的值,这就导致在多进程并发下,最终结果是不可预期的。所以对这块临界区的访问需要通过信号量来进行进程同步。 308 | 309 | 但共享内存的优势也很明显,首先可以通过共享内存进行通信的进程不需要像无名管道一样需要通信的进程间有亲缘关系。其次内存共享的速度也比较快,不存在读取文件、消息传递等过程,只需要到相应映射到的内存地址直接读写数据即可。 310 | 311 | **信号量** 312 | 313 | 在提到共享内存方式时也提到,进程共享内存和多线程共享全局变量非常相似。所以在使用内存共享的方式是也需要通过信号量来完成进程间同步。多线程同步的信号量是POSIX信号量, 314 | 而在进程里使用SYSTEM V信号量。 315 | 316 | - 相关接口 317 | 318 | - 创建信号量:int semget(key_t key, int nsems, int semflag); 319 | 320 | 创建成功返回信号量标识符,失败返回-1。 321 | 322 | - key:进程pid。 323 | 324 | - nsems:创建信号量的个数。 325 | 326 | - semflag:指定信号量读写权限。 327 | 328 | - 改变信号量值:int semop(int semid, struct sembuf *sops, unsigned nsops); 329 | 330 | 我们所需要做的主要工作就是串讲sembuf变量并设置其值,然后调用semop,把设置好的sembuf变量传递进去。 331 | 332 | struct sembuf结构体定义如下: 333 | ```C++ 334 | struct sembuf{ 335 | short sem_num; 336 | short sem_op; 337 | short sem_flg; 338 | }; 339 | ``` 340 | 成功返回信号量标识符,失败返回-1。 341 | 342 | - semid:信号量集标识符,由semget()函数返回。 343 | 344 | - sops:指向struct sembuf结构的指针,先设置好sembuf值再通过指针传递。 345 | 346 | - nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作。 347 | 348 | - 直接控制信号量信息:int semctl(int semid, int semnum, int cmd, union semun arg); 349 | 350 | - semid:信号量集标识符。 351 | 352 | - semnum:信号量集数组上的下标,表示某一个信号量。 353 | 354 | - arg:union semun类型。 355 | 356 | **辅助命令** 357 | 358 | ipcs命令用于报告共享内存、信号量和消息队列信息。 359 | 360 | - ipcs -a:列出共享内存、信号量和消息队列信息。 361 | 362 | - ipcs -l:列出系统限额。 363 | 364 | - ipcs -u:列出当前使用情况。 365 | 366 | **套接字** 367 | 368 | - [详见socket交互流程](https://github.com/linw7/Skill-Tree/blob/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C.md) 369 | 370 | - [详见网络I/O模型](#netio) 371 | 372 | --- 373 | 374 | ### 同步互斥机制 375 | 376 | > 待补充 377 | 378 | --- 379 | 380 | ### 网络I/O模型 381 | 382 | 在描述这块内容的诸多书籍中,很多都只说笼统的概念,我们将问题具体化,暂时只考虑服务器端的网络I/O情形。我们假定目前的情形是服务器已经在监听用户请求,建立连接后服务器调用read()函数等待读取用户发送过来的数据流,之后将接收到的数据打印出来。 383 | 384 | 所以服务器端简单是这样的流程:建立连接 -> 监听请求 -> 等待用户数据 -> 打印数据。我们总结网络通信中的等待: 385 | 386 | - 建立连接时等待对方的ACK包(TCP)。 387 | 388 | - 等待客户端请求(HTTP)。 389 | 390 | - 输入等待:服务器用户数据到达内核缓冲区(read函数等待)。 391 | 392 | - 输出等待:用户端等待缓冲区有足够空间可以输入(write函数等待)。 393 | 394 | 另外为了能够解释清楚网络I/O模型,还需要了解一些基础。对服务器而言,打印出用户输入的字符串(printf函数)和从网络中获取数据(read函数)需要单独来看。服务器首先accept用户连接请求后首先调用read函数等待数据,这里的read函数是系统调用,运行于内核态,使用的也是内核地址空间,并且从网络中取得的数据需要先写入到内核缓冲区。当read系统调用获取到数据后将这些数据再复制到用户地址空间的用户缓冲区中,之后返回到用户态执行printf函数打印字符串。我们需要明确两点: 395 | 396 | - read执行在内核态且数据流先读入内核缓冲区;printf运行于用户态,打印的数据会先从内核缓冲区复制到进程的用户缓冲区,之后打印出来。 397 | 398 | - printf函数一定是在read函数已经准备好数据之后才能执行,但read函数作为I/O操作通常需要等待而触发阻塞。调用read函数的是服务器进程,一旦被read调用阻塞,整个服务器在获取到用户数据前都不能接受任何其他用户的请求(单进程/线程)。 399 | 400 | 有了上面的基础,我们就可以介绍下面四种网路I/O模型。 401 | 402 | **阻塞式** 403 | 404 | - 阻塞表示一旦调用I/O函数必须等整个I/O完成才返回。正如上面提到的那种情形,当服务器调用了read函数之后,如果不是立即接收到数据,服务器进程会被阻塞,之后一直在等待用户数据到达,用户数据到达后首先会写进内核缓冲区,之后内核缓冲区数据复制到用户进程(服务器进程)缓冲区。完成了上述所有的工作后,才会把执行权限返回给用户(从内核态 -> 用户态)。 405 | 406 | - 很显然,阻塞式I/O的效率实在太低,如果用户输入数据迟迟不到的话,整个服务器就会一直被阻塞(单进程/线程)。为了不影响服务器接收其他进程的连接,我们可以考虑多进程模型,这样当服务器建立连接后为连接的用户创建新线程,新线程即使是使用阻塞式I/O也仅仅是这一个线程被阻塞,不会影响服务器等待接收新的连接。 407 | 408 | - 多线程模型下,主线程等待用户请求,用户有请求到达时创建新线程。新线程负责具体的工作,即使是因为调用了read函数被阻塞也不会影响服务器。我们还可以进一步优化创建连接池和线程池以减小频繁调用I/O接口的开销。但新问题随之产生,每个新线程或者进程(加入使用对进程模型)都会占用大量系统资源,除此之外过多的线程和进程在调度方面开销也会大很对,所以这种模型并不适合大并发量。 409 | 410 | **非阻塞I/O** 411 | 412 | - 阻塞和非阻塞最大的区别在于调用I/O系统调用后,是等整个I/O过程完成再把操作权限返回给用户还是会立即返回。 413 | 414 | - 可以使用以下语句将句柄fd设置为非阻塞I/O:fcntl(fd, F_SETFL, O_NONBLOCK); 415 | 416 | - 非阻塞I/O在调用后会立即返回,用户进程对返回的返回值判断以区分是否完成了I/O。如果返回大于0表示完成了数据读取,返回值即读取的字节数;返回0表示连接已经正常断开;返回-1表示错误,接下来用户进程会不停地询问kernel是否准备完毕。 417 | 418 | - 非阻塞I/O虽然不再会完全阻塞用户进程,但实际上由于用户进程需要不停地询问kernel是否准备完数据,所以整体效率依旧非常低,不适合做并发。 419 | 420 | **I/O多路复用(事件驱动模型)** 421 | 422 | 前面已经论述了多进程、多进程模型会因为开销巨大和调度困难而导致并不能承受高并发量。但不适用这种模型的话,无论是阻塞还是非阻塞方式都会导致整个服务器停滞。 423 | 424 | 所以对于大并发量,我们需要一种代理模型可以帮助我们集中去管理所有的socket连接,一旦某个socket数据到达了就执行其对应的用户进程,I/O多路复用就是这么一种模型。Linux下I/O多路复用的系统调用有select,poll和epoll,但从本质上来讲他们都是同步I/O范畴。 425 | 426 | 1. select 427 | 428 | - 相关接口: 429 | 430 | int select (int maxfd, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout); 431 | 432 | FD_ZERO(int fd, fd_set* fds) //清空集合 433 | 434 | FD_SET(int fd, fd_set* fds) //将给定的描述符加入集合 435 | 436 | FD_ISSET(int fd, fd_set* fds) //将给定的描述符从文件中删除 437 | 438 | FD_CLR(int fd, fd_set* fds) //判断指定描述符是否在集合中 439 | 440 | - 参数: 441 | maxfd:当前最大文件描述符的值+1(≠ MAX_CONN)。 442 | 443 | readfds:指向读文件队列集合(fd_set)的指针。 444 | 445 | writefds:同上,指向读集合的指针。 446 | 447 | writefds:同上,指向错误集合的指针。 448 | 449 | timeout:指向timeval结构指针,用于设置超时。 450 | 451 | - 其他: 452 | 453 | 判断和操作对象为set_fd集合,集合大小为单个进程可打开的最大文件数1024或2048(可重新编译内核修改但不建议)。 454 | 455 | 2. poll 456 | - 相关接口: 457 | int poll(struct pollfd *fds, unsigned int nfds, int timeout); 458 | 459 | - 结构体定义: 460 | struct pollfd{ 461 | int fd; // 文件描述符 462 | short events; // 等到的事件 463 | short revents; // 实际发生的事件 464 | } 465 | 466 | - 参数: 467 | fds:指向pollfd结构体数组的指针。 468 | 469 | nfds:pollfd数组当前已被使用的最大下标。 470 | 471 | timeout:等待毫秒数。 472 | 473 | - 其他: 474 | 475 | 判断和操作对象是元素为pollfd类型的数组,数组大小自己设定,即为最大连接数。 476 | 477 | 3. epoll 478 | 479 | - 相关接口: 480 | int epoll_create(int size); // 创建epoll句柄 481 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // 事件注册函数 482 | int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 483 | 484 | - 结构体定义: 485 | struct epoll_event{ 486 | __uint32_t events; 487 | epoll_data_t data; 488 | }; 489 | typedef union epoll_data{ 490 | void *ptr; 491 | int fd; 492 | __uint32_t u32; 493 | __uint64_t u64; 494 | }epoll_data_t; 495 | 496 | - 参数: 497 | 498 | size:用来告诉内核要监听的数目。 499 | 500 | epfd:epoll函数的返回值。 501 | 502 | op:表示动作(EPOLL_CTL_ADD/EPOLL_CTL_FD/EPOLL_CTL_DEL)。 503 | 504 | fd:需要监听的fd。 505 | 506 | events:指向epoll_event的指针,该结构记录监听的事件。 507 | 508 | maxevents:告诉内核events的大小。 509 | 510 | timeout:超时时间(ms为单位,0表示立即返回,-1将不确定)。 511 | 512 | 4. select、poll和epoll区别 513 | - 操作方式及效率: 514 | 515 | select是遍历,需要遍历fd_set每一个比特位(= MAX_CONN),O(n);poll是遍历,但只遍历到pollfd数组当前已使用的最大下标(≠ MAX_CONN),O(n);epoll是回调,O(1)。 516 | 517 | - 最大连接数: 518 | 519 | select为1024/2048(一个进程打开的文件数是有限制的);poll无上限;epoll无上限。 520 | 521 | - fd拷贝: 522 | 523 | select每次都需要把fd集合从用户态拷贝到内核态;poll每次都需要把fd集合从用户态拷贝到内核态;epoll调用epoll_ctl时拷贝进内核并放到事件表中,但用户进程和内核通过mmap映射共享同一块存储,避免了fd从内核赋值到用户空间。 524 | 525 | - 其他: 526 | 527 | select每次内核仅仅是通知有消息到了需要处理,具体是哪一个需要遍历所有的描述符才能找到。epoll不仅通知有I/O到来还可通过callback函数具体定位到活跃的socket,实现伪AIO。 528 | 529 | **异步I/O模型** 530 | 531 | - 上面三种I/O方式均属于同步I/O。 532 | 533 | - 从阻塞式I/O到非阻塞I/O,我们已经做到了调用I/O请求后立即返回,但不停轮询的操作效率又很低,如果能够既像非阻塞I/O能够立即返回又能不一直轮询的话会更符合我们的预期。 534 | 535 | - 之所以用户进程会不停轮询就是因为在数据准备完毕后内核不会回调用户进程,只能通过用户进程一次又一次轮询来查询I/O结果。如果内核能够在完成I/O后通过消息告知用户进程来处理已经得到的数据自然是最好的,异步I/O就是这么回事。 536 | 537 | - 异步I/O就是当用户进程发起I/O请求后立即返回,直到内核发送一个信号,告知进程I/O已完成,在整个过程中,都没有进程被阻塞。看上去异步I/O和非阻塞I/O的区别在于:判断数据是否准备完毕的任务从用户进程本身被委托给内核来完成。这里所谓的异步只是操作系统提供的一直机制罢了。 538 | -------------------------------------------------------------------------------- /编程语言C++.md: -------------------------------------------------------------------------------- 1 | # 编程语言(C/C++) 2 | 3 | > 都是语言,为什么英语比C++难这么多呢? 4 | 5 | --- 6 | 7 | # 目录 8 | 9 | | Chapter 1 | Chapter 2 | Chapter 3| Chapter 4 | 10 | | :---------: | :---------: | :---------: | :---------: | 11 | | [编程基础](base)|[面向对象基础](#oop)|[标准模板库](#stl)|[编译及调试](#other)| 12 | 13 | --- 14 | 15 | # 内容 16 | 17 | ### 编程基础 18 | 19 | C/C++的内容又多又杂,常常看到有人罗列相关书单,觉得毫无意义,我不相信他们真的完全掌握了其中任何一本。学习任何东西,首先要掌握基本概念,基础不牢地动山摇,因为高级的内容都是通过低级的概念来描述的。当基本概念都没理解透,学习再多都是空中楼阁。这里罗列了一些听基本的问题,虽然看着不难,但是精确理解每句话中的每个词真的并不容易。 20 | 21 | 1. 变量声明和定义区别? 22 | 23 | - 声明仅仅是把变量的声明的位置及类型提供给编译器,并不分配内存空间;定义要在定义的地方为其分配存储空间。 24 | 25 | - 相同变量可以再多处声明(外部变量extern),但只能在一处定义。 26 | 27 | 2. "零值比较"? 28 | - bool类型:if(flag) 29 | 30 | - int类型:if(flag == 0) 31 | 32 | - 指针类型:if(flag == null) 33 | 34 | - float类型:if((flag >= -0.000001) && (flag <= 0. 000001)) 35 | 36 | 3. strlen和sizeof区别? 37 | - sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得;strlen是字符处理的库函数。 38 | 39 | - sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是字符指针且结尾是'\0'的字符串。 40 | 41 | - **因为sizeof值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小。** 42 | 43 | 4. 同一不同对象可以互相赋值吗? 44 | - 可以,但含有指针成员时需要注意。 45 | 46 | - 对比类的对象赋值时深拷贝和浅拷贝。 47 | 48 | 5. 结构体内存对齐问题? 49 | - 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。 50 | 51 | - 未特殊说明时,按结构体中size最大的成员对齐(若有double成员),按8字节对齐。 52 | 53 | 6. static作用是什么?在C和C++中有何区别? 54 | - static可以修饰局部变量(静态局部变量)、全局变量(静态全局变量)和函数,被修饰的变量存储位置在静态区。对于静态局部变量,相对于一般局部变量其生命周期长,直到程序运行结束而非函数调用结束,且只在第一次被调用时定义;对于静态全局变量,相对于全局变量其可见范围被缩小,只能在本文件中可见;修饰函数时作用和修饰全局变量相同,都是为了限定访问域。 55 | 56 | - C++的static除了上述两种用途,还可以修饰类成员(静态成员变量和静态成员函数),静态成员变量和静态成员函数不属于任何一个对象,是所有类实例所共有。 57 | 58 | - static的数据记忆性可以满足函数在不同调用期的通信,也可以满足同一个类的多个实例间的通信。 59 | 60 | - 未初始化时,static变量默认值为0。 61 | 62 | 7. 结构体和类的区别? 63 | - 结构体的默认限定符是public;类是private。 64 | 65 |   - ~~结构体不可以继承,类可以。~~ C++中结构体也可以继承。 66 | 67 | 8. malloc和new的区别? 68 | - malloc和free是标准库函数,支持覆盖;new和delete是运算符,并且支持重载。 69 | 70 | - malloc仅仅分配内存空间,free仅仅回收空间,不具备调用构造函数和析构函数功能,用malloc分配空间存储类的对象存在风险;new和delete除了分配回收功能外,还会调用构造函数和析构函数。 71 | 72 | - malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。 73 | 74 | 9. 指针和引用区别? 75 |    - 引用只是别名,不占用具体存储空间,只有声明没有定义;指针是具体变量,需要占用存储空间。 76 | 77 | - 引用在声明时必须初始化为另一变量,一旦出现必须为typename refname &varname形式;指针声明和定义可以分开,可以先只声明指针变量而不初始化,等用到时再指向具体变量。 78 | 79 | - 引用一旦初始化之后就不可以再改变(变量可以被引用为多次,但引用只能作为一个变量引用);指针变量可以重新指向别的变量。 80 | 81 | - 不存在指向空值的引用,必须有具体实体;但是存在指向空值的指针。 82 | 83 | 10. 宏定义和函数有何区别? 84 | - 宏在编译时完成替换,之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用,执行起来更快;函数调用在运行时需要跳转到具体调用函数。 85 | 86 | - 宏函数属于在结构中插入代码,没有返回值;函数调用具有返回值。 87 | 88 | - 宏函数参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。 89 | 90 | - 宏函数不要在最后加分号。 91 | 92 | 11. 宏定义和const区别? 93 | - 宏替换发生在编译阶段之前,属于文本插入替换;const作用发生于编译过程中。 94 | 95 | - 宏不检查类型;const会检查数据类型。 96 | 97 | - 宏定义的数据没有分配内存空间,只是插入替换掉;const定义的变量只是值不能改变,但要分配内存空间。 98 | 99 | 12. 宏定义和typedef区别? 100 | - 宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。 101 | 102 | - 宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。 103 | 104 | - 宏不检查类型;typedef会检查数据类型。 105 | 106 | - 宏不是语句,不在在最后加分号;typedef是语句,要加分号标识结束。 107 | 108 | - 注意对指针的操作,typedef char * p_char和#define p_char char *区别巨大。 109 | 110 | 13. 宏定义和内联函数(inline)区别? 111 | - 在使用时,宏只做简单字符串替换(编译前)。而内联函数可以进行参数类型检查(编译时),且具有返回值。 112 | 113 | - 内联函数本身是函数,强调函数特性,具有重载等功能。 114 | 115 | - 内联函数可以作为某个类的成员函数,这样可以使用类的保护成员和私有成员。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了。 116 | 117 | 14. 条件编译#ifdef, #else, #endif作用? 118 | - 可以通过加#define,并通过#ifdef来判断,将某些具体模块包括进要编译的内容。 119 | 120 | - 用于子程序前加#define DEBUG用于程序调试。 121 | 122 | - 应对硬件的设置(机器类型等)。 123 | 124 | - 条件编译功能if也可实现,但条件编译可以减少被编译语句,从而减少目标程序大小。 125 | 126 | 15. 区别以下几种变量? 127 | 128 | const int a; 129 | int const a; 130 | const int *a; 131 | int *const a; 132 | 133 | - int const a和const int a均表示定义常量类型a。 134 | 135 | - const int *a,其中a为指向int型变量的指针,const在 * 左侧,表示a指向不可变常量。(看成const (*a),对引用加const) 136 | 137 | - int *const a,依旧是指针类型,表示a为指向整型数据的常指针。(看成const(a),对指针const) 138 | 139 | 16. volatile有什么作用? 140 | - volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。 141 | 142 | - 多线程中被几个任务共享的变量需要定义为volatile类型。 143 | 144 | 17. 什么是常引用? 145 | - 常引用可以理解为常量指针,形式为const typename & refname = varname。 146 | 147 | - 常引用下,原变量值不会被别名所修改。 148 | 149 | - 原变量的值可以通过原名修改。 150 | 151 | - 常引用通常用作只读变量别名或是形参传递。 152 | 153 | 18. 区别以下指针类型? 154 | 155 | int *p[10] 156 | int (*p)[10] 157 | int *p(int) 158 | int (*p)(int) 159 | 160 | - int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。 161 | 162 | - int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。 163 | 164 | - int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。 165 | 166 | - int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。 167 | 168 | 19. 常量指针和指针常量区别? 169 | - 常量指针是一个指针,读成常量的指针,指向一个只读变量。如int const *p或const int *p。 170 | 171 | - 指针常量是一个不能给改变指向的指针。如int *const p。 172 | 173 | 20. a和&a有什么区别? 174 | 175 | 假设数组int a[10]; 176 | int (*p)[10] = &a; 177 | 178 | - a是数组名,是数组首元素地址,+1表示地址值加上一个int类型的大小,如果a的值是0x00000001,加1操作后变为0x00000005。*(a + 1) = a[1]。 179 | 180 | - &a是数组的指针,其类型为int (*)[10](就是前面提到的数组指针),其加1时,系统会认为是数组首地址加上整个数组的偏移(10个int型变量),值为数组a尾元素后一个元素的地址。 181 | 182 | - 若(int *)p ,此时输出 *p时,其值为a[0]的值,因为被转为int *类型,解引用时按照int类型大小来读取。 183 | 184 | 21. 数组名和指针(这里为指向数组首元素的指针)区别? 185 | - 二者均可通过增减偏移量来访问数组中的元素。 186 | 187 | - 数组名不是真正意义上的指针,可以理解为常指针,所以数组名没有自增、自减等操作。 188 | 189 | - 当数组名当做形参传递给调用函数后,就失去了原有特性,退化成一般指针,多了自增、自减操作,但sizeof运算符不能再得到原数组的大小了。 190 | 191 | 22. 野指针是什么? 192 | - 也叫空悬指针,不是指向null的指针,是指向垃圾内存的指针。 193 | 194 | - 产生原因及解决办法: 195 | - 指针变量未及时初始化 => 定义指针变量及时初始化,要么置空。 196 | 197 | - 指针free或delete之后没有及时置空 => 释放操作后立即置空。 198 | 199 | 23. 堆和栈的区别? 200 | 201 | - 申请方式不同。 202 | 203 | - 栈由系统自动分配。 204 | 205 | - 堆由程序员手动分配。 206 | 207 | - 申请大小限制不同。 208 | 209 | - 栈顶和栈底是之前预设好的,大小固定,可以通过ulimit -a查看,由ulimit -s修改。 210 | 211 | - 堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。 212 | 213 | - 申请效率不同。 214 | 215 | - 栈由系统分配,速度快,不会有碎片。 216 | 217 | - 堆由程序员分配,速度慢,且会有碎片。 218 | 219 | 24. delete和delete[]区别? 220 | 221 | - delete只会调用一次析构函数。 222 | 223 | - delete[]会调用数组中每个元素的析构函数。 224 | 225 | 226 | ### 面向对象基础 227 | 228 | 能够准确理解下面这些问题是从C程序员向C++程序员进阶的基础。当然了,这只是一部分。 229 | 230 | 1. 面向对象三大特性? 231 | 232 | - 封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问。 233 | 234 | - 继承性:让某种类型对象获得另一个类型对象的属性和方法。 235 | 236 | - 多态性:同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态)。 237 | 238 | 2. public/protected/private的区别? 239 | - public的变量和函数在类的内部外部都可以访问。 240 | 241 | - protected的变量和函数只能在类的内部和其派生类中访问。 242 | 243 | - private修饰的元素只能在类内访问。 244 | 245 | 3. 对象存储空间? 246 | - 非静态成员的数据类型大小之和。 247 | 248 | - 编译器加入的额外成员变量(如指向虚函数表的指针)。 249 | 250 | - 为了边缘对齐优化加入的padding。 251 | 252 | 4. C++空类有哪些成员函数? 253 | - 首先,空类大小为1字节。 254 | 255 | - 默认函数有: 256 | - 构造函数 257 | 258 | - 析构函数 259 | 260 | - 拷贝构造函数 261 | 262 | - 赋值运算符 263 | 264 | 5. 构造函数能否为虚函数,析构函数呢? 265 | - 析构函数: 266 | - 析构函数可以为虚函数,并且一般情况下基类析构函数要定义为虚函数。 267 | 268 | - 只有在基类析构函数定义为虚函数时,调用操作符delete销毁指向对象的基类指针时,才能准确调用派生类的析构函数(从该级向上按序调用虚函数),才能准确销毁数据。 269 | 270 | - 析构函数可以是纯虚函数,含有纯虚函数的类是抽象类,此时不能被实例化。但派生类中可以根据自身需求重新改写基类中的纯虚函数。 271 | 272 | - 构造函数: 273 | - 构造函数不能定义为虚函数。在构造函数中可以调用虚函数,不过此时调用的是正在构造的类中的虚函数,而不是子类的虚函数,因为此时子类尚未构造好。 274 | 275 | 6. 构造函数调用顺序,析构函数呢? 276 | - 调用所有虚基类的构造函数,顺序为从左到右,从最深到最浅 277 | 278 | - 基类的构造函数:如果有多个基类,先调用纵向上最上层基类构造函数,如果横向继承了多个类,调用顺序为派生表从左到右顺序。 279 | 280 | - 如果该对象需要虚函数指针(vptr),则该指针会被设置从而指向对应的虚函数表(vtbl)。 281 | 282 | - 成员类对象的构造函数:如果类的变量中包含其他类(类的组合),需要在调用本类构造函数前先调用成员类对象的构造函数,调用顺序遵照在类中被声明的顺序。 283 | 284 | - 派生类的构造函数。 285 | 286 | - 析构函数与之相反。 287 | 288 | 7. 拷贝构造函数中深拷贝和浅拷贝区别? 289 | - 深拷贝时,当被拷贝对象存在动态分配的存储空间时,需要先动态申请一块存储空间,然后逐字节拷贝内容。 290 | 291 | - 浅拷贝仅仅是拷贝指针字面值。 292 | 293 | - 当使用浅拷贝时,如果原来的对象调用析构函数释放掉指针所指向的数据,则会产生空悬指针。因为所指向的内存空间已经被释放了。 294 | 295 | 8. 拷贝构造函数和赋值运算符重载的区别? 296 | - 拷贝构造函数是函数,赋值运算符是运算符重载。 297 | 298 | - 拷贝构造函数会生成新的类对象,赋值运算符不能。 299 | 300 | - 拷贝构造函数是直接构造一个新的类对象,所以在初始化对象前不需要检查源对象和新建对象是否相同;赋值运算符需要上述操作并提供两套不同的复制策略,另外赋值运算符中如果原来的对象有内存分配则需要先把内存释放掉。 301 | 302 | - 形参传递是调用拷贝构造函数(调用的被赋值对象的拷贝构造函数),但并不是所有出现"="的地方都是使用赋值运算符,如下: 303 | 304 | Student s; 305 | Student s1 = s; // 调用拷贝构造函数 306 | Student s2; 307 | s2 = s; // 赋值运算符操作 308 | 309 | **注:类中有指针变量时要重写析构函数、拷贝构造函数和赋值运算符** 310 | 311 | 9. 虚函数和纯虚函数区别? 312 | - 虚函数是为了实现动态编联产生的,目的是通过基类类型的指针指向不同对象时,自动调用相应的、和基类同名的函数(使用同一种调用形式,既能调用派生类又能调用基类的同名函数)。虚函数需要在基类中加上virtual修饰符修饰,因为virtual会被隐式继承,所以子类中相同函数都是虚函数。当一个成员函数被声明为虚函数之后,其派生类中同名函数自动成为虚函数,在派生类中重新定义此函数时要求函数名、返回值类型、参数个数和类型全部与基类函数相同。 313 | 314 | - 纯虚函数只是相当于一个接口名,但含有纯虚函数的类不能够实例化。 315 | 316 | 10. 覆盖、重载和隐藏的区别? 317 | - 覆盖是派生类中重新定义的函数,其函数名、参数列表(个数、类型和顺序)、返回值类型和父类完全相同,只有函数体有区别。派生类虽然继承了基类的同名函数,但用派生类对象调用该函数时会根据对象类型调用相应的函数。覆盖只能发生在类的成员函数中。 318 | 319 | - 隐藏是指派生类函数屏蔽了与其同名的函数,这里仅要求基类和派生类函数同名即可。其他状态同覆盖。可以说隐藏比覆盖涵盖的范围更宽泛,毕竟参数不加限定。 320 | 321 | - 重载是具有相同函数名但参数列表不同(个数、类型或顺序)的两个函数(不关心返回值),当调用函数时根据传递的参数列表来确定具体调用哪个函数。重载可以是同一个类的成员函数也可以是类外函数。 322 | 323 | 11. 在main执行之前执行的代码可能是什么? 324 | 325 | - 全局对象的构造函数。 326 | 327 | 12. 哪几种情况必须用到初始化成员列表? 328 | 329 | - 初始化一个const成员。 330 | 331 | - 初始化一个reference成员。 332 | 333 | - 调用一个基类的构造函数,而该函数有一组参数。 334 | 335 | - 调用一个数据成员对象的构造函数,而该函数有一组参数。 336 | 337 | 13. 什么是虚指针? 338 | 339 | - 虚指针或虚函数指针是虚函数的实现细节。 340 | 341 | - 虚指针指向虚表结构。 342 | 343 | 14. 重载和函数模板的区别? 344 | 345 | - 重载需要多个函数,这些函数彼此之间函数名相同,但参数列表中参数数量和类型不同。在区分各个重载函数时我们并不关心函数体。 346 | 347 | - 模板函数是一个通用函数,函数的类型和形参不直接指定而用虚拟类型来代表。但只适用于参个数相同而类型不同的函数。 348 | 349 | 15. this指针是什么? 350 | 351 | - this指针是类的指针,指向对象的首地址。 352 | 353 | - this指针只能在成员函数中使用,在全局函数、静态成员函数中都不能用this。 354 | 355 | - this指针只有在成员函数中才有定义,且存储位置会因编译器不同有不同存储位置。 356 | 357 | 16. 类模板是什么? 358 | 359 | - 用于解决多个功能相同、数据类型不同的类需要重复定义的问题。 360 | 361 | - 在建立类时候使用template及任意类型标识符T,之后在建立类对象时,会指定实际的类型,这样才会是一个实际的对象。 362 | 363 | - 类模板是对一批仅数据成员类型不同的类的抽象,只要为这一批类创建一个类模板,即给出一套程序代码,就可以用来生成具体的类。 364 | 365 | 17. 构造函数和析构函数调用时机? 366 | 367 | - 全局范围中的对象:构造函数在所有函数调用之前执行,在主函数执行完调用析构函数。 368 | 369 | - 局部自动对象:建立对象时调用构造函数,离开作用域时调用析构函数。 370 | 371 | - 动态分配的对象:建立对象时调用构造函数,调用释放时调用析构函数。 372 | 373 | - 静态局部变量对象:建立时调用一次构造函数,主函数结束时调用析构函数。 374 | 375 | --- 376 | 377 | ### 标准模板库 378 | 379 | STL内容虽然看起来很多,单独成书都不是问题(《STL源码剖析》),但从实际使用状况来看,我认为只需要知道以下几点就可以了: 380 | 381 | - 怎么用? 382 | 383 | 各种STL基本的增删改查怎么使用。每种容器都提供了很多操作,但实际增删改查我们通常只需要掌握透彻一种方式即可。有些功能只是出于通用性考虑才存在的,但对于相应的STL这些操作完全可以忽略。所以我对STL使用的看法是,不需要花太多时间去了解所有功能,只要掌握最基本的即可,要把精力放在对需求的了解并选择适合的数据结构。 384 | 385 | - 怎么实现? 386 | 387 | 本身STL就是封装了我们常用的数据结构,所以最先需要了解每种数据结构的特性。而且了解实现方式对我们能够准确、高效使用STL打下了基础。 388 | 389 | - 如何避免错误? 390 | 391 | 在第二阶段了解了STL的实现之后,我们已经可以很清楚地知道他们底层使用的是什么数据结构以及该数据结构做什么操作比较高效。但还有一点需要注意的就是怎么才能用对他们,避免一些未知的错误,比如迭代器失效问题。 392 | 393 | **string** 394 | 395 | 396 | **vector** 397 | 398 | 用法: 399 | 400 | 定义: 401 | vector vec; 402 | 403 | 插入元素: 404 | vec.push_back(element); 405 | vec.insert(iterator, element); 406 | 407 | 删除元素: 408 | vec.pop_back(); 409 | vec.erase(iterator); 410 | 411 | 修改元素: 412 | vec[position] = element; 413 | 414 | 遍历容器: 415 | for(auto it = vec.begin(); it != vec.end(); ++it) {......} 416 | 417 | 其他: 418 | vec.empty(); //判断是否空 419 | vec.size(); // 实际元素 420 | vec.capacity(); // 容器容量 421 | vec.begin(); // 获得首迭代器 422 | vec.end(); // 获得尾迭代器 423 | vec.clear(); // 清空 424 | 425 | 实现: 426 | 427 | [模拟Vector实现](https://github.com/linw7/Skill-Tree/blob/master/code/my_vector.cpp) 428 | 429 | - 线性表,数组实现。 430 | - 支持随机访问。 431 | 432 | - 插入删除操作需要大量移动数据。 433 | 434 | - 需要连续的物理存储空间。 435 | 436 | - 每当大小不够时,重新分配内存(*2),并复制原内容。 437 | 438 | 错误避免: 439 | 440 | [迭代器失效](https://github.com/linw7/Skill-Tree/blob/master/code/vector_iterator.cpp) 441 | 442 | - 插入元素 443 | - 尾后插入:size < capacity时,首迭代器不失效尾迭代实现(未重新分配空间),size == capacity时,所有迭代器均失效(需要重新分配空间)。 444 | 445 | - 中间插入:size < capacity时,首迭代器不失效但插入元素之后所有迭代器失效,size == capacity时,所有迭代器均失效。 446 | 447 | - 删除元素 448 | - 尾后删除:只有尾迭代失效。 449 | 450 | - 中间删除:删除位置之后所有迭代失效。 451 | 452 | **map** 453 | 454 | 用法: 455 | 456 | 定义: 457 | map mymap; 458 | 459 | 插入元素: 460 | mymap.insert(pair(key, value)); // 同key不插入 461 | mymap.insert(map::value_type(key, value)); // 同key不插入 462 | mymap[key] = value; // 同key覆盖 463 | 464 | 删除元素: 465 | mymap.erase(key); // 按值删 466 | mymap.erase(iterator); // 按迭代器删 467 | 468 | 修改元素: 469 | mymap[key] = new_value; 470 | 471 | 遍历容器: 472 | for(auto it = mymap.begin(); it != mymap.end(); ++it) { 473 | cout << it->first << " => " << it->second << '\n'; 474 | } 475 | 476 | 实现: 477 | 478 | [RBTree实现](https://github.com/linw7/Skill-Tree/tree/master/code/RBTree) 479 | 480 | - 树状结构,RBTree实现。 481 | - 插入删除不需要数据复制。 482 | 483 | - 操作复杂度仅跟树高有关。 484 | 485 | - RBTree本身也是二叉排序树的一种,key值有序,且唯一。 486 | - 必须保证key可排序。 487 | 488 | 基于红黑树实现的map结构(实际上是map, set, multimap,multiset底层均是红黑树),不仅增删数据时不需要移动数据,其所有操作都可以在O(logn)时间范围内完成。另外,基于红黑树的map在通过迭代器遍历时,得到的是key按序排列后的结果,这点特性在很多操作中非常方便。 489 | 490 | 面试时候现场写红黑树代码的概率几乎为0,但是红黑树一些基本概念还是需要掌握的。 491 | 492 | 1. 它是二叉排序树(继承二叉排序树特显): 493 | - 若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值。 494 | 495 | - 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值。 496 | 497 | - 左、右子树也分别为二叉排序树。 498 | 499 | 2. 它满足如下几点要求: 500 | - 树中所有节点非红即黑。 501 | 502 | - 根节点必为黑节点。 503 | 504 | - 红节点的子节点必为黑(黑节点子节点可为黑)。 505 | 506 | - 从根到NULL的任何路径上黑结点数相同。 507 | 508 | 3. 查找时间一定可以控制在O(logn)。 509 | 510 | 4. 红黑树的节点定义如下: 511 | ```C++ 512 | enum Color { 513 | RED = 0, 514 | BLACK = 1 515 | }; 516 | struct RBTreeNode { 517 | struct RBTreeNode*left, *right, *parent; 518 | int key; 519 | int data; 520 | Color color; 521 | }; 522 | ``` 523 | 所以对红黑树的操作需要满足两点:1.满足二叉排序树的要求;2.满足红黑树自身要求。通常在找到节点通过和根节点比较找到插入位置之后,还需要结合红黑树自身限制条件对子树进行左旋和右旋。 524 | 525 | 相比于AVL树,红黑树平衡性要稍微差一些,不过创建红黑树时所需的旋转操作也会少很多。相比于最简单的BST,BST最差情况下查找的时间复杂度会上升至O(n),而红黑树最坏情况下查找效率依旧是O(logn)。所以说红黑树之所以能够在STL及Linux内核中被广泛应用就是因为其折中了两种方案,既减少了树高,又减少了建树时旋转的次数。 526 | 527 | 从红黑树的定义来看,红黑树从根到NULL的每条路径拥有相同的黑节点数(假设为n),所以最短的路径长度为n(全为黑节点情况)。因为红节点不能连续出现,所以路径最长的情况就是插入最多的红色节点,在黑节点数一致的情况下,最可观的情况就是黑红黑红排列......最长路径不会大于2n,这里路径长就是树高。 528 | 529 | 530 | **set** 531 | 532 | --- 533 | 534 | ### 编译及调试 535 | 536 | **编译** 537 | 538 | 预处理 539 | 540 | - 展开所有的宏定义,完成字符常量替换。 541 | 542 | - 处理条件编译语句,通过是否具有某个宏来决定过滤掉哪些代码。 543 | 544 | - 处理#include指令,将被包含的文件插入到该指令所在位置。 545 | 546 | - 过滤掉所有注释语句。 547 | 548 | - 添加行号和文件名标识。 549 | 550 | - 保留所有#pragma编译器指令。 551 | 552 | 编译 553 | 554 | - 词法分析。 555 | 556 | - 语法分析。 557 | 558 | - 语义分析。 559 | 560 | - 中间语言生成。 561 | 562 | - 目标代码生成与优化。 563 | 564 | 链接 565 | 566 | 各个源代码模块独立的被编译,然后将他们组装起来成为一个整体,组装的过程就是链接。被链接的各个部分本本身就是二进制文件,所以在被链接时需要将所有目标文件的代码段拼接在一起,然后将所有对符号地址的引用加以修正。 567 | 568 | - 静态链接 569 | 570 | 静态链接最简单的情况就是在编译时和静态库链接在一起成为完整的可执行程序。这里所说的静态库就是对多个目标文件(.o)文件的打包,通常静态链接的包名为lib****.a,静态链接所有被用到的目标文件都会复制到最终生成的可执行目标文件中。这种方式的好处是在运行时,可执行目标文件已经完全装载完毕,只要按指令序执行即可,速度比较快,但缺点也有很多,在讲动态链接时会比较一下。 571 | 572 | 既然静态链接是对目标文件的打包,这里介绍些打包命令。 573 | 574 | gcc -c test1.c // 生成test1.o 575 | gcc -c test2.c // 生成test2.c 576 | ar cr libtest.a test1.o test2.o 577 | 578 | 首先编译得到test1.o和test2.o两个目标文件,之后通过ar命令将这两个文件打包为.a文件,文件名格式为lib + 静态库名 + .a后缀。在生成可执行文件需要使用到它的时候只需要在编译时加上即可。需要注意的是,使用静态库时加在最后的名字不是libtest.a,而是l + 静态库名。 579 | 580 | gcc -o main main.c -ltest 581 | 582 | - 动态链接 583 | 584 | 静态链接发生于编译阶段,加载至内存前已经完整,但缺点是如果多个程序都需要使用某个静态库,则该静态库会在每个程序中都拷贝一份,非常浪费内存资源,所以出现了动态链接的方式来解决这个问题。 585 | 586 | 动态链接在形式上倒是和静态链接非常相似,首先也是需要打包,打包成动态库,不过文件名格式为lib + 动态库名 + .so后缀。不过动态库的打包不需要使用ar命令,gcc就可以完成,但要注意在编译时要加上-fPIC选项,打包时加上-shared选项。 587 | 588 | gcc -fPIC -c test1.c 589 | gcc -fPIC -c test2.c 590 | gcc -shared test1.o test2.o -o libtest.so 591 | 592 | 使用动态链接的用法也和静态链接相同。 593 | 594 | gcc -o main main.c -ltest 595 | 596 | 如果仅仅像上面的步骤是没有办法正常使用库的,我们可以通过加-Lpath指定搜索库文件的目录(-L.表示当前目录),默认情况下会到环境变量LD_LIBRARY_PATH指定的目录下搜索库文件,默认情况是/usr/lib,我们可以将库文件拷贝到那个目录下再链接。 597 | 598 | 比较静态库和动态库我们可以得到二者的优缺点。 599 | 600 | - 动态库运行时会先检查内存中是否已经有该库的拷贝,若有则共享拷贝,否则重新加载动态库(C语言的标准库就是动态库)。静态库则是每次在编译阶段都将静态库文件打包进去,当某个库被多次引用到时,内存中会有多份副本,浪费资源。 601 | 602 | - 动态库另一个有点就是更新很容易,当库发生变化时,如果接口没变只需要用新的动态库替换掉就可以了。但是如果是静态库的话就需要重新被编译。 603 | 604 | - 不过静态库也有优点,主要就是静态库一次性完成了所有内容的绑定,运行时就不必再去考虑链接的问题了,执行效率会稍微高一些。 605 | 606 | makefile编写 607 | 608 | 对于大的工程通常涉及很多头文件和源文件,编译起来很很麻烦,makefile正是为了自动化编译产生的,makefile像是编译说明书,指示编译的步骤和条件,之后被make命令解释。 609 | 610 | - 基本规则 611 | 612 | A:B 613 | (tab) 614 | 615 | 其中A是语句最后生成的文件,B是生成A所依赖的文件,比如生成test.o依赖于test.c和test.h,则写成test.o:test.c test.h。接下来一行的开头必须是tab,再往下就是实际的命令了,比如gcc -c test.c -o test.o。 616 | 617 | - 变量 618 | 619 | makefile的书写非常像shell脚本,可以在文件中定义"变量名 = 变量值"的形式,之后需要使用这个变量时只需要写一个$符号加上变量名即可,当然,和shell一样,最好用()包裹起语句来。 620 | 621 | **链接** 622 | 623 | 符号解析 624 | 625 | - 可重定位目标文件 626 | 627 | 对于独立编译的可重定位目标文件,其ELF文件格式包括ELF头(指定文件大小及字节序)、.text(代码段)、.rodata(只读数据区)、.data(已初始化数据区)、.bss(未初始化全局变量)、.symtab(符号表)等,其中链接时最需要关注的就是符号表。每个可重定位目标文件都有一张符号表,它包含该模块定义和引用的符号的信息,简而言之就是我们在每个模块中定义和引用的全局变量(包括定义在本模块的全局变量、静态全局变量和引用自定义在其他模块的全局变量)需要通过一张表来记录,在链接时通过查表将各个独立的目标文件合并成一个完整的可执行文件。 628 | 629 | - 解析符号表 630 | 631 | 解析符号引用的目的是将每个引用与可重定位目标文件的符号表中的一个符号定义联系起来。 632 | 633 | 重定位 634 | 635 | - 合并节 636 | 637 | 多个可重定位目标文件中相同的节合并成一个完整的聚合节,比如多个目标文件的.data节合并成可执行文件的.data节。链接器将运行时存储地址赋予每个节,完成这步每条指令和全局变量都有运行时地址了。 638 | 639 | - 重定位符号引用 640 | 641 | 这步修改全部代码节和数据节对每个符号的符号引用,使其指向正确的运行时地址。局部变量可以通过进栈、出栈临时分配,但全局变量("符号")的位置则是在各个可重定位目标文件中预留好的。通过上一步合并节操作后,指令中所有涉及符号的引用都会通过一定的寻址方式来定位该符号,比如相对寻址、绝对寻址等。 642 | 643 | 可执行目标文件 644 | 645 | - ELF头部 646 | 647 | 描述文件总体格式,并且包括程序的入口点(entry point),也就是程序运行时执行的第一条指令地址。 648 | 649 | - 段头部表 650 | 651 | 描述了可执行文件数据段、代码段等各段的大小、虚拟地址、段对齐、执行权限等。实际上通过段头部表描绘了虚拟存储器运行时存储映像,比如每个UNIX程序的代码段总是从虚拟地址Ox0804800开始的。 652 | 653 | - 其他段 654 | 655 | 和可重定位目标文件各段基本相同,但完成了多个节的合并和重定位工作。 656 | 657 | 加载 658 | 659 | - 克隆 660 | 661 | 新程序的执行首先需要通过父进程外壳通过fork得到一个子进程,该子进程除了pid等标识和父进程不同外其他基本均与父进程相同。 662 | 663 | - 重新映射 664 | 665 | 当子进程执行execve系统调用时会先清空子进程现有的虚拟存储器段(简而言之就是不再映射到父进程的各个段),之后重新创建子进程虚拟存储器各段和可执行目标文件各段的映射。这个阶段我们可以理解为对复制来的父进程页表进程重写,映射到外存中可执行文件的各个段。 666 | 667 | - 虚页调入 668 | 669 | 加载过程并没有实际将磁盘中可执行文件调入内存,所做的工作紧紧是复制父进程页表、清空旧页表、建立新页表映射工作。之后加载器跳转到入口地址_start开始执行程序,接下来的过程需要配合虚拟存储器来完成。CPU获得指令的虚拟地址后,若包含该指令或数据的页尚未调入内存则将其从外存中调入,调入内存后修改页表得到虚拟页号和物理页号的对应关系。之后重新取同一条指令或数据时因该页已经被调入内存,所以通过虚拟地址得到虚拟页号,虚拟页号通过查页表可以得到物理页号,通过物理页号 + 页内偏移得到具体的物理地址,此时可以通过物理地址取得想要的数据。 670 | -------------------------------------------------------------------------------- /code/RBTree/RBtree.c: -------------------------------------------------------------------------------- 1 | #include "RBtree.h" 2 | #include "RBtree_priv.h" 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | /****************************************************************************** 9 | * Section 1: Creation and Deallocation 10 | *****************************************************************************/ 11 | /* Creates an empty Red-Black tree. */ 12 | rb_tree RBcreate() { 13 | rb_tree ret; /* The tree we are returning */ 14 | if ((ret = malloc(sizeof(*ret))) == NULL) { 15 | fprintf(stderr, "Error: out of memory.\n"); 16 | return NULL; 17 | } 18 | /* We can't use rb_new_node() because it wants to set some of the values 19 | * to tree->nil. */ 20 | if ((ret->nil = malloc(sizeof(*ret->nil))) == NULL) { 21 | fprintf(stderr, "Error: out of memory.\n"); 22 | /* Allocation of ret had been successful; we need to free it. */ 23 | free(ret); 24 | return NULL; 25 | } 26 | ret->nil->color = 'b'; 27 | ret->nil->lchild = ret->nil; 28 | ret->nil->rchild = ret->nil; 29 | ret->nil->parent = ret->nil; 30 | ret->root = ret->nil; 31 | return ret; 32 | } 33 | /* Frees an entire tree. */ 34 | void RBfree(rb_tree tree) { 35 | rb_free_subtree(tree, tree->root); 36 | rb_free_node(tree->nil); 37 | free(tree); 38 | } 39 | /* Helper routine: frees a subtree rooted at specified node. */ 40 | static void rb_free_subtree(rb_tree tree, rb_node node) { 41 | if (node == tree->nil) return; /* We only free tree->nil once */ 42 | rb_free_subtree(tree, node->lchild); 43 | rb_free_subtree(tree, node->rchild); 44 | rb_free_node(node); 45 | } 46 | /* Creates a new node. */ 47 | static rb_node rb_new_node(rb_tree tree, int data) { 48 | rb_node ret; 49 | /* We take nodes from the memory pool if we can; else just allocate it. */ 50 | if (rb_mem_pool != NULL) { 51 | ret = rb_mem_pool; 52 | rb_mem_pool = ret->parent; 53 | } else { 54 | if ((ret = malloc(sizeof(*ret))) == NULL) { 55 | fprintf(stderr, "Error: out of memory.\n"); 56 | return NULL; 57 | } 58 | } 59 | ret->key = data; 60 | ret->parent = tree->nil; 61 | ret->lchild = tree->nil; 62 | ret->rchild = tree->nil; 63 | ret->color = 'r'; 64 | return ret; 65 | } 66 | /* Frees a node to the memory pool. */ 67 | static void rb_free_node(rb_node node) { 68 | node->parent = rb_mem_pool; 69 | rb_mem_pool = node; 70 | } 71 | /* Frees entire memory pool to main memory. */ 72 | void RBcleanup() { 73 | while (rb_mem_pool != NULL) { 74 | rb_node cur = rb_mem_pool; 75 | rb_mem_pool = cur->parent; 76 | free(cur); 77 | } 78 | } 79 | 80 | 81 | 82 | 83 | /****************************************************************************** 84 | * Section 2: Insertion 85 | *****************************************************************************/ 86 | /* Inserts an element with specified key into tree. */ 87 | int RBinsert(rb_tree tree, int key) { 88 | /* The node we will create */ 89 | rb_node newnode; 90 | /* newnode's parent */ 91 | rb_node newparent = tree->nil; 92 | /* The position into which we will put newnode */ 93 | rb_node pos = tree->root; 94 | /* Locate the correct position */ 95 | while (pos != tree->nil) { 96 | newparent = pos; 97 | if (key < pos->key) { 98 | pos = pos->lchild; 99 | } else if (key > pos->key) { 100 | pos = pos->rchild; 101 | } else { 102 | /* We don't support two nodes with the same value. */ 103 | fprintf(stderr, "Error: node %i already in the tree.\n", key); 104 | return 0; 105 | } 106 | } 107 | /* Allocate our node */ 108 | newnode = rb_new_node(tree, key); 109 | if (newnode == NULL) { 110 | return 0; 111 | } 112 | /* Set up the parent node */ 113 | newnode->parent = newparent; 114 | if (newparent == tree->nil) { 115 | tree->root = newnode; 116 | } else if (key < newparent->key) { 117 | newparent->lchild = newnode; 118 | } else { 119 | newparent->rchild = newnode; 120 | } 121 | /* Fix the tree structure */ 122 | rb_insert_fix(tree, newnode); 123 | return 1; 124 | } 125 | /* Corrects for properties violated on an insertion. */ 126 | static void rb_insert_fix(rb_tree tree, rb_node n) { 127 | rb_node gp = n->parent->parent, /* grandparent */ 128 | uncle = rb_get_uncle(tree, n); 129 | /* Case 1: uncle is colored red */ 130 | while (n->parent->color == 'r' && uncle->color == 'r') { 131 | gp->color = 'r'; 132 | uncle->color = 'b'; 133 | n->parent->color = 'b'; 134 | n = gp; 135 | gp = n->parent->parent; 136 | uncle = rb_get_uncle(tree, n); 137 | } 138 | 139 | if (n->parent->color == 'b') { 140 | if (n == tree->root) n->color = 'b'; 141 | return; 142 | } 143 | 144 | /* Case 2: node is "close to" uncle */ 145 | if ((n->parent->lchild == n) == (gp->lchild == uncle)) { 146 | rb_node new_n = n->parent; 147 | rb_rotate(tree, new_n, new_n->rchild == n); 148 | n = new_n; 149 | } /* Fall through */ 150 | /* Case 3: node is "far from" uncle */ 151 | n->parent->color = 'b'; 152 | gp->color = 'r'; 153 | rb_rotate(tree, gp, gp->lchild == uncle); 154 | tree->root->color = 'b'; 155 | } 156 | /* Helper routine: returns the uncle of a given node. */ 157 | static rb_node rb_get_uncle(rb_tree tree, rb_node n) { 158 | rb_node gp; 159 | if (n->parent == tree->nil || n->parent->parent == tree->nil) { 160 | return tree->nil; 161 | } 162 | gp = n->parent->parent; 163 | return (gp->lchild == n->parent) ? gp->rchild : gp->lchild; 164 | } 165 | 166 | 167 | 168 | 169 | /****************************************************************************** 170 | * Section 3: Deletion 171 | *****************************************************************************/ 172 | /* Deletes an element with a particular key. */ 173 | int RBdelete(rb_tree tree, int key) { 174 | /* The node with the actual key */ 175 | rb_node dead = rb_get_node_by_key(tree, key); 176 | /* The node where we will fix the tree structure */ 177 | rb_node fixit; 178 | /* Original color of the deleted node */ 179 | char orig_col = dead->color; 180 | /* Node does not exist, so we cannot delete it */ 181 | if (dead == tree->nil) { 182 | fprintf(stderr, "Error: node %i does not exist.\n", key); 183 | return 0; 184 | } 185 | /* Here we perform binary tree deletion */ 186 | if (dead->lchild == tree->nil) { 187 | fixit = dead->rchild; 188 | rb_transplant(tree, dead, fixit); 189 | } else if (dead->rchild == tree->nil) { 190 | fixit = dead->lchild; 191 | rb_transplant(tree, dead, fixit); 192 | } else { 193 | /* Replace dead with its successor */ 194 | rb_node successor = rb_min(tree, dead->rchild); 195 | orig_col = successor->color; 196 | fixit = successor->rchild; 197 | if (successor->parent == dead) { 198 | fixit->parent = successor; 199 | } else { 200 | /* Put the successor's right child into its place */ 201 | rb_transplant(tree, successor, successor->rchild); 202 | successor->rchild = dead->rchild; 203 | successor->rchild->parent = successor; 204 | } 205 | rb_transplant(tree, dead, successor); 206 | successor->lchild = dead->lchild; 207 | successor->lchild->parent = successor; 208 | successor->color = dead->color; 209 | } 210 | rb_free_node(dead); 211 | /* Only need to fix if we deleted a black node */ 212 | if (orig_col == 'b') { 213 | rb_delete_fix(tree, fixit); 214 | } 215 | return 1; 216 | } 217 | /* Helper routine: transplants node `from' into node `to's position. */ 218 | static void rb_transplant(rb_tree tree, rb_node to, rb_node from) { 219 | if (to->parent == tree->nil) { 220 | tree->root = from; 221 | } else if (to == to->parent->lchild) { 222 | to->parent->lchild = from; 223 | } else { 224 | to->parent->rchild = from; 225 | } 226 | from->parent = to->parent; 227 | } 228 | /* Corrects for properties violated on a deletion. */ 229 | static void rb_delete_fix(rb_tree tree, rb_node n) { 230 | /* It's always safe to change the root black, and if we reach a red 231 | * node, we can fix the tree by changing it black. */ 232 | while (n != tree->root && n->color == 'b') { 233 | /* Instead of duplicating code, we just have a flag to test 234 | * which direction we are dealing with. */ 235 | int is_left = (n == n->parent->lchild); 236 | rb_node sibling = (is_left) ? n->parent->rchild : n->parent->lchild; 237 | /* Case 1: sibling red */ 238 | if (sibling->color == 'r') { 239 | sibling->color = 'b'; 240 | sibling->parent->color = 'r'; 241 | rb_rotate(tree, sibling->parent, is_left); 242 | sibling = (is_left) ? n->parent->rchild : n->parent->rchild; 243 | } 244 | /* Case 2: sibling black, both sibling's children black */ 245 | if (sibling->lchild->color == 'b' && sibling->rchild->color == 'b') { 246 | sibling->color = 'r'; 247 | n = n->parent; 248 | } else { 249 | /* Case 3: sibling black, "far" child black */ 250 | if (( is_left && sibling->rchild->color == 'b') || 251 | (!is_left && sibling->lchild->color == 'b')) { 252 | if (is_left) { 253 | sibling->lchild->color = 'b'; 254 | } else { 255 | sibling->rchild->color = 'b'; 256 | } 257 | sibling->color = 'r'; 258 | rb_rotate(tree, sibling, !is_left); 259 | sibling = (is_left) ? n->parent->rchild : n->parent->lchild; 260 | } /* Fall through */ 261 | /* Case 4: sibling black, "far" child red */ 262 | sibling->color = n->parent->color; 263 | n->parent->color = 'b'; 264 | if (is_left) { 265 | sibling->rchild->color = 'b'; 266 | } else { 267 | sibling->lchild->color = 'b'; 268 | } 269 | rb_rotate(tree, n->parent, is_left); 270 | /* We're done, so set n to the root node */ 271 | n = tree->root; 272 | } 273 | } 274 | n->color = 'b'; 275 | } 276 | 277 | 278 | 279 | 280 | /****************************************************************************** 281 | * Section 4: I/O 282 | *****************************************************************************/ 283 | /* Writes a tree to stdout in preorder format. */ 284 | void RBwrite(rb_tree tree) { 285 | if (tree->root == tree->nil) { 286 | fprintf(stderr, "Error: empty tree\n"); 287 | return; 288 | } 289 | /* Special case to account for missing semicolon */ 290 | printf("%c, %d", tree->root->color, tree->root->key); 291 | rb_preorder_write(tree, tree->root->lchild); 292 | rb_preorder_write(tree, tree->root->rchild); 293 | putchar('\n'); 294 | } 295 | /* Helper routine: write an entire subtree to stdout. */ 296 | static void rb_preorder_write(rb_tree tree, rb_node n) { 297 | if (n == tree->nil) return; 298 | /* Instead of having to keep track of "is this the last node or not?", 299 | * we just print the first node with no semicolon, then print the 300 | * semicolon BEFORE the other nodes. */ 301 | printf("; %c, %d", n->color, n->key); 302 | rb_preorder_write(tree, n->lchild); 303 | rb_preorder_write(tree, n->rchild); 304 | } 305 | /* Reads a tree in preorder format from RBREADFILE. */ 306 | /* This function implements an algorithm which is O(n) in the number of nodes, 307 | * more efficient than the trivial O(n*log(n)) algorithm. */ 308 | rb_tree RBread(char *fname) { 309 | rb_tree ret; 310 | rb_node root; 311 | FILE *infp = fopen(fname, "r"); 312 | if (infp == NULL) { 313 | fprintf(stderr, "Error: couldn't read file %s.\n", fname); 314 | return NULL; 315 | } 316 | /* Create the tree to return */ 317 | ret = RBcreate(); 318 | if (ret != NULL) { 319 | root = rb_read_node(ret, infp); 320 | /* Read in nodes from negative infinity to INT_MAX. */ 321 | ret->root = rb_read_subtree(ret, &root, INT_MAX, infp); 322 | } 323 | fclose(infp); 324 | return ret; 325 | } 326 | /* Reads a tree in preorder format, limited by the maximum value of max. */ 327 | static rb_node rb_read_subtree(rb_tree tree, rb_node *next, int max, FILE *fp) { 328 | rb_node ret = *next; 329 | /* Either the tree is complete or we don't belong here */ 330 | if (ret == NULL || ret->key > max) { 331 | return tree->nil; 332 | } 333 | *next = rb_read_node(tree, fp); 334 | /* Nodes up to my own value belong to my left subtree */ 335 | ret->lchild = rb_read_subtree(tree, next, ret->key - 1, fp); 336 | ret->lchild->parent = ret; 337 | /* Nodes up to my maximum belong to my right subtree */ 338 | ret->rchild = rb_read_subtree(tree, next, max, fp); 339 | ret->rchild->parent = ret; 340 | return ret; 341 | } 342 | /* Helper routine: read a single node from file fp. */ 343 | static rb_node rb_read_node(rb_tree tree, FILE *fp) { 344 | rb_node n; /* the node to return */ 345 | char col; /* the color of the node */ 346 | int data; /* the data of the node */ 347 | /* Skip optional semicolon */ 348 | fscanf(fp, " ; "); 349 | /* If node is invalid (or we've reached EOF), die a painful death */ 350 | if (fscanf(fp, " %c, %d ", &col, &data) != 2 || (col != 'b' && col != 'r')) { 351 | return NULL; 352 | } 353 | n = rb_new_node(tree, data); 354 | if (n != NULL) n->color = col; 355 | return n; 356 | } 357 | 358 | 359 | 360 | 361 | /****************************************************************************** 362 | * Section 5: General helper routines 363 | *****************************************************************************/ 364 | /* Returns a node with the given key. */ 365 | static rb_node rb_get_node_by_key(rb_tree haystack, int needle) { 366 | rb_node pos = haystack->root; /* our current position */ 367 | while (pos != haystack->nil) { 368 | if (pos->key == needle) { 369 | return pos; 370 | } else if (needle < pos->key) { 371 | pos = pos->lchild; 372 | } else { 373 | pos = pos->rchild; 374 | } 375 | } 376 | return haystack->nil; 377 | } 378 | /* Rotates a tree around the given root. */ 379 | static void rb_rotate(rb_tree tree, rb_node root, int go_left) { 380 | /* Instead of duplicating code, we just 381 | * have a flag to indicate the direction to rotate. */ 382 | /* The new top node */ 383 | rb_node newroot = (go_left) ? root->rchild : root->lchild; 384 | /* We swap the center child and the old top node */ 385 | if (go_left) { 386 | root->rchild = newroot->lchild; 387 | if (root->rchild != tree->nil) { 388 | root->rchild->parent = root; 389 | } 390 | newroot->lchild = root; 391 | } else { 392 | root->lchild = newroot->rchild; 393 | if (root->lchild != tree->nil) { 394 | root->lchild->parent = root; 395 | } 396 | newroot->rchild = root; 397 | } 398 | /* Now we set up the parent nodes */ 399 | newroot->parent = root->parent; 400 | root->parent = newroot; 401 | /* We update old top node's parent to point to the new top node */ 402 | if (newroot->parent == tree->nil) { 403 | tree->root = newroot; 404 | } else if (newroot->parent->lchild == root) { 405 | newroot->parent->lchild = newroot; 406 | } else { 407 | newroot->parent->rchild = newroot; 408 | } 409 | } 410 | /* Returns minimum node in the given subtree. */ 411 | static rb_node rb_min(rb_tree tree, rb_node node) { 412 | while (node->lchild != tree->nil) 413 | node = node->lchild; 414 | return node; 415 | } 416 | /* Computes height of the tree rooted at node n. */ 417 | static int rb_height(rb_tree tree, rb_node n) { 418 | int l, r; 419 | if (n == tree->nil) return 0; 420 | l = rb_height(tree, n->lchild); 421 | r = rb_height(tree, n->rchild); 422 | return 1 + ((l > r) ? l : r); 423 | } 424 | 425 | 426 | 427 | 428 | /****************************************************************************** 429 | * Section 6: SVG 430 | *****************************************************************************/ 431 | /* Draws an SVG picture of the tree in the specified file. */ 432 | void RBdraw(rb_tree tree, char *fname) { 433 | FILE *fp; /* file to print to */ 434 | int height = rb_height(tree, tree->root); /* height of the tree */ 435 | int width; /* width of the image */ 436 | int adjwidth; /* adjusted width of the image in px */ 437 | double factor; /* adjust factor for the node positions based on width and adjwidth */ 438 | if (height == 0) return; 439 | if ((fp = fopen(fname, "w")) == NULL) { 440 | fprintf(stderr, "Error: couldn't open %s for writing.\n", fname); 441 | return; 442 | } 443 | width = (1<<(height-1)) * (2*RADIUS + PADDING) - PADDING + 2*IMGBORDER; 444 | adjwidth = (width > MAXWIDTH) ? MAXWIDTH : width; 445 | /* If it weren't for this factor, calculations would be a lot easier. */ 446 | factor = (height == 1) ? 1.0 : (adjwidth-2*(RADIUS+IMGBORDER)) / (width-2*(RADIUS+IMGBORDER)); 447 | fprintf(fp, "\n" 448 | "\n" 449 | "\n", 451 | adjwidth, (int)(height * (2*RADIUS + PADDING) - PADDING + 2*IMGBORDER)); 452 | rb_draw_subtree(fp, tree, tree->root, calcpos(height-1, 0, factor), RADIUS+IMGBORDER, height-1, 0, factor); 453 | fputs("\n", fp); 454 | fclose(fp); 455 | } 456 | /* This method has complicated and seemingly-arbitrary arguments to reduce on 457 | * computation. It's a private method, so I feel justified in making it hard to 458 | * call. 459 | * 460 | * Arguments are: 461 | * fp - file pointer to print to 462 | * tree - red-black tree to print 463 | * n - current node 464 | * x - correct x position of center of node 465 | * y - correct y position of center of node 466 | * h - distance from bottom of tree (not necessarily the height of this 467 | * particular node) 468 | * rowpos - position in layer (leftmost node in layer is 0, then 1, etc.) 469 | * factor - correction factor when the image is > MAXWIDTH. 470 | */ 471 | static void rb_draw_subtree(FILE *fp, rb_tree tree, rb_node n, double x, double y, 472 | int h, int rowpos, double factor) { 473 | /* string for the color of the node */ 474 | char *col = (n->color == 'b') ? "black" : "red"; 475 | /* y position for next row */ 476 | double ny = y + 2*RADIUS + PADDING; 477 | 478 | /* Draw left subtree */ 479 | if (n->lchild != tree->nil) { 480 | /* x position of left child */ 481 | double nx = calcpos(h-1, 2*rowpos, factor); 482 | fprintf(fp, "\n", 484 | x, y, nx, ny); 485 | rb_draw_subtree(fp, tree, n->lchild, nx, ny, h-1, 2*rowpos, factor); 486 | } 487 | /* Draw right subtree */ 488 | if (n->rchild != tree->nil) { 489 | /* x position of right child */ 490 | double nx = calcpos(h-1, 2*rowpos+1, factor); 491 | fprintf(fp, "\n", 493 | x, y, nx, ny); 494 | rb_draw_subtree(fp, tree, n->rchild, nx, ny, h-1, 2*rowpos+1, factor); 495 | } 496 | /* Draw the node itself */ 497 | fprintf(fp, "\n", x, y, RADIUS, col); 499 | /* And write the node key */ 500 | fprintf(fp, "%d\n", x, y, n->key); 502 | } 503 | /* Calculates x position of circle exp rows from the bottom, at position rowpos 504 | * in its row. factor corrects for an image which would be wider than MAXWIDTH. */ 505 | static double calcpos(int exp, int rowpos, double factor) { 506 | /* This equation took quite a bit of diagramming on paper to come up with. */ 507 | return ((1<Tencent 8 | 9 | ### 模拟1 10 | 11 | Q1 : 12 | 13 | 题目: 14 | 随着IP网络的发展,为了节省可分配的注册IP地址,有一些地址被拿出来用于私有IP地址,以下不属于私有IP地址范围的是: 15 | A. 10.6.207.84 16 | B. 172.23.30.28 17 | C. 172.32.50.80 18 | D. 192.168.1.100 19 | 20 | 答案: 21 | C 22 | 23 | 解答: 24 | 1. 私有IP地址共有三个范围段: 25 | A: 10.0.0.0~10.255.255.255,即10.0.0.0/8。 26 | B: 172.16.0.0~172.31.255.255,即172.16.0.0/12。 27 | C: 192.168.0.0~192.168.255.255,即192.168.0.0/16。 28 | 2. 私有IP在公网上不能使用,但在内网内可以通过NAT技术分配给具体设备,节省IP地址。 29 | 30 | Q2 : 31 | 32 | 题目: 33 | 下列关于一个类的静态成员的描述中,不正确的是: 34 | A. 该类的对象共享其静态成员变量的值 35 | B. 静态成员变量可被该类的所有方法访问 36 | C. 该类的静态方法能访问该类的静态成员变量 37 | D. 该类的静态数据成员变量的值不可修改 38 | 39 | 答案: 40 | D 41 | 42 | 解答: 43 | 1. 类的静态成员和对象无关,和类相关,一个类的所有实例共享同一个静态成员。 44 | 2. 静态成员函数不能调用非静态成员。 45 | 3. 非静态成员函数可以调用静态成员。 46 | 4. 静态成员变量必须初始化,且可以修改。 47 | 48 | Q3 : 49 | 50 | 题目: 51 | C++将父类的析构函数定义为虚函数,下列正确的是哪个: 52 | A. 释放父类指针时能正确释放子类对象 53 | B. 释放子类指针时能正确释放父类对象 54 | C. 这样做是错误的 55 | D. 以上全错 56 | 57 | 答案: 58 | A 59 | 60 | 解答: 61 | 1. 基类通常应定义一个虚析构函数,以确保能正确执行析构函数。 62 | 2. 基类指针指向派生类对象,若基类析构函数未声明为虚函数,则只会调用基类析构函数。 63 | 3. 基类声明为虚函数,释放指向派生类对象的基类指针时会先调用派生类析构函数,之后调用基类析构函数。 64 | 65 | Q4 : 66 | 67 | 题目: 68 | 下列哪一个不属于关系数据库的特点: 69 | A. 数据冗余度小 70 | B. 数据独立性高 71 | C. 数据共享性好 72 | D. 多用户访问 73 | 74 | 答案: 75 | D 76 | 77 | 解答: 78 | 1. 数据库存在的一个目的就是统一管理数据,减少数据冗余度。 79 | 2. 数据独立性,指数据和其管理软件独立,以及数据及其结构的独立。 80 | 3. 数据库就是为了方便用户之间共享数据。 81 | 4. 数据库中存在锁机制,如果多用户访问可能导致数据不一致等。 82 | 83 | Q5 : 84 | 85 | 题目: 86 | typedef char *String_t和#define String_d char *这两句在使用上有什么区别? 87 | 88 | 答案: 89 | 1. typedef char *String_t定义了一个新的类型别名,有类型检查,更安全。发生在编译阶段。 90 | 2. #define String_d char *仅仅是做字符串替换,无类型检查。发生在预编译阶段。 91 | 3. 用法区别:String_t a, b; 92 | String_d c, d; -> char *c, d; 93 | a, b ,c是char*类型,而d为char类型。 94 | 95 | Q6 : 96 | 97 | 题目: 98 | void Func(char str_arg[2]){ 99 | int m = sizeof(str_arg); 100 | int n = strlen(str_arg); 101 | printf("%d\n", m); 102 | printf("%d\n", n); 103 | } 104 | int main(void){ 105 | char str[]="Hello"; 106 | Func(str); 107 | } 108 | 输出结果为: 109 | 110 | 答案: 111 | 4,5 112 | 1. str为定义在main函数中的数组。 113 | 2. 数组作为参数传递给函数会退化为指针。 114 | 3. sizeof(指针变量) = 指针变量大小,strlen(指针变量) = 指针所指向的字符串长(遇'\0'停止)。 115 | 116 | Q7 : 117 | 118 | 题目: 119 | 给定一个字符串,求出其最长的重复子串。 120 | 121 | 答案: 122 | 123 | --- 124 | 125 | ### 模拟2 126 | 127 | Q1 : 128 | 129 | 题目: 130 | Internet物理地址和IP地址转换采用什么协议? 131 | 132 | 答案: 133 | 1. MAC地址 -> IP地址:ARP协议。 134 | 2. IP地址 -> MAC地址:RARP协议。 135 | 136 | [Q2](http://blog.csdn.net/lanchengxiaoxiao/article/details/7880276) : 137 | 138 | 题目: 139 | static有什么用途? 140 | 141 | 答案: 142 | 1. 修饰变量: 143 | 静态局部变量:只定义一次,程序运行期间一直存在,作用于局限于定义的函数内。多线程中需要加锁保护。 144 | 静态全局变量:程序运行期间一直存在,作用域为定义它的源文件。 145 | 2. 修饰函数: 146 | 一个被声明为静态的函数只可被这一模块内的其它函数调用。 147 | 148 | Q3 : 149 | 150 | 题目: 151 | 引用与指针有什么区别? 152 | 153 | 答案: 154 | 1. 指针是个实体,指针的内容是变量地址。引用只是变量别名。 155 | 2. 指针可以指向新的变量地址。引用只能在定义时被初始化一次,之后不可变。 156 | 3. 指针可以为空。引用不能为空。 157 | 4. 指针可以用const修饰,引用不能用const修饰。 158 | 5. 获取变量值指针需要解引用。引用不需要解引用。 159 | 6. 指针变量需要分配实际内存空间。引用不需要分配内存空间,本身不是变量。 160 | 7. 指针的sizeof得到的是指针变量的大小。引用得到的是实际变量的大小。 161 | 8. 指针变量++是地址值的增加。引用的++是实际变量值得增加。 162 | 163 | Q4 : 164 | 165 | 题目: 166 | 全局变量和局部变量在内存中是否有区别?如果有,是什么区别? 167 | 168 | 答案: 169 | 1. 作用域: 170 | 全局变量:具有全局作用域,只需要定义在一个源文件中就可以在所有源文件中使用。不包含变量定义的文件引用时要用extern声明。 171 | 局部变量:具有局部作用域,只在函数运行期间存在,函数结束后就被销毁。 172 | 2. 生存周期: 173 | 全局变量:定义在静态区,与静态变量存储在一起,伴随程序整个生命周期。 174 | 局部变量:定义在栈上,函数结束后释放。 175 | 176 | Q5 : 177 | 178 | 题目: 179 | 什么是平衡二叉树? 180 | 181 | 答案: 182 | 1. 空树或者左右两棵子树高度差绝对值小于1,且子树递归满足此定义。 183 | 2. 最小平衡二叉树节点公式:F(n) = F(n - 1) + F(n - 2) + 1。 184 | 185 | Q6 : 186 | 187 | 题目: 188 | 堆栈溢出一般是由什么原因导致的? 189 | 190 | 答案: 191 | 1. 循环的递归调用(每次递归都需要压栈)。 192 | 2. 大数据结构的局部变量。 193 | 194 | Q7 : 195 | 196 | 题目: 197 | 什么函数不能声明为虚函数? 198 | 199 | 答案: 200 | 1. 构造函数。虚函数主要针对对象而言,而构造函数是在对象创建之前。 201 | 2. 内联函数。不能再运行中动态确定其位置。 202 | 3. 静态成员函数。全局通用,不受限于具体对象。 203 | 204 | Q8 : 205 | 206 | 题目: 207 | 写出floatx与“零值”比较的if语句。 208 | 209 | 答案: 210 | 1. if (fabs(x) < 0.00001f) 211 | 212 | Q9 : 213 | 214 | 题目: 215 | 不能做switch()的参数类型是? 216 | 217 | 答案: 218 | 1. 只能是char,int,enum。 219 | 2. 不能是bool,long,string,float,double。 220 | 221 | Q10 : 222 | 223 | 题目: 224 | 用户输入M、N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序。 225 | 226 | 答案: 227 | 228 | --- 229 | 230 | ### 模拟3 231 | 232 | Q1 : 233 | 234 | 题目: 235 | 写出下列代码的输出内容: 236 | int inc(int a){ 237 | return(++a); 238 | } 239 | int multi(int*a, int*b, int*c){ 240 | return(*c = *a**b); 241 | } 242 | typedef int(FUNC1)(int in); 243 | typedef int(FUNC2)(int*, int*, int*); 244 | void show(FUNC2 fun, int arg1, int*arg2){ 245 | FUNC1 *p = &inc; 246 | int temp = p(arg1); 247 | fun(&temp, &arg1, arg2); 248 | printf("%d\n", *arg2); 249 | } 250 | int main(){ 251 | int a; 252 | show(multi, 10, &a); 253 | return 0; 254 | } 255 | 256 | 答案: 257 | 110 258 | 259 | Q2 : 260 | 261 | 题目: 262 | 如何引用一个已经定义过的全局变量? 263 | 264 | 答案: 265 | 1. 用extern重新声明已经在别的模块中定义的全局变量,如果写错变量名将会在链接阶段报错。 266 | 2. 引用定义了该全局变量的头文件,如果拼写错误会在编译阶段报错。 267 | 268 | Q3 : 269 | 270 | 题目: 271 | 语句for(; 1; )有什么问题?它是什么意思? 272 | 273 | 答案: 274 | 1. 一直循环执行。 275 | 2. 此处如果中间是0,则一次不执行。 276 | 277 | Q4 : 278 | 279 | 题目: 280 | static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别? 281 | 282 | 答案: 283 | 1. static全局变量仅能在定义的源文件中使用,全局变量可以在所有源文件中使用。 284 | 2. static局部变量定义于静态区,生命周期为程序整个运行阶段,多次调用函数只定义一次。局部变量定义于栈,调用函数退出即销毁,多次调用多次分配。 285 | 3. static函数只能在定义的源文件中使用。普通函数可以在头文件中声明,包含该头文件的源文件均可调用该函数。 286 | 287 | Q5 : 288 | 289 | 题目: 290 | 请找出下面代码中的所有错误: 291 | #include 292 | int main(){ 293 | char*src = "hello,world"; 294 | char* dest = NULL; 295 | int len = strlen(src); 296 | dest = (char*)malloc(len); (1) 297 | char* d = dest; 298 | char* s = src[len]; (2) 299 | while(len-- != 0) (3) 300 | d++ = s--; (4) 301 | printf("%s", dest); 302 | return 0; 303 | } 304 | 305 | 答案: 306 | 1. 分配的空间要为len + 1,用于存放'\0'。 307 | 2. s = &src[len]这里是取地址。 308 | 3. 改为while(len-- >= 0)。 309 | 4. 改为*d++ = *s--。 310 | 311 | Q6 : 312 | 313 | 题目: 314 | 搜索引擎的日志要记录所有查询串,有一千万条查询,不重复的不超过三百万,要统计最热门的10条查询。 315 | 条件:串内存<1G,字符串长0-255。 316 | 给出主要解决思路,算法及其复杂度分析。 317 | 318 | 答案: 319 | 1. 面对的问题有: 320 | (1)1G内存不够一次性装入所有数据? 321 | (2)如何去统计每个记录出现次数? 322 | (3)如何快速得到前十的记录? 323 | 2. 解决方式: 324 | (1)255约2^8,一百万约2^20,即一百万记录约256MB,一千万约2.6GB。分多次处理。 325 | (2)利用hash统计,定义map,key为string类型日志,value为日志出现次数。 326 | (3)利用大根堆,取top 10,复杂度O(nlogn)。 327 | 3. 新问题: 328 | 如何划分数据?如何归并结果? 329 | 4. 方案: 330 | (1)哈希表常驻内存,大小(255 +4) * 3 * 1000000,约800MB。 331 | (2)分13(200MB * 13 = 2.6GB)次调入日志数据,每次取200MB数据进行hash。 332 | --- 333 | 334 | ### 模拟4 335 | 336 | Q1 : 337 | 338 | 题目: 339 | 考虑函数原型void hello(int a, int b = 7, char* pszC = "*"),下面的函数调用中,属于不合法调用的是: 340 | A. hello(5); 341 | B. hello(5, 8); 342 | C. hello(6, "#"); 343 | D. hello(0, 0, "#"); 344 | 345 | 答案: 346 | C 347 | 348 | 解答: 349 | 1. 参数从左往右依次赋值。 350 | 2. 有默认值时,调用函数参数缺失时使用默认值。 351 | 3. 参数中字符串会转为指向字符串的指针。 352 | 353 | Q2 : 354 | 355 | 题目: 356 | 下列程序的运行结果为: 357 | #include 358 | using namespace std; 359 | void main(){ 360 | int a = 2; 361 | int b = ++a; 362 | cout << a / 6 << endl; 363 | } 364 | A. 0.5 365 | B. 0 366 | C. 0.7 367 | D. 0.666666 368 | 369 | 答案: 370 | B 371 | 372 | 解答: 373 | 1. 这里的6仅仅是整型数,所以和a进行操作时不存在精度提升。 374 | 2. a的值进过++a之后变为3,3 / 6 = 0。 375 | 376 | Q3 : 377 | 378 | 题目: 379 | #define ADD(x, y) x + y 380 | int m = 3; 381 | m += m * ADD(m, m); 382 | m的值为多少: 383 | A. 15 384 | B. 12 385 | C. 18 386 | D. 58 387 | 388 | 答案: 389 | A 390 | 391 | 解答: 392 | 1. 原式 = m + [m * m + m] = 3 + [3 * 3 + 3] = 15 393 | 2. 有+=时先算右边部分。 394 | 3. 除非出现++m,否则在同一条语句内,变量值不会改变。 395 | 396 | Q4 : 397 | 398 | 题目: 399 | 下面哪种情况下,B不能隐式转换为A? 400 | A. class B:public A{} 401 | B. class A:public B{} 402 | C. class B{operator A();} 403 | D. class A{A(const B&);} 404 | 405 | 答案: 406 | B 407 | 408 | 解答: 409 | 1. 派生类 -> 基类,向上级转换是隐式的,只需要丢弃多余的部分即可,反之基类没有多余的空间存放B独有的变量。 410 | 2. C是隐式类型转换操作符。 411 | 3. D是拷贝构造函数进行隐式转化。 412 | Q5 : 413 | 414 | 题目: 415 | 假设你在编写一个使用多线程技术的程序,当程序中止运行时,需要怎样一个机制来安全有效的中止所有的线程? 416 | 417 | 答案: 418 | 1. 主线程检查是否有子线程在运行。 419 | 2. 若有则发起线程退出操作(quit)。 420 | 3. wait线程完全停止,delete线程对象。 421 | 4. 等待所有线程结束(发出finish信号),才退出程序。 422 | 423 | Q6 : 424 | 425 | 题目: 426 | 从程序健壮性进行分析,下面的FillUserInfo函数和main函数分别存在什么问题? 427 | #define MAX_NAME_LEN 20 428 | struct USERINFO{ 429 | int nAge; 430 | char szName[MAX_NAME_LEN]; 431 | }; 432 | void FillUserInfo(USERINFO *parUserInfo){ 433 | stu::cout << "请输入用户的个数:"; 434 | int nCount = 0; 435 | std::cin >> nCount; 436 | for (int i = 0; i < nCount; i++){ 437 | std::cout << "请输入年龄:"; 438 | std::cin >> parUserInfo[i]->nAge; 439 | std::string strName; 440 | std::cout << "请输入姓名:"; 441 | std::cin >> strName; 442 | strcpy(parUserInfo[i].szName, strName.c_str()); 443 | } 444 | } 445 | int main(int argc, char *argv[]){ 446 | USERINFO arUserInfos[100] = {0}; 447 | FillUserInfo(arUserInfos); 448 | printf("The first name is:"); 449 | printf(arUserInfos[0].szName); 450 | printf("\n"); 451 | return 0; 452 | } 453 | 454 | 答案: 455 | 456 | --- 457 | 458 | ### 模拟5 459 | 460 | Q1 : 461 | 462 | 题目: 463 | 设某种二叉树有如下特点:每个结点要么是叶子结点,要么有2棵子树。假如一棵这样的二叉树中有m(m > 0)个叶子结点,那么该二叉树上的结点总数为: 464 | A. 2m + 1 465 | B. 2m - 1 466 | C. 2(m - 1) 467 | D. 2m 468 | 469 | 答案: 470 | B 471 | 472 | 解答: 473 | 1. 关键考点: 474 | (1)叶子节点数 = 度为2的节点数 + 1 475 | (2)树的度 = 所有节点度的和 476 | (3)树的节点数 = 树的度 + 1 477 | 2. 计算步骤: 478 | (1)度为2的节点数 = m - 1 479 | (2)树的度 = m * 0 + 0 * 0 + (m - 1) * 2 = 2m - 2 480 | (3)树的节点数 = (2m - 2) + 1 = 2m - 1 481 | Q2 : 482 | 483 | 题目: 484 | 中断响应时间是指: 485 | A. 从中断处理开始到中断处理结束所用的时间 486 | B. 从发出中断请求到中断处理结束所用的时间 487 | C. 从发出中断请求到进入中断处理所用的时间 488 | D. 从中断处理结束到再次中断请求的时间 489 | 490 | 答案: 491 | C 492 | 493 | 解答: 494 | 见答案。 495 | 496 | Q3 : 497 | 498 | 题目: 499 | 试写出“背包题目”的非递归解法。 500 | 501 | 答案: 502 | 503 | --- 504 | 505 | ### 模拟6 506 | 507 | Q1 : 508 | 509 | 题目: 510 | 下推自动识别机的语言是: 511 | A. 0型语言 512 | B. 1型语言 513 | C. 2型语言 514 | D. 3型语言 515 | 516 | 答案: 517 | C 518 | 519 | 解答: 520 | 1. 0型文法产生的语言称为0型语言。 521 | 2. 1型文法产生的语言称为1型语言,也称作上下文有关语言。 522 | 3. 2型文法产生的语言称为2型语言,也称作上下文无关语言。 523 | 4. 3型文法产生的语言称为3型语言,也称作正规语言。 524 | 525 | Q2 : 526 | 527 | 题目: 528 | 浏览器访问某页面,HTTP协议返回状态码为403时表示: 529 | A. 找不到该页面 530 | B. 禁止访问 531 | C. 内部服务器访问 532 | D. 服务器繁忙 533 | 534 | 答案: 535 | B 536 | 537 | 解答: 538 | 1. 100-199,指定客服端相应的某些动作 539 | 2. 200-299,表示请求成功 540 | 3. 300-399,用于已经移动的文件并且包含在定位头信息中指定 541 | 4. 400-499,客服端错误 542 | 5. 500-599,服务端错误 543 | 544 | Q3 : 545 | 546 | 题目: 547 | 递归函数最终会结束,那么这个函数一定: 548 | A. 使用了局部变量 549 | B. 有一个分支不调用自身 550 | C. 使用了全局变量或者使用了一个或多个参数 551 | D. 没有循环调用 552 | 553 | 答案: 554 | B 555 | 556 | 解答: 557 | 1. 分支不调用自身即函数出口。 558 | 559 | Q4 : 560 | 561 | 题目: 562 | 编译过程中,语法分析器的任务是: 563 | A. 分析单词是怎样构成的 564 | B. 分析单词串是如何构成语言和说明的 565 | C. 分析语句和说明是如何构成程序的 566 | D. 分析程序的结构 567 | 568 | 答案: 569 | B, C, D 570 | 571 | 解答: 572 | 1. 词法分析:词法分析是编译过程的第一个阶段。这个阶段的任务是从左到右的读取每个字符,然后根据构词规则识别单词。词法分析可以用lex等工具自动生成。 573 | 2. 语法分析:语法分析是编译过程的一个逻辑阶段。语法分析在词法分析的基础上,将单词序列组合成各类语法短语,如“程序”,“语句”,“表达式”等等。语法分析程序判断程序在结构上是否正确。 574 | 3. 语义分析:属于逻辑阶段。对源程序进行上下文有关性质的审查,类型检查。如赋值语句左右端类型匹配问题。 575 | 576 | Q5 : 577 | 578 | 题目: 579 | 进程进入等待状态有哪几种方式: 580 | A. CPU调度给优先级更高的线程 581 | B. 阻塞的线程获得资源或者信号 582 | C. 在时间片轮转的情况下,如果时间片到了 583 | D. 获得spinlock未果 584 | 585 | 答案: 586 | D 587 | 588 | 解答: 589 | 1. A和C均是由从运行态转为就绪状态。 590 | 2. B是由阻塞状态转为就绪状态。 591 | 3. 自旋锁(spinlock)是一种保护临界区最常见的技术。在同一时刻只能有一个进程获得自旋锁,其他企图获得自旋锁的任何进程将一直进行尝试。 592 | Q6 : 593 | 594 | 题目: 595 | 同一进程下的线程可以共享以下: 596 | A. stack 597 | B. data section 598 | C. register set 599 | D. file fd 600 | 601 | 答案: 602 | B, D 603 | 604 | 解答: 605 | 1. 线程共享的内容包括: 606 | 进程代码段 607 | 进程的公有数据 608 | 进程打开的文件描述符 609 | 信号的处理器 610 | 进程的当前目录 611 | 进程用户ID与进程组ID 612 | 2. 线程独有的内容包括: 613 | 线程ID 614 | 寄存器组的值 615 | 线程的堆栈 616 | 错误返回码 617 | 线程的信号屏蔽码 618 | 619 | Q7 : 620 | 621 | 题目: 622 | 设计模式中,属于结构型模式的有哪些: 623 | A. 状态模式 624 | B. 装饰模式 625 | C. 代理模式 626 | D. 观察者模式 627 | 628 | 答案: 629 | B, C 630 | 631 | 解答: 632 | 1. 创建型模式: 633 | 单例模式 634 | 抽象工厂模式 635 | 建造者模式 636 | 工厂模式 637 | 原型模式 638 | 2. 结构型模式: 639 | 适配器模式 640 | 桥接模式 641 | 装饰模式 642 | 组合模式 643 | 外观模式 644 | 享元模式 645 | 代理模式 646 | 3. 行为型模式: 647 | 模版方法模式 648 | 命令模式 649 | 迭代器模式 650 | 观察者模式 651 | 中介者模式 652 | 备忘录模式 653 | 解释器模式 654 | 状态模式 655 | 策略模式 656 | 职责链模式 657 | 访问者模式 658 | 659 | Q8 : 660 | 661 | 题目: 662 | Unix系统中,哪些可以用于进程间的通信: 663 | A. Socket 664 | B. 共享内存 665 | C. 消息队列 666 | D. 信号量 667 | 668 | 答案: 669 | A, B,C,D 670 | 671 | 解答: 672 | 1. Linux进程间通信:管道、信号、消息队列、共享内存、信号量、套接字。 673 | 2. Linux线程间通信:互斥量、信号量、条件变量。 674 | 3. Windows进程间通信:管道、消息队列、共享内存、信号量、套接字。 675 | 3. Windows线程间通信:互斥量、信号量、临界区、事件。 676 | 677 | Q9 : 678 | 679 | 题目: 680 | 设t是给定的一棵二叉树,下面的递归程序count(t)用于求得: 681 | typedef struct node{ 682 | int data; 683 | struct node *lchild, *rchild; 684 | }node; 685 | int N2, NL, NR, N0; 686 | void count(node *t){ 687 | if (t->lchild != NULL) 688 | if (t->rchild != NULL) N2++; 689 | else NL++; 690 | else if(t->rchild != NULL) 691 | NR++; 692 | else N0++; 693 | if(t->lchild != NULL) 694 | count(t->lchild); 695 | if(t->rchild != NULL) 696 | count(t->rchild); 697 | } 698 | 答案: 699 | 700 | Q10 : 701 | 702 | 题目: 703 | 请设计一个排队系统,能够让每个进入队伍的用户都能看到自己在队列中所处的位置和变化,队伍可能随时有人加入和退出;当有人退出影响到用户的位置排名时需要及时反馈到用户。 704 | 705 | 答案: 706 | 707 | Q11 : 708 | 709 | 题目: 710 | A、B两个整数集合,设计一个算法求他们的交集,尽可能的高效。 711 | 712 | 答案: 713 | 714 | --- 715 | 716 | ### 模拟6 717 | 718 | Q1 : 719 | 720 | 题目: 721 | 如何减少换页错误: 722 | A. 进程倾向于占用CPU 723 | B. 访问局部性(locality of reference)满足进程要求 724 | C. 进程倾向于占用I/O 725 | D. 使用基于最短剩余时间(shortest remaining time)的调度机制 726 | 727 | 答案: 728 | B 729 | 730 | 解答: 731 | 1. 换页错误又称缺页错误,当一个程序试图访问没有映射到物理内存的地方时,就会出现缺页错误。 732 | 2. 减少缺页发生的方法: 733 | 增加作业分配的内存块数。 734 | 增加页面大小。 735 | 页面替换算法。 736 | 程序满足局部性原理。 737 | 738 | Q2 : 739 | 740 | 题目: 741 | 有1000亿条记录,每条记录由url,ip,时间组成,设计一个系统能够快速查询以下内容。 742 | 1. 给定url和时间段(精确到分钟)统计url的访问次数。 743 | 2. 给定ip和时间段(精确到分钟)统计ip的访问次数。 744 | 745 | 答案: 746 | 747 | Q3 : 748 | 749 | 题目: 750 | 751 | 给定一个包含了用户query的日志文件,对于输入的任意一个字符串s,输出以s为前缀的在日志中出现频率最高的前10条query。 752 | 至少有26台机器,每个机器存储以26个字母开头的query日志文件(机器1以a字母开头的,机器2以b字母开头……)。 753 | 各机器维护一张哈希表,每条query在哈希表中存放其地址(哈希地址为链式的),并对其进行排序,按频率由高到低进行排序。 754 | 当用户进行搜索时,可以很快定位到某台机器,并根据哈希表,返回出现频率最高的前10条query。 755 | 756 | 提示: 757 | 1. 可以预处理日志。 758 | 2. 假设query超过10亿条,每个query不超过50字节。 759 | 3. 考虑在大查询量的情况下如何实现分布式服务。 760 | 761 | 答案: 762 | 763 | 764 | --- 765 | 766 | ### 模拟7 767 | 768 | Q1 : 769 | 770 | 题目: 771 | 下列哪些http方法对于服务端和用户端一定是安全的? 772 | A. GET 773 | B. HEAD 774 | C. TRACE 775 | D. OPTION 776 | E. POST 777 | 778 | 答案: 779 | C 780 | 781 | 解答: 782 | 783 | Q2 : 784 | 785 | 题目: 786 | 一个系统,提供多个http协议的接口,返回的结果Y有json格式和jsonp格式。Json的格式为{"code":100,"msg":"aaa"},为了保证该协议变更之后更好的应用到多个接口,为了保证修改协议不影响到原先逻辑的代码,以下哪些设计模式是需要的?协议的变更指的是日后可能返回xml格式,或者是根据需求统一对返回的消息进行过滤。 787 | A. Aadapter 788 | B. factory method 789 | C. proxy 790 | D. decorator 791 | E. composite 792 | 793 | 答案: 794 | A, B, D 795 | 796 | 解答: 797 | 798 | --- 799 | 800 | ### 模拟8 801 | 802 | Q1 : 803 | 804 | 题目: 805 | 在数据库系统中,产生不一致的根本原因是: 806 | A. 数据存储量太大 807 | B. 没有严格保护数据 808 | C. 未对数据进行完整性控制 809 | D. .数据冗余 810 | 811 | 答案: 812 | 813 | 814 | 解答: 815 | 816 | Q2 : 817 | 818 | 题目: 819 | 请问下面的程序一共输出多少个“-”? 820 | int main(void){ 821 | int i; 822 | for(i = 0; i < 2; i++){ 823 | fork(); 824 | printf("-"); 825 | } 826 | return 0; 827 | } 828 | A. 2 829 | B. 4 830 | C. 6 831 | D. 8 832 | 833 | 答案: 834 | 835 | 836 | 解答: 837 | 838 | Q3 : 839 | 840 | 题目: 841 | 请问下面的程序一共输出多少个“-”?为什么? 842 | #include 843 | #include 844 | #include 845 | int main(void){ 846 | int i; 847 | for (i = 0; i < 2; i++){ 848 | fork(); 849 | printf("-\n"); 850 | } 851 | return 0; 852 | } 853 | A. 4 854 | B. 5 855 | C. 6 856 | D. 8 857 | 858 | 答案: 859 | 860 | 861 | 解答: 862 | 863 | Q4 : 864 | 865 | 题目: 866 | 867 | A. 868 | B. 869 | C. 870 | D. 871 | 872 | 答案: 873 | 874 | 875 | 解答: 876 | 877 | Q5 : 878 | 879 | 题目: 880 | 881 | A. 882 | B. 883 | C. 884 | D. 885 | 886 | 答案: 887 | 888 | 889 | 解答: 890 | --- 891 | 892 | ## NetEase 893 | 894 | --- 895 | 896 | ## 360 897 | --------------------------------------------------------------------------------