并发服务器创建:
1.在tcp简单服务器的基础上,增加了进程(线程)的数量,并且可以对多个进程同时进行处理请求。
并发:多通道程序设计模型
TCP并发服务器的搭建方法:
1.通过多进程来实现
2.通过多线程来实现
创建流程:
socket() 创建套接字
bind() 与套接字与本地主机捆绑
listen() 将套接字设置为被动监听状态,并设置同时可以连接客户端 的数量
while(1) {
connfd = accept() 将客户的ip等信息与套接字链接,并返回一个用于通信的套接字
pid_t pid = fork(); 创建父子进程
if(pid > 0) 大于0为父进程,负责监听,关闭通信套接字
{ }
else if(pid == 0) 等于0为子进程,负责通信,关闭监听套接字close() continue ; 关闭不需要的套接字
}
要解决的问题:
1.子进程负责通信 客户端;父进程负责监听 服务端;
2.对僵尸进程的处理,以及子进程和父进程的顺序,父进程监听的时候子进程的通信暂时停止,子进程在用通信时,父进程监听停止
代码如下:
//宏
#define IP "0"
#define PORT 7777
#define LINKNUM 1000
int init_server(char *ip,int port,int backlog)
{ //监听套接字的创建
int listenfd = socket(PF_INET,SOCK_STREAM,0);
if(-1 == listenfd)
{
perror("socket");
exit(-1);
}
//地址重用
int opt = 1;
int op = setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
//ip初始化,进行套接字和主机的连接
struct sockaddr_in seraddr = {0};
seraddr.sin_family = PF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(ip);
int ret = bind(listenfd,(struct sockaddr *)&seraddr,sizeof(seraddr));
if(-1 == ret)
{
perror("bind");
exit(-1);
}
//监听listen
ret = listen(listenfd,backlog);
if(-1 == ret)
{
perror("listen");
exit(-1);
}
return listenfd;
}
//子进程的通信函数
int com_clinet(int connfd)
{
int ret;
char buf[BUFSIZ] = {0};
while(1)
{
memset(buf,0,BUFSIZ);
int num = read(connfd,buf,BUFSIZ);
if(-1 == num)
{
perror("read");
close(connfd);
exit(-1);
}
else if(0 == num)
{
printf("connfd quit\n");
exit(0);
}
printf("buf :\n%s\n",buf);
int i = 0;
for(;i < num;i++)
{
buf[i] = toupper(buf[i]);
}
write(connfd,buf,num);
}
}
void child_hander(int sign) //回收结束的子进程
{
while(waitpid(-1,NULL,WNOHANG) > 0);
printf("child was died\n");
}
int main()
{
int listenfd; //监听套接字
int connfd; //通信套接字
signal(SIGCHLD,child_hander); //注册一个子进程的信号,在子进程结束或者停止时,会给父进程一个返回值,,父进程收到这个返回值后对子进程占有的资源进行回收;
listenfd = init_server(IP,PORT,LINKNUM); //调用监听套接字的创建和初始化等操作
if(-1 == listenfd)
{
perror("init");
exit(-1);
}
printf("sock success....\n");
while(1)
{
struct sockaddr_in clin_addr = {0};
int len = sizeof(clin_addr);
connfd = accept(listenfd,(struct sockaddr *)&clin_addr,&len);//将接受到的对方的地址与套接字进行捆绑,并返回一个新的套接字,用于通信;
if(-1 == connfd){
if(errno = EINTR)
{
continue;
}
else
{
perror("accept");
exit(0);
}
}
printf("ip: %s---port:%d\n",inet_ntoa(clin_addr.sin_addr),ntohs(clin_addr.sin_port));
pid_t pid = fork(); //创建子进程
if(0 == pid) //子进程关闭监听套接字进行通信
{
close(listenfd);
com_clinet(connfd);
}
else{ //父进程关闭通信套接字
close(connfd);
continue;
}
}
close(listenfd); //关闭服务器,关闭监听
return 0;
}
相关函数解释:
1·signal(参数1,参数2);
参数1:我们要进行处理的信号。系统的信号我们可以再终端键入 kill -l查看(共64个)。其实这些信号时系统定义的宏。
参数2:我们处理的方式
产生SIGCHLD信号的条件:
1·子进程结束的时候
2·子进程收到SIGSTOP信号
3·当子进程停止时,收到SIGCONT信号
SIGCHLD信号的作用
1·子进程退出后,内核会给它的父进程发送SIGCHLD信号,父进程收到这个信号后可以对子进程进行回收。
2·使用SIGCHLD信号完成对子进程的回收可以避免父进程阻塞等待而不能执行其他操作,只有当父进程收到SIGCHLD信号之后才去调用信号捕捉函数完成对子进程的回收,未收到SIGCHLD信号之前可以处理其他操作。
2·return和exit的区别:
1. exit用于结束正在运行的整个程序,它将参数返回给操作系统,把控制权交给操作系统;而return 是退出当前函数,返回函数值,把控制权交给调用函数。
2. exit是系统调用级别,它表示一个进程的结束;而return 是语言级别的,它表示调用堆栈的返回。
3shutdown()和close()
Shutdown(套接字的返回值,0);关闭套接字的读端
Shutdown(套接字的返回值,1);关闭套接字的写端
Shutdown(套接字的返回值,2);关闭套接字的写端和读端;等同于close(返回值);