并发服务器概念:
并发服务器同一时刻可以处理多个客户机的请求
设计思路:
并发服务器是在循环服务器基础上优化过来的
(1)每连接一个客户机,服务器立马创建子进程或者子线程来跟新的客户机通信 (accept之后的),服务器不会与客户端进行通信!!!
(2)IO多路复用技术
1、多进程实现并发服务器
思想:
主进程专门用于连接多个客户端的请求,若有一路客户端连接进来,主进程就创建一个子进程,用该子进程来处理该客户端的业务数据。
回顾:创建进程
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:创建一个子进程
参数:无
返回值:pid_t就是int类型的别名
返回值大于0,代表此时是父进程,该值的含义为创建成功的子进程的ID号
返回值等于0,代表此时是子进程
返回值小于0,创建失败可以perror
源代码:
tcp_server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>//sockaddr_in
#include <unistd.h>
#include <arpa/inet.h> // 包含 inet_addr 函数的声明
#include <sys/select.h>
#include <sys/time.h>
#define BUF_SIZE 20
int main(int argc, const char *argv[])
{
//1.socket
int iServer = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == iServer){
puts("----------1、create socket error!");
return -1;
}
printf("----------1、create socket ok! iServer:%d", iServer);//0,1,2标准输入输出出错,iServer为3
//2.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");//将点分十进制ip地址转换为32位无符号整数
int ret = bind(iServer, (struct sockaddr *)&stServer, sizeof(struct sockaddr));
if(-1 == ret){
puts("----------2、bind error!");
return -1;
}
puts("----------2、bind ok!");
//3.listen
ret = listen(iServer, 5);
if(-1 == ret){
puts("----------3、listen error!");
return -1;
}
puts("----------3、listen ok!");
//4.accept
struct sockaddr_in stClient;//存放对方的主机信息
socklen_t len = sizeof(struct sockaddr_in);
char buf[BUF_SIZE] = {0};
fd_set stFdr;//文件描述符集合表,大小1024
FD_ZERO(&stFdr);//将文件描述符集合表中所有内容清零
while(1){
FD_SET(iServer, &stFdr);
FD_SET(0, &stFdr);
//select
ret = select(iServer + 1, &stFdr, NULL, NULL, NULL);
if(ret <= 0){
continue;
}
printf("select ok, ret = %d\r\n", ret);
//FD_ISSET
if(FD_ISSET(0, &stFdr)){
memset(buf, 0, BUF_SIZE);
fgets(buf, BUF_SIZE, stdin);
printf("fgets ok, data = %s\r\n", buf);
}
if(FD_ISSET(iServer, &stFdr)){
int iClient = accept(iServer, (struct sockaddr *)&stClient, &len);
if(-1 == iClient){
continue;//当前客户端出错转向下一个客户端
}
printf("----------4、accept ok! iClient = %d\r\n",iClient );//标准输入输出出错,所以下一个打开的文件一定是3
//5.recv/send
ret = recv(iClient, buf, BUF_SIZE, 0);
if(ret <= 0){
close(iClient);
continue;
}
printf("----------recv data ok! buf = %s\r\n",buf);
//send
ret = send(iClient, buf, BUF_SIZE, 0);
if(ret <= 0){
close(iClient);
continue;
}
printf("----------send data ok! %s\r\n",buf);
//close(iClient);//断开当前客户端
}
}
return 0;
}
tcp_client.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define BUF_SIZE 20
//main函数参数,如果需要键入IP则给定即可
int main(int argc, const char *argv[])
{
//1、socket
int iClient = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == iClient){
puts("----------1、create socket error!");
return -1;
}
puts("----------1、create socket ok!");
//2、connect
struct sockaddr_in stServer;
stServer.sin_family = AF_INET;
stServer.sin_port = htons(8888);
//stServer.sin_addr.s_addr = inet_addr("192.168.15.71");
stServer.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(iClient, (struct sockaddr *)&stServer, sizeof(struct sockaddr_in));
if(-1 == ret){
puts("----------2、connect error!");
return -1;
}
puts("----------2、connect ok!");
char buf[BUF_SIZE] = {0};
while(1){
//gets();
//char *fgets(char *s, int size, FILE *stream);
fgets(buf, BUF_SIZE, stdin);//更安全,边界检查
//3、send recv
ret = send(iClient, buf, BUF_SIZE, 0);
if(-1 == ret){
puts("----------3、send data error!");
}
printf("----------3、send data ok! buf = %s\r\n",buf);
//recv
//函数原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);
memset(buf, 0, BUF_SIZE);
ret = recv(iClient, buf, BUF_SIZE, 0);
if(-1 == ret){
puts("----------4、recv error!");
return -1;
}
printf("----------4、recv data ok! buf = %s\r\n",buf);
}
close(iClient);
return 0;
}
思考:
多进程并发服务器的缺点:每连接一个客户端,就为其创建子进程,客户端数量比较大时,服务器的运 行效率就会变低。
注:
以上代码只能实现:
①客户端连接到服务器端,只能发送一条数据,之后发送不成功
②服务器端可以检测标准输入给自己
测试结果如下图:
2、多进程实现并发服务器-优化版本
tcp_server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>//sockaddr_in
#include <unistd.h>
#include <arpa/inet.h> // 包含 inet_addr 函数的声明
#include <sys/select.h>
#include <sys/time.h>
#define BUF_SIZE 20
int main(int argc, const char *argv[])
{
//1.socket
int iServer = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == iServer){
puts("----------1、create socket error!");
return -1;
}
printf("----------1、create socket ok! iServer:%d", iServer);//0,1,2标准输入输出出错,iServer为3
//2.bind
struct sockaddr_in stServer;
stServer.sin_family = AF_INET;//第一个成员
stServer.sin_port = htons(9999);//第二个成员
stServer.sin_addr.s_addr = inet_addr("127.0.0.1");//将点分十进制ip地址转换为32位无符号整数
int ret = bind(iServer, (struct sockaddr *)&stServer, sizeof(struct sockaddr));
if(-1 == ret){
puts("----------2、bind error!");
return -1;
}
puts("----------2、bind ok!");
//3.listen
ret = listen(iServer, 5);
if(-1 == ret){
puts("----------3、listen error!");
return -1;
}
puts("----------3、listen ok!");
//4.accept
struct sockaddr_in stClient;//存放对方的主机信息
socklen_t len = sizeof(struct sockaddr_in);
char buf[BUF_SIZE] = {0};
fd_set stFdr;//文件描述符集合表,大小1024
FD_ZERO(&stFdr);//将文件描述符集合表中所有内容清零
FD_SET(iServer, &stFdr);//iServer添加到原文件描述符集合表中
int max = iServer;
while(1)
{
//select
fd_set stFdrTmp = stFdr; //定义临时文件描述符集合表
ret = select(max + 1, &stFdrTmp, NULL, NULL, NULL);
if(ret <= 0)
{
printf("select error!\r\n");
continue;
}
printf("select ok, ret = %d\r\n", ret);
int i = 0;
for(i = 0; i < max + 1; i++)
{
if(FD_ISSET(i, &stFdrTmp))
{ // 循环判断哪个文件描述符被置位
//操作
if(i == iServer)
{
// i == 3, 操作
int iClient = accept(iServer, (struct sockaddr *)&stClient, &len);
if(-1 == iClient)
{
continue;//当前客户端出错转向下一个客户端
}
printf("----------4、accept ok! iClient = %d\r\n",iClient );//标准输入输出出错,所以下一个打开的文件一定是4
FD_SET(iClient, &stFdr);
//更新max
if(max < iClient)
{
max = iClient;
}
}
else
{ // 与多个客户端保持连接
//recv/send
ret = recv(i, buf, BUF_SIZE, 0);
if(ret > 0)
{
printf("recv:%s\r\n", buf);
send(i, buf, BUF_SIZE, 0);
}
else
{
close(i);
FD_CLR(i, &stFdr);
}
}
}
}
}
return 0;
}
tcp_client.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define BUF_SIZE 20
//main函数参数,如果需要键入IP则给定即可
int main(int argc, const char *argv[])
{
//1、socket
int iClient = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == iClient){
puts("----------1、create socket error!");
return -1;
}
puts("----------1、create socket ok!");
//2、connect
struct sockaddr_in stServer;
stServer.sin_family = AF_INET;
stServer.sin_port = htons(9999);
//stServer.sin_addr.s_addr = inet_addr("192.168.15.71");
//stServer.sin_addr.s_addr = inet_addr("192.168.15.71");
stServer.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(iClient, (struct sockaddr *)&stServer, sizeof(struct sockaddr_in));
if(-1 == ret){
puts("----------2、connect error!");
return -1;
}
puts("----------2、connect ok!");
char buf[BUF_SIZE] = {0};
while(1){
//gets();
//char *fgets(char *s, int size, FILE *stream);
fgets(buf, BUF_SIZE, stdin);//更安全,边界检查
//3、send recv
ret = send(iClient, buf, BUF_SIZE, 0);
if(-1 == ret){
puts("----------3、send data error!");
}
printf("----------3、send data ok! buf = %s\r\n",buf);
//recv
//函数原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);
memset(buf, 0, BUF_SIZE);
ret = recv(iClient, buf, BUF_SIZE, 0);
if(-1 == ret){
puts("----------4、recv error!");
return -1;
}
printf("----------4、recv data ok! buf = %s\r\n",buf);
}
close(iClient);
return 0;
}
注:
以上代码可以实现:
①多个客户端与服务器连接 并 发送&回显数据
测试结果如下图: