先看我的测试结果:
最最low的服务器:
功能:监听新用户
监听注册描述符的EPOLLIN和EPOLLOUT。
触发EPOLLIN的话,打印缓冲区
触发EPOLLOUT,发送固定字符串
代码:
/*************************************************************************
> File Name: 1.cpp
> Author: 朱紫钰
> Mail: zhuziyu1157817544@gmail.com
> Created Time: 2017年08月01日 星期二 12时54分24秒
************************************************************************/
#include<iostream>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/epoll.h>
#include<errno.h>
#include<stdio.h>
#include"server_epoll.h"
#define server_port 20000
using namespace std;
int main(int argc,char *argv[])
{
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(server_port);
address.sin_addr.s_addr = htonl(INADDR_ANY);
epoll_class epoll_object(5);
//int epollfd = epoll_create(5);
//struct epoll_event events[65535];
int listenfd = socket(PF_INET,SOCK_STREAM,0);
bind(listenfd,(struct sockaddr*)&address,sizeof(address));
listen(listenfd,5);
epoll_object.server_addfd(listenfd);
while(1){
int ret = epoll_object.server_epoll_wait();
if((ret < 0) && (errno != EINTR)){
perror("errno : ");
break;
}
cout << "ret = " << ret << endl;
for(int i = 0;i < ret;i++){
int sockfd = epoll_object.events[i].data.fd;
if( listenfd == sockfd){
cout << "有新客户连接了!" << endl;
struct sockaddr_in client_address;
socklen_t client_length = sizeof(client_address);
int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client_length);
cout << "所创建的客户套接字是: "<< connfd << endl;
if(connfd < 0) {
perror("errno : ");
continue;
}
epoll_object.server_addfd(connfd);
continue;
}
else if(epoll_object.events[i].events & EPOLLIN){
char buf[4096];
memset(buf,0,4096);
int ret2 = recv(sockfd,buf,4095,0);
if(ret2 == 0){
close(sockfd);
printf("套接字为%d的客户端要关闭了!\n",sockfd);
epoll_object.server_delfd(sockfd);
continue;
}
cout << "buf :" << buf << endl;
cout << "以上就是来自套接字" << sockfd << "发来的信息,处理信息中..." << endl;
epoll_object.server_modfd(sockfd,EPOLLOUT);
cout << "可以向描述符是"<< sockfd << "的客户端发送数据"<< endl;
}
else if(epoll_object.events[i].events & EPOLLOUT){
cout << "已经触发写事件,正在写入请稍候..." << endl;
char buf[4096];
char buf2[65535];
char readbuf[4096];
int filefd = open("../servermanage/index.html",O_RDONLY);
read(filefd,readbuf,4096);
strcpy(buf2,readbuf);
memset(buf,0,4096);
strcat(buf,"HTTP/1.1 200 OK\r\n");
strcat(buf,"Host: 127.0.0.1\r\n");
strcat(buf,"Connection: keep-alive\r\n\n");
//strcat(buf,buf2);
send(sockfd,buf,strlen(buf),0);
cout << "写入成功!\n" << endl;
cout << endl << endl;
epoll_object.server_modfd(sockfd,EPOLLIN);
}
}
}
}
再看最low的客户端:
功能:
主线程和服务器三次握手建立连接,主线程和子线程都往相同的connfd中send和recv,且是阻塞型。
代码:
/*************************************************************************
> File Name: 1.c
> Author: 朱紫钰
> Mail: zhuziyu1157817544@gmail.com
> Created Time: 2017年08月03日 星期四 15时22分28秒
************************************************************************/
#include<stdio.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/types.h>
#include<sys/socket.h>
void *pthread_work(void* sockfd)
{
int connfd = *(int*)sockfd;
while(1){
send(connfd,"这是来自客户端子线程请求",64,0);
sleep(1);
char buf[1024] = {0};
recv(connfd,buf,1024,0);
printf("这是客户端%d的子线程接收到的请求!\n",connfd);
printf("%s\n\n",buf);
sleep(4);
}
}
int main()
{
struct sockaddr_in client_address;
client_address.sin_family = AF_INET;
client_address.sin_port = htons(20000);
inet_aton("127.0.0.1",&client_address.sin_addr);
int connfd = socket(AF_INET,SOCK_STREAM,0);
connect(connfd,(struct sockaddr*)&client_address,sizeof(client_address));
pthread_t pthread;
pthread_create(&pthread,NULL,(void*)pthread_work,(void*)&connfd);
while(1){
send(connfd,"这是来自客户端的消息\n",32,0);
sleep(1);
char buf[1024] = {0};
recv(connfd,buf,1024,0);
printf("这是客户端%d受到的来自服务器的消息:",connfd);
printf("%s\n\n",buf);
sleep(4);
}
return 0;
}
运行结果和分析:
服务器结果:
可以看到,子线程和主线程共用sockfd = 5.这是因为我的客户端设置主线程子线程共用sockfd = 5。
客户端结果:
以上是客户端接收到的结果。
这只是我用自己写的客户端测试了我自己写的low服务器,根据结果,发现一旦建立客户端和服务器建立了链接,除非客户端自己关闭,否则,服务器不会主动从epoll去除这个套接字描述符。如果客户端不主动关闭,那么这个通信的道路仍然是完好可通信的!
那么,如果真正应用过程中,客户端变成高达上的浏览器会怎么样?
我们知道,谷歌一个标签开一个进程,以下是我让浏览器去请求我写的low服务器的运行结果。谷歌浏览器要根据服务器返回的index.html去请求后续资源。资源有:index.html , in.php , zhuziyu.html , 还有一系列图片。
原服务器代码中注释了 :
//strcat(buf,buf2);是为了给我写的客户端发送更为简单的内容进行测试。现在我把这个注释拿掉.
index.html代码:
<html>
<head>
<meta http-equiv = "Content-Type" content = "text/html ; charset = utf-8">
<style>
.block{
text-align:center;
}
.size{
font-size:24px;
font-color:##660099;
}
body{
background: url("http://127.0.0.1:20000/home/zhuziyu/server/servermanage/朝阳.jpg")
no-repeat center bottom; background-size: cover; background-attachment:fixed;
}
</style>
</head>
<body class = "block">
<form action="in.php" method = "post">
Name:<br>
<input type="text" name="name" >
<br>
Password:<br>
<input type="password" name="password">
<br><br>
<input type="submit" value="Submit">
</form>
<p class = "size"><strong>If you submit right,you will get the secret!<strong></p>
</body>
</html>
in.php代码:
<?php
$name = $_POST["name"];
$password = $_POST["password"];
?>
<?php
if($password == "123456"){
header("Location: http://127.0.0.1/home/zhuziyu/server/servermanege/zhuziyu.html");
}
else{
header("Location: http://127.0.0.1/home/zhuziyu/server/servermanege");
}
?>
zhuziyu.html代码:
<html>
<head>
<meta http-equiv = "Content-Type" content = "text/html ; charset = utf-8">
<title> secret </title>
<style>
body{
background: url("http://127.0.0.1:20000/home/zhuziyu/server/servermanege/橙色.jpg")
no-repeat center bottom; background-size: cover; background-attachment:fixed;
}
.day{
text-align:center;
}
.pic1{
width:200px;
height:200px;
position: absolute;
top:100px;
left:450px;
background: url("http://127.0.0.1:20000/home/zhuziyu/server/servermanege/Butteri1.jpg");
}
.pic2{
width:200px;
height:200px;
position: absolute;
top:100px;
left:700px;
background: url("http://127.0.0.1:20000/home/zhuziyu/server/servermanege/butter.jpg");
}
.pic3{
width:200px;
height:200px;
position: absolute;
top:350px;
left:450px;
background: url("http://127.0.0.1:20000/home/zhuziyu/server/servermanege/i.jpg");
}
.pic4{
width:200px;
height:200px;
position: absolute;
top:350px;
left:700px;
background: url("http://127.0.0.1/home:20000/zhuziyu/server/servermanege/butteri2.jpg");
}
.text{
text-align:center;
}
</style>
</head>
<body>
<div class = "text" style= "FONT-SIZE: 26pt; FILTER: wave(add=0,lightstrength=50,strength=3,freq=2,phrase=10); WIDTH: 100%; COLOR: #CC3300; LINE-HEIGHT: 100%; FONT-FAMILY: 华文行楷" ><strong>Hope Butter and Laura are together forever!Everyday is made of happiness!</strong></div>
<div class = "pic1" ></div>
<div class = "pic2" ></div>
<div class = "pic3" ></div>
<div class = "pic4" ></div>
</body>
</html>
现在用浏览器充当客户端,我们来看结果:
结果1:
可以在终端下看到,连接的套接字描述符是5,请求的url是/,我默认服务器响应返回index.html。
结果2:
可以看到,相同的标签,因为原index.html包括了一个叫http://127.0.0.1:20000/home/zhuziyu/server/servermanage/橙色.jpg的资源,因此浏览器向服务器请求该资源。从图片终端下可以看到浏览器已经把请求该资源的http请求包发给了服务器。
但是
这个有关图片url的请求包竟然被当作了一个新的客户端处理,即套接字描述符是7。我测试过,如果有很多url请求包,每一个url都包含一个要申请的资源,那么服务器都会把它当作新的客户端处理,也就是说,当一个index.html包含了多个url需要向服务器请求资源时候,有多少个资源请求,则在服务器对应多少各用户套接字描述符,如果在大胆猜测一下,那就是这个标签开了多个线程,每一个线程负责请求一个资源。
而且
看第三张图片:
当我关闭这个标签时候,才会显示套接字5和7关闭了.也就是说,我打开一个标签,标签内部,大胆猜测一下昂,至少是线程级别,一个线程维护一个资源请求和响应的通路。因此,我认为一个线程会创建一个套接字,然后就有很多的套接字,不同的套接字来合作对应同一个标签界面的组合搭建。又或许,多个线程请求的资源通过通信的方式,被同一个线程组合起来,最终构成了我们看到的界面,里面有php , css , jpg , png 等等。
而我之前写的那个很low的客户端,表明主线程和子线程可以共用一个套接字,因此,我认为,是浏览器自己的选择,导致一个标签创建了很多套接字,我认为我的服务器中有关epoll的系统调用正确。
但是说了这么半天,我觉得我需要用命令去验证这个想法。
pstree -p 指定进程 :查看指定进程的线程
于是我运行我的服务器,还有谷歌浏览器(只打开了127.0.0.1:20000这一个标签页)。经验证,30646和30661中30661是标签:
30661下面是多线程的。
但是我没有办法验证验证,在这30661下的线程中,有多个线程共同合作完成同一个标签界面的组合。悲伤…
有一解答,感觉解了我一半的问题,但是线程之间的工作问题,还不清楚:
http://www.ha97.com/2908.html