Demo:HTTP-Server

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),来唯一标识一个实体,特定命名空间+名字,不需要知道地址或访问机制
RUL、URN 和 URI

网页访问

网页访问过程

鼠标点击某链接
地址栏输入域名并回车
浏览器分析URL
浏览器向DNS请求解析域名的IP地址
DNS返回IP地址
浏览器与IP地址的服务器建立TCP连接port=80
浏览器向服务器请求home目录下的mypage.html文件/home/mypage.html
服务器响应返回mypage.html文件
S/C端释放TCP连接
浏览器解析mypage.html并显示给用户

PS:为加快响应用户的速度,音视频是点击页面上的播放按钮后再次建立连接并传输
从Client请求到得到文档计时:RTT*2+文档传输时间

Client Server 第一次TCP请求 第一次TCP应答 Round-Trip Time 第三次TCP应答+HTTP请求 HTTP响应+文档的传输 Round-Trip Time 传输结束 Client Server

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)”,将请求和响应暂存于本地磁盘中,新请求与暂存请求相同时直接返回暂存响应,举例

PC1
HTTP Proxy Server
PC2
Router1
Router2
源点服务器Origin Server

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间传递的状态信息

张三 哔哩哔哩 用户名与密码 为张三生成一个唯一识别码123456作为后端索引 响应报文的首部行Set-cookie:123456 浏览器在Cookie文件中管理主机名和识别码 张三再次请求时的首部行Cookie:123456 记录用户操作(购物结算等) 张三 哔哩哔哩

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)

http_request
http_response
Client
Server
CGI
Database
dynamic_document

活动文档(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;/

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值