一、多进程并发服务器
1、只能处理单链接
- 创建套接字 - 监听
- 绑定
- 监听 - listen(fd,128)
- +++++++++++++++++
- 接收连接请求
- 通信
使用多进程的方式,解决服务器处理多连续的问题:
2、共享
读时共享,写时复制。
文件描述符
内存映射区 – mmap
3、父进程的角色是什么?
等待接受客户端连接 – accept
有链接:创建一个子进程 fork()
将通信的文件描述符关闭
4、子进程的角色是什么?
通信
使用accept返回值 - fd
关闭监听的文件描述符
浪费资源
5、创建的进程的个数有限制吗?
受硬件限制
文件描述符默认也是有上限的1024
6、子进程资源回收
wait/waitpid
使用信号回收
①信号捕捉:signal、sigaction(推荐)
②捕捉信号:SIGCHLD
7、多进程伪代码
void recyle(int num)
{
while(waitpid(-1,NULL,wnohang)>0);
}
int main()
{
//监听
int lfd=sock();
//绑定
bind();
//设置监听
listen();
//信号回收了进程
struct sigaction act;
act.sa_handler=recyle;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD,&act,NULL);
//父进程
while(1)
{
int cfd=accept();
//创建子进程
pid_t pid=fork();
//子进程
if(pid==0)
{
close(fd);
//通信
while(1)
{
int len=read();
if(len==-1)
{
exit(1);
}
else if(len==0)
{
close(cfd);
break;
}
else
write();
}
//退出子进程
return 0;//exit(1);
}
else
{
//父进程
close(cfd);
}
}
}
8、代码实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
//进程回收函数
void recyle(int num)
{
pid_t pid;
while((pid=waitpid(-1,NULL,WNOHANG))>0)
{
printf("child died,pid=%d\n",pid);
}
}
int main(int argc,const char* argv[])
{
if(argc<2)
{
printf("eg: ./a.out port\n");
exit(1);
}
struct sockaddr_in serv_addr;
socklen_t serv_len=sizeof(serv_addr);
int port=atoi(argv[1]);
//创建套接字
int lfd=socket(AF_INET,SOCK_STREAM,0);
//初始化服务器 sockaddr_in
memset(&serv_addr,0,serv_len);
serv_addr.sin_family=AF_INET;//地址族
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);//监听本机所有的IP
serv_addr.sin_port=htons(port);//设置端口
//绑定IP和端口
bind(lfd,(struct sockaddr*)&serv_addr,serv_len);
//设置同时监听的最大个数
listen(lfd,36);
printf("start accept ......\n");
//使用信号回收子进程pcb
struct sigaction act;
act.sa_handler=recyle;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD,&act,NULL);
struct sockaddr_in client_addr;
socklen_t cli_len =sizeof(client_addr);
while(1)
{
//父进程接收连接请求
//accept处理完连接请求后,就会阻塞直到等到下一个连接请求
//但当它在阻塞的过程中,它被信号中断了(如子进程结束,需要父进程回收资源)
//此时父进程将被信号中断去回收子进程,那么当他回来的时候就不是回到阻塞状态
//直接返回-1,此时errno=EINTR
int cfd=accept(lfd,(struct sockaddr*)&client_addr,&cli_len);
while(cfd==-1&&errno==EINTR)
{
cfd=accept(lfd,(struct sockaddr*)&client_addr,&cli_len);
//perror("accept error");
//exit(1);
}
printf("connect asucessful\n");
//创建子进程
pid_t pid=fork();
if(pid==0)
{
//文件描述符的关闭
close(lfd);
//child process
//通信过程
char ip[64];
while(1)
{
//client ip port
printf("client IP:%s,port:%d\n",inet_ntop(AF_INET,
&client_addr.sin_addr.s_addr,ip,sizeof(ip)),
ntohs(client_addr.sin_port));
char buf[1024];
int len=read(cfd,buf,sizeof(buf));
if(len==-1)
{
perror("read error");
exit(1);
}
else if(len==0)
{
printf("客户端断开了连接\n");
close(cfd);
}
else
{
printf("recv buf:%s\n",buf);
write(cfd,buf,len);//写回去
}
}
//干掉子进程
return 0;
}
else if(pid>0)
{
//parent process
close(cfd);
}
}
}
若没有nc,则到bin目录下#yum install -y nc安装
二、多线程并发服务器
1、线程共享:
- 全局数据区
- 堆区
- 一块有效内存的地址
2、多线程伪代码
typedef struct sockInfo
{
pthread_t id;
int fd;
struct sockaddr_in addr;
}SockInfo;
void worker(void *arg)//对参数进行强转
{
while(1)
{
//打印客户端ip和port
read();
write();
}
}
int main()
{
//监听
int lfd=sock();
//绑定
bind();
//设置监听
listen();
SockIndo sock[256];
//父线程
while(1)
{
sock[i].fd=accept(lfd,&client,&len);
//创建子线程
pthread_create(&sock[i].id,NULL,worker,&sock[i]);
//线程分离
pthread_detach(sock[i].id);
}
}
3、多线程实现代码
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <pthread.h>
#include <stdio.h>
#include<stdlib.h>
//自定义数据结构
typedef struct SockInfo
{
int fd;
struct sockaddr_in addr;
pthread_t id;
}SockInfo;
//子线程处理函数
void *worker(void *argc)
{
char ip[64];
char buf[1024];
SockInfo *info=(SockInfo *)argc;
//通信
while(1)
{
printf("client IP:%d,port:%d\n",inet_ntop(AF_INET,&info->addr.sin_addr.s_addr,ip,sizeof(ip)),ntohs(info->addr.sin_port));
int len=read(info->fd,buf,sizeof(buf));
if(len==-1)
{
perror("read error");
pthread_exit(NULL);
}
else if(len==0)
{
printf("客户端已断开连接\n");
close(info->fd);
break;
}
else
{
printf("recv buf:%s\n",buf);
write(info->fd,buf,len);
}
}
return NULL;
}
int main(int argc,const char* argv[])
{
if(argc<2)
{
printf("eg: ./a.out port\n");
exit(1);
}
struct sockaddr_in serv_addr;
socklen_t serv_len=sizeof(serv_addr);
int port=atoi(argv[1]);
//创建套接字
int lfd=socket(AF_INET,SOCK_STREAM,0);
//初始化服务器 sockaddr_in
memset(&serv_addr,0,serv_len);
serv_addr.sin_family=AF_INET;//地址族
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);//监听本机所有的IP
serv_addr.sin_port=htons(port);//设置端口
//绑定IP和端口
bind(lfd,(struct sockaddr*)&serv_addr,serv_len);
//设置同时监听的最大个数
listen(lfd,36);
printf("start accept ......\n");
int i=0;
SockInfo info[256];
//规定 fd==-1
for(i=0;i<sizeof(info)/sizeof(info[0]);++i)
{
info[i].fd=-1;
}
socklen_t cli_len =sizeof(struct sockaddr_in);
while(1)
{
//选一个没有被使用的最小的数组元素
for(i=0;i<256;++i)
{
if(info[i].fd==-1)
break;
}
if(i==256)
{
break;
}
//主线程 -等待接受连接请求
info[i].fd=accept(lfd,(struct sockaddr*)&info[i].addr,&cli_len);
//创建子线程 - 通信
pthread_create(&info[i].id,NULL,worker,&info[i]);
//设置线程分离
pthread_detach(info[i].id);
}
close(lfd);
//之推出主线程
pthread_exit(NULL);
return 0;
}