欢迎光临
我们一直在努力

应用层协议 | HTTP协议 | TCP并发服务器

一、HTTP协议

 1.       超文本传输协议

                超文本:图片、视频、文字存放在一个文件中(html文件,网络存储,存储在远端服务器上)

        url:统一资源定位符

<协议> : <主机> : <端口> / <资源路径>

                <主机>:主机的IP地址,若需在互联网通信,则须为公有IP地址,不能为私有IP地址

                <端口>:默认为80

        HTTP协议传输层基于TCP

2.HTTP传输流程

        一次HTTP通信: 

客户端 服务端
监听TCP连接
发起TCP连接
发送HTTP请求报文
回复HTTP请求报文
close

close

3.HTTP请求报文

        方法:GET方法;POST方法;PUT方法

        URL:资源路径

        版本号:http/1.1

        \\r\\n:区分信息格式

二、TCP并发服务器

1.TCP并发服务器问题

        服务端需要连接多个客户端时,服务器需要使用accept等待三次握手连接,同时还需要recv等待接收所有客户端发送的数据,由于accept和recv都是阻塞IO,所以会导致程序逻辑无法编写

2.解决方法

        (1)TCP并发服务器线程/进程模型

                优:实现简单;

                缺:资源消耗大,每个TCP连接都会分配一个线程任务

        (2)TCP并发服务器多路复用模型

3.Linux系统的4种IO模型

(1)阻塞IO:

        效率高,数据没来时,CPU阻塞等待,不占用CPU资源

(2)非阻塞IO

        效率低,数据没来时不阻塞等待,向后执行,但需要轮询(CPU过一会来问下数据有没有来)是否有IO事件发生

(3)异步信号驱动IO

 

        当内核监测到有IO事件发送时,主动向应用层上报信号事件

        只能绑定一个文件描述符

(4)多路复用IO

        用一个函数接口监听多个文件描述符是否产生IO事件,只要其中一个产生事件,则不再阻塞,用户可以处理对应的事件

        函数接口:select   poll  epoll

4.函数接口

(1)fcntl

int fcntl(int fd, int cmd, …/*arg*/)

功能:向文件描述符发送cmd命令

参数:

fd:文件描述符

cmd:

        F_GETFL 获得属性

        F_SETFL 设置属性

返回值:

         成功返回0;

        失败返回-1;

flags = fcntl(fd, F_GETFL);

flags |= O_NONBLOCK;

fcntl(fd, F_SETFL, flags); //非阻塞

flags = fcntl(fd, F_GETFL);

flags |= O_ASYNC;

//设置fd为异步IO(当有事件发生时,内核会发送SIGIO信号通知应用层

fcntl(fd, F_SETFL, flags);

//设置fd对应的异步信号由当前进程接收

fcntl(fd, _F_SETOWN, getpid());

图示:

(2)select

        头文件:#include<sys/select.h>

int select(int nfds, fd_set *readfds, fd_set * writefds, fd_set *exceptfds, struct timeval *timeout);

功能:监听文件描述符集合中是否有事件发生

参数:

        nfds:最大的文件描述符 + 1;

        readfds:读文件描述符集合;

        writefds:写文件描述符集合;

        execptfds:其余文件描述符集合;

        timeout:超时时间(传NULL表示一直阻塞)

返回值:

        成功返回产生事件的文件描述符的个数;

        失败返回-1

        如果超时仍然没有事件发生返回0

void FD_CLR(int fd, fd_set *set);

将文件描述符fd从文件描述符集合中删除

void FD_ISSET(int fd, fd_set *set);

判断fd是否仍然在文件描述符集合中

void FD_SET(int fd, fd_set *set);

将fd加入文件描述符集合中

void FD_ZERO(int fd, fd_set *set);

将文件描述符集合清0

(3)poll

int poll(struct pollfd *fds, nfs_t nfds, int timeout);

功能:监听事件集合中的事件是否发生

参数:

fds:监听事件集合的数组首地址

nfds:事件数组的元素个数

timeout:超时时间

        -1:一直阻塞直到事件发生

返回值:

        成功返回产生事件的个数

        失败返回-1

        超时还没有事件发生返回0

                POLLIN:是否有事件可读;

                POLLOUT:是否有事件可写;

//将文件描述符放到数组中

fds[0].fd = fd;

fds[0].events = POLLIN;

//poll去监听(一次性监听2个)

nready = poll(fds, 2, -1);

//判断revents中是否有事件发生(fd中是否有事件可读)

if(fds[0].revents & POLLIN)

(4)epoll

int epoll_create(int size)

功能:创建一张内核监听的事件表

参数:size:时间表事件的个数

返回值:

        成功返回事件的文件描述符

        失败返回-1

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

功能:向内核事件表中添加、修改、删除事件

参数:

        epfd:内核事件表的文件描述符

        op:

               EPOLL_CTL_ADD 添加

               EPOLL_CTL_MOD 修改

               EPOLL_CTL_DEL 删除

        fd:操作的文件描述符

        event:事件集合

返回值:

        成功返回0

        失败返回-1

int epoll_wait(int epfd, struct epoll_events, int maxwvents, int timeout);

功能:监听事件表中的事件

参数:

        epfd:文件描述符

        events:存放产生事件的空间首地址

        maxevents:数组的大小

        timeout:超时时间

返回值:

        成功返回产生事件的文件描述符个数

        失败返回-1

        超时返回0

三、多路复用服务器

select版

四、多路复用IO三个函数接口的区别

1.select

   有4个缺点:

        select监听的文件描述符集合是一个数组,是有上限的(一般为1024)

        select监听的文件描述符集合在应用层,当事件发生时需要产生一次内核层向应用层数据的拷贝,会产生资源开销

        select只能工作在水平触发模式(只要有事件发生就通知处理)(低速)

//边沿触发:一个事件只通知一次(高速模式)

        select必须将所有在文件描述符集合中的元素找一遍才能找到产生事件的文件描述符

2.poll

        监听的事件集合在链表中,没有上限的限制

        其余与select特点相同(3个缺点)

3.epoll

        没有文件描述符上限的限制

        epoll创建一张内核监听事件表,所以事件表在内核中,无需与应用层交互,效率高

        epoll可工作在水平触发(默认)和边沿触发模式

        epoll可将产生事件的所有文件描述符返回,而不需要手动检测所有文件描述符是否有事件发生

示例:使用epoll实现TCP并发服务器

client.c:

#include"head.h"

int main(void)
{
int sockfd = 0;
struct sockaddr_in recvaddr;
int ret = 0;
ssize_t nret = 0;
char tmpbuff[4096] = {0};
int cnt = 0;

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("fail to socket");
return -1;
}

recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(50000);
recvaddr.sin_addr.s_addr = inet_addr("192.168.0.125");

ret = connect(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if(-1 == ret)
{
perror("fail to connect");
return -1;
}

while(1)
{
sprintf(tmpbuff, "hello——–%d", cnt);
cnt++;
nret = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
if(-1 == nret)
{
perror("fail to nret");
return -1;
}

memset(tmpbuff, 0, sizeof(tmpbuff));
nret = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if(-1 == nret)
{
perror("fail to recv");
return -1;
}
else if(0 == nret)
{
printf("关闭连接\\n");
break;
}
printf("recv:%s\\n", tmpbuff);
}

close(sockfd);

return 0;
}

server.c:

#include"head.h"

int main(void)
{
int sockfd = 0;
struct sockaddr_in recvaddr;
int ret = 0;
ssize_t nret = 0;
int confd = 0;
int i = 0;
char tmpbuff[4096] = {0};
struct epoll_event env;
struct epoll_event retenv[10240];
int epfd = 0;

epfd = epoll_create(1024);
if(-1 == epfd)
{
perror("fail to epoll_create");
return -1;
}

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("fail to socket");
return -1;
}

recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(50000);
recvaddr.sin_addr.s_addr = inet_addr("192.168.0.125");
ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if(-1 == ret)
{
perror("fail to bind");
return -1;
}

ret = listen(sockfd, 10);
if(-1 == ret)
{
perror("fail to listen");
return -1;
}

env.events = EPOLLIN;
env.data.fd = sockfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &env);
if(-1 == ret)
{
perror("fail to epoll_ctl");
return -1;
}

while(1)
{
ret = epoll_wait(epfd, retenv, 1024, -1);
if(-1 == ret)
{
perror("fail to epoll_wait");
return -1;
}

for(i = 0; i < ret; i++)
{
if(retenv[i].data.fd == sockfd)
{
confd = accept(sockfd,NULL, NULL);
if(-1 == confd)
{
perror("fail to accept");
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
close(sockfd);
continue;
}

env.events = EPOLLIN;
env.data.fd = confd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, confd, &env);
if(-1 == ret)
{
perror("fail to epoll_ctl");
return -1;
}
}
else
{
memset(tmpbuff, 0, sizeof(tmpbuff));
nret = recv(retenv[i].data.fd, tmpbuff, sizeof(tmpbuff), 0);
if(-1 == nret)
{
perror("fail to recv");
epoll_ctl(epfd, EPOLL_CTL_DEL, retenv[i].data.fd, NULL);
close(retenv[i].data.fd);
continue;
}
else if(0 == nret)
{
printf("关闭连接\\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, retenv[i].data.fd, NULL);
close(retenv[i].data.fd);
continue;
}

printf("RECV:%s\\n", tmpbuff);

sprintf(tmpbuff, "%s——echo", tmpbuff);
nret = send(retenv[i].data.fd, tmpbuff, strlen(tmpbuff), 0);
if(-1 == nret)
{
perror("fail to send");
epoll_ctl(epfd, EPOLL_CTL_DEL, retenv[i].data.fd, NULL);
close(retenv[i].data.fd);
continue;
}
}
}

}
close(sockfd);

return 0;
}

赞(0)
未经允许不得转载:171主机测评 » 应用层协议 | HTTP协议 | TCP并发服务器
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址