同一个时刻可以响应多个客户端的请求,常用的模型有多进程模型/多线程模型/IO多路复用模型。
目录
多进程模型
每来一个客户端连接,开一个子进程来专门处理客户端的数据,实现简单,但是系统开销相对较大,更推荐使用线程模型。伪代码如下:
socket()
bind();
listen();
while(1)
{
accept();
if(fork() == 0) //子进程
{
while(1)
{
process();
}
close(client_fd);
exit();
}
else
{
}
}
多线程模型
每来一个客户端连接,开一个子线程来专门处理客户端的数据,实现简单,占用资源较少,属于使用比较广泛的模型,伪代码如下:
socket()
bind();
listen();
while(1)
{
accept();
pthread_create();
}
多线程模型示例代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <netinet/ip.h> /* superset of previous */
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#define N 64
void *func(void *arg)
{
char buf[N] = {0};
// int *p = (int *)arg;
// int clifd = *p;
int clifd = *((int *)arg);
while (1)
{
//接收客户端的消息,如果客户端退出的话,服务器也退出
//接收服务器的消息
bzero(buf, N);
int len = recv(clifd, buf, N, 0);
if(len < 0)
{
perror("recv err");
break;
}
else if(len == 0)
{
printf("client exit\n");
break;
}
else
{
printf("recv from %d client = %s\n", clifd, buf);
}
}
close(clifd);
}
int main(int argc, char const *argv[])
{
//创建套接字
int serverfd = socket(AF_INET, SOCK_STREAM, 0);
if(serverfd < 0)
{
perror("socket err");
return -1;
}
//绑定自己的地址
struct sockaddr_in myaddr;
socklen_t addrlen = sizeof(myaddr);
memset(&myaddr, 0, addrlen);
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(8888);
myaddr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(serverfd, (struct sockaddr *)&myaddr, addrlen);
if(ret < 0)
{
perror("bind err");
return -1;
}
//启动监听
ret = listen(serverfd, 5);
if(ret < 0)
{
perror("bind err");
return -1;
}
while (1)
{
//接收客户端的连接
//定义代表客户端的结构体变量
struct sockaddr_in cliaddr;
int clifd = accept(serverfd, (struct sockaddr *)&cliaddr, &addrlen);
if(clifd < 0)
{
perror("accept err");
return -1;
}
printf("新的连接过来了 = %d\n", clifd);
//创建一个线程,来处理新的客户端连接
pthread_t tid;
pthread_create(&tid, NULL, func, &clifd);
}
close(serverfd);
return 0;
}
IO多路复用模型
借助select、poll、epoll机制,将新连接的客户端描述符增加到描述符表中,只需要一个线程即可处理所有的客户端连接,在嵌入式开发中应用广泛,不过代码写起了稍显繁琐。伪代码如下:
socket()
bind();
listen();
while(1)
{
设置监听读写文件描述符集合(FD_*);
调用select;
如果是监听套接字就绪,说明有新的连接请求
{
建立连接(accept);
加入到监听文件描述符集合;
}
否则说明是一个已经连接过的描述符
{
进行操作(send或者recv);
}
}
IO多路复用模型示例代码:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <netinet/ip.h> /* superset of previous */
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/select.h>
#define N 64
int main(int argc, char const *argv[])
{
//创建套接字
int serverfd = socket(AF_INET, SOCK_STREAM, 0);
if(serverfd < 0)
{
perror("socket err");
return -1;
}
//绑定自己的地址
struct sockaddr_in myaddr;
socklen_t addrlen = sizeof(myaddr);
memset(&myaddr, 0, addrlen);
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(8888);
myaddr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(serverfd, (struct sockaddr *)&myaddr, addrlen);
if(ret < 0)
{
perror("bind err");
return -1;
}
//启动监听
ret = listen(serverfd, 5);
if(ret < 0)
{
perror("bind err");
return -1;
}
//构造文件描述符表
fd_set rdfds, tmpfds;
FD_ZERO(&rdfds); //清表
FD_SET(serverfd, &rdfds);
int maxfd = serverfd;
while (1)
{
//后续客户端连入或者退出,只让rdfds变化,然后select每次改变的是临时的一张表:tmpfds
tmpfds = rdfds;
select(maxfd + 1, &tmpfds, NULL, NULL, NULL);
//代码运行到这,代表表里某个设备有数据:服务器fd,客户端fd
for (size_t i = 0; i < maxfd + 1; i++)
{
//如果这个成立,代表当前表这个描述符有数据
if(FD_ISSET(i, &tmpfds))
{
//判断这个描述符是谁?服务器/客户端
//代表是服务器fd有了事件发生:客户端连入事件
if(i == serverfd)
{
//接收这个客户端的连接
int clifd = accept(serverfd, NULL, NULL);
printf("new client fd = %d\n", clifd);
//并把这个客户端的描述符加入到表中
FD_SET(clifd, &rdfds);
//更新maxfd
if(clifd > maxfd)
{
maxfd = clifd;
}
}
else //代表客户端有数据
{
char buf[N] = {0};
bzero(buf, N);
int len = recv(i, buf, N, 0);
if(len < 0)
{
perror("recv err");
break;
}
else if(len == 0)
{
//客户端退出,应该删掉表里的描述符
printf("client %d quit\n", i);
FD_CLR(i, &rdfds);
close(i);
break;
}
else
{
printf("recv from %d client = %s\n", i, buf);
}
}
}
}
}
close(serverfd);
return 0;
}