用TCP模拟网页服务器开发记录

一、所需要撑握的技术:

1、TCP 下的socket应用
2、多线程动态创建,实现多个客户端的同时访问
3、了解html的语法结构和javaScript的开发
4、了解http数据包格式,包括json、get、post等格式
5、了解http的通讯过程
以下应用介绍,不再介绍基础的知识点,请大家自已通过网络或书籍学习

二、TCP模拟网页服务器的优点

1、不需要专门设计客户端软件。只需网络浏览器即可,实现手机、电脑等平台上查看。
2、客户端显示内容完全由网页服务器决定,不存在客户端软件的升级管理
3、客户也可模拟客户端的方式,按http协议格式与产品进行通讯,实现二次开发。

三、http通讯步骤

在这里插入图片描述
以上过程注意事项:
1、服务器可以不主动断开TCP连接,但不建议这么做,原因为JSON格式回复时,网页端会不知何时结束,导致一直连接着,网页端的显示也是不正常。
2、服务器端返回get包时,一定要严格按http的格式,特别是内容长度是按字节数的,如果字节数超了,网页端就不认后面的数据。

四、应用介绍

以下介绍是通过Qt平台介绍,由于是采用TCP socket模拟,所以在单片机上设计时,完全按此思路设计。服务器代码主要是涉及TCP服务器程序的开发,此处只供参考:

//头文件
#ifndef HTTPSEVERRUN_H
#define HTTPSEVERRUN_H

#include <QTcpSocket>
#include <QTcpServer>
#include <QThread>
#include <QDebug>
#include <QByteArray>


#ifdef  HTTPSEVERRUN_C
#define HTTPSEVERRUN_TEMP
#else
#define HTTPSEVERRUN_TEMP extern
#endif

class HttpSeverTcpClientSocket : public QTcpSocket
{
    Q_OBJECT
public:
    explicit HttpSeverTcpClientSocket(QObject *parent = 0);
    ~HttpSeverTcpClientSocket();
    void deleteSelf(void);
signals:
    void updateClients(QByteArray);
    void disconnected(int);
public slots:
    void dataReceived();
    void slotDisconnected();
private:
    void httpSeverTCP(QByteArray readData);
    unsigned char  m_address;//modbus tcp address
};

class HttpServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit HttpServer(QObject *parent = 0 ,QString IP = "",int port= 0);
    ~HttpServer();
    QList<HttpSeverTcpClientSocket*> tcpClientSocketList;
signals:
    void updateServer(QByteArray);
public slots:
    void updateClients(QByteArray);
    void slotDisconnected(int);
protected:
    void incomingConnection(int socketDescriptor);
};

class HttpSeverRun : public QObject
{
    Q_OBJECT
public:
    explicit HttpSeverRun(QObject *parent = 0);
    ~HttpSeverRun();
signals:
    void dataArrive(QByteArray ba); //send data to deal
public slots:
   void updateServer(QByteArray);
private:
    QThread *m_thread;
    HttpServer*  server;//服务器任务
};

HTTPSEVERRUN_TEMP class HttpSeverRun *GS_HttpSeverRun;

//CPP文件
 #define HTTPSEVERRUN_C
#include "HttpSeverRun.h"
#include "ToolFunc.h"
#include "netsetdev.h"
#include "manage.h"
#include "SerialCommManage.h"
#include "SystemFunction.h"
#include "autoStepRun.h"

HttpSeverRun::HttpSeverRun(QObject *parent):QObject(parent)
{
        m_thread = new QThread();
        setNetParaStr temp;
        readNetPara(&temp);
        QString str;
        ToolFunc::UnicodeCharsToQString((INT8U *)(temp.IP),str);        server =  new HttpServer(this,str,80);//绑定服务器的端口,不一定要求为80,只是80是网页的默认端口号
        server->serverError();
        
        this->moveToThread(m_thread);
        server->moveToThread(m_thread);
        m_thread->start();
}

HttpSeverRun::~HttpSeverRun()
{
    m_thread->quit();
    m_thread->wait();
    delete m_thread;
}

void HttpSeverRun::updateServer(QByteArray readData )
{
  //qDebug()<<"read = "<<  readData;
}


HttpSeverTcpClientSocket::HttpSeverTcpClientSocket(QObject *parent ) :QTcpSocket(parent)
{
    GSPara_lock.lock();
    m_address = GSPara.SCOMParaData.address_1;
    GSPara_lock.unlock();
    connect(this,SIGNAL(readyRead()),this,SLOT(dataReceived()));
    connect(this,SIGNAL(disconnected()),this,SLOT(slotDisconnected()));
}

HttpSeverTcpClientSocket::~HttpSeverTcpClientSocket()
{
    //qDebug()<<"~TcpClientSocket()";
}

void HttpSeverTcpClientSocket::deleteSelf()
{
    deleteLater();
}

void HttpSeverTcpClientSocket::dataReceived()
{
    while(bytesAvailable() > 0)
    {
        QByteArray msg = read(1024);
        if(msg.size()>0)
        {
            emit updateClients(msg);
            httpSeverTCP(msg);
        }
    }
}

void HttpSeverTcpClientSocket::slotDisconnected()
{
    emit disconnected(this->socketDescriptor());
}
//以下就是网页服务器端服务的内容
//要点是:根据客户端发来的数据包内容回应数据即可。回应时一定要按结合html和http格式进行回应。
void HttpSeverTcpClientSocket::httpSeverTCP(QByteArray readData)
{
    INT8U   readBuff[2000];
    INT8U   txdTemps[2000];
    INT8U   txdLength;
    INT32S     L_temp;//发送长度
    //接收数据处理
    //判断数据
    if((checkStopOrIotLockState() != 0)||(checkPowerState() == 0))
    {
        return;
    }
    //判定接收数据包
    L_temp = readData.size();
    //qDebug()<<readData;
    QString str(readData);
    int x = str.indexOf("GET / HTTP/");
    if(x != -1)
    {
        QString content = QString("\
<!DOCTYPE html>\r\n\
<html lang=\"en\">\r\n\
<head>\r\n\
<meta charset=\"UTF-8\">\r\n\
<meta http-equiv='Content-Type' content='text/html'; charset='GB2312'/>\r\n\
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n\
<title>D38-3实时称重</title>\r\n\
<style type='text/css'>\r\n\
body {text-align:left; background-color:#c0deed;font-family:Verdana;}\r\n\
#main {margin-right:auto;margin-left:auto;margin-top:5px;}\r\n\
.LeftLabel {display:inline-block;width:100px;}\r\n\
.LeftLabelH {display:inline-block;width:200px;}\r\n\
.AutoState{width:260px}\r\n\
h3{color:#66b3ff; text-decoration:underline;height: 10px;margin-top: 0px;}\r\n\
#main p {height: 10px;}\r\n\
</style>\r\n\
</head>\r\n\
<body>\r\n\
<div id='main'>\r\n\
<div style='background:snow; display:block;padding:5px 10px;'>\r\n\
<h3>D38-3实时称重</h3>\r\n\
<p><label id='ctime'></label></p>\r\n\
<form id='frmSetting' method='POST'action='/allconfig' >\r\n\
<p><label  class='LeftLabel'>设备名称:</label>\r\n\
<input type='text' id='meterName' name='meterName' size='16' disabled='disabled' />\r\n\
</p>\r\n\
<p> <label for='realWeight' class=\"LeftLabel\">实时重量:</label><input style=\"margin-left: 6px; background-color: #e2e6cc;text-align: right;\" type='text' id='realWeight' disabled='disabled'\r\n\
name='realWeight' size='12' />\r\n\
<label id='unit'>kg</label></p>\r\n\
<p> <label class='LeftLabel'>称重状态:</label>\r\n\
<input type='text' id='realWeightState' disabled='disabled' name='realWeightState' size='16' />  \r\n\
</p>\r\n\
<p><label  class='LeftLabel'>仪表ID:</label>\r\n\
<input type='text' id='meterID' name='meterID' size='16' disabled='disabled' />\r\n\
</p>\r\n\
<p> <label  class=\"LeftLabelH\">无人值守状态:</label>\r\n\
</p>\r\n\
<div style=\"width:253px; height:110px;background-color:#F5F5F5;border: 1px solid rgb(26, 25, 25);border-radius:3px;\">\r\n\
<label id='atuostate' style='padding: 5px;display:inline-block;width:245px;word-break:break-all'></label>\r\n\
</div>\r\n\
</form>\r\n\
</div>\r\n\
</div>\r\n\
<div style='margin-top: 10px;' >\r\n\
<div style='background:snow; display:block;padding:10px 10px;'>\r\n\
<h3 style='color:#66b3ff; text-decoration:underline;'>操作</h3>\r\n\
<p> <label for='username' class=\"LeftLabel\">账号:</label><input type='text' value='admin' id='username' size='15'  />\r\n\
</p>\r\n\
<p> <label for='userpassword' class=\"LeftLabel\">密码:</label><input type='password' id='userpassword' size='15' />\r\n\
</p>\r\n\
<div style=\"width: 300px;background:snow; display:block;padding:0px 10px;height: 170px; \">\r\n\
<div>\r\n\
<button  οnclick=\"manulZero()\" id='btn_manulZero' style=\"width: 100px;height: 40px; margin-top: 0px;font-size: 20px; float: left;\">置 零</button>\r\n\
</div>    \r\n\
<div>\r\n\
<button  οnclick=\"manulStop()\" id='btn_manulStop' style=\"width: 100px;height: 40px; margin-top: 0px;font-size: 20px;float: left; margin-left: 30px;\">停 止</button>\r\n\
</div> \r\n\
<div>\r\n\
<button  οnclick=\"manulOpen()\" id='btn_manulOpen' style=\"width: 100px;height: 40px; margin-top: 20px;font-size: 20px;float: left; \">启动</button>\r\n\
</div> \r\n\
<div>\r\n\
<button  οnclick=\"manulUp()\" id='btn_manulUp' style=\"width: 100px;height: 40px; margin-top: 20px;font-size: 20px;float: left; margin-left: 30px;\">抬杆</button>\r\n\
</div> \r\n\
<div>\r\n\
<button  οnclick=\"restart()\" id='btn_manulrestart' style=\"background:#4CAF50;border:none;color:white; width: 100px;height: 40px; margin-top: 20px;font-size: 20px;float: left;border-radius:3px; \">重启</button>\r\n\
</div> \r\n\
</div>\r\n\
</div>\r\n\
<script>\r\n\
setInterval('updateWeightState()',500);\r\n\
function updateWeightState()\r\n\
{\r\n\
const xhr = new   XMLHttpRequest();\r\n\
xhr.open('post','/updateWeightState');\r\n\
xhr.send();\r\n\
xhr.onreadystatechange = function()\r\n\
{\r\n\
if(xhr.readyState == 4)\r\n\
{\r\n\
if(xhr.status>=200 && xhr.status <300)\r\n\
{\r\n\
let data = JSON.parse(xhr.response);\r\n\
document.getElementById('realWeight').value=data.realWeight;\r\n\
document.getElementById('unit').value=data.unit;\r\n\
document.getElementById('realWeightState').value=data.realWeightState;\r\n\
document.getElementById('meterID').value=data.meterID;\r\n\
document.getElementById('meterName').value=data.meterName;\r\n\
document.getElementById('ctime').innerHTML=data.ctime;\r\n\
document.getElementById('atuostate').innerHTML=data.atuostate;\r\n\
}\r\n\
}\r\n\
}\r\n\
}\r\n\
function manulZero()\r\n\
{\r\n\
var r = confirm('是否要【置零】操作?');\r\n\
if(r == false) \r\n\
{ \r\n\
return;\r\n\
}\r\n\
var usernameTemp = document.getElementById('username').value;\r\n\
var userpasswordTemp = document.getElementById('userpassword').value;\r\n\
var IDTemp = document.getElementById('meterID').value;\r\n\
var postcontent = '/maul/manulZero'+'?'+'meterID='+IDTemp+'&username='+usernameTemp+'&userpassword='+userpasswordTemp;\r\n\
const xhr = new   XMLHttpRequest();\r\n\
xhr.open('get',postcontent);\r\n\
xhr.send();\r\n\
xhr.onreadystatechange = function()\r\n\
{\r\n\
if(xhr.readyState == 4)\r\n\
{\r\n\
if(xhr.status>=200 && xhr.status <300)\r\n\
{\r\n\
//console.log(xhr.response);\r\n\
let data = JSON.parse(xhr.response);\r\n\
alert(data.result);\r\n\
}\r\n\
}\r\n\
}\r\n\
}\r\n\
function manulStop()\r\n\
{\r\n\
var r = confirm('是否要【停止】无人值守工作的操作?');\r\n\
if(r == false) \r\n\
{ \r\n\
return;\r\n\
}\r\n\
var usernameTemp = document.getElementById('username').value;\r\n\
var userpasswordTemp = document.getElementById('userpassword').value;\r\n\
var IDTemp = document.getElementById('meterID').value;\r\n\
var postcontent = '/maul/manulStop'+'?'+'meterID='+IDTemp+'&username='+usernameTemp+'&userpassword='+userpasswordTemp;\r\n\
const xhr = new   XMLHttpRequest();\r\n\
xhr.open('get',postcontent);\r\n\
xhr.send();\r\n\
xhr.onreadystatechange = function()\r\n\
{\r\n\
if(xhr.readyState == 4)\r\n\
{\r\n\
if(xhr.status>=200 && xhr.status <300)\r\n\
{\r\n\
//console.log(xhr.response);\r\n\
let data = JSON.parse(xhr.response);\r\n\
alert(data.result);\r\n\
}\r\n\
}\r\n\
}\r\n\
btnDelay(2);\r\n\
}\r\n\
function manulOpen()\r\n\
{\r\n\
var r;\r\n\
if(document.getElementById('atuostate').innerHTML == '可按【启动】进入无人值守界面')\r\n\
{\r\n\
  r = confirm('是否要进入无人值守界面!');\r\n\
}\r\n\
else\r\n\
{\r\n\
   r = confirm('是否要【启动】无人值守操作?操作前请确认栏杆机降杆安全!');\r\n\
}\r\n\
if(r == false) \r\n\
{ \r\n\
return;\r\n\
}\r\n\
var usernameTemp = document.getElementById('username').value;\r\n\
var userpasswordTemp = document.getElementById('userpassword').value;\r\n\
var IDTemp = document.getElementById('meterID').value;\r\n\
var postcontent = '/maul/manulOpen'+'?'+'meterID='+IDTemp+'&username='+usernameTemp+'&userpassword='+userpasswordTemp;\r\n\
const xhr = new   XMLHttpRequest();\r\n\
xhr.open('get',postcontent);\r\n\
xhr.send();\r\n\
xhr.onreadystatechange = function()\r\n\
{\r\n\
if(xhr.readyState == 4)\r\n\
{\r\n\
if(xhr.status>=200 && xhr.status <300)\r\n\
{\r\n\
//console.log(xhr.response);\r\n\
let data = JSON.parse(xhr.response);\r\n\
alert(data.result);\r\n\
}\r\n\
}\r\n\
}\r\n\
btnDelay(2);\r\n\
}\r\n\
function manulUp()\r\n\
{\r\n\
var r = confirm('是否要【抬杆】操作?此操作在无人值守开启状态下无效!');\r\n\
if(r == false) \r\n\
{ \r\n\
return;\r\n\
}\r\n\
var usernameTemp = document.getElementById('username').value;\r\n\
var userpasswordTemp = document.getElementById('userpassword').value;\r\n\
var IDTemp = document.getElementById('meterID').value;\r\n\
var postcontent = '/maul/manulUp'+'?'+'meterID='+IDTemp+'&username='+usernameTemp+'&userpassword='+userpasswordTemp;\r\n\
const xhr = new   XMLHttpRequest();\r\n\
xhr.open('get',postcontent);\r\n\
xhr.send();\r\n\
xhr.onreadystatechange = function()\r\n\
{\r\n\
if(xhr.readyState == 4)\r\n\
{\r\n\
if(xhr.status>=200 && xhr.status <300)\r\n\
{\r\n\
//console.log(xhr.response);\r\n\
let data = JSON.parse(xhr.response);\r\n\
alert(data.result);\r\n\
}\r\n\
}\r\n\
}\r\n\
btnDelay(3);\r\n\
}\r\n\
function restart()\r\n\
{\r\n\
var r = confirm('是否要重启仪表!');\r\n\
if(r == false) \r\n\
{ \r\n\
return;\r\n\
}\r\n\
var usernameTemp = document.getElementById('username').value;\r\n\
var userpasswordTemp = document.getElementById('userpassword').value;\r\n\
var IDTemp = document.getElementById('meterID').value;\r\n\
var postcontent = '/maul/restart'+'?'+'meterID='+IDTemp+'&username='+usernameTemp+'&userpassword='+userpasswordTemp;\r\n\
const xhr = new   XMLHttpRequest();\r\n\
xhr.open('get',postcontent);\r\n\
xhr.send();\r\n\
xhr.onreadystatechange = function()\r\n\
{\r\n\
if(xhr.readyState == 4)\r\n\
{\r\n\
if(xhr.status>=200 && xhr.status <300)\r\n\
{\r\n\
//console.log(xhr.response);\r\n\
let data = JSON.parse(xhr.response);\r\n\
alert(data.result);\r\n\
}\r\n\
}\r\n\
}\r\n\
btnDelay(2);\r\n\
}\r\n\
function  btnDelay(delayTimeS)\r\n\
{\r\n\
var btn_zero = document.getElementById('btn_manulZero');\r\n\
var btn_open = document.getElementById('btn_manulOpen');\r\n\
var btn_up = document.getElementById('btn_manulUp');\r\n\
var btn_stop = document.getElementById('btn_manulStop');\r\n\
var btn_restart = document.getElementById('btn_manulrestart');\r\n\
btn_zero.disabled = true;\r\n\
btn_open.disabled = true;\r\n\
btn_up.disabled = true;\r\n\
btn_stop.disabled = true;\r\n\
btn_restart.disabled = true;\r\n\
var time =  delayTimeS;\r\n\
var timer = setInterval(countDown,1000);\r\n\
function countDown(){\r\n\
time--;\r\n\
if(time >= 0){\r\n\
btn_zero.innerHTML = '置 零'+time+'S';\r\n\
btn_open.innerHTML = '启 动'+time+'S';\r\n\
btn_up.innerHTML = '抬 杆'+time+'S';\r\n\
btn_stop.innerHTML = '停 止'+time+'S'; \r\n\
btn_restart.innerHTML = '重 启'+time+'S'; \r\n\
}\r\n\
else{\r\n\
btn_zero.innerHTML = '置 零';\r\n\
btn_open.innerHTML = '启 动';\r\n\
btn_up.innerHTML   = '抬 杆';\r\n\
btn_stop.innerHTML = '停 止';\r\n\
btn_restart.innerHTML = '重 启';\r\n\
btn_zero.disabled  = false;\r\n\
btn_open.disabled  = false;\r\n\
btn_up.disabled    = false;\r\n\
btn_stop.disabled  = false;\r\n\
btn_restart.disabled  = false;\r\n\
clearTimeout(timer);\r\n\
}\r\n\
}\r\n\
}\r\n\
</script>\r\n\
</body>\r\n\
</html>\
  ");
    QByteArray contentBytes = content.toUtf8();
   QString sendData=QString(
"HTTP/1.1 200 OK\r\n\
Date: Mon, 27 Jul 2009 12:28:53 GMT\r\n\
Server: ApacheLast-Modified: Wed, 22 Jul 2009 19:15:56 \r\n\
GMTETag: \"34aa387-d-1568eb00\"\r\n\
Accept-Ranges: bytes\r\n\
Content-Type: text/html;charset=UTF-8\r\n\
Content-Length: %1\r\n\
\r\n").arg(contentBytes.size());

    QByteArray bytes = sendData.toUtf8();
    bytes.append(contentBytes);
    write(bytes);
    flush();
    close();
    return ;
    }
    x = str.indexOf("POST /updateWeightState HTTP/");//读取数据
        if(x != -1)
        {
          ******
           QString content = QString(
           "{\"realWeight\":\"%1\",\"unit\":\"%2\",\"realWeightState\":\"%3\",\"meterID\":\"%4\",\"ctime\":\"%5\",\"atuostate\":\"%6\",\"meterName\":\"%7\"}")\
                   .arg(realWeight).arg(unit).arg(realWeightState).arg(meterID).arg(currentTime).arg(P_autoStepRun->readRunStateContent()).arg(bangName);
           QByteArray bytes = content.toUtf8();
           //qDebug()<<"===1==="<<bytes;
           write(bytes);
           //发送完成,后断开连接
           flush();
           close();
           return ;
         }
      //其它指令处理与上相似,不现重复写
        return ;
}

HttpServer::HttpServer(QObject *parent, QString IP, int port):QTcpServer(parent)
{
    listen(QHostAddress(IP),port);
}

HttpServer::~HttpServer()
{
  //qDebug()<<"~Server()";
}

void HttpServer::updateClients(QByteArray msg)
{
    emit updateServer(msg);
}

void HttpServer::slotDisconnected(int descriptor)
{
    for(int i = 0; i < tcpClientSocketList.count();i++)
    {
        HttpSeverTcpClientSocket *item = tcpClientSocketList.at(i);
        if(item->socketDescriptor() == descriptor)
        {
            tcpClientSocketList.removeAt(i);
           item->deleteSelf();
            return;
        }
    }
    return;
}
void HttpServer::incomingConnection(int socketDescriptor)
{
    HttpSeverTcpClientSocket * TcpClientSocketTemp = new HttpSeverTcpClientSocket(this);
    connect(TcpClientSocketTemp,SIGNAL(updateClients(QByteArray)),this,SLOT(updateClients(QByteArray)));
    connect(TcpClientSocketTemp,SIGNAL(disconnected(int)),this,SLOT(slotDisconnected(int)));
    TcpClientSocketTemp->setSocketDescriptor(socketDescriptor);
    tcpClientSocketList.append(TcpClientSocketTemp);
}

五、效果:

在这里插入图片描述
测试过10多人同时登录,功能正常;通过2天客户端连续在线测试,功能正常。满足将来客户的应用需求。
异常点:苹果手机自带浏览器,只显示界面,而不能刷新,可以需要对JSON数据包格式进行完善,以上代码之所以这么写,也是由于没有找到相应的介绍,完全是本人自已通过多种试验总结的方法。庆幸的是在苹果手机上安装百度浏览器即可解决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值