HTTP协议
HTTP(HyperText Transfer Protocol)超文本传输协议,用于解决万维网(www)Server/Client间统一通信的问题,面向事务(transaction-oriented,一系列信息的完整传输交换)应用层协议
URL与URI
URI(Uniform Resource Identifier), 区别不同网络事物的抽象概念,如sina.com.cn代表新浪网,qq号代表一个账户
URL(Uniform Resource Locator),统一资源定位符 = “协议://IP地址:端口/路径和文件名”,访问机制+网络位置,(大小写不敏感)
URN(Uniform Resource Name),来唯一标识一个实体,特定命名空间+名字,不需要知道地址或访问机制
网页访问
网页访问过程
PS:为加快响应用户的速度,音视频是点击页面上的播放按钮后再次建立连接并传输
从Client请求到得到文档计时:RTT*2+文档传输时间
HTTP特性
- 面向文本(text-oriented)
- 无连接
HTTP本身不建立或维持连接 - 无状态(stateless)
Server不记录Client的历史,每次HTTP请求都是新的,如此支持HTTP服务器应用的高并发 - 持续链接(persistent connection,HTTP/1.1后)
解决2*RTT时间+变量空间的浪费,Server响应后一段时间内保持TCP连接继续HTTP请求响应
非流水线式(without pipelining):Client等到Server的响应才请求,和HTTP1.0差别不大
流水线式(with pipelining):Clinet流水线请求+Server流水线响应,"真"提速
代理服务器
Proxy Server,又被称为“万维网高速缓存(Web cache)”,将请求和响应暂存于本地磁盘中,新请求与暂存请求相同时直接返回暂存响应,举例
Client优先访问Proxy Server,Proxy Server未缓存响应时,由Proxy Server 向 Origin Server 发起TCP链接及HTTP请求,收到请求对象后,先复制缓存在本地存储器中再通过请求时建立的TCP连接返回对象
HTTP报文
分为请求报文和响应报文,ASCII编码的字段组成
-
开始行
CR(Carriage Return)回车 ’ \r ’ 与 LF(Line Feed)换行 ‘\n’ 结尾。 换行与回车的历史起源
请求报文中: 请求行 (Request-Line)
GET http://IP:8080/home/mypage.html HTTP/1.1
方法(method)有:GET,PUT,OPTION,DELETE,TRACE,CONNECT,POST
响应报文中: 状态行 (Status-Line)
HTTP/1.1 202 Accepted
HTTP/1.1 404 Not Found
状态码(Status-Code)有:
1xx:通知信息
2xx:成功
3xx:重定向
4xx:请求错误(不能完成)
5xx:服务器差错无法完成请求 -
首部行
-
实体主体(entity body)
举例:
GET /home/mypage.html HTTP/1.1\r\n {URL使用相对路径}
Host: 域名\r\n {首部给出域名}
Connection: Close\r\n {传输结束请求文档即可释放连接}
Accept-Language: cn\r\n
HTTP/1.1 301 Moved Permanently {永久转移}
Location: http://IP:port/location/mypage.html {新的URL}
Cookie
Server与Client间传递的状态信息
HTML
HyperText Markup Language 超文本标记语言
<html>
<head>
<title>好诗</title>
</head>
<body>
<H1>一级标题</H1>
<H2>春江花月夜</H2>
<p>
春江潮水连海平,海上明月共潮生。<br>
滟滟随波千万里,何处春江无月明! <br>
江流宛转绕芳甸,月照花林皆似霰; <br>
空里流霜不觉飞,汀上白沙看不见。 <br>
江天一色无纤尘,皎皎空中孤月轮。 <br>
江畔何人初见月?江月何年初照人? <br>
人生代代无穷已,江月年年望相似。
</P>
<p>
My UNIX do not have Chiness charactor set
</P>
</body>
</html>
使用ASCII编码,浏览器直接打开查看效果
HTML允许在网页中插入图像(内含图像inline image),其中gif格式压缩后体积最小
远程连接:终点在其他网站
本地连接:终点在本计算机
静态文档(static document)
工人写完后不再改变,简单但不灵活
动态文档(dynamic document)
浏览器访问Server时程序动态创建
通用网关接口(CGI,Common Gateway Interface
是一种定义动态文档的创建,数据输入(给Server应用程序) 和 输出的标准
CGI程序 是 脚本程序(script language)
活动文档(active document)
- 服务器推送(server push):服务器运行程序更新文档,服务器 及 网络 开销随用户数量增大
- 活动文档(active document):服务器返回活动程序副本,在浏览器运行,开销小
TCP/IP
了解Socket套接字编程,主要
变量有:
sockaddr
socket(_fd)
函数有:
Client端的socket()、send()、recv()
Server端Listen()、accept()
#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
//-------------------
//V1.0 接收请求,显示报文
//-------------------
using namespace std;
int main(void)
{
int listener=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(listener==(int)(~0))
{
cout<<"Listenner Initiate Failure!"<<endl;
return 0;
}
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port=htons(8080); //http服务端口
ServerAddr.sin_addr.s_addr=INADDR_ANY;
if(bind(listener,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr))== -1)
{
cout<<"Binding Failure!"<<endl;
return 0;
}
if(listen(listener,5)==-1)
{
cout<<"Listening Start Failure!"<<endl;
return 0;
}
int Client_id;
struct sockaddr_in ClientAddr;
socklen_t ClientAddrLen = sizeof(ClientAddr);
while(1)
{
cout<<"Listening..."<<endl;
Client_id=accept(listener,(struct sockaddr*)&ClientAddr,&ClientAddrLen);
if(Client_id==(int)(~0))
{
cout<<"Accept Error!"<<endl;
continue;
}
cout<<"Accept message from Client :"<<endl;
char recvData[512]={};
recv(Client_id,recvData,512,0);
cout<<recvData<<endl;
const char* cmd=recvData;
close(Client_id);
if(!strcmp(cmd,"CloseServer"))
break;
}
close(listener);
return 0;
}
在Unix系统下运行以上源文件编写的HTTPServer,再使用其他主机上的浏览器通过URL访问任意Server上的资源,HTTPServer都会打印HTTP请求的内容
可以手写响应报文:
char sendData[512]={0};
strcpy(sendData,"HTTP/1.1 202 OK!\r\ncontent-type: text/html; charset=UTF-8\r\n\r\n<html><head><title>Try</title></head><body><H1>Title-C1</H1></body></html>");
send(Client_id,&sendData,512,0);
再使用其他主机上的浏览器通过URL访问任意Server上的资源,HTTPServer将返回响应报文
在浏览器中可以看到响应报文实体(html文档)在浏览器中显示的效果
按下F12使用控制台,刷新后重新访问URL,可查看响应报文的标头
文件存取
首先提取请求中的文件名,然后使用中的ifstream类open(请求的file),open()函数搜索可执行文件所在的目录下是否存在目标文件,成功打开后读取内容,并与HTTP的开始行、首部行,一并返回
//-----------------------------------
//V2.0 接受请求,分析URL,响应传输html文档
//-----------------------------------
#include <iostream>
#include <string.h>
#include <string>
#include <fstream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main(void)
{
int listener=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(listener==(int)(~0))
{
cout<<"Listenner Initiate Failure!"<<endl;
return 0;
}
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port=htons(8080);
ServerAddr.sin_addr.s_addr=INADDR_ANY;
setsockopt(listener, SOL_SOCKET, SO_REUSEPORT, (char*)&ServerAddr, sizeof(ServerAddr));
if(bind(listener,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr))== -1)
{
cout<<"Binding Failure!"<<endl;
return 0;
}
if(listen(listener,5)==-1)
{
cout<<"Listening Start!"<<endl;
return 0;
}
int Client_id;
struct sockaddr_in ClientAddr;
socklen_t ClientAddrLen = sizeof(ClientAddr);
while(1)
{
cout<<"Listening..."<<endl;
//--接收请求--
Client_id=accept(listener,(struct sockaddr*)&ClientAddr,&ClientAddrLen);
if(Client_id==(int)(~0))
{
cout<<"Accept Error!"<<endl;
continue;
}
cout<<"Accept message from Client :"<<endl;
char recvData[512]={0};
recv(Client_id,recvData,512,0);
recvData[511]='\0';
cout<<recvData<<endl;
//--处理请求--
string fpath(recvData);
string::size_type start=fpath.find('/');
string::size_type end=fpath.find(' ',start);
string fname = fpath.substr(start+1,end-start-1); //文件名提却
ifstream inFile;
string content;
inFile.open(fname.c_str(), ios::in);
if (inFile) //条件成立,则说明文件打开成功
{
cout << "File Open Succuss!" << endl;
string tmp;
while(inFile>>tmp)
content=content+tmp;
cout<<content<<endl;
inFile.close();
}
else
cout << "File doesn't exist" << endl;
//--发送响应--
char sendData[1024]={0};
char httpHeader[255]={0};
if(content.empty()) //404 not found httpheader
strcpy(httpHeader,"HTTP/1.1 404 file not found!\r\n\r\n\0");
else //202 OK httpheader
strcpy(httpHeader,"HTTP/1.1 202 OK!\r\ncontent-type: text/html; charset=UTF-8\r\n\r\n\0");
cout<<httpHeader<<endl;
strcpy(sendData,httpHeader);
strcat(sendData,content.c_str());
send(Client_id,&sendData,1024,0);
//--关闭check--
const char* cmd=recvData;
close(Client_id);
if(!strcmp(cmd,"CloseServer"))
break;
}
close(listener);
return 0;
}
进程与线程
线程间通信
accept(),recv()会阻塞,需要特别小心
如多线程项目中,收到退出信号后,阻塞态的主线程在收到(不可能到来的)下一个信号前,无法响应退出命令
可以将他们设置为非阻塞
并发回显测试
/* -----------------------------------------------------------------
Server.V3.0.CentOS 多线程接受,回显,主线程main中accept()会阻塞,CloseServer需发送两次以上
-------------------------------------------------------------------*/
#include <iostream>
#include <string.h>
#include <string>
#include <fstream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/errno.h>
#include <pthread.h>
#include <deque>
#include <vector>
using namespace std;
#define ERROR ((int)(~0))
#define MAX_THREAD_NUM 4
#define MAX_REQ_NUM 512
bool exit_flag=false;
int thread_no[MAX_THREAD_NUM];
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
vector<deque<int> > Client_id(MAX_THREAD_NUM); //线程请求队列
void *pthread_start_fcn(void *); //线程启动函数
int find_shortest_deque(vector<deque<int> >&); //最短队列查找
bool empty_check(vector<deque<int> >&); //空队列检测
bool full_check(vector<deque<int> >&); //满队列检测
int main(void)
{
//监听套接字
int listener=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(listener==ERROR)
{
cout<<"Listenner Initiate Failure!"<<endl;
return 0;
}
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port=htons(8080);
ServerAddr.sin_addr.s_addr=INADDR_ANY;
setsockopt(listener, SOL_SOCKET, SO_REUSEPORT, (char*)&ServerAddr, sizeof(ServerAddr));
if(bind(listener,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr))== -1)
{
cout<<"Binding Failure!"<<endl;
return 0;
}
if(listen(listener,5)==-1)
{
cout<<"Listening Initiation Failure!"<<endl;
return 0;
}
cout<<"Listening..."<<endl;
//初始化处理线程
pthread_mutex_lock(&lock);
for (int i=0;i<MAX_THREAD_NUM;i++)
{
thread_no[i]=i;
pthread_t tmp;
pthread_create(&tmp,NULL,pthread_start_fcn,&thread_no[i]);
pthread_detach(tmp);
}
pthread_mutex_unlock(&lock);
//开始接收套接字
struct sockaddr_in ClientAddr;
socklen_t ClientAddrLen = sizeof(ClientAddr);
while(true)
{
if(exit_flag==false)
{
//--接收请求--
//cout<<"Before Accept!"<<endl; //阻塞bug定位A
int id = accept(listener,(struct sockaddr*)&ClientAddr,&ClientAddrLen);
//cout<<"After Accept!"<<endl; //阻塞bug定位B
if( id == ERROR )
{
cout<<"Accept Error!"<<endl;
continue;
}
else if (!full_check(Client_id)) //压入 请求队列最短的线程 处理
{
pthread_mutex_lock(&lock);
Client_id[find_shortest_deque(Client_id)].push_back(id);
pthread_mutex_unlock(&lock);
}
else //队列满,放弃处理 关闭套接字
close(id);
}
if(exit_flag==true) //队列不空不能关闭
break; //关闭服务器
}
close(listener);
return 0;
}
void *pthread_start_fcn(void *arg)
{
int thread_no=*(int*)arg;
while(1)
{
if(!Client_id[thread_no].empty()) //队列非空
{
//上锁
pthread_mutex_lock(&lock);
int id = Client_id[thread_no][0];
Client_id[thread_no].pop_front();
pthread_mutex_unlock(&lock);
//解锁--处理请求--
char recv_data[512]={0};
recv(id,recv_data,512,0);
recv_data[511]='\0';
cout<<recv_data<<endl;
if(!strcmp(recv_data,"CloseServer"))
{
exit_flag=true;
cout<<"Kill:"<<endl;
}
else
send(id,&recv_data,512,0);
close(id);
}
else if(exit_flag == true)
{
cout<<"T"<<thread_no<<endl;
break;
}
}
return NULL;
}
int find_shortest_deque(vector<deque<int> > &Client_id)
{
int shortest = 0;
int min_req = MAX_REQ_NUM;
for(int i=0;i<MAX_THREAD_NUM;i++)
{
if(Client_id[i].size()<min_req)
{
min_req = Client_id[i].size();
shortest = i;
}
}
return shortest;
}
bool empty_check(vector<deque<int> > &Client_id)
{
bool empty = true;
for(int i=0;i<MAX_THREAD_NUM;i++)
if(Client_id[i].size()>0)
{
empty = false;
break;
}
return empty;
}
bool full_check(vector<deque<int> > &Client_id)
{
bool full = true;
for(int i=0;i<MAX_THREAD_NUM;i++)
if(Client_id[i].size()<MAX_REQ_NUM)
{
full = false;
break;
}
return full;
}
/*
Client.3.0.windows. 并发回显测试Client端
须在Visual Studio的项目属性-->链接器-->命令行中输入"ws2_32.lib"
*/
#include<winsock2.h>
#include<iostream>
#include<string.h>
#pragma warning(disable : 4996)
#define REQ_NUM 50 //并发请求数量
char ip_addr[] = { "192.168.31.246" };
using namespace std;
int main(void)
{
WORD socket_ver = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(socket_ver, &data) != 0)
{
cout << "WSAStartup failed!" << endl;
return 0;
}
while (true)
{
string input;
cin >> input;
if (!strcmp(input.c_str(), "CloseClient"))
break;
for (int i = 0; i < REQ_NUM; i++)
{
SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (client == INVALID_SOCKET)
{
cout << "Socket Init Failure!" << endl;
continue;
}
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(8080);
server.sin_addr.S_un.S_addr = inet_addr(ip_addr);
if (connect(client, (const sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
{
cout << "Connect Failure!" << endl;
break;
}
char sendmsg[255] = { '\0' };
char no[10] = { '\0' };
sprintf(no, "%d", i);
strcat(sendmsg, "REQ_No.");
strcat(sendmsg, no);
strcat(sendmsg, " : ");
strcat(sendmsg, input.c_str());
send(client, sendmsg, 255, 0);
char recvmsg[255] = {};
recv(client, recvmsg, 255, 0);
cout << "Server:" << endl << recvmsg << endl;
closesocket(client);
}
}
WSACleanup();
return 0;
}
并发HTTP请求测试
/* --------------------------------------------------------
Server.V3.1.CentOS 多线程接受HTTP请求,分析URL,传输html文档
----------------------------------------------------------*/
#include <iostream>
#include <string.h>
#include <string>
#include <fstream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/errno.h>
#include <pthread.h>
#include <deque>
#include <vector>
using namespace std;
#define ERROR ((int)(~0))
#define MAX_THREAD_NUM 4
#define MAX_REQ_NUM 512
bool exit_flag = false;
int thread_no[MAX_THREAD_NUM];
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
vector<deque<int>> Client_id(MAX_THREAD_NUM); //线程请求队列
void *pthread_start_fcn(void *); //线程启动函数
int find_shortest_deque(vector<deque<int>> &); //最短队列查找
bool empty_check(vector<deque<int>> &); //空队列检测
bool full_check(vector<deque<int>> &); //满队列检测
void send_failure(int, int); //向套接字发送 失败状态码
void cycle_deque(void); //队列轮转
int main(void)
{
//监听套接字
int listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listener == ERROR)
{
cout << "Listenner Initiate Failure!" << endl;
return 0;
}
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(8080);
ServerAddr.sin_addr.s_addr = INADDR_ANY;
setsockopt(listener, SOL_SOCKET, SO_REUSEPORT, (char *)&ServerAddr, sizeof(ServerAddr));
if (bind(listener, (struct sockaddr *)&ServerAddr, sizeof(ServerAddr)) == -1)
{
cout << "Binding Failure!" << endl;
return 0;
}
if (listen(listener, 5) == -1)
{
cout << "Listening Initiation Failure!" << endl;
return 0;
}
//初始化处理线程
pthread_mutex_lock(&lock);
for (int i = 0; i < MAX_THREAD_NUM; i++)
{
thread_no[i] = i;
pthread_t tmp;
pthread_create(&tmp, NULL, pthread_start_fcn, &thread_no[i]);
pthread_detach(tmp);
}
pthread_mutex_unlock(&lock);
//开始接收套接字
struct sockaddr_in ClientAddr;
socklen_t ClientAddrLen = sizeof(ClientAddr);
while (true)
{
if (exit_flag == false)
{
cout << "Listening..." << endl;
//--接收请求--
//cout<<"Before Accept!"<<endl;
int id = accept(listener, (struct sockaddr *)&ClientAddr, &ClientAddrLen);
//cout<<"After Accept!"<<endl;
if (id == ERROR)
{
cout << "Accept Error!" << endl;
continue;
}
else if (!full_check(Client_id)) //压入 请求队列最短的线程 处理
{
cycle_deque();
pthread_mutex_lock(&lock);
Client_id[thread_no[0]].push_back(id); //循环轮转
//Client_id[find_shortest_deque(Client_id)].push_back(id); //最短队列
pthread_mutex_unlock(&lock);
}
else //队列满,放弃处理 关闭套接字
{
send_failure(id, 501);
close(id);
}
}
if (exit_flag == true) //队列不空不能关闭
break; //关闭服务器
}
close(listener);
return 0;
}
void *pthread_start_fcn(void *arg)
{
int thread_no = *(int *)arg;
while (1)
{
if (!Client_id[thread_no].empty()) //队列非空
{
//上锁
pthread_mutex_lock(&lock);
int id = Client_id[thread_no][0];
Client_id[thread_no].pop_front();
pthread_mutex_unlock(&lock);
//解锁--处理请求--
char recv_data[512] = {0};
recv(id, recv_data, 512, 0);
recv_data[511] = '\0';
cout << "Client:" << endl << recv_data << endl;
//--处理请求--
string fpath(recv_data);
string::size_type start = fpath.find('/');
string::size_type end = fpath.find(' ', start);
string fname = fpath.substr(start + 1, end - start - 1); //文件名提却
ifstream inFile;
string content;
inFile.open(fname.c_str(), ios::in);
if (inFile) //条件成立,则说明文件打开成功
{
cout << "Succuss!" << endl;
string tmp;
while (inFile >> tmp)
content = content + tmp;
inFile.close();
char sendData[1024] = {0};
strcpy(sendData, "HTTP/1.1 202 OK!\r\ncontent-type: text/html; charset=UTF-8\r\n\0");
string label("REQ_No.");
int pos = fpath.find(label);
if(pos!=string::npos) //使用字符串查找拍除浏览器访问的情况
{
string req_no = fpath.substr(pos,fpath.find(string("\r\n"),pos)-pos);
strcat(sendData, req_no.c_str());
}
char no[10] = { '\0' };
sprintf(no, "%d", thread_no);
strcat(sendData, "\r\nThread_No. : "); //客户端处理线程识别标识,勿改
strcat(sendData, no);
strcat(sendData, "\r\n\r\n\0");
strcat(sendData, content.c_str());
strcat(sendData, "\n\n");
send(id, &sendData, 1024, 0);
}
else
{
if (!strcmp(fname.c_str(), "CloseServer"))
{
exit_flag = true;
cout << "Closing..." << endl;
char sendData[255]={"Input any thing to close Server:\0"};
send(id, &sendData, 255, 0);
}
else
{
cout << "Failed" << endl;
send_failure(id, 404);
}
}
close(id);
}
else if (exit_flag == true)
break;
}
return NULL;
}
int find_shortest_deque(vector<deque<int>> &Client_id)
{
int shortest = 0;
int min_req = MAX_REQ_NUM;
for (int i = 0; i < MAX_THREAD_NUM; i++)
{
if (Client_id[i].size() < min_req)
{
min_req = Client_id[i].size();
shortest = i;
}
}
return shortest;
}
bool empty_check(vector<deque<int>> &Client_id)
{
bool empty = true;
for (int i = 0; i < MAX_THREAD_NUM; i++)
if (Client_id[i].size() > 0)
{
empty = false;
break;
}
return empty;
}
bool full_check(vector<deque<int>> &Client_id)
{
bool full = true;
for (int i = 0; i < MAX_THREAD_NUM; i++)
if (Client_id[i].size() < MAX_REQ_NUM)
{
full = false;
break;
}
return full;
}
void send_failure(int id, int state)
{
char httpHeader[255] = {0};
strcpy(httpHeader, "HTTP/1.1 ");
char state_char[10] = {0};
sprintf(state_char, "%d", state);
strcat(httpHeader, state_char);
strcat(httpHeader, " Failed!\r\n\r\n\0");
send(id, &httpHeader, 255, 0);
return;
}
void cycle_deque(void)
{
int tmp=thread_no[0];
for (int i=0;i<MAX_THREAD_NUM-1;i++)
thread_no[i]=thread_no[i+1];
thread_no[MAX_THREAD_NUM-1]=tmp;
return;
}
/*
Client.3.1.windows. 并发HTTP请求测试Client端
须在Visual Studio的项目属性-->链接器-->命令行中输入"ws2_32.lib"
*/
#include<winsock2.h>
#include<iostream>
#include<string.h>
#include<string>
#include<thread>
#include<deque>
#pragma warning(disable : 4996)
#define REQ_NUM 50
char ip_addr[] = { "192.168.31.246" };
using namespace std;
deque<SOCKET> Client_id;
void recv_thread(); //接收线程函数
bool exit_flag = false;
bool send_flag = false;
string recv_content;
int main(void)
{
WORD socket_ver = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(socket_ver, &data) != 0)
{
cout << "WSAStartup failed!" << endl;
return 0;
}
thread recv_thread(recv_thread);
recv_thread.detach();
while (true)
{
string input;
cin >> input;
if (!strcmp(input.c_str(), "CloseClient"))
{
exit_flag = true;
break;
}
// http 头部
char httpheader[255] = { 0 };
strcat(httpheader, "GET /");
strcat(httpheader, input.c_str());
strcat(httpheader, " HTTP/1.1\r\n");
strcat(httpheader, "Host: ");
strcat(httpheader, ip_addr);
strcat(httpheader, "\r\nConnection: keep-alive\r\n\0");
for (int i = 0; i < REQ_NUM; i++)
{
SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (client == INVALID_SOCKET)
{
cout << "Socket Init Failure!" << endl;
continue;
}
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(8080);
server.sin_addr.S_un.S_addr = inet_addr(ip_addr);
if (connect(client, (const sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
{
cout << "Connect Failure!" << endl;
break;
}
char sendmsg[1024] = { '\0' };
strcat(sendmsg, httpheader);
char no[10] = { '\0' };
sprintf(no, "%d", i);
strcat(sendmsg, "REQ_No."); //用于Server中识别请求计数label
strcat(sendmsg, no);
strcat(sendmsg, "\r\n\r\n");
send(client, sendmsg, 1024, 0);
Client_id.push_back(client);
if (!strcmp(input.c_str(), "CloseServer"))
break;
}
send_flag = true;//发送完毕
}
WSACleanup();
return 0;
}
void recv_thread(void)
{
while (exit_flag == false)
{
if (Client_id.size()>0)
{
SOCKET client = Client_id.front();
Client_id.pop_front();
char recvmsg[1024] = { '\0' };
recv(client, recvmsg, 1024, 0); //recv()阻塞
string tmp = string(recvmsg);
int pos = tmp.find("Thread_No");
if (pos != string::npos)
recv_content = recv_content + tmp.substr(0, tmp.find("\r\n", pos))+"\n\n\n";
else
recv_content = recv_content + tmp;
closesocket(client);
}
else if(send_flag) //接受完Client_id中请求的响应后
{
send_flag = false;
cout << "Server:" << endl << recv_content << endl;
}
}
return;
}
进程间通信
IPC(interprocess communication),操作系统上不同进程间的消息传递(message passing),同步(synchronization)
-
消息传递发展历程:
管道(pipe)
System V message queue
Posix 消息队列
远程过程调用(Remote Procedure Call,RPC) -
同步方式发展历程:
文件
记录上锁(record locking)
System V semaphore / System V shared memory
Posix semaphore / Posix shared memory
mutex / condition variable
read-write lock
数据库
使用HTTP协议操作sqlite3数据库
类unix系统内置sqlite3,使用>>man sqlite3等相关命令确认
本文暂不涉及高并发读写,在了解原子性的实现后再做补充
Server端需要下载sqlite3数据库的源码,即sqlite3.c和sqlite3.h两个文件(官方)
/* --------------------------------------------------------
Server.V4.0.CentOS 多线程接受HTTP请求,分析URL,传输html文档,接收SQL数据库操作请求
----------------------------------------------------------*/
#include <iostream>
#include <string.h>
#include <string>
#include <fstream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/errno.h>
#include <pthread.h>
#include <deque>
#include <vector>
#include <stdlib.h>
#include"sqltest/sqlite3.h"
using namespace std;
#define ERROR ((int)(~0))
#define MAX_THREAD_NUM 2
#define MAX_REQ_NUM 512
bool exit_flag = false;
int thread_no[MAX_THREAD_NUM];
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
vector<deque<int> > Client_id(MAX_THREAD_NUM); //线程请求队列
sqlite3 *db; //打开的数据库
void *pthread_start_fcn(void *); //线程启动函数
int find_shortest_deque(vector<deque<int> > &); //最短队列查找
bool empty_check(vector<deque<int> > &); //空队列检测
bool full_check(vector<deque<int> > &); //满队列检测
void send_failure(int, int); //向套接字发送 失败状态码
void cycle_deque(void); //队列轮转
int sql_callback(void* a,int b,char**d,char**e); //sql回调函数
void URIdecode(string &sqlcmd); //URI解码
int main(void)
{
int rc;
//建立打开数据库文件
rc = sqlite3_open("sqltest.db", &db);
if( rc ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
exit(0);
}else{
fprintf(stderr, "Opened database successfully\n");
}
//监听套接字
int listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listener == ERROR)
{
cout << "Listenner Initiate Failure!" << endl;
return 0;
}
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(8080);
ServerAddr.sin_addr.s_addr = INADDR_ANY;
setsockopt(listener, SOL_SOCKET, SO_REUSEPORT, (char *)&ServerAddr, sizeof(ServerAddr));
if (bind(listener, (struct sockaddr *)&ServerAddr, sizeof(ServerAddr)) == -1)
{
cout << "Binding Failure!" << endl;
return 0;
}
if (listen(listener, 5) == -1)
{
cout << "Listening Initiation Failure!" << endl;
return 0;
}
//初始化处理线程
pthread_mutex_lock(&lock);
for (int i = 0; i < MAX_THREAD_NUM; i++)
{
thread_no[i] = i;
pthread_t tmp;
pthread_create(&tmp, NULL, pthread_start_fcn, &thread_no[i]);
pthread_detach(tmp);
}
pthread_mutex_unlock(&lock);
//开始接收套接字
struct sockaddr_in ClientAddr;
socklen_t ClientAddrLen = sizeof(ClientAddr);
while (true)
{
if (exit_flag == false)
{
cout << "Listening..." << endl;
//--接收请求--
//cout<<"Before Accept!"<<endl;
int id = accept(listener, (struct sockaddr *)&ClientAddr, &ClientAddrLen);
//cout<<"After Accept!"<<endl;
if (id == ERROR)
{
cout << "Accept Error!" << endl;
continue;
}
else if (!full_check(Client_id)) //压入 请求队列最短的线程 处理
{
pthread_mutex_lock(&lock);
Client_id[thread_no[0]].push_back(id); //循环轮转
//Client_id[find_shortest_deque(Client_id)].push_back(id); //最短队列
pthread_mutex_unlock(&lock);
cycle_deque();
}
else //队列满,放弃处理 关闭套接字
{
send_failure(id, 501);
close(id);
}
}
if (exit_flag == true) //队列不空不能关闭
break; //关闭服务器
}
sqlite3_close(db); //关闭数据库文件
close(listener);
return 0;
}
void *pthread_start_fcn(void *arg)
{
int thread_no = *(int *)arg;
while (true)
{
if (!Client_id[thread_no].empty()) //队列非空
{
//上锁
pthread_mutex_lock(&lock);
int id = Client_id[thread_no][0];
Client_id[thread_no].pop_front();
pthread_mutex_unlock(&lock);
//解锁--处理请求--
char recv_data[512] = {0};
recv(id, recv_data, 512, 0);
recv_data[511] = '\0';
cout << "Client:" << endl << recv_data << endl;
//--处理请求--
string fpath(recv_data);
string::size_type start = fpath.find('/');
string::size_type end = fpath.find(' ', start);
string fname = fpath.substr(start + 1, end - start - 1); //文件名提却
if(!strcmp(fname.substr(0,3).c_str(),"sql")) //sql命令
{
char sendData[1024] = {0};
strcpy(sendData, "HTTP/1.1 202 OK!\r\ncontent-type: text/html; charset=UTF-8\r\n\r\n\0");
char no[10] = { '\0' };
sprintf(no, "%d", thread_no);
strcat(sendData, "Thread_No. : ");
strcat(sendData, no);
strcat(sendData, "<br>Operation:");
//sql命令提取
string sqlcmd=fname.substr(4,fname.find('/',4));
URIdecode(sqlcmd);
strcat(sendData, sqlcmd.c_str());
char *zErrMsg = 0;
int rc = sqlite3_exec(db, sqlcmd.c_str(), sql_callback, NULL, &zErrMsg);
if( rc != SQLITE_OK )
{
strcat(sendData, "<br>failed !");
strcat(sendData, "<br>SQL error : <br>");
strcat(sendData, zErrMsg);
cout<<"failed !"<<endl<<zErrMsg<<endl;
sqlite3_free(zErrMsg);
}
else
{
strcat(sendData, "<br>Done!");
cout<<"sql success!"<<endl;
}
send(id, &sendData, 1024, 0);
}
else if(!strcmp(fname.c_str(), "CloseServer"))
{
exit_flag = true;
cout << "Closing..." << endl;
char sendData[255]={"Input any thing to close Server:\0"};
send(id, &sendData, 255, 0);
}
else
{
ifstream inFile;
string content;
inFile.open(fname.c_str(), ios::in);
if (inFile) //条件成立,则说明文件打开成功
{
cout << "Succuss!" << endl;
string tmp;
while (inFile >> tmp)
content = content + tmp;
inFile.close();
char sendData[1024] = {0};
strcpy(sendData, "HTTP/1.1 202 OK!\r\ncontent-type: text/html; charset=UTF-8\r\n\0");
string label("REQ_No.");
int pos = fpath.find(label);
if(pos!=string::npos) //使用字符串查找拍除浏览器访问的情况
{
string req_no = fpath.substr(pos,fpath.find(string("\r\n"),pos)-pos);
strcat(sendData, req_no.c_str());
}
char no[10] = { '\0' };
sprintf(no, "%d", thread_no);
strcat(sendData, "\r\nThread_No. : "); //客户端处理线程识别标识,勿改
strcat(sendData, no);
strcat(sendData, "\r\n\r\n\0");
strcat(sendData, content.c_str());
strcat(sendData, "\n\n");
send(id, &sendData, 1024, 0);
}
else
{
cout << "Failed" << endl;
send_failure(id, 404);
}
}
close(id);
}
else if (exit_flag == true)
break;
}
return NULL;
}
int find_shortest_deque(vector<deque<int> > &Client_id)
{
int shortest = 0;
int min_req = MAX_REQ_NUM;
for (int i = 0; i < MAX_THREAD_NUM; i++)
{
if (Client_id[i].size() < min_req)
{
min_req = Client_id[i].size();
shortest = i;
}
}
return shortest;
}
bool empty_check(vector<deque<int> > &Client_id)
{
bool empty = true;
for (int i = 0; i < MAX_THREAD_NUM; i++)
if (Client_id[i].size() > 0)
{
empty = false;
break;
}
return empty;
}
bool full_check(vector<deque<int> > &Client_id)
{
bool full = true;
for (int i = 0; i < MAX_THREAD_NUM; i++)
if (Client_id[i].size() < MAX_REQ_NUM)
{
full = false;
break;
}
return full;
}
void send_failure(int id, int state)
{
char httpHeader[255] = {0};
strcpy(httpHeader, "HTTP/1.1 ");
char state_char[10] = {0};
sprintf(state_char, "%d", state);
strcat(httpHeader, state_char);
strcat(httpHeader, " Failed!\r\n\r\n\0");
send(id, &httpHeader, 255, 0);
return;
}
void cycle_deque(void)
{
int tmp=thread_no[0];
for (int i=0;i<MAX_THREAD_NUM-1;i++)
thread_no[i]=thread_no[i+1];
thread_no[MAX_THREAD_NUM-1]=tmp;
return;
}
int sql_callback(void *data, int argc, char **argv, char **azColName)
{
int i;
fprintf(stderr, "%s: ", (const char*)data);
for(i=0; i<argc; i++)
{
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
void URIdecode(string &sqlcmd)
{
for(int pos=sqlcmd.find("%20",4);pos!=string::npos;pos=sqlcmd.find("%20",pos))
{
sqlcmd.replace(pos,3," ");
pos++;
}
for(int pos=sqlcmd.find("%3E",4);pos!=string::npos;pos=sqlcmd.find("%3E",pos))
{
sqlcmd.replace(pos,3,">");
pos++;
}
for(int pos=sqlcmd.find("%3C",4);pos!=string::npos;pos=sqlcmd.find("%3C",pos))
{
sqlcmd.replace(pos,3,"<");
pos++;
}
for(int pos=sqlcmd.find("%22",4);pos!=string::npos;pos=sqlcmd.find("%22",pos))
{
sqlcmd.replace(pos,3,"\"");
pos++;
}
sqlcmd.erase(sqlcmd.end()-1);
return;
}
使用浏览器输入URL即可交由Server解析sql命令,从Client端操作Server端数据库
http://IP:8080/sql.cmd/
举例
http://192.168.31.201:8080/sql.create table A (name char,age int);/
http://192.168.31.201:8080/sql.select * from A where age>25;/