1、多进程并发服务器分析
**并发**:将CPU分割成不同的时间碎片,不同的进程就是去抢时间碎片,而且进程对于抢时间碎片是有优先级的,也就是有的进程抢到时间碎片的概率更大。
通俗点并发就是在一短时间之内,所能处理的进程的个数,高并发就是处理的进程个数多。
(1)只能处理单链接
1. 创建套接字-这个套接字用于监听(也就是用于监听的文件描述符(一个缓存区)和用于通信的文件描述符是不同的)
2. 绑定
3. 监听–listen(fd,128);
4. -----------------------------
5. 接受连接请求-会生成一个用于通信的文件描述符(一个用于通信的缓存区)
6. 通信
子进程就是用来进行通信的。
//伪代码:
void recyle(int num)
{
while(waitpid(-1,NULL,WNOHANG)>0);
}
int main()
{
// 创建监听的套接字文件描述符
int lfd = socket();
//绑定
bind();
//设置监听
listen();
//父进程
while(1)
{
int cfd = accept();//父进程会在这里阻塞,直到有客服端建立连接,通过返回的cfd今次那个判断
//当建立链接之后,创建一个子进程用于通信,因为进程间共享文件描述符,所以父进程建立的通信的描述符子进程也可以用
pid_t = pid;
if(pid==0)
{
close(lfd);//将用于监听的套接字文件描述符关闭,用cfd即可
//通信
while(1)
{
int len = read();//read相当于接受数据,也就是在读的缓存区接收数据
if(len == -1)
exit;
else if(len == 0)
{
close(cfd);
break;
}
write();
}
//退出子进程
return 0;//或者exit(0)
}
else{//父进程进行资源回收
close(cfd);//父进程不用于通信,所以关闭,此时子进程的cfd不会关闭,因为进程写时复制
//不能用while回收子进程,因为这样会父进程不会往下进程,也就是只能接受一个子进程通信,所以用信号进行回收
struct sigaction act;
act.sa_handler = recyle;
act.sa_falgs = 0;
sigemptyset($act.sa_mask);
sigaction(SIGCHLD,&act,NULL);
}
小结:父进程用于监听,子进程用于通信,代码中看似只有一个cfd,但实际每一个子进程都有自己独立的cfd,因为会复制一份,所以进程之间的cfd是不会相互影响的。
2、多进程并发服务器分析
多线程并发服务器与进程的相似,不同的地方是创建线程的代码,以及线程之间的共享资源,地址空间这些细节会使得实现有一些细节的不同。
进程中只要声明一个cfd就能搞定,因为进程有读时共享,写时复制,而线程并不能一个cfd搞定,因为所有的线程会共享全局变量,所以线程之间是相同的cfd地址,会造成混乱。所以需要用数组去保存每一个线程的用于通信的文件描述符。
线程:①共享全局静态存储区和堆内存;②栈是独立的
//伪代码:
typedef struct sockInfo
{
pthread_t id;
int fd;
struct sockaddr addr;
} SockInfo;
void* worker(void *arg)
{
while(1)
{
//打印客户端ip和port
read();
write();
}
}
int main()
{
// 创建监听的套接字文件描述符
int lfd = socket();
int* fds = new int[100];//最多100个线程
//绑定
bind();
//设置监听
listen();
SockInfo sock[256];
//父线程
while(1)
{
sock[i].fd = accept(lfd,&client,&len);
//创建子线程
//子线程进行通信
pthread_create(&sock[i].id,NULL,worker,&sock[i]);
//分离父子线程
pthread_deathch(sock[i].id);
}
}
小结:不能用一块内存存取多个进程的文件描述符,线程中执行的是一个回调函数内容,因为线程中的数据需要是独立的,所以我们定义一个结构体,将每一个线程需要的东西全封装在结构体中。