diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index 0b406073..91246bb0 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit 0b406073125080ab8edd13ee7c14e573e54baa35 +Subproject commit 91246bb01475c7336040a4b7ec35d0584887f365 diff --git a/README.md b/README.md index efff6ddf..0eef5ac4 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ - HTTP server,suppor directory meun、RESTful http api. - HTTP client,downloader,uploader,and http api requester. - Cookie supported. - - WebSocket Server. + - WebSocket Server and Client. - File access authentication. - Others @@ -95,7 +95,7 @@ | RTSP[S] Play Server | Y | | RTSP[S] Push Server | Y | | RTMP | Y | -| HTTP[S]/WebSocket | Y | +| HTTP[S]/WebSocket[S] | Y | - Client supported: @@ -106,6 +106,7 @@ | RTMP Player | Y | | RTMP Pusher | Y | | HTTP[S] | Y | +| WebSocket[S] | Y | diff --git a/README_CN.md b/README_CN.md index 33a43a08..918d068a 100644 --- a/README_CN.md +++ b/README_CN.md @@ -51,7 +51,7 @@ - 完整HTTP API服务器,可以作为web后台开发框架。 - 支持跨域访问。 - 支持http客户端、服务器cookie - - 支持WebSocket服务器 + - 支持WebSocket服务器和客户端 - 支持http文件访问鉴权 - 其他 @@ -110,7 +110,7 @@ | RTSP[S] Play Server | Y | | RTSP[S] Push Server | Y | | RTMP | Y | - | HTTP[S]/WebSocket | Y | + | HTTP[S]/WebSocket[S] | Y | - 支持的客户端类型 @@ -121,6 +121,7 @@ | RTMP Player | Y | | RTMP Pusher | Y | | HTTP[S] | Y | + | WebSocket[S] | Y | ## 后续任务 - 完善支持H265 diff --git a/server/main.cpp b/server/main.cpp index cb64df2e..fa72cc9b 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -270,13 +270,13 @@ int main(int argc,char *argv[]) { shellSrv->start(shellPort); rtspSrv->start(rtspPort);//默认554 rtmpSrv->start(rtmpPort);//默认1935 - //http服务器,支持websocket - httpSrv->start(httpPort);//默认80 + //http服务器 + httpSrv->start(httpPort);//默认80 //如果支持ssl,还可以开启https服务器 TcpServer::Ptr httpsSrv(new TcpServer()); //https服务器,支持websocket - httpsSrv->start(httpsPort);//默认443 + httpsSrv->start(httpsPort);//默认443 //支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问 TcpServer::Ptr rtspSSLSrv(new TcpServer()); diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index b986e7ac..bd8686db 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -195,15 +195,14 @@ void HttpSession::onManager() { } } - -inline bool HttpSession::checkWebSocket(){ +bool HttpSession::checkWebSocket(){ auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"]; if(Sec_WebSocket_Key.empty()){ return false; } auto Sec_WebSocket_Accept = encodeBase64(SHA1::encode_bin(Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); - KeyValue headerOut; + KeyValue headerOut = makeHttpHeader(); headerOut["Upgrade"] = "websocket"; headerOut["Connection"] = "Upgrade"; headerOut["Sec-WebSocket-Accept"] = Sec_WebSocket_Accept; @@ -223,12 +222,17 @@ inline bool HttpSession::checkWebSocket(){ } //如果checkLiveFlvStream返回false,则代表不是websocket-flv,而是普通的websocket连接 + if(!onWebSocketConnect(_parser)){ + sendResponse("501 Not Implemented",headerOut,""); + shutdown(SockException(Err_shutdown,"WebSocket server not implemented")); + return true; + } sendResponse("101 Switching Protocols",headerOut,""); return true; } //http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2 //如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。 -inline bool HttpSession::checkLiveFlvStream(const function &cb){ +bool HttpSession::checkLiveFlvStream(const function &cb){ auto pos = strrchr(_parser.Url().data(),'.'); if(!pos){ //未找到".flv"后缀 @@ -316,9 +320,9 @@ inline bool HttpSession::checkLiveFlvStream(const function &cb){ return true; } -inline bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) ; +bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) ; -inline static string findIndexFile(const string &dir){ +static string findIndexFile(const string &dir){ DIR *pDir; dirent *pDirent; if ((pDir = opendir(dir.data())) == NULL) { @@ -336,7 +340,7 @@ inline static string findIndexFile(const string &dir){ return ""; } -inline string HttpSession::getClientUid(){ +string HttpSession::getClientUid(){ //如果http客户端不支持cookie,那么我们可以通过url参数来追踪用户 //如果url参数也没有,那么只能通过ip+端口号来追踪用户 //追踪用户的目的是为了减少http短链接情况的重复鉴权验证,通过缓存记录鉴权结果,提高性能 @@ -349,13 +353,13 @@ inline string HttpSession::getClientUid(){ //字符串是否以xx结尾 -static inline bool end_of(const string &str, const string &substr){ +static bool end_of(const string &str, const string &substr){ auto pos = str.rfind(substr); return pos != string::npos && pos == str.size() - substr.size(); }; //拦截hls的播放请求 -static inline bool checkHls(BroadcastHttpAccessArgs){ +static bool checkHls(BroadcastHttpAccessArgs){ if(!end_of(args._streamid,("/hls.m3u8"))) { //不是hls return false; @@ -371,7 +375,7 @@ static inline bool checkHls(BroadcastHttpAccessArgs){ return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender); } -inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function &callback_in){ +void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function &callback_in){ auto path = path_in; replace(const_cast(path),"//","/"); @@ -472,13 +476,12 @@ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const f } -inline void HttpSession::Handle_Req_GET(int64_t &content_len) { +void HttpSession::Handle_Req_GET(int64_t &content_len) { //先看看是否为WebSocket请求 if(checkWebSocket()){ content_len = -1; - auto parserCopy = _parser; - _contentCallBack = [this,parserCopy](const char *data,uint64_t len){ - onRecvWebSocketData(parserCopy,data,len); + _contentCallBack = [this](const char *data,uint64_t len){ + WebSocketSplitter::decode((uint8_t *)data,len); //_contentCallBack是可持续的,后面还要处理后续数据 return true; }; @@ -666,7 +669,7 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) { }); } -inline bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) { +bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) { string strPathPrefix(strFullPath); string last_dir_name; if(strPathPrefix.back() == '/'){ @@ -764,7 +767,8 @@ inline bool makeMeun(const string &httpPath,const string &strFullPath, string &s ss.str().swap(strRet); return true; } -inline void HttpSession::sendResponse(const char* pcStatus, const KeyValue& header, const string& strContent) { + +void HttpSession::sendResponse(const char* pcStatus, const KeyValue& header, const string& strContent) { _StrPrinter printer; printer << "HTTP/1.1 " << pcStatus << "\r\n"; for (auto &pr : header) { @@ -775,7 +779,8 @@ inline void HttpSession::sendResponse(const char* pcStatus, const KeyValue& head send(strSend); _ticker.resetTime(); } -inline HttpSession::KeyValue HttpSession::makeHttpHeader(bool bClose, int64_t iContentSize,const char* pcContentType) { + +HttpSession::KeyValue HttpSession::makeHttpHeader(bool bClose, int64_t iContentSize,const char* pcContentType) { KeyValue headerOut; GET_CONFIG(string,charSet,Http::kCharSet); GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond); @@ -814,14 +819,14 @@ string HttpSession::urlDecode(const string &str){ return ret; } -inline void HttpSession::urlDecode(Parser &parser){ +void HttpSession::urlDecode(Parser &parser){ parser.setUrl(urlDecode(parser.Url())); for(auto &pr : _parser.getUrlArgs()){ const_cast(pr.second) = urlDecode(pr.second); } } -inline bool HttpSession::emitHttpEvent(bool doInvoke){ +bool HttpSession::emitHttpEvent(bool doInvoke){ ///////////////////是否断开本链接/////////////////////// GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount); @@ -857,7 +862,8 @@ inline bool HttpSession::emitHttpEvent(bool doInvoke){ } return consumed; } -inline void HttpSession::Handle_Req_POST(int64_t &content_len) { + +void HttpSession::Handle_Req_POST(int64_t &content_len) { GET_CONFIG(uint64_t,maxReqSize,Http::kMaxReqSize); GET_CONFIG(int,maxReqCnt,Http::kMaxReqCount); @@ -944,7 +950,8 @@ void HttpSession::responseDelay(bool bClose, } sendResponse(codeOut.data(), headerOut, contentOut); } -inline void HttpSession::sendNotFound(bool bClose) { + +void HttpSession::sendNotFound(bool bClose) { GET_CONFIG(string,notFound,Http::kNotFound); sendResponse("404 Not Found", makeHttpHeader(bClose, notFound.size()), notFound); } diff --git a/src/Http/HttpSession.h b/src/Http/HttpSession.h index 4ab9dcad..e6199668 100644 --- a/src/Http/HttpSession.h +++ b/src/Http/HttpSession.h @@ -72,8 +72,8 @@ protected: void onWrite(const Buffer::Ptr &data) override ; void onDetach() override; std::shared_ptr getSharedPtr() override; - //HttpRequestSplitter override + //HttpRequestSplitter override int64_t onRecvHeader(const char *data,uint64_t len) override; void onRecvContent(const char *data,uint64_t len) override; @@ -94,29 +94,32 @@ protected: shutdown(SockException(Err_shutdown,"http post content is too huge,default closed")); } - void onWebSocketDecodeHeader(const WebSocketHeader &packet) override{ - shutdown(SockException(Err_shutdown,"websocket connection default closed")); - }; - - void onRecvWebSocketData(const Parser &header,const char *data,uint64_t len){ - WebSocketSplitter::decode((uint8_t *)data,len); + /** + * websocket客户端连接上事件 + * @param header http头 + * @return true代表允许websocket连接,否则拒绝 + */ + virtual bool onWebSocketConnect(const Parser &header){ + WarnL << "http server do not support websocket default"; + return false; } + //WebSocketSplitter override /** - * 发送数据进行websocket协议打包后回调 - * @param buffer - */ + * 发送数据进行websocket协议打包后回调 + * @param buffer websocket协议数据 + */ void onWebSocketEncodeData(const Buffer::Ptr &buffer) override; private: - inline void Handle_Req_GET(int64_t &content_len); - inline void Handle_Req_POST(int64_t &content_len); - inline bool checkLiveFlvStream(const function &cb = nullptr); - inline bool checkWebSocket(); - inline bool emitHttpEvent(bool doInvoke); - inline void urlDecode(Parser &parser); - inline void sendNotFound(bool bClose); - inline void sendResponse(const char *pcStatus,const KeyValue &header,const string &strContent); - inline KeyValue makeHttpHeader(bool bClose=false,int64_t iContentSize=-1,const char *pcContentType="text/html"); + void Handle_Req_GET(int64_t &content_len); + void Handle_Req_POST(int64_t &content_len); + bool checkLiveFlvStream(const function &cb = nullptr); + bool checkWebSocket(); + bool emitHttpEvent(bool doInvoke); + void urlDecode(Parser &parser); + void sendNotFound(bool bClose); + void sendResponse(const char *pcStatus,const KeyValue &header,const string &strContent); + KeyValue makeHttpHeader(bool bClose=false,int64_t iContentSize=-1,const char *pcContentType="text/html"); void responseDelay(bool bClose, const string &codeOut, const KeyValue &headerOut, @@ -134,14 +137,14 @@ private: * @param is_dir path是否为目录 * @param callback 有权限或无权限的回调 */ - inline void canAccessPath(const string &path,bool is_dir,const function &callback); + void canAccessPath(const string &path,bool is_dir,const function &callback); /** * 获取用户唯一识别id * 有url参数返回参数,无参数返回ip+端口号 * @return */ - inline string getClientUid(); + string getClientUid(); //设置socket标志 void setSocketFlags(); diff --git a/src/Http/WebSocketClient.cpp b/src/Http/WebSocketClient.cpp deleted file mode 100644 index 1ec0f7a5..00000000 --- a/src/Http/WebSocketClient.cpp +++ /dev/null @@ -1,288 +0,0 @@ -#include "WebSocketClient.h" - - - -int mediakit::WebSocketClient::send(const string& buf) -{ - if (_sock) - { - if (_WSClientStatus == WORKING) - { - _session->send(buf); - return 0; - } - else - { - return -1; - } - } -} - -void mediakit::WebSocketClient::clear() -{ - _method.clear(); - _path.clear(); - _parser.Clear(); - _recvedBodySize = 0; - _totalBodySize = 0; - _aliveTicker.resetTime(); - _chunkedSplitter.reset(); - HttpRequestSplitter::reset(); -} - -const std::string & mediakit::WebSocketClient::responseStatus() const -{ - return _parser.Url(); -} - -const mediakit::WebSocketClient::HttpHeader & mediakit::WebSocketClient::responseHeader() const -{ - return _parser.getValues(); -} - -const mediakit::Parser& mediakit::WebSocketClient::response() const -{ - return _parser; -} - -const std::string & mediakit::WebSocketClient::getUrl() const -{ - return _url; -} - -int64_t mediakit::WebSocketClient::onResponseHeader(const string &status, const HttpHeader &headers) -{ - DebugL << status; - //Content-LengthֶʱĬϺȫcontent - return -1; -} - -void mediakit::WebSocketClient::onResponseBody(const char *buf, int64_t size, int64_t recvedSize, int64_t totalSize) -{ - DebugL << size << " " << recvedSize << " " << totalSize; -} - -void mediakit::WebSocketClient::onResponseCompleted() -{ - DebugL; -} - -int64_t mediakit::WebSocketClient::onRecvHeader(const char *data, uint64_t len) -{ - _parser.Parse(data); - if (_parser.Url() == "101") - { - switch (_WSClientStatus) - { - case HANDSHAKING: - { - StrCaseMap& valueMap = _parser.getValues(); - auto key = valueMap.find("Sec-WebSocket-Accept"); - if (key != valueMap.end() && key->second.length() > 0) { - onConnect(SockException()); - } - break; - } - } - return -1; - } - else - { - shutdown(SockException(Err_shutdown, _parser.Url().c_str())); - return 0; - } - return -1; -} - -void mediakit::WebSocketClient::onRecvContent(const char *data, uint64_t len) -{ - if (_chunkedSplitter) { - _chunkedSplitter->input(data, len); - return; - } - auto recvedBodySize = _recvedBodySize + len; - if (_totalBodySize < 0) { - //޳ȵcontent,֧INT64_MAXֽ - onResponseBody(data, len, recvedBodySize, INT64_MAX); - _recvedBodySize = recvedBodySize; - return; - } - - //̶ȵcontent - if (recvedBodySize < _totalBodySize) { - //contentδ - onResponseBody(data, len, recvedBodySize, _totalBodySize); - _recvedBodySize = recvedBodySize; - return; - } - - //content - onResponseBody(data, _totalBodySize - _recvedBodySize, _totalBodySize, _totalBodySize); - bool biggerThanExpected = recvedBodySize > _totalBodySize; - onResponseCompleted_l(); - if (biggerThanExpected) { - //contentݱʵСôֻȡǰ沿ֵIJϿ - shutdown(SockException(Err_shutdown, "http response content size bigger than expected")); - } -} - -void mediakit::WebSocketClient::onConnect(const SockException &ex) -{ - _aliveTicker.resetTime(); - if (ex) { - onDisconnect(ex); - return; - } - - //ȼhttpͻֻһݣֻhttpͷʡڴ棩 - _sock->setReadBuffer(std::make_shared(1 * 1024)); - - _totalBodySize = 0; - _recvedBodySize = 0; - HttpRequestSplitter::reset(); - _chunkedSplitter.reset(); - - if (_WSClientStatus == WSCONNECT) - { - //Websocket - string random = get_random(16); - auto Sec_WebSocket_Key = encodeBase64(SHA1::encode_bin(random)); - _key = Sec_WebSocket_Key; - string p = generate_websocket_client_handshake(_ip.c_str(), _port, _url.c_str(), _key.c_str()); - TcpClient::send(p); - _WSClientStatus = HANDSHAKING; - } - else if (_WSClientStatus == HANDSHAKING) - { - _WSClientStatus = WORKING; - } - - onFlush(); -} - -void mediakit::WebSocketClient::onRecv(const Buffer::Ptr &pBuf) -{ - _aliveTicker.resetTime(); - if (_WSClientStatus == HANDSHAKING || _WSClientStatus == WSCONNECT) - HttpRequestSplitter::input(pBuf->data(), pBuf->size()); - else if (_WSClientStatus == WORKING) - { - WebSocketSplitter::decode((uint8_t *)pBuf->data(), pBuf->size()); - } -} - -void mediakit::WebSocketClient::onErr(const SockException &ex) -{ - _session->onError(ex); - onDisconnect(ex); -} - -void mediakit::WebSocketClient::onManager() -{ - if (_WSClientStatus != WORKING) - { - if (_fTimeOutSec > 0 && _aliveTicker.elapsedTime() > _fTimeOutSec * 1000) { - //ʱ - shutdown(SockException(Err_timeout, "ws server respone timeout")); - } - } - else - _session->onManager(); -} - -std::string mediakit::WebSocketClient::generate_websocket_client_handshake(const char* ip, uint16_t port, const char * path, const char * key) -{ - /** -* @brief ҵݱƬĵƬС 65535 - 14 - 1 -*/ -#define DATA_FRAME_MAX_LEN 65520 -#define HANDSHAKE_SIZE 1024 - char buf[HANDSHAKE_SIZE] = { 0 }; - - snprintf(buf, HANDSHAKE_SIZE, - "GET %s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Key: %s\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n", - path, ip, port, key); - string temBuf(buf); - return temBuf; -} - -std::string mediakit::WebSocketClient::get_random(size_t n) -{ - random_device rd; - _StrPrinter printer; - for (int i = 0; i < n; i++) - { - unsigned int rnd = rd(); - printer << rnd % 9; - } - - return string(printer); -} - -void mediakit::WebSocketClient::onWebSocketDecodeHeader(const WebSocketHeader &packet) -{ - //°ԭİյ - _remian_data.clear(); - - if (_firstPacket) { - //ǸWebSocketỰͨHttpỰ - _firstPacket = false; - //˴ȡݲwebsocketЭ - } -} - -void mediakit::WebSocketClient::onWebSocketDecodePlayload(const WebSocketHeader &packet, const uint8_t *ptr, uint64_t len, uint64_t recved) -{ - _remian_data.append((char *)ptr, len); -} - -void mediakit::WebSocketClient::onWebSocketDecodeComplete(const WebSocketHeader &header_in) -{ - WebSocketHeader& header = const_cast(header_in); - auto flag = header._mask_flag; - header._mask_flag = false; - - switch (header._opcode) { - case WebSocketHeader::CLOSE: { - shutdown(SockException(Err_timeout, "session timeouted")); - } - break; - case WebSocketHeader::PING: { - const_cast(header)._opcode = WebSocketHeader::PONG; - WebSocketSplitter::encode(header, (uint8_t *)_remian_data.data(), _remian_data.size()); - } - break; - case WebSocketHeader::CONTINUATION: { - - } - break; - case WebSocketHeader::TEXT: - case WebSocketHeader::BINARY: { - BufferString::Ptr buffer = std::make_shared(_remian_data); - _session->onRecv(buffer); - } - break; - default: - break; - } - _remian_data.clear(); - header._mask_flag = flag; -} - -void mediakit::WebSocketClient::onWebSocketEncodeData(const uint8_t *ptr, uint64_t len) -{ - TcpClient::send(string((char*)ptr, len)); -} - -void mediakit::WebSocketClient::onResponseCompleted_l() -{ - _totalBodySize = 0; - _recvedBodySize = 0; - onResponseCompleted(); -} diff --git a/src/Http/WebSocketClient.h b/src/Http/WebSocketClient.h index 51a00874..6eeae0ac 100644 --- a/src/Http/WebSocketClient.h +++ b/src/Http/WebSocketClient.h @@ -1,205 +1,371 @@ -#ifndef Http_WebSocketClient_h -#define Http_WebSocketClient_h +/* + * MIT License + * + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZLMEDIAKIT_WebSocketClient_H +#define ZLMEDIAKIT_WebSocketClient_H -#include -#include -#include -#include #include "Util/util.h" -#include "Util/mini.h" -#include "Network/TcpClient.h" -#include "Common/Parser.h" -#include "HttpRequestSplitter.h" -#include "HttpCookie.h" -#include "HttpChunkedSplitter.h" -#include "strCoding.h" -#include "Http/HttpClient.h" -#include "Http/WebSocketSplitter.h" -#include "Http/WebSocketSession.h" -#include -#include -#include "Common/config.h" -#include "Util/SHA1.h" #include "Util/base64.h" - - - -using namespace std; +#include "Util/SHA1.h" +#include "Network/TcpClient.h" +#include "HttpClientImp.h" +#include "WebSocketSplitter.h" using namespace toolkit; -namespace mediakit { +namespace mediakit{ + +template +class HttpWsClient; /** -* @brief 客户端的状态 -*/ -typedef enum WSClientStatus { - WSCONNECT, - HANDSHAKING, ///握手中 - WORKING, ///工作中 -} WSClientStatus; - -class WebSocketClient : public TcpClient , public HttpRequestSplitter, public WebSocketSplitter -{ + * 辅助类,用于拦截TcpClient数据发送前的拦截 + * @tparam ClientType TcpClient派生类 + * @tparam DataType 这里无用,为了声明友元用 + */ +template +class ClientTypeImp : public ClientType { public: - typedef StrCaseMap HttpHeader; - typedef std::shared_ptr Ptr; - WebSocketClient() :_WSClientStatus(WSCONNECT) {} - virtual ~WebSocketClient() {} - - template - void startConnect(const string &strUrl, uint16_t iPort, float fTimeOutSec = 3) - { - _ip = strUrl; - _port = iPort; - TcpClient::startConnect(strUrl, iPort, fTimeOutSec); - - typedef function onBeforeSendCB; - /** - * 该类实现了TcpSession派生类发送数据的截取 - * 目的是发送业务数据前进行websocket协议的打包 - */ - class SessionImp : public SessionType { - public: - SessionImp(const Socket::Ptr &pSock) :SessionType(pSock) {} - - ~SessionImp() {} - - /** - * 设置发送数据截取回调函数 - * @param cb 截取回调函数 - */ - void setOnBeforeSendCB(const onBeforeSendCB &cb) { - _beforeSendCB = cb; - } - protected: - /** - * 重载send函数截取数据 - * @param buf 需要截取的数据 - * @return 数据字节数 - */ - int send(const Buffer::Ptr &buf) override { - if (_beforeSendCB) { - return _beforeSendCB(buf); - } - return SessionType::send(buf); - } - private: - onBeforeSendCB _beforeSendCB; - }; - std::shared_ptr temSession = std::make_shared(_sock); - //此处截取数据并进行websocket协议打包 - weak_ptr weakSelf = dynamic_pointer_cast(WebSocketClient::shared_from_this()); - - _sock->setOnErr([weakSelf](const SockException &ex) { - auto strongSelf = weakSelf.lock(); - if (!strongSelf) { - return; - } - strongSelf->onErr(ex); - }); - - temSession->setOnBeforeSendCB([weakSelf](const Buffer::Ptr &buf) { - auto strongSelf = weakSelf.lock(); - if (strongSelf) { - WebSocketHeader header; - header._fin = true; - header._reserved = 0; - header._opcode = WebSocketHeader::BINARY; - header._mask_flag = false; - strongSelf->WebSocketSplitter::encode(header, (uint8_t *)buf->data(), buf->size()); - } - return buf->size(); - }); - _session = temSession; - _session->onManager(); - } - - virtual int send(const string& buf); - - virtual void clear(); - - const string &responseStatus() const; - - const HttpHeader &responseHeader() const; - - const Parser& response() const; - - const string &getUrl() const; + typedef function onBeforeSendCB; + friend class HttpWsClient; + template + ClientTypeImp(ArgsType &&...args): ClientType(std::forward(args)...){} + ~ClientTypeImp() override {}; protected: - virtual int64_t onResponseHeader(const string &status,const HttpHeader &headers);; + /** + * 发送前拦截并打包为websocket协议 + * @param buf + * @return + */ + int send(const Buffer::Ptr &buf) override{ + if(_beforeSendCB){ + return _beforeSendCB(buf); + } + return ClientType::send(buf); + } + /** + * 设置发送数据截取回调函数 + * @param cb 截取回调函数 + */ + void setOnBeforeSendCB(const onBeforeSendCB &cb){ + _beforeSendCB = cb; + } +private: + onBeforeSendCB _beforeSendCB; +}; - virtual void onResponseBody(const char *buf,int64_t size,int64_t recvedSize,int64_t totalSize);; +/** + * 此对象完成了weksocket 客户端握手协议,以及到TcpClient派生类事件的桥接 + * @tparam ClientType TcpClient派生类 + * @tparam DataType websocket负载类型,是TEXT还是BINARY类型 + */ +template +class HttpWsClient : public HttpClientImp , public WebSocketSplitter{ +public: + typedef shared_ptr Ptr; + + HttpWsClient(ClientTypeImp &delegate) : _delegate(delegate){ + _Sec_WebSocket_Key = encodeBase64(SHA1::encode_bin(makeRandStr(16, false))); + } + ~HttpWsClient(){} + + /** + * 发起ws握手 + * @param ws_url ws连接url + * @param fTimeOutSec 超时时间 + */ + void startWsClient(const string &ws_url,float fTimeOutSec){ + string http_url = ws_url; + replace(http_url,"ws://","http://"); + replace(http_url,"wss://","https://"); + setMethod("GET"); + addHeader("Upgrade","websocket"); + addHeader("Connection","Upgrade"); + addHeader("Sec-WebSocket-Version","13"); + addHeader("Sec-WebSocket-Key",_Sec_WebSocket_Key); + _onRecv = nullptr; + sendRequest(http_url,fTimeOutSec); + } +protected: + //HttpClientImp override + + /** + * 收到http回复头 + * @param status 状态码,譬如:200 OK + * @param headers http头 + * @return 返回后续content的长度;-1:后续数据全是content;>=0:固定长度content + * 需要指出的是,在http头中带有Content-Length字段时,该返回值无效 + */ + int64_t onResponseHeader(const string &status,const HttpHeader &headers) override { + if(status == "101"){ + auto Sec_WebSocket_Accept = encodeBase64(SHA1::encode_bin(_Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); + if(Sec_WebSocket_Accept == const_cast(headers)["Sec-WebSocket-Accept"]){ + //success + onWebSocketException(SockException()); + return 0; + } + shutdown(SockException(Err_shutdown,StrPrinter << "Sec-WebSocket-Accept mismatch")); + return 0; + } + + shutdown(SockException(Err_shutdown,StrPrinter << "bad http status code:" << status)); + return 0; + }; /** * 接收http回复完毕, */ - virtual void onResponseCompleted(); + void onResponseCompleted() override {} + + //TcpClient override /** - * http链接断开回调 - * @param ex 断开原因 + * 定时触发 */ - virtual void onDisconnect(const SockException &ex){} + void onManager() override { + if(_onRecv){ + //websocket连接成功了 + _delegate.onManager(); + } else{ + //websocket连接中... + HttpClientImp::onManager(); + } + } - //HttpRequestSplitter override - int64_t onRecvHeader(const char *data, uint64_t len) override; + /** + * 数据全部发送完毕后回调 + */ + void onFlush() override{ + if(_onRecv){ + //websocket连接成功了 + _delegate.onFlush(); + } else{ + //websocket连接中... + HttpClientImp::onFlush(); + } + } - void onRecvContent(const char *data, uint64_t len) override; + /** + * tcp连接结果 + * @param ex + */ + void onConnect(const SockException &ex) override{ + if(ex){ + //tcp连接失败,直接返回失败 + onWebSocketException(ex); + return; + } + //开始websocket握手 + HttpClientImp::onConnect(ex); + } -protected: - virtual void onConnect(const SockException &ex) override; + /** + * tcp收到数据 + * @param pBuf + */ + void onRecv(const Buffer::Ptr &pBuf) override{ + if(_onRecv){ + //完成websocket握手后,拦截websocket数据并解析 + _onRecv(pBuf); + }else{ + //websocket握手数据 + HttpClientImp::onRecv(pBuf); + } + } - virtual void onRecv(const Buffer::Ptr &pBuf) override; + /** + * tcp连接断开 + * @param ex + */ + void onErr(const SockException &ex) override{ + //tcp断开或者shutdown导致的断开 + onWebSocketException(ex); + } - virtual void onErr(const SockException &ex) override; + //WebSocketSplitter override - virtual void onFlush() override {}; + /** + * 收到一个webSocket数据包包头,后续将继续触发onWebSocketDecodePlayload回调 + * @param header 数据包头 + */ + void onWebSocketDecodeHeader(const WebSocketHeader &header) override{ + _payload.clear(); + } - virtual void onManager() override; + /** + * 收到webSocket数据包负载 + * @param header 数据包包头 + * @param ptr 负载数据指针 + * @param len 负载数据长度 + * @param recved 已接收数据长度(包含本次数据长度),等于header._playload_len时则接受完毕 + */ + void onWebSocketDecodePlayload(const WebSocketHeader &header, const uint8_t *ptr, uint64_t len, uint64_t recved) override{ + _payload.append((char *)ptr,len); + } -protected: - string generate_websocket_client_handshake(const char* ip, uint16_t port, const char * path, const char * key); - string get_random(size_t n); + /** + * 接收到完整的一个webSocket数据包后回调 + * @param header 数据包包头 + */ + void onWebSocketDecodeComplete(const WebSocketHeader &header_in) override{ + WebSocketHeader& header = const_cast(header_in); + auto flag = header._mask_flag; + //websocket客户端发送数据需要加密 + header._mask_flag = true; - void onWebSocketDecodeHeader(const WebSocketHeader &packet) override; + switch (header._opcode){ + case WebSocketHeader::CLOSE:{ + //服务器主动关闭 + WebSocketSplitter::encode(header,nullptr); + shutdown(SockException(Err_eof,"websocket server close the connection")); + } + break; + case WebSocketHeader::PING:{ + //心跳包 + header._opcode = WebSocketHeader::PONG; + WebSocketSplitter::encode(header,std::make_shared(std::move(_payload))); + } + break; + case WebSocketHeader::CONTINUATION:{ - void onWebSocketDecodePlayload(const WebSocketHeader &packet, const uint8_t *ptr, uint64_t len, uint64_t recved) override; + } + break; + case WebSocketHeader::TEXT: + case WebSocketHeader::BINARY:{ + //接收完毕websocket数据包,触发onRecv事件 + _delegate.onRecv(std::make_shared(std::move(_payload))); + } + break; + default: + break; + } + _payload.clear(); + header._mask_flag = flag; + } - void onWebSocketDecodeComplete(const WebSocketHeader &header_in) override; + /** + * websocket数据编码回调 + * @param ptr 数据指针 + * @param len 数据指针长度 + */ + void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{ + HttpClientImp::send(buffer); + } +private: + void onWebSocketException(const SockException &ex){ + if(!ex){ + //websocket握手成功 + //此处截取TcpClient派生类发送的数据并进行websocket协议打包 + weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + _delegate.setOnBeforeSendCB([weakSelf](const Buffer::Ptr &buf){ + auto strongSelf = weakSelf.lock(); + if(strongSelf){ + WebSocketHeader header; + header._fin = true; + header._reserved = 0; + header._opcode = DataType; + //客户端需要加密 + header._mask_flag = true; + strongSelf->WebSocketSplitter::encode(header,buf); + } + return buf->size(); + }); - virtual void onWebSocketEncodeData(const uint8_t *ptr, uint64_t len); + //设置sock,否则shutdown等接口都无效 + _delegate.setSock(HttpClientImp::_sock); + //触发连接成功事件 + _delegate.onConnect(ex); + //拦截websocket数据接收 + _onRecv = [this](const Buffer::Ptr &pBuf){ + //解析websocket数据包 + WebSocketSplitter::decode((uint8_t*)pBuf->data(),pBuf->size()); + }; + return; + } + + //websocket握手失败或者tcp连接失败或者中途断开 + if(_onRecv){ + //握手成功之后的中途断开 + _onRecv = nullptr; + _delegate.onErr(ex); + return; + } + + //websocket握手失败或者tcp连接失败 + _delegate.onConnect(ex); + } private: - void onResponseCompleted_l(); - -protected: - bool _isHttps; -private: - string _ip; - int _port; - string _url; - string _method; - string _path; - //recv - int64_t _recvedBodySize; - int64_t _totalBodySize; - Parser _parser; - string _lastHost; - Ticker _aliveTicker; - float _fTimeOutSec = 0; - std::shared_ptr _chunkedSplitter; - - std::string _key; ///客户端的key - WSClientStatus _WSClientStatus; ///客户端状态 - bool _firstPacket = true; - string _remian_data; - - std::shared_ptr _session; - + string _Sec_WebSocket_Key; + function _onRecv; + ClientTypeImp &_delegate; + string _payload; }; -} /* namespace mediakit */ -#endif /* Http_HttpClient_h */ +/** + * Tcp客户端转WebSocket客户端模板, + * 通过该模板,开发者再不修改TcpClient派生类任何代码的情况下快速实现WebSocket协议的包装 + * @tparam ClientType TcpClient派生类 + * @tparam DataType websocket负载类型,是TEXT还是BINARY类型 + * @tparam useWSS 是否使用ws还是wss连接 + */ +template +class WebSocketClient : public ClientTypeImp{ +public: + typedef std::shared_ptr Ptr; + + template + WebSocketClient(ArgsType &&...args) : ClientTypeImp(std::forward(args)...){ + _wsClient.reset(new HttpWsClient(*this)); + } + ~WebSocketClient() override {} + + /** + * 重载startConnect方法, + * 目的是替换TcpClient的连接服务器行为,使之先完成WebSocket握手 + * @param strUrl websocket服务器ip或域名 + * @param iPort websocket服务器端口 + * @param fTimeOutSec 超时时间 + */ + void startConnect(const string &strUrl, uint16_t iPort, float fTimeOutSec = 3) override { + string ws_url; + if(useWSS){ + //加密的ws + ws_url = StrPrinter << "wss://" + strUrl << ":" << iPort << "/" ; + }else{ + //明文ws + ws_url = StrPrinter << "ws://" + strUrl << ":" << iPort << "/" ; + } + _wsClient->startWsClient(ws_url,fTimeOutSec); + } +private: + typename HttpWsClient::Ptr _wsClient; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_WebSocketClient_H diff --git a/src/Http/WebSocketSession.h b/src/Http/WebSocketSession.h index d8f2504e..fe4d443a 100644 --- a/src/Http/WebSocketSession.h +++ b/src/Http/WebSocketSession.h @@ -34,7 +34,7 @@ * 用户只要实现WebSock协议下的具体业务协议,譬如基于WebSocket协议的Rtmp协议等 * @tparam SessionType 业务协议的TcpSession类 */ -template +template class WebSocketSession : public HttpSessionType { public: WebSocketSession(const Socket::Ptr &pSock) : HttpSessionType(pSock){} @@ -61,6 +61,37 @@ public: _weakServer = const_cast(server).shared_from_this(); } protected: + /** + * websocket客户端连接上事件 + * @param header http头 + * @return true代表允许websocket连接,否则拒绝 + */ + bool onWebSocketConnect(const Parser &header) override{ + //创建websocket session类 + _session = std::make_shared(HttpSessionType::getIdentifier(),HttpSessionType::_sock); + auto strongServer = _weakServer.lock(); + if(strongServer){ + _session->attachServer(*strongServer); + } + + //此处截取数据并进行websocket协议打包 + weak_ptr weakSelf = dynamic_pointer_cast(HttpSessionType::shared_from_this()); + _session->setOnBeforeSendCB([weakSelf](const Buffer::Ptr &buf){ + auto strongSelf = weakSelf.lock(); + if(strongSelf){ + WebSocketHeader header; + header._fin = true; + header._reserved = 0; + header._opcode = DataType; + header._mask_flag = false; + strongSelf->WebSocketSplitter::encode(header,buf); + } + return buf->size(); + }); + + //允许websocket客户端 + return true; + } /** * 开始收到一个webSocket数据包 * @param packet @@ -68,34 +99,6 @@ protected: void onWebSocketDecodeHeader(const WebSocketHeader &packet) override{ //新包,原来的包残余数据清空掉 _remian_data.clear(); - - if(_firstPacket){ - //这是个WebSocket会话而不是普通的Http会话 - _firstPacket = false; - _session = std::make_shared(HttpSessionType::getIdentifier(),HttpSessionType::_sock); - - auto strongServer = _weakServer.lock(); - if(strongServer){ - - //此处截取数据并进行websocket协议打包 - weak_ptr weakSelf = dynamic_pointer_cast(HttpSessionType::shared_from_this()); - _session->setOnBeforeSendCB([weakSelf](const Buffer::Ptr &buf) { - auto strongSelf = weakSelf.lock(); - if (strongSelf) { - WebSocketHeader header; - header._fin = true; - header._reserved = 0; - header._opcode = WebSocketHeader::TEXT; - header._mask_flag = false; - strongSelf->WebSocketSplitter::encode(header, (uint8_t *)buf->data(), buf->size()); - } - return buf->size(); - }); - - _session->attachServer(*strongServer); - } - - } } /** @@ -124,7 +127,7 @@ protected: } break; case WebSocketHeader::PING:{ - const_cast(header)._opcode = WebSocketHeader::PONG; + header._opcode = WebSocketHeader::PONG; HttpSessionType::encode(header,std::make_shared(_remian_data)); } break; @@ -191,42 +194,10 @@ private: string _identifier; }; private: - bool _firstPacket = true; string _remian_data; weak_ptr _weakServer; std::shared_ptr _session; }; -/** -* 回显会话 -*/ -class EchoSession : public TcpSession { -public: - EchoSession(const Socket::Ptr &pSock) : TcpSession(pSock){ - DebugL; - } - virtual ~EchoSession(){ - DebugL; - } - - void attachServer(const TcpServer &server) override{ - DebugL << getIdentifier() << " " << TcpSession::getIdentifier(); - } - void onRecv(const Buffer::Ptr &buffer) override { - send(buffer); - } - void onError(const SockException &err) override{ - WarnL << err.what(); - } - //每隔一段时间触发,用来做超时管理 - void onManager() override{ - DebugL; - } -}; - - -typedef WebSocketSession EchoWebSocketSession; -typedef WebSocketSession SSLEchoWebSocketSession; - #endif //ZLMEDIAKIT_WEBSOCKETSESSION_H diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..4ae8569f --- /dev/null +++ b/tests/README.md @@ -0,0 +1,32 @@ +此目录下的所有.cpp文件将被编译成可执行程序(不包含此目录下的子目录). +子目录DeviceHK为海康IPC的适配程序,需要先下载海康的SDK才能编译, +由于操作麻烦,所以仅把源码放在这仅供参考. + +- test_benchmark.cpp + rtsp/rtmp性能测试客户端 + +- test_httpApi.cpp + http api 测试服务器 + +- test_httpClient.cpp + http 测试客户端 + +- test_player.cpp + rtsp/rtmp带视频渲染的客户端 + +- test_pusher.cpp + 先拉流再推流的测试客户端 + +- test_pusherMp4.cpp + 解复用mp4文件再推流的测试客户端 + +- test_server.cpp + rtsp/rtmp/http等服务器 + +- test_wsClient.cpp + websocket测试客户端 + +- test_wsServer.cpp + websocket回显测试服务器 + + diff --git a/tests/README.txt b/tests/README.txt deleted file mode 100644 index 1c4c521d..00000000 --- a/tests/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -此目录下的所有.cpp文件将被编译成可执行程序(不包含此目录下的子目录). -子目录DeviceHK为海康IPC的适配程序,需要先下载海康的SDK才能编译, -由于操作麻烦,所以仅把源码放在这仅供参考. diff --git a/tests/test_httpApi.cpp b/tests/test_httpApi.cpp index bf8ff4d3..8694eac6 100644 --- a/tests/test_httpApi.cpp +++ b/tests/test_httpApi.cpp @@ -124,11 +124,11 @@ int main(int argc,char *argv[]){ //开启http服务器 TcpServer::Ptr httpSrv(new TcpServer()); - httpSrv->start(mINI::Instance()[Http::kPort]);//默认80 + httpSrv->start(mINI::Instance()[Http::kPort]);//默认80 //如果支持ssl,还可以开启https服务器 TcpServer::Ptr httpsSrv(new TcpServer()); - httpsSrv->start(mINI::Instance()[Http::kSSLPort]);//默认443 + httpsSrv->start(mINI::Instance()[Http::kSSLPort]);//默认443 InfoL << "你可以在浏览器输入:http://127.0.0.1/api/my_api?key0=val0&key1=参数1" << endl; diff --git a/tests/test_server.cpp b/tests/test_server.cpp index 6ba58bd8..6c9d6bf7 100644 --- a/tests/test_server.cpp +++ b/tests/test_server.cpp @@ -300,13 +300,13 @@ int main(int argc,char *argv[]) { shellSrv->start(shellPort); rtspSrv->start(rtspPort);//默认554 rtmpSrv->start(rtmpPort);//默认1935 - //http服务器,支持websocket - httpSrv->start(httpPort);//默认80 + //http服务器 + httpSrv->start(httpPort);//默认80 //如果支持ssl,还可以开启https服务器 TcpServer::Ptr httpsSrv(new TcpServer()); - //https服务器,支持websocket - httpsSrv->start(httpsPort);//默认443 + //https服务器 + httpsSrv->start(httpsPort);//默认443 //支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问 TcpServer::Ptr rtspSSLSrv(new TcpServer()); @@ -332,12 +332,12 @@ int main(int argc,char *argv[]) { } if(httpPort != mINI::Instance()[Http::kPort].as()){ httpPort = mINI::Instance()[Http::kPort]; - httpSrv->start(httpPort); + httpSrv->start(httpPort); InfoL << "重启http服务器" << httpPort; } if(httpsPort != mINI::Instance()[Http::kSSLPort].as()){ httpsPort = mINI::Instance()[Http::kSSLPort]; - httpsSrv->start(httpsPort); + httpsSrv->start(httpsPort); InfoL << "重启https服务器" << httpsPort; } diff --git a/tests/test_wsClient.cpp b/tests/test_wsClient.cpp new file mode 100644 index 00000000..a0b025e5 --- /dev/null +++ b/tests/test_wsClient.cpp @@ -0,0 +1,84 @@ +/* + * MIT License + * + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include "Util/MD5.h" +#include "Util/logger.h" +#include "Http/WebSocketClient.h" +using namespace std; +using namespace toolkit; +using namespace mediakit; + +class EchoTcpClient : public TcpClient { +public: + EchoTcpClient(const EventPoller::Ptr &poller = nullptr){ + InfoL; + } + ~EchoTcpClient() override { + InfoL; + } +protected: + void onRecv(const Buffer::Ptr &pBuf) override { + DebugL << pBuf->toString(); + } + //被动断开连接回调 + void onErr(const SockException &ex) override { + WarnL << ex.what(); + } + //tcp连接成功后每2秒触发一次该事件 + void onManager() override { + send("echo test!"); + DebugL << "send echo test"; + } + //连接服务器结果回调 + void onConnect(const SockException &ex) override{ + DebugL << ex.what(); + } + + //数据全部发送完毕后回调 + void onFlush() override{ + DebugL; + } +}; + +int main(int argc, char *argv[]) { + //设置退出信号处理函数 + static semaphore sem; + signal(SIGINT, [](int) { sem.post(); });// 设置退出信号 + + //设置日志 + Logger::Instance().add(std::make_shared()); + Logger::Instance().setWriter(std::make_shared()); + + WebSocketClient::Ptr client = std::make_shared >(); + client->startConnect("121.40.165.18",8800); + + sem.wait(); + return 0; +} + diff --git a/tests/test_wsServer.cpp b/tests/test_wsServer.cpp new file mode 100644 index 00000000..12891889 --- /dev/null +++ b/tests/test_wsServer.cpp @@ -0,0 +1,88 @@ +/* + * MIT License + * + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include "Util/MD5.h" +#include "Util/logger.h" +#include "Http/WebSocketSession.h" +using namespace std; +using namespace toolkit; +using namespace mediakit; + +/** +* 回显会话 +*/ +class EchoSession : public TcpSession { +public: + EchoSession(const Socket::Ptr &pSock) : TcpSession(pSock){ + DebugL; + } + virtual ~EchoSession(){ + DebugL; + } + + void attachServer(const TcpServer &server) override{ + DebugL << getIdentifier() << " " << TcpSession::getIdentifier(); + } + void onRecv(const Buffer::Ptr &buffer) override { + //回显数据 + send(buffer); + } + void onError(const SockException &err) override{ + WarnL << err.what(); + } + //每隔一段时间触发,用来做超时管理 + void onManager() override{ + DebugL; + } +}; + +int main(int argc, char *argv[]) { + //设置日志 + Logger::Instance().add(std::make_shared()); + Logger::Instance().setWriter(std::make_shared()); + + SSL_Initor::Instance().loadCertificate((exeDir() + "ssl.p12").data()); + + TcpServer::Ptr httpSrv(new TcpServer()); + //http服务器,支持websocket + httpSrv->start>(80);//默认80 + + TcpServer::Ptr httpsSrv(new TcpServer()); + //https服务器,支持websocket + httpsSrv->start>(443);//默认443 + + DebugL << "请打开网页:http://www.websocket-test.com/,连接 ws://127.0.0.1/测试"; + + //设置退出信号处理函数 + static semaphore sem; + signal(SIGINT, [](int) { sem.post(); });// 设置退出信号 + sem.wait(); + return 0; +} +