进程简单来说就是一个正在运行的程序。包括其运行代码和运行代码所用的资源,一个CPU可以存在多个进程但是同一时间只允许一个进程工作。但CPU切换速度很快,给我们感觉像是所有进程同时运行。
线程是操作系统最小度量单位。线程和进程最大的区别就是共不共享数据,同时线程是进程的一部分,也就是进程可以由多个线程构成。进程好比火车,线程好比车厢。不同火车之间的信息当然不共享,所以用起来比较麻烦,比如说打个电话。而线程好比同一列火车上的不同车厢,走过去打个招呼就能就交流。
使用多线程还是多进程?…Emmm其实我也不知道…曾经我想处理一个图片,但是如果只用一个程序跑的话太慢了,只用一个CPU核,所以我分别尝试了多线程和多进程…结果我的多进程是好用的…所有CPU核全部跑满看着很得劲(当然可能是因为操作系统是WIN的,WIN本身更偏向于多进程,而UNIX类的更多偏向多进程)
创建进程
创建进程使用的是fork函数,工作原理如下:
从上图可以看出,进程是完完整整的把父进程的所有都复制给了子进程,包括数据段空间,代码段空间和堆栈空间等。
fork函数会返回一个值,如果这个值是0的话,就是子进程,是其他的话就是父进程。其他值是子进程的进程号。
下面做个小测试:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{pid_t pid;pid = fork();if (pid == -1){printf("Error");return 0;}else if (pid == 0){printf("Child");}else{printf("Parent %dn",pid);}return 0;
}
结果如下…Child其实emmmm是输出结果但是吧,我猜父进程结束,子进程结束。命令行只管父进程?当然不是了…是因为父进程先于子进程结束所以会导致这样的状况,所以emmm就有了等待进程结束的命令waitpid。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>int main()
{pid_t pid,pid_wait;int status;pid = fork();if (pid == -1){printf("Error");return 0;}else if (pid == 0){printf("Childn");printf("?");}else{printf("Parent %dn",pid);pid_wait = waitpid(pid,&status,0);printf("Child process %d returned!n",pid_wait);}printf("What happened?: %d",pid);return 0;
}
这就是先进入父进程然后输出了Parent 57616父进程卡住,等待子进程然后子进程结束输出what happened(pid=0判断子进程)然后父进程的What happen。破案了
进程之间的通讯
进程之间的通讯是个很麻烦的事情,有两种方式,一种是管道,一种是共享内存。管道这种方式其实蛮简单的感觉像UART半双工通讯,两个进程之间只能单独写或单独读。
例子如下:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{int fd[2];pid_t pid;char buf[64] = "I'm parent!n";char line[64];if (pipe(fd)!=0){fprintf(stderr,"Fauk to create pipe!n");return 0;}pid=fork();if (pid<0){fprintf(stderr,"Fail to create");return 0;}else if (0==pid){close(fd[0]);//shutdown readwrite(fd[1],buf,strlen(buf));close(fd[1]);//shutdown write}else{close(fd[1]);read(fd[0],line,64);printf("Date from parents:%s",line);close(fd[0]);} return 0;
}
第二种就是共享内存,共享内存就是在内存中开辟一段空间供不同进程访问。
写共享内存:
shmget()函数用来创建共享内存,第一个参数是一个特殊标识,只要不重复就可,但一般由ftok()函数生成,第二个参数是大小字节数,第三个是内存操作方式
shmat()是获得一个共享内存ID对应的起始地址。第二个参数是指定共享内存地址,0是首地址,第三个参数一般写0,让代表需要让系统决定共享内存地址
shmdt()分离一块共享内存,估摸着就是释放掉。
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main()
{int shmid; // 定义共享内存idchar *ptr;char *shm_str = "string in a share memory";shmid = shmget(0x90, 1024, SHM_W|SHM_R|IPC_CREAT|IPC_EXCL); // 创建共享内存if (-1==shmid)perror("create share memory");ptr = (char*)shmat(shmid, 0, 0); // 通过共享内存id获得共享内存地址if ((void*)-1==ptr)perror("get share memory");strcpy(ptr, shm_str); // 把字符串写入共享内存shmdt(ptr);return 0;
}
读共享内存:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main()
{int shmid; // 定义共享内存idchar *ptr;char *shm_str = "string in a share memory";shmid = shmget(0x90, 1024, SHM_W|SHM_R|IPC_CREAT|IPC_EXCL); // 创建共享内存if (-1==shmid)perror("create share memory");ptr = (char*)shmat(shmid, 0, 0); // 通过共享内存id获得共享内存地址if ((void*)-1==ptr)perror("get share memory");strcpy(ptr, shm_str); // 把字符串写入共享内存shmdt(ptr);return 0;
}
如果再次运行写程序就会报错
原因是当前共享地址Key用过,毕竟我们写的是0x90是固定的,使用ipcs可以看到
其中的1024是我们创建的,可以用ipcrm -m 4751372释放掉 (4751372是shmid)
创建线程
我们先看个例子然后从例子中学习:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> void* thread_func(void *arg) // 线程函数
{ int *val = arg; printf("Hi, I'm a thread!n"); if (NULL!=arg) // 如果参数不为空,打印参数内容 printf("argument set: %dn", *val);
} int main()
{ pthread_t tid; // 线程ID int t_arg = 100; // 给线程传入的参数值 if (pthread_create(&tid, NULL, thread_func, &t_arg)) // 创建线程perror("Fail to create thread"); sleep(1); // 睡眠1秒,等待线程执行 printf("Main thread!n"); return 0;
}
可以看到pthread_create函数有4个参数,第一个是线程ID最后会回写的,第二个是用来设置线程属性的,没有就NULL,第三个就是函数指针,指定线程函数,第四个就是指定函数的传入参数。如果创建成功就会返回0,不成功返回错误号。
PS:如果直接gcc -o 编译的话会报错,因为pthread.h不是标准库中的函数,所以要加上参数 -lphread进行编译
取消线程,看例子就能理解:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> void* thread_func(void *arg) // 线程函数
{int *val = arg;printf("Hi, I'm a thread!n");if (NULL!=arg) { // 如果参数不为空,打印参数内容while(1)printf("argument set: %dn", *val);}
}int main()
{pthread_t tid; // 线程IDint t_arg = 100; // 给线程传入的参数值if (pthread_create(&tid, NULL, thread_func, &t_arg)) // 创建线程perror("Fail to create thread");sleep(1); // 睡眠1秒,等待线程执行printf("Main thread!n");pthread_cancel(tid); // 取消线程return 0;
}
输出结果:
…最前面一个Hi,I’m thread!然后无数个argumen…然后结束
基础就是大学生计算机基础课程里应该有学过这个图:
我们主要先看TCP/IP协议也就是传输层和网络互联层的。
IP协议负责数据包的传输管理,实现两个基本功能:寻址和分段
寻址:就是IP协议根据数据报头中的地址传送数据报文。而IP协议根据目的地址选择报文在网络中的传输路径的过程叫做路由。(大家是不是知道…路由器为啥叫路由器了…)
分段:就是为了适应在不同网络中传输TCP/IP协议产生的分段机制…
TCP协议是传输层协议,TCP是一个面向连接可靠传输的协议,TCP协议层会对数据包进行排列并错误检测,如果缺少数据包就会重传丢失数据包。(感觉UDP就是TCP的不稳定不安全版本)
之前废话一堆…其实Socket通讯我觉得最重要,毕竟…怎么实现才最重要么…
面向连接的Socket通信
这是面向连接的Socket通信框图
我们实现的时候就根据这个框图走。
总结如下:
服务器端工作流程图:
客户端工作流程
依旧从程序看操作,演示本机和本机通讯的例子:
服务器:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>#define EHCO_PORT 8080
#define MAX_CLIENT_NUM 10int main()
{int sock_fd;struct sockaddr_in serv_addr;int clientfd;struct sockaddr_in clientAdd;char buff[101];socklen_t len;int closing =0;int n;/* 创建socket */sock_fd = socket(AF_INET, SOCK_STREAM, 0);if(sock_fd==-1) {perror("create socket error!");return 0;} else {printf("Success to create socket %dn", sock_fd);}/* 设置server地址结构 */bzero(&serv_addr, sizeof(serv_addr)); // 初始化结构占用的内存serv_addr.sin_family = AF_INET; // 设置地址传输层类型serv_addr.sin_port = htons(EHCO_PORT); // 设置监听端口serv_addr.sin_addr.s_addr = htons(INADDR_ANY); // 设置服务器地址bzero(&(serv_addr.sin_zero), 8);/* 把地址和套接字绑定 */if(bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr))!= 0) {printf("bind address fail! %dn", errno);close(sock_fd);return 0;} else {printf("Success to bind address!n");}/* 设置套接字监听 */if(listen(sock_fd ,MAX_CLIENT_NUM) != 0) {perror("listen socket error!n");close(sock_fd);return 0;} else {printf("Success to listenn");}/* 创建新连接对应的套接字 */len = sizeof(clientAdd);clientfd = accept(sock_fd, (struct sockaddr*)&clientAdd, &len);if (clientfd<=0) {perror("accept() error!n");close(sock_fd);return 0;}/* 接收用户发来的数据 */while((n = recv(clientfd,buff, 100,0 )) > 0) {buff[n] = '