2.1循环服务器模型
同一时刻只能响应一个客户端的请求
socket();
bind();
listen();
while(1)
{
accept();
while(1)
{
process();//处理
}
close();
}
2.2并发服务器模型
同一时刻能响应多个客户端的请求,常用模型:多进程模型、多线程模型、IO多路复用
多进程模型
每来一个客户端连接,开一个子进程来专门处理客户端的数据,实现简单,但是系统开销相对较大,更推荐使用线程模型。
伪代码:
socket();
bind();
listen();
while(1)
{
accept();
if(fork()==0)//子进程
{
while(1)
{
process();//处理
}
close();
exit();
}
else
{
}
}
多进程特点 :
- fork之前的代码被复制,但是不会重新执行一遍;fork之后的代码被复制,并且被再次执行一遍
- fork之后两个进程相互独立,子进程拷贝了父进程的所有代码,但是内存空间独立
- fork之前打开的文件,fork之后拿到的是同一个文件描述符,操作的是同一个文件指针
例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
printf("hello world\n");
int a = 100;
int fd = open("./poll.c", O_RDONLY);
pid_t pid = fork();
if(pid < 0 )
{
perror("fork err:");
return -1;
}
else if(pid == 0)
{
char buf[32]="";
a = 200;
printf("child:a = %d\n",a);
read(fd,buf,10);
printf("buf:%s\n",buf);
printf("childfd:%d\n",fd);
}
else
{
sleep(1);
char buf[32]="";
printf("father:a=%d\n",a);
read(fd,buf,10);
printf("fbuf:%s\n",buf);
printf("fatherfd:%d\n",fd);
}
printf("---------%d\n",a);
return 0;
}
注意:收到客户端消息后,打印下是来自哪个客户端的数据(来电显示)
使用SIGCHLD来处理子进程结束的信号,信号函数中回收进程资源。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#define N 128
void handler(int arg)
{
waitpid(-1,NULL,WNOHANG);
}
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("please input %s port\n", argv[0]);
return -1;
}
// 1.创建套接字 协议族 类型 协议
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err:");
return -1;
}
// 2.填充结构体(ipv4)
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET; // 协议族ipv4
saddr.sin_port = htons(atoi(argv[1])); // 端口号(网络字节序)
// saddr.sin_addr.s_addr = inet_addr(argv[1]); // ip地址(网络字节序)
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
socklen_t len = sizeof(caddr);
// 3.绑定
int ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
if (ret < 0)
{
perror("bind err:");
return -1;
}
// 4.监听
if (listen(sockfd, 5) < 0)
{
perror("listen err");
return -1;
}
signal(SIGCHLD,handler);
while (1)
{
// 5.等待连接
// int acceptfd = accept(sockfd, NULL, NULL);
int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("accpet err:");
return -1;
}
printf("acceptfd = %d\n", acceptfd);
printf("client ip :%s,port:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
pid_t pid =fork();
if(pid < 0)
{
perror("fork err");
return -1;
}
else if(pid == 0)
{
close(sockfd);
char buf[N]={};
int ret;
while(1)
{
ret = recv(acceptfd,buf,N,0);
if(ret < 0 )
{
perror("recv err");
return -1;
}
else if(ret == 0)
{
printf("client exit\n");
break;
}
else
{
printf("buf:%s\n",buf);
}
}
exit(-1);
}
close(acceptfd);
}
close(sockfd);
return 0;
}
多线程模型
每来一个客户端连接,开一个子线程来专门处理客户端的数据,实现简单,占用资源较少,属于使用比较广泛的模型:
伪代码:
socket();
bind();
listen()
while(1)
{
accept();
pthread_create();
}
多线程实现并发服务器:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define N 128
void * mythread(void * arg)
{
int acceptfd = *(int *)arg;
char buf[N]={};
int ret;
while(1)
{
ret = recv(acceptfd,buf,N,0);
if(ret < 0)
{
perror("recv err:");
break;
}
else if(ret == 0)
{
printf("client exit\n");
close(acceptfd);
break;
}
else
{
printf("%d said:%s\n",acceptfd,buf);
}
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("please input %s port\n", argv[0]);
return -1;
}
// 1.创建套接字 协议族 类型 协议
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err:");
return -1;
}
// 2.填充结构体(ipv4)
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET; // 协议族ipv4
saddr.sin_port = htons(atoi(argv[1])); // 端口号(网络字节序)
// saddr.sin_addr.s_addr = inet_addr(argv[1]); // ip地址(网络字节序)
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
socklen_t len = sizeof(caddr);
// 3.绑定
int ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
if (ret < 0)
{
perror("bind err:");
return -1;
}
// 4.监听
if (listen(sockfd, 5) < 0)
{
perror("listen err");
return -1;
}
while (1)
{
// 5.等待连接
// int acceptfd = accept(sockfd, NULL, NULL);
int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("accpet err:");
return -1;
}
printf("acceptfd = %d\n", acceptfd);
printf("client ip :%s,port:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
pthread_t tid;
pthread_create(&tid,NULL,mythread,&acceptfd);
pthread_detach(tid);
}
close(sockfd);
return 0;
}
IO多路复用模型
借助select、poll、epoll机制,将新连接的客户端描述符增加到描述符表中,只需要一个线程即可处理所有的客户端连接,在嵌入式开发中应用广泛,不过代码写起来稍显繁琐。