服务器模型
–》运行服务器s,运行一个客户端c1,此时连接成功且通信没有问题,再运行一个客户端c2,发现显示connect OK(说明客户端和服务器进行了三次握手,但不代表服务器就一定能处理客户端的业务—》除非服务器执行了accept后拿到与c2通信的新的套接字),但是c2与服务器的业务对接不了,此时c1选择结束结束业务,此时s也跟着退出了。
实际上:服务器是可以处理多个客户端的业务,其次服务器也不应该因为客户端的退出它自己也退出
因此:服务器模型应该为循环服务器或者并发服务器。
循环服务器
TCP服务器端运行后等待客户端的连接请求。
TCP服务器接受一个客户端的连接后开始处理,完成了客户的所有请求后断开连接
注意:TCP循环服务器同一时刻只能响应一个客户端的请求以及业务的处理。
只有在当前客户的所有请求都完成后,服务器才能处理下一个客户的连接/服务请求。
如果某个客户端一直占用服务器资源,那么其它的客户端都不能被处理。
故:TCP服务器一般很少采用循环服务器模型。
TCP循环服务器设计框架:
socket();
bind();
listen();
while(1)
{
accept();
while(1)
{
server_com();
}
}
close();
并发服务器
概念:并发服务器同一时刻可以处理多个客户机的请求
设计思路:并发服务器是在循环服务器基础上优化过来的
如何优化的???
(1)每连接一个客户机,服务器立马创建子进程或者子线程来跟新的客户机通信
(accept之后的),服务器不会与客户端进行通信!!!
(2)IO多路复用技术
select 与epoll可连接最大客户端的数量:
实现并发服务器的方式:
- 多进程
- 多线程
- select函数
- poll和epoll(自己查询)
多进程实现并发服务器
最多可以创建1021个进程
思想:主进程专门用于连接多个客户端的请求,若有一个客户端连接进来,主进程就创建一个子进程,用该子进程来创建客户端的业务数据
在accept之后,创建子进程
server:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define BUF_SIZE 20
//tcpserver
int main()
{
//socket
int serverfd = socket(AF_INET, SOCK_STREAM,0);
//判断socket返回值
if(-1 == serverfd)
{
//返回值为-1,代表创建软通道出错,打印出错原因并返回
perror("socket error");
return -1;
}
//创建软通道成功
printf("socket ok------\r\n");
//bind
struct sockaddr_in stserver;
//使用struct sockaddr_in结构体接受参数2的参数并强转为struct sockaddr结构体
stserver.sin_family = AF_INET;
stserver.sin_port = htons(8888);
stserver.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = bind(serverfd, (struct sockaddr*)&stserver, sizeof(struct sockaddr));
//判断的返回值
if(-1 == ret)
{
//失败打印失败原因并返回
perror("bind error");
return -1;
}
//绑定服务端主机成功
printf("bind ok------\r\n");
//listen
ret = listen(serverfd, 5);
//返回值判断
if(-1 == ret)
{
//失败打印失败原因并返回
perror("listen error");
return -1;
}
//监听创建成功
printf("listen ok------\r\n");
//接受客户端的信息
struct sockaddr_in stclient;
//结构体大小
socklen_t len = sizeof(struct sockaddr);
//创建收发数据的缓冲区
char buf[BUF_SIZE] = {0};
while(1)
{
//清空接收数据的缓冲区
memset(buf, 0, BUF_SIZE);
//accept
int newfd = accept(serverfd, (struct sockaddr*)&stclient, &len);
//返回值为为客户端创建新的软通道
if(-1 == newfd)
{
//创建失败,进行下一次侦听接收
perror("accept error");
continue;
}
//连接成功并软通道创建成功
printf("accept ok-----newfd:%d\r\n", newfd);
//fork
pid_t pid = fork();
if(-1 == pid)
{
perror("fork error");
close(newfd);
continue;
}
if(0 == pid)
{
while(1)
{
memset(buf, 0, BUF_SIZE);
ret = recv(newfd, buf, BUF_SIZE, 0);
if(ret <= 0)
{
perror("recv error");
close(newfd);
exit(0);
}
printf("recv%d data:%s\r\n", newfd, buf);
//send
send(newfd, buf, BUF_SIZE, 0);
}
}
}
return 0;
}
client:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUF_SIZE 20
//功能:TCPclient
int main()
{
//socket
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == clientfd)
{
perror("socket error");
return -1;
}
//创建软通道成功
printf("socket ok----\r\n");
//connect
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8888);
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in));
//判断connect的返回值
if(-1 == ret)
{
//失败打印失败原因并返回
perror("connect error----\r\n");
return -1;
}
//连接服务端成功
printf("connect ok....\r\n");
//创建数据缓冲区
char buf[BUF_SIZE] = {0};
while(1)
{
memset(buf, 0, BUF_SIZE );
printf("please write:\r\n");
//从标准输入端口输入数据,存储在buf里
fgets(buf, BUF_SIZE, stdin);
//send为发送数据
ret = send(clientfd, buf, BUF_SIZE, 0);
//返回值判断
if(-1 == ret)
{
//失败返回原因,不做返回,接受服务端的数据
perror("send error");
}
//清空接收数据的缓冲区
memset(buf, 0, BUF_SIZE );
//recv接收数据
ret = recv(clientfd, buf, BUF_SIZE, 0);
//返回值判断
if(-1 == ret)
{
//失败返回原因并返回
perror("recv error");
return -1;
}
//成功打印接受的数据
printf("recv data : %s\r\n", buf);
}
//关闭软通道描述符
close(clientfd);
return 0;
}
或注意:
子进程不管被杀死或者自己主动选择exit(0)退出,此时子进程的退出资源必须被回收!
如何回收?
思想:让父进程监测子进程的退出状态(信号:SIGCHLD)—》信号注册signal(信号名称,信号处理函数)
代码呈现:
//1、先注册信号
signal(SIGCHLD, handler);
//2、实现信号处理函数handler
void handler(int signum)
{
//使用waitpid来回收子进程的退出资源
while(waitpid(-1, NULL, WNOHANG) > 0)
{
printf("已成功回收子进程的退出资源!\n");
}
}
关于子进程资源一定记得被回收的方法:
思考:
多进程并发服务器的缺点:每连接一个客户端,就为其创建子进程,客户端数量比较大时,服务器的运行效率就会变低。
多线程实现并发服务器
思想:主进程专门用于连接多个客户端的请求,若有一个客户端连接进来,主进程就创建一个子线程,用该子线程来创建客户端的业务数据
在accept创建子线程
server
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define BUF_SIZE 20
//tcpserver
void* ThreadFunc(void * arg)
{
int iClient = *(int *)arg;
char buf[BUF_SIZE] = {0};
int ret = -1;
while(1)
{
ret = recv(iClient, buf, BUF_SIZE, 0);
if(ret <= 0)
{
perror("recv error");
close(iClient);
pthread_exit("thread exit");
}
printf("recv data:%s\r\n", buf);
send(iClient, buf, BUF_SIZE, 0);
}
}
int main()
{
//socket
int serverfd = socket(AF_INET, SOCK_STREAM,0);
//判断socket返回值
if(-1 == serverfd)
{
//返回值为-1,代表创建软通道出错,打印出错原因并返回
perror("socket error");
return -1;
}
//创建软通道成功
printf("socket ok------\r\n");
//bind
struct sockaddr_in stserver;
//使用struct sockaddr_in结构体接受参数2的参数并强转为struct sockaddr结构体
stserver.sin_family = AF_INET;
stserver.sin_port = htons(8888);
stserver.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = bind(serverfd, (struct sockaddr*)&stserver, sizeof(struct sockaddr));
//判断的返回值
if(-1 == ret)
{
//失败打印失败原因并返回
perror("bind error");
return -1;
}
//绑定服务端主机成功
printf("bind ok------\r\n");
//listen
ret = listen(serverfd, 5);
//返回值判断
if(-1 == ret)
{
//失败打印失败原因并返回
perror("listen error");
return -1;
}
//监听创建成功
printf("listen ok------\r\n");
//接受客户端的信息
struct sockaddr_in stclient;
//结构体大小
socklen_t len = sizeof(struct sockaddr);
//创建收发数据的缓冲区
char buf[BUF_SIZE] = {0};
while(1)
{
//清空接收数据的缓冲区
memset(buf, 0, BUF_SIZE);
//accept
int newfd = accept(serverfd, (struct sockaddr*)&stclient, &len);
//返回值为为客户端创建新的软通道
if(-1 == newfd)
{
//创建失败,进行下一次侦听接收
perror("accept error");
continue;
}
//连接成功并软通道创建成功
printf("accept ok-----newfd:%d\r\n", newfd);
//thread
pthread_t tID = -1;
if(0 != pthread_create(&tID, NULL, ThreadFunc, &newfd))
{
perror("create thread error");
close(newfd);
continue;
}
printf("create thread ok newfd:%d\r\n", newfd);
}
return 0;
}
client:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUF_SIZE 20
//功能:TCPclient
int main()
{
//socket
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
//判断socket返回值
if(-1 == clientfd)
{
//返回值为-1,代表创建软通道出错,打印出错原因并返回
perror("socket error");
return -1;
}
//创建软通道成功
printf("socket ok----\r\n");
//connect
struct sockaddr_in serveraddr;
//使用struct sockaddr_in结构体接受参数2的参数并强转为struct sockaddr结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8888);
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in));
//判断connect的返回值
if(-1 == ret)
{
//失败打印失败原因并返回
perror("connect error----\r\n");
return -1;
}
//连接服务端成功
printf("connect ok....\r\n");
//创建数据缓冲区
char buf[BUF_SIZE] = {0};
while(1)
{
memset(buf, 0, BUF_SIZE );
printf("please write:\r\n");
//从标准输入端口输入数据,存储在buf里
fgets(buf, BUF_SIZE, stdin);
//send为发送数据
ret = send(clientfd, buf, BUF_SIZE, 0);
//返回值判断
if(-1 == ret)
{
//失败返回原因,不做返回,接受服务端的数据
perror("send error");
}
//清空接收数据的缓冲区
memset(buf, 0, BUF_SIZE );
//recv接收数据
ret = recv(clientfd, buf, BUF_SIZE, 0);
//返回值判断
if(-1 == ret)
{
//失败返回原因并返回
perror("recv error");
return -1;
}
//成功打印接受的数据
printf("recv data : %s\r\n", buf);
}
//关闭软通道描述符
close(clientfd);
return 0;
}
思考:
多线程并发服务器的缺点:主线程退出了,子线程也会跟着退出。
select函数实现并发服务器
IO多路复用实现并发
思想分三步:
- 构建一张文件描述符集合表,表的大小1024bit,每一个bit位表示一个文件描述符所对应的IO通道是否有数据,若有数据,该bit位为1,若没有数据,该bit位为0.
- 使用**select()**函数监控指定范围的文件描述符对应的IO通道,是否有数据发生;如果有数据,返回有几路通道有数据同时更新相应bit位置为1,其他bit位置为0;否则,一直阻塞等待.
- 对文件描述符集合表的置位结果做出判断和响应。使用宏函数FD_ISSET(fd, &stFdr);返回值如果是true,则要响应对应文件描述符的IO操作.如果没有被置位,则不处理。
访问顺序是:3435
server端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#define BUF_SIZE 20
//tcp multiplexing
int main()
{
//socket
int serverfd = socket(AF_INET, SOCK_STREAM,0);
//判断socket返回值
if(-1 == serverfd)
{
perror("socket error");
return -1;
}
//创建软通道成功
printf("socket ok------\r\n");
//bind
struct sockaddr_in stserver;
stserver.sin_family = AF_INET;
stserver.sin_port = htons(8888);
stserver.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = bind(serverfd, (struct sockaddr*)&stserver, sizeof(struct sockaddr));
//判断的返回值
if(-1 == ret)
{
//失败打印失败原因并返回
perror("bind error");
return -1;
}
//绑定服务端主机成功
printf("bind ok------\r\n");
//listen
ret = listen(serverfd, 5);
//返回值判断
if(-1 == ret)
{
//失败打印失败原因并返回
perror("listen error");
return -1;
}
//监听创建成功
printf("listen ok------\r\n");
//接受客户端的信息
struct sockaddr_in stclient;
//结构体大小
socklen_t len = sizeof(struct sockaddr);
//创建收发数据的缓冲区
char buf[BUF_SIZE] = {0};
//文件描述集合表
fd_set stFdr;
//清零
FD_ZERO(&stFdr);
FD_SET(serverfd, &stFdr);
//用来存储最大描述符
int max = serverfd;
while(1)
{
//select
//临时文件描述符表
fd_set stFdrTmp = stFdr;
//监控所有文件描述符
ret = select(max+1, &stFdrTmp, NULL, NULL, NULL);
if(ret < 0)
{
perror("select error");
continue;
}
printf("select ok ret=%d\r\n", ret);
//FD_ISSET
int i = 0;
for(; i < max + 1; i++)
{
if(FD_ISSET(i, &stFdrTmp))
{
if(i == serverfd)
{
int newfd = accept(serverfd, (struct sockaddr*)&stclient, &len);
if(-1 == newfd)
{
perror("accept error");
continue;
}
printf("accept ok newfd:%d\r\n", newfd);
//将新的描述符,加入到原始文件描述符集合中
FD_SET(newfd, &stFdr);
//更新max
if(max < newfd) max = newfd;
}
else
{
ret = recv(i, buf, BUF_SIZE ,0);
if(ret > 0)
{
printf("recv data;%s\r\n", buf);
send(i, buf, BUF_SIZE ,0);
}
else
{
close(i);
FD_CLR(i, &stFdr);
}
}
}
}
}
return 0;
}
client端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUF_SIZE 20
//功能:TCPclient
int main()
{
//socket
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
//判断socket返回值
if(-1 == clientfd)
{
//返回值为-1,代表创建软通道出错,打印出错原因并返回
perror("socket error");
return -1;
}
//创建软通道成功
printf("socket ok----\r\n");
//connect
struct sockaddr_in serveraddr;
//使用struct sockaddr_in结构体接受参数2的参数并强转为struct sockaddr结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8888);
serveraddr.sin_addr.s_addr = inet_addr("192.168.15.71");
int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in));
//判断connect的返回值
if(-1 == ret)
{
//失败打印失败原因并返回
perror("connect error----\r\n");
return -1;
}
//连接服务端成功
printf("connect ok....\r\n");
//创建数据缓冲区
char buf[BUF_SIZE] = {0};
while(1)
{
printf("please write:\r\n");
//从标准输入端口输入数据,存储在buf里
fgets(buf, BUF_SIZE, stdin);
//send为发送数据
ret = send(clientfd, buf, BUF_SIZE, 0);
//返回值判断
if(-1 == ret)
{
//失败返回原因,不做返回,接受服务端的数据
perror("send error");
}
//清空接收数据的缓冲区
memset(buf, 0, BUF_SIZE );
//recv接收数据
ret = recv(clientfd, buf, BUF_SIZE, 0);
//返回值判断
if(-1 == ret)
{
//失败返回原因并返回
perror("recv error");
return -1;
}
//成功打印接受的数据
printf("recv data : %s\r\n", buf);
}
//关闭软通道描述符
close(clientfd);
return 0;
}