Commit 9e462daa authored by alexstocks's avatar alexstocks

add websocket echo example

parent 6dc995dd
/******************************************************
# DESC : echo package
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-08-22 17:44
# FILE : utils.go
******************************************************/
package main
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"unsafe"
)
import (
log "github.com/AlexStocks/log4go"
)
////////////////////////////////////////////
// echo command
////////////////////////////////////////////
type echoCommand uint32
const (
heartbeatCmd = iota
echoCmd
)
var echoCommandStrings = [...]string{
"heartbeat",
"echo",
}
func (c echoCommand) String() string {
return echoCommandStrings[c]
}
////////////////////////////////////////////
// EchoPkgHandler
////////////////////////////////////////////
const (
echoPkgMagic = 0x20160905
maxEchoStringLen = 0xff
echoHeartbeatRequestString = "ping"
echoHeartbeatResponseString = "pong"
)
var (
ErrNotEnoughSteam = errors.New("packet stream is not enough")
ErrTooLargePackage = errors.New("package length is exceed the echo package's legal maximum length.")
ErrIllegalMagic = errors.New("package magic is not right.")
)
var (
echoPkgHeaderLen int
)
func init() {
echoPkgHeaderLen = (int)((uint)(unsafe.Sizeof(EchoPkgHeader{})))
}
type EchoPkgHeader struct {
Magic uint32
LogID uint32 // log id
Sequence uint32 // request/response sequence
ServiceID uint32 // service id
Command uint32 // operation command code
Code int32 // error code
Len uint16 // body length
_ uint16
_ int32 // reserved, maybe used as package md5 checksum
}
type EchoPackage struct {
H EchoPkgHeader
B string
}
func (this EchoPackage) String() string {
return fmt.Sprintf("log id:%d, sequence:%d, command:%s, echo string:%s",
this.H.LogID, this.H.Sequence, (echoCommand(this.H.Command)).String(), this.B)
}
func (this EchoPackage) Marshal() (*bytes.Buffer, error) {
var (
err error
buf *bytes.Buffer
)
buf = &bytes.Buffer{}
err = binary.Write(buf, binary.LittleEndian, this.H)
if err != nil {
return nil, err
}
buf.WriteByte((byte)(len(this.B)))
buf.WriteString(this.B)
return buf, nil
}
func (this *EchoPackage) Unmarshal(buf *bytes.Buffer) (int, error) {
var (
err error
len byte
)
if buf.Len() < echoPkgHeaderLen {
return 0, ErrNotEnoughSteam
}
// header
err = binary.Read(buf, binary.LittleEndian, &(this.H))
if err != nil {
return 0, err
}
if this.H.Magic != echoPkgMagic {
log.Error("@this.H.Magic{%x}, right magic{%x}", this.H.Magic, echoPkgMagic)
return 0, ErrIllegalMagic
}
if buf.Len() < (int)(this.H.Len) {
return 0, ErrNotEnoughSteam
}
if maxEchoStringLen < this.H.Len {
return 0, ErrTooLargePackage
}
len, err = buf.ReadByte()
if err != nil {
return 0, nil
}
this.B = (string)(buf.Next((int)(len)))
return (int)(this.H.Len) + 1 + echoPkgHeaderLen, nil
}
/******************************************************
# DESC : getty utility
# DESC : echo package
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
......
This diff is collapsed.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Echo Example</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<ul class="pages">
<li class="chat page">
<div class="chatArea">
<ul class="messages"></ul>
</div>
<input class="inputMessage" placeholder=":img url"/>
</li>
</ul>
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="encoding.js"></script>
<script src="struct.min.js"></script>
<script src="main.js"></script>
</body>
</html>
$(function() {
var FADE_TIME = 150; // ms
var TYPING_TIMER_LENGTH = 400; // ms
var COLORS = [
'#e21400', '#91580f', '#f8a700', '#f78b00',
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
];
var seq = 0;
var echoCommand = 0x01;
var heartbeatCommand = 0x02;
/**
* create struct
*
* @param {object} data object
* @param {number} default value[optional, default 0]
* @param {boolean} endian[optional, default true]
*/
var echoPkgHeader = new Struct ({
Magic:'uint32',
LogID:'uint32', // log id
Sequence: 'uint32', // request/response sequence
ServiceID:'uint32', // service id
Command:'uint32', // operation command code
Code :'int32', // error code
Len:'uint16', // body length
extra1:'uint16',
extra2:'int32' // reserved, maybe used as package md5 checksum
}, 0, true);
var echoPkgBody;
// echoPkgHeader.endianness = true; // true is Little-endian
// var echoPkg = new Struct({
// H:echoPkgHeader
// B:string,
// }, 0, true);
// Initialize varibles
var $window = $(window);
var $messages = $('.messages'); // Messages area
var $inputMessage = $('.inputMessage'); // Input message input box
var $chatPage = $('.chat.page'); // The chatroom page
var socket = new WebSocket('ws://192.168.35.1:10000/echo');
// // Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'. In default it is 'blob'.
// socket.binaryType = 'arraybuffer';
// socket.binaryType = ''
// var decoder = new TextDecoder('utf-8')
function socketSend (o, silence) {
if (socket.readyState != socket.OPEN) {
if (!silence) {
addChatMessage({
Message: '!!Connection closed'
});
}
return
}
socket.send(JSON.stringify(o))
}
function marshalEchoPkg(header, msg) {
var tmp = new Uint8Array(header.byteLength + 1 + msg.length);
tmp.set(new Uint8Array(header), 0);
tmp[header.byteLength] = msg.length
var ma = new TextEncoder("utf-8").encode(msg);
tmp[0] = msg.length;
tmp.set(ma, header.byteLength + 1);
return tmp;
// return tmp.buffer;
}
function AB2Str(buf) {
var decoder = new TextDecoder('utf-8');
// var json = JSON.parse(decoder.decode(new DataView(buf)))
var msg = decoder.decode(new DataView(buf));
// console.log('response text msg: ' + msg);
return msg;
}
function unmarshalEchoPkg(data) {
var dataAB = new Uint8Array(data);
var echoPkgHeader = new Struct ({
Magic:'uint32',
LogID:'uint32', // log id
Sequence: 'uint32', // request/response sequence
ServiceID:'uint32', // service id
Command:'uint32', // operation command code
Code :'int32', // error code
Len:'uint16', // body length
extra1:'uint16',
extra2:'int32' // reserved, maybe used as package md5 checksum
}, 0, true);
var hLen = echoPkgHeader.byteLength;
var rspHeaderData = dataAB.subarray(0, hLen);
var rspHeader = echoPkgHeader.read(rspHeaderData.buffer);
// console.log("echo rsp package header:" + rspHeader.Magic);
console.log('echo response{seq:' + rspHeader.Sequence + ', msg len:' + dataAB[hLen] + '}');
addChatMessage({Message:
'echo response{seq:' + rspHeader.Sequence +
', msg len:' + dataAB[hLen] +
', msg:' + AB2Str(new Uint8Array(dataAB.subarray(hLen + 1)).buffer) +
'}'
}
);
}
// Sends a chat message
function sendMessage () {
var message = $inputMessage.val();
// console.log("input mesage:" + message)
// Prevent markup from being injected into the message
message = cleanInput(message);
// console.log("after cleanInput mesage:" + message)
// Prevent markup from being injected into the message
// if there is a non-empty message and a socket connection
if (message) {
$inputMessage.val('');
addChatMessage({
Message: message
});
var pkgHeaderArrayBuffer = echoPkgHeader.write({
Magic:0x20160905,
LogID:Math.round(Math.random() * 2147483647),
Command:echoCommand,
Sequence:seq++,
Code:0,
Len:message.length,
extra1:0,
extra2:0
});
// console.log(marshalEchoPkg(pkgHeaderArrayBuffer, message));
socket.send(marshalEchoPkg(pkgHeaderArrayBuffer, message));
}
}
//断开连接
function disconnect() {
if (socket != null) {
socket.close();
socket = null;
addChatMessage({
Message: '!!SYSTEM-WS-Close, connection disconnect'
})
}
}
// Log a message
function log (message, options) {
var $el = $('<li>').addClass('log').text(message);
addMessageElement($el, options);
}
var imgReg = /:img\s+(\S+)/
// Adds the visual chat message to the message list
function addChatMessage (data, options) {
// Don't fade the message in if there is an 'X was typing'
options = options || {};
var regRes = imgReg.exec(data.Message)
if (regRes != null) {
var $messageBodyDiv = $('<img src="' + regRes[1] + '">');
} else {
var $messageBodyDiv = $('<span class="messageBody">')
.text(data.Message);
}
var typingClass = data.Typing ? 'typing' : '';
var $messageDiv = $('<li class="message"/>')
.addClass(typingClass)
.append($messageBodyDiv);
addMessageElement($messageDiv, options);
}
// Adds a message element to the messages and scrolls to the bottom
// el - The element to add as a message
// options.fade - If the element should fade-in (default = true)
// options.prepend - If the element should prepend
// all other messages (default = false)
function addMessageElement (el, options) {
// console.log("@el:" + el)
// console.log("@options:" + options)
var $el = $(el);
// Setup default options
if (!options) {
options = {};
}
if (typeof options.fade === 'undefined') {
options.fade = true;
}
if (typeof options.prepend === 'undefined') {
options.prepend = false;
}
// Apply options
if (options.fade) {
$el.hide().fadeIn(FADE_TIME);
}
if (options.prepend) {
$messages.prepend($el);
} else {
$messages.append($el);
}
$messages[0].scrollTop = $messages[0].scrollHeight;
}
// Prevents input from having injected markup
function cleanInput (input) {
return $('<div/>').text(input).text();
}
$window.keydown(function (event) {
// When the client hits ENTER on their keyboard
if (event.which === 13) {
sendMessage();
}
});
// Click events
// Focus input when clicking on the message input's border
$inputMessage.click(function () {
$inputMessage.focus();
});
socket.onopen = function() {
console.log('websocket extensions:' + socket.extensions);
}
// Socket events
socket.onmessage = function(e) {
// e.data is blob
var fileReader = new FileReader(); // 用filereader来转blob为arraybuffer
fileReader.onload = function() {
var arrayBuffer = this.result; // 得到arraybuffer
// var decoder = new TextDecoder('utf-8') // 上面回复中给的encoding.js、encoding-indexes.js
// console.log('decoder data:' + decoder.decode(new DataView(arrayBuffer)))
var dv = new DataView(arrayBuffer);
// console.log('decoder data:' + dv.buffer)
var dva = new Uint8Array(dv.buffer);
unmarshalEchoPkg(dv.buffer)
};
fileReader.readAsArrayBuffer(e.data); // 此处读取blob
}
socket.onclose = function() {
addChatMessage({
Message: '!!SYSTEM-WS-Close, connection closed'
});
}
socket.onerror = function() {
addChatMessage({
Message: '!!SYSTEM-WS-Error, connection closed'
});
}
});
/*!
* C-Like Data Structure for JavaScript.
*
* @see Struct.js on GitHub <https://github.com/firejune/struct.js>
* @author Joon Kyoung <firejune@gmail.com>
* @license MIT
* @version 0.9.1
*
*/
(function(E,r){function A(a){return!!a&&a.constructor===C}function D(a){return!!a&&a.constructor===Error}function x(a){return!!a&&(a===Array||a.constructor===Array)}function F(a){return!!a&&(a===Object||a.constructor===Object)}function t(a){return!!a&&(a===String||a.constructor===String)}function w(a){return!!a&&(a===ArrayBuffer||a.constructor===ArrayBuffer)}function B(a){return a.charAt(0).toUpperCase()+a.slice(1)}function u(a,b){var c={},e=null,l;for(l in a)if(a.hasOwnProperty(l))if(e=a[l],F(e))c[l]=
u(e,b);else if(e=b(e,l,c)){c=e;break}return c}function G(a,b){for(var c in b)a.hasOwnProperty(c)&&(F(a[c])?a[c]=G(a[c],b[c]):a[c][1]=b[c]);return a}function H(a){var b={},c;for(c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}function J(a,b){return u(a,function(a,e,l){var k=x(a)&&a[0]||a,k=A(k)&&k||k.toLowerCase(),m=t(a)?b:a[1],g=x(a)&&3<=a.length&&a[2]||(t(m)||x(m)||w(m))&&m.length||w(m)&&m.byteLength||1;a=x(a)&&(t(g)||1<g)?t(g)&&a[3]===r&&!0||a[3]:!1;if(m===r||null===m)m="";l[e]=[k,m,g,a]})}function I(a){var b=
0;u(a,function(a){var e=a[0],l=a[1],k=a[2];a=a[3];var m=y[e],g="uint8";m===r&&A(e)&&(m=e.byteLength);if(t(k)||t(l))g=k,k=l&&l.length||0;w(l)&&(k=l.byteLength||0);!0===a&&(b+=y[g]);b+=k*m});return b}var C=function(a,b,c){this.endianness=c===r&&!0||c;this.defaultValue=b||0;this.struct=J(a,this.defaultValue);this.byteLength=I(this.struct);this.emptyBuffer=new ArrayBuffer(this.byteLength);this.constructor=C;this._debug=!1;this._struct={};this._debug&&console.log("STRUCT.CREATE","defaultValue:",this.defaultValue,
"byteLength:",this.byteLength,"endianness:",this.endianness,"struct:",this.struct)};C.prototype={update:function(a){this.struct=G(this.struct,a||{});this.byteLength=I(this.struct);this.emptyBuffer=new ArrayBuffer(this.byteLength);return H(this.struct)},read:function(a,b){if(a===r&&b===r)return u(this.struct,function(a,c,b){b[c]=a[1]});var c=this,e=this.endianness,l=a instanceof DataView&&a||new DataView(a);if(0===a.byteLength)return Error("Uncaught IndexSizeError: Buffer size was zero byte.");this.offset=
b||0;this._debug&&console.info("STRUCT.READ","byteLength:",a.byteLength,"readOffset:",this.offset);var k=u(this.struct,function(b,g,k){var d=[],q=b[0],h=b[1],p=b[2],r=b[3];b=y[q]||1;var f="uint8",v=0;t(p)&&(f=p,p=h.byteLength||h.length);if(a.byteLength<=c.offset)return Error("Uncaught IndexSizeError: Index or size was negative.");if(!0===r){try{p=l["get"+B(f)](c.offset,e)/b}catch(n){return console.error(n,c.offset,c.offset+b,a.byteLength),Error(n)}c.offset+=y[f];c._struct[g+"Size"]=[f,p*b];c._debug&&
console.log(g+"Size",c._struct[g+"Size"],c.offset)}if(w(h)){d=c.offset+p*b;if(a.byteLength<d)return Error("Uncaught IndexSizeError: Index or size was negative.");d=a.slice(c.offset,d);c.offset+=p*b}else for(;v<p;){A(q)?(b=a.slice(c.offset),d[v]=q.read(b),b=q.byteLength):d[v]=c.offset+b>a.byteLength?Error("Uncaught IndexSizeError: Index or size was negative."):l["get"+B(q)](c.offset,e);if(D(d[v]))return d[v];c.offset+=b;v++}if(w(h)||t(h)||x(h)||1<p){if(t(h)){h=d;p=[];d=h.length;for(b=0;b<d;)p[b]=String.fromCharCode(h[b]),
b++;h=p.join("")}else h=d;k[g]=h}else k[g]=d[0];c._struct[g]=[q,k[g]];c._debug&&console.log(g,c._struct[g],c.offset)});D(k)||(this.byteLength=this.offset,this.offset!=a.byteLength&&200<=k.type&&6!=this.offset&&console.warn("Incorrect buffer size readed",this.offset,a.byteLength,k));return D(k)&&k||H(k)},write:function(a){var b=this,c=0,e=this.endianness;a!==r&&this.update(a);var l=new DataView(this.emptyBuffer);this._debug&&console.info("STRUCT.WRITE","byteLength:",this.byteLength);u(this.struct,
function(a,m){var g=[],f=a[0],d=a[1],q=a[2],h=a[3],p=y[f],n="uint8",u=0;p===r&&A(f)&&(n=q,q=d.length,p=1);if(t(q)||t(d))n=q,q=d.length;w(d)&&(q=d.byteLength);!0===h&&(l["set"+B(n)](c,q*p,e),c+=y[n],b._struct[m+"Size"]=[n,q*p],b._debug&&console.log(m+"Size",b._struct[m+"Size"],c));b._struct[m]=[f,d];if(w(d)||t(d)||x(d)||1<q){w(d)&&(g=Array.prototype.slice.call(new (E[B(f)+"Array"])(d)));!d||d!==Function&&d.constructor!==Function||(g=[b.defaultValue]);x(d)&&(g=d);if(h=d&&t(d))for(var h=[],n=d.length,
v=0;v<n;)h[v]=d.charCodeAt(v),v++;g=h||g}else g=[d];for(;u<q;){if(A(f)){d=f.write(g[u]);d=new Uint8Array(d);for(h=0;h<d.length;)l.setUint8(c+h,d[h]),h++;c+=d.length}else l["set"+B(f)](c,g[u],e),c+=p;u++}b._debug&&console.log(m,b._struct[m],c)});c!=this.emptyBuffer.byteLength&&console.warn("Incorrect buffer size writed");return l.buffer}};var y={int8:1,uint8:1,int16:2,uint16:2,int32:4,uint32:4,float32:4,int64:8,uint64:8,float64:8};if(DataView.prototype.getUint64===r&&DataView.prototype.setUint64===
r&&DataView.prototype.getInt64===r&&DataView.prototype.setInt64===r){var f=function(a){return 0<=a&&31>a?1<<a:f[a]||(f[a]=Math.pow(2,a)-1)},n=function(a,b){this.lo=a;this.hi=b};n.prototype={valueOf:function(){return this.lo+f(32)*this.hi},toString:function(){return Number.prototype.toString.apply(this.valueOf(),arguments)}};n.fromNumber=function(a){var b=Math.floor(a/f(32));a-=b*f(32);return new n(a,b)};var z=function(){n.apply(this,arguments)};z.prototype="create"in Object?Object.create(n.prototype):
new n;z.prototype.valueOf=function(){return this.hi<f(31)?n.prototype.valueOf.apply(this,arguments):-(f(32)-this.lo+f(32)*(f(32)-1-this.hi))};z.fromNumber=function(a){var b;0<=a?(b=n.fromNumber(a),a=b.lo,b=b.hi):(b=Math.floor(a/f(32)),a-=b*f(32),b+=f(32));return new z(a,b)};DataView.prototype.getUint64=function(a,b){for(var c=b?[0,4]:[4,0],e=0;2>e;e++)c[e]=this.getUint32(a+c[e],b);return(new n(c[0],c[1])).valueOf()};DataView.prototype.setUint64=function(a,b,c){b=n.fromNumber(b);var e=c?{lo:0,hi:4}:
{lo:4,hi:0},f;for(f in e)this.setUint32(a+e[f],b[f],c)};DataView.prototype.getInt64=function(a,b){for(var c=b?[0,4]:[4,0],e=0;2>e;e++)c[e]=this.getUint32(a+c[e],b);return(new z(c[0],c[1])).valueOf()};DataView.prototype.setInt64=function(a,b){value=z.fromNumber(value);var c=b?{lo:0,hi:4}:{lo:4,hi:0},e;for(e in c)this.setUint32(a+c[e],value[e],b)}}E.Struct=C})(this);
\ No newline at end of file
/* Fix user-agent */
* {
box-sizing: border-box;
}
html {
font-weight: 300;
-webkit-font-smoothing: antialiased;
}
html, input {
font-family:
"HelveticaNeue-Light",
"Helvetica Neue Light",
"Helvetica Neue",
Helvetica,
Arial,
"Lucida Grande",
sans-serif;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
ul {
list-style: none;
word-wrap: break-word;
}
/* Pages */
.pages {
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
.page {
height: 100%;
position: absolute;
width: 100%;
}
/* Chat page */
/* Font */
.messages {
font-size: 150%;
}
.inputMessage {
font-size: 100%;
}
.log {
color: gray;
font-size: 70%;
margin: 5px;
text-align: center;
}
/* Messages */
.chatArea {
height: 100%;
padding-bottom: 60px;
}
.messages {
height: 100%;
margin: 0;
overflow-y: scroll;
padding: 10px 20px 10px 20px;
}
.message.typing .messageBody {
color: gray;
}
.username {
float: left;
font-weight: 700;
overflow: hidden;
padding-right: 15px;
text-align: right;
}
/* Input */
.inputMessage {
border: 10px solid #000;
bottom: 0;
height: 60px;
left: 0;
outline: none;
padding-left: 10px;
position: absolute;
right: 0;
width: 100%;
}
/******************************************************
# DESC : echo client
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-06 17:24
# FILE : client.go
******************************************************/
package main
import (
"math/rand"
"sync"
"sync/atomic"
"time"
)
import (
"github.com/AlexStocks/getty"
log "github.com/AlexStocks/log4go"
)
var (
reqID uint32
src = rand.NewSource(time.Now().UnixNano())
)
func init() {
rand.Seed(time.Now().UnixNano())
}
////////////////////////////////////////////////////////////////////
// echo client
////////////////////////////////////////////////////////////////////
type EchoClient struct {
lock sync.RWMutex
sessions []*clientEchoSession
gettyClient *getty.Client
}
func (this *EchoClient) isAvailable() bool {
if this.selectSession() == nil {
return false
}
return true
}
func (this *EchoClient) close() {
client.lock.Lock()
if client.gettyClient != nil {
for _, s := range this.sessions {
log.Info("close client session{%s, last active:%s, request number:%d}",
s.session.Stat(), s.active.String(), s.reqNum)
s.session.Close()
}
client.gettyClient.Close()
client.gettyClient = nil
client.sessions = client.sessions[:0]
}
client.lock.Unlock()
}
func (this *EchoClient) selectSession() *getty.Session {
// get route server session
this.lock.RLock()
defer this.lock.RUnlock()
count := len(this.sessions)
if count == 0 {
log.Info("client session arrray is nil...")
return nil
}
return this.sessions[rand.Int31n(int32(count))].session
}
func (this *EchoClient) addSession(session *getty.Session) {
log.Debug("add session{%s}", session.Stat())
if session == nil {
return
}
this.lock.Lock()
this.sessions = append(this.sessions, &clientEchoSession{session: session, active: time.Now()})
this.lock.Unlock()
}
func (this *EchoClient) removeSession(session *getty.Session) {
if session == nil {
return
}
this.lock.Lock()
for i, s := range this.sessions {
if s.session == session {
this.sessions = append(this.sessions[:i], this.sessions[i+1:]...)
log.Debug("delete session{%s}, its index{%d}", session.Stat(), i)
break
}
}
log.Info("after remove session{%s}, left session number:%d", session.Stat(), len(this.sessions))
this.lock.Unlock()
}
func (this *EchoClient) updateSession(session *getty.Session) {
if session == nil {
return
}
this.lock.Lock()
for i, s := range this.sessions {
if s.session == session {
this.sessions[i].active = time.Now()
this.sessions[i].reqNum++
break
}
}
this.lock.Unlock()
}
func (this *EchoClient) getClientEchoSession(session *getty.Session) (clientEchoSession, error) {
var (
err error
echoSession clientEchoSession
)
this.lock.Lock()
err = errSessionNotExist
for _, s := range this.sessions {
if s.session == session {
echoSession = *s
err = nil
break
}
}
this.lock.Unlock()
return echoSession, err
}
func (this *EchoClient) heartbeat(session *getty.Session) {
var pkg EchoPackage
pkg.H.Magic = echoPkgMagic
pkg.H.LogID = (uint32)(src.Int63())
pkg.H.Sequence = atomic.AddUint32(&reqID, 1)
// pkg.H.ServiceID = 0
pkg.H.Command = heartbeatCmd
pkg.B = echoHeartbeatRequestString
pkg.H.Len = (uint16)(len(pkg.B))
if err := session.WritePkg(&pkg); err != nil {
log.Warn("session.WritePkg(session{%s}, pkg{%s}) = error{%v}", session.Stat(), pkg, err)
session.Close()
this.removeSession(session)
}
}
/******************************************************
# DESC : env var & configure
# MAINTAINER : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-06 16:53
# FILE : config.go
******************************************************/
package main
import (
"fmt"
"os"
"path"
"time"
)
import (
// "github.com/AlexStocks/gocolor"
log "github.com/AlexStocks/log4go"
config "github.com/koding/multiconfig"
)
const (
APP_CONF_FILE string = "APP_CONF_FILE"
APP_LOG_CONF_FILE string = "APP_LOG_CONF_FILE"
)
var (
conf *Config
)
type (
GettySessionParam struct {
TcpNoDelay bool `default:"true"`
TcpKeepAlive bool `default:"true"`
TcpRBufSize int `default:"262144"`
TcpWBufSize int `default:"65536"`
PkgRQSize int `default:"1024"`
PkgWQSize int `default:"1024"`
TcpReadTimeout string `default:"1s"`
tcpReadTimeout time.Duration
TcpWriteTimeout string `default:"5s"`
tcpWriteTimeout time.Duration
WaitTimeout string `default:"7s"`
waitTimeout time.Duration
SessionName string `default:"echo-client"`
}
// Config holds supported types by the multiconfig package
Config struct {
// local
AppName string `default:"echo-client"`
LocalHost string `default:"127.0.0.1"`
// server
ServerHost string `default:"127.0.0.1"`
ServerPort int `default:"10000"`
ProfilePort int `default:"10086"`
// session pool
ConnectionNum int `default:"16"`
ConnectInterval string `default:"5s"`
connectInterval time.Duration
// heartbeat
HeartbeatPeriod string `default:"15s"`
heartbeatPeriod time.Duration
// session
SessionTimeout string `default:"60s"`
sessionTimeout time.Duration
// echo
EchoString string `default:"hello"`
EchoTimes int `default:"10"`
// app
FailFastTimeout string `default:"5s"`
failFastTimeout time.Duration
// session tcp parameters
GettySessionParam GettySessionParam `required:"true"`
}
)
func initConf() {
var (
err error
confFile string
)
// configure
confFile = os.Getenv(APP_CONF_FILE)
if confFile == "" {
panic(fmt.Sprintf("application configure file name is nil"))
return // I know it is of no usage. Just Err Protection.
}
if path.Ext(confFile) != ".toml" {
panic(fmt.Sprintf("application configure file name{%v} suffix must be .toml", confFile))
return
}
conf = new(Config)
config.MustLoadWithPath(confFile, conf)
conf.connectInterval, err = time.ParseDuration(conf.ConnectInterval)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(ConnectionInterval{%#v}) = error{%v}", conf.ConnectInterval, err))
return
}
conf.heartbeatPeriod, err = time.ParseDuration(conf.HeartbeatPeriod)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(HeartbeatPeroid{%#v}) = error{%v}", conf.HeartbeatPeriod, err))
return
}
conf.sessionTimeout, err = time.ParseDuration(conf.SessionTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(SessionTimeout{%#v}) = error{%v}", conf.SessionTimeout, err))
return
}
conf.failFastTimeout, err = time.ParseDuration(conf.FailFastTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(FailFastTimeout{%#v}) = error{%v}", conf.FailFastTimeout, err))
return
}
conf.GettySessionParam.tcpReadTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpReadTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(TcpReadTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpReadTimeout, err))
return
}
conf.GettySessionParam.tcpWriteTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpWriteTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(TcpWriteTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpWriteTimeout, err))
return
}
conf.GettySessionParam.waitTimeout, err = time.ParseDuration(conf.GettySessionParam.WaitTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(WaitTimeout{%#v}) = error{%v}", conf.GettySessionParam.WaitTimeout, err))
return
}
// gocolor.Info("config{%#v}\n", conf)
// log
confFile = os.Getenv(APP_LOG_CONF_FILE)
if confFile == "" {
panic(fmt.Sprintf("log configure file name is nil"))
return
}
if path.Ext(confFile) != ".xml" {
panic(fmt.Sprintf("log configure file name{%v} suffix must be .xml", confFile))
return
}
log.LoadConfiguration(confFile)
log.Info("config{%#v}", conf)
return
}
/******************************************************
# DESC : echo package handler
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-04 13:08
# FILE : handler.go
******************************************************/
package main
import (
"errors"
"time"
)
import (
"github.com/AlexStocks/getty"
log "github.com/AlexStocks/log4go"
)
var (
errSessionNotExist = errors.New("session not exist!")
)
////////////////////////////////////////////
// EchoMessageHandler
////////////////////////////////////////////
type clientEchoSession struct {
session *getty.Session
active time.Time
reqNum int32
}
type EchoMessageHandler struct {
}
func newEchoMessageHandler() *EchoMessageHandler {
return &EchoMessageHandler{}
}
func (this *EchoMessageHandler) OnOpen(session *getty.Session) error {
client.addSession(session)
return nil
}
func (this *EchoMessageHandler) OnError(session *getty.Session, err error) {
log.Info("session{%s} got error{%v}, will be closed.", session.Stat(), err)
client.removeSession(session)
}
func (this *EchoMessageHandler) OnClose(session *getty.Session) {
log.Info("session{%s} is closing......", session.Stat())
client.removeSession(session)
}
func (this *EchoMessageHandler) OnMessage(session *getty.Session, pkg interface{}) {
p, ok := pkg.(*EchoPackage)
if !ok {
log.Error("illegal packge{%#v}", pkg)
return
}
log.Debug("get echo package{%s}", p)
client.updateSession(session)
}
func (this *EchoMessageHandler) OnCron(session *getty.Session) {
clientEchoSession, err := client.getClientEchoSession(session)
if err != nil {
log.Error("client.getClientSession(session{%s}) = error{%#v}", session.Stat(), err)
return
}
if conf.sessionTimeout.Nanoseconds() < time.Since(clientEchoSession.active).Nanoseconds() {
log.Warn("session{%s} timeout{%s}, reqNum{%d}",
session.Stat(), time.Since(clientEchoSession.active).String(), clientEchoSession.reqNum)
client.removeSession(session)
return
}
client.heartbeat(session)
}
/******************************************************
# DESC : echo client app
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-06 17:24
# FILE : main.go
******************************************************/
package main
import (
// "flag"
"fmt"
"net"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
// "strings"
"sync/atomic"
"syscall"
"time"
)
import (
"github.com/AlexStocks/getty"
"github.com/AlexStocks/gocolor"
"github.com/AlexStocks/goext/net"
"github.com/AlexStocks/goext/time"
log "github.com/AlexStocks/log4go"
)
const (
pprofPath = "/debug/pprof/"
)
var (
client EchoClient
)
////////////////////////////////////////////////////////////////////
// main
////////////////////////////////////////////////////////////////////
func main() {
initConf()
initProfiling()
initClient()
gocolor.Info("%s starts successfull! its version=%s\n", conf.AppName, Version)
log.Info("%s starts successfull! its version=%s\n", conf.AppName, Version)
go test()
initSignal()
}
func initProfiling() {
var (
addr string
)
addr = gxnet.HostAddress(conf.LocalHost, conf.ProfilePort)
log.Info("App Profiling startup on address{%v}", addr+pprofPath)
go func() {
log.Info(http.ListenAndServe(addr, nil))
}()
}
func newSession(session *getty.Session) error {
var (
ok bool
tcpConn *net.TCPConn
)
if tcpConn, ok = session.Conn().(*net.TCPConn); !ok {
panic(fmt.Sprintf("%s, session.conn{%#v} is not tcp connection\n", session.Stat(), session.Conn()))
}
tcpConn.SetNoDelay(conf.GettySessionParam.TcpNoDelay)
tcpConn.SetKeepAlive(conf.GettySessionParam.TcpKeepAlive)
tcpConn.SetReadBuffer(conf.GettySessionParam.TcpRBufSize)
tcpConn.SetWriteBuffer(conf.GettySessionParam.TcpWBufSize)
session.SetName(conf.GettySessionParam.SessionName)
session.SetPkgHandler(NewEchoPackageHandler())
session.SetEventListener(newEchoMessageHandler())
session.SetRQLen(conf.GettySessionParam.PkgRQSize)
session.SetWQLen(conf.GettySessionParam.PkgWQSize)
session.SetReadDeadline(conf.GettySessionParam.tcpReadTimeout)
session.SetWriteDeadline(conf.GettySessionParam.tcpWriteTimeout)
session.SetCronPeriod((int)(conf.heartbeatPeriod.Nanoseconds() / 1e6))
session.SetWaitTime(conf.GettySessionParam.waitTimeout)
log.Debug("client new session:%s\n", session.Stat())
return nil
}
func initClient() {
client.gettyClient = getty.NewClient(
(int)(conf.ConnectionNum),
conf.connectInterval,
gxnet.HostAddress(conf.ServerHost, conf.ServerPort),
)
client.gettyClient.RunEventLoop(newSession)
}
func uninitClient() {
client.close()
}
func initSignal() {
// signal.Notify的ch信道是阻塞的(signal.Notify不会阻塞发送信号), 需要设置缓冲
signals := make(chan os.Signal, 1)
// It is not possible to block SIGKILL or syscall.SIGSTOP
signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
sig := <-signals
log.Info("get signal %s", sig.String())
switch sig {
case syscall.SIGHUP:
// reload()
default:
go time.AfterFunc(conf.failFastTimeout, func() {
// log.Warn("app exit now by force...")
// os.Exit(1)
log.Exit("app exit now by force...")
})
// 要么survialTimeout时间内执行完毕下面的逻辑然后程序退出,要么执行上面的超时函数程序强行退出
uninitClient()
// fmt.Println("app exit now...")
log.Exit("app exit now...")
return
}
}
}
func echo() {
var pkg EchoPackage
pkg.H.Magic = echoPkgMagic
pkg.H.LogID = (uint32)(src.Int63())
pkg.H.Sequence = atomic.AddUint32(&reqID, 1)
// pkg.H.ServiceID = 0
pkg.H.Command = echoCmd
pkg.B = conf.EchoString
pkg.H.Len = (uint16)(len(pkg.B))
if session := client.selectSession(); session != nil {
err := session.WritePkg(&pkg)
if err != nil {
log.Warn("session.WritePkg(session{%s}, pkg{%s}) = error{%v}", session.Stat(), pkg, err)
session.Close()
client.removeSession(session)
}
}
}
func test() {
for {
if client.isAvailable() {
break
}
time.Sleep(1e6)
}
var (
cost int64
counter gxtime.CountWatch
)
counter.Start()
for i := 0; i < conf.EchoTimes; i++ {
echo()
}
cost = counter.Count()
log.Info("after loop %d times, echo cost %d ms", conf.EchoTimes, cost/1e6)
gocolor.Info("after loop %d times, echo cost %d ms", conf.EchoTimes, cost/1e6)
}
/******************************************************
# DESC : echo stream parser
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-04 13:08
# FILE : readwriter.go
******************************************************/
package main
import (
"bytes"
"errors"
"time"
)
import (
"github.com/AlexStocks/getty"
log "github.com/AlexStocks/log4go"
)
type EchoPackageHandler struct {
}
func NewEchoPackageHandler() *EchoPackageHandler {
return &EchoPackageHandler{}
}
func (this *EchoPackageHandler) Read(ss *getty.Session, data []byte) (interface{}, int, error) {
var (
err error
len int
pkg EchoPackage
buf *bytes.Buffer
)
buf = bytes.NewBuffer(data)
len, err = pkg.Unmarshal(buf)
if err != nil {
if err == ErrNotEnoughSteam {
return nil, 0, nil
}
return nil, 0, err
}
return &pkg, len, nil
}
func (this *EchoPackageHandler) Write(ss *getty.Session, pkg interface{}) error {
var (
ok bool
err error
startTime time.Time
echoPkg *EchoPackage
buf *bytes.Buffer
)
startTime = time.Now()
if echoPkg, ok = pkg.(*EchoPackage); !ok {
log.Error("illegal pkg:%+v\n", pkg)
return errors.New("invalid echo package!")
}
buf, err = echoPkg.Marshal()
if err != nil {
log.Warn("binary.Write(echoPkg{%#v}) = err{%#v}", echoPkg, err)
return err
}
err = ss.WriteBytes(buf.Bytes())
log.Info("WriteEchoPkgTimeMs = %s", time.Since(startTime).String())
return err
}
/******************************************************
# DESC : echo client version
# MAINTAINER : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-06 11:23
# FILE : version.go
******************************************************/
package main
var (
Version = "0.3.07"
)
#!/usr/bin/env bash
# ******************************************************
# DESC : getty app devops script
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : LGPL V3
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-05-13 02:01
# FILE : load.sh
# ******************************************************
APP_NAME="APPLICATION_NAME"
APP_ARGS=""
PROJECT_HOME=""
OS_NAME=`uname`
if [[ ${OS_NAME} == "Linux" ]]; then
PROJECT_HOME=`pwd`
PROJECT_HOME=${PROJECT_HOME}"/"
fi
export APP_CONF_FILE=${PROJECT_HOME}"TARGET_CONF_FILE"
export APP_LOG_CONF_FILE=${PROJECT_HOME}"TARGET_LOG_CONF_FILE"
usage() {
echo "Usage: $0 start"
echo " $0 stop"
echo " $0 term"
echo " $0 restart"
echo " $0 list"
exit
}
start() {
APP_LOG_PATH=${PROJECT_HOME}"logs/"
mkdir -p ${APP_LOG_PATH}
APP_BIN=${PROJECT_HOME}sbin/${APP_NAME}
chmod u+x ${APP_BIN}
# CMD="nohup ${APP_BIN} ${APP_ARGS} >>${APP_NAME}.nohup.out 2>&1 &"
CMD="${APP_BIN}"
eval ${CMD}
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'`
if [[ ${OS_NAME} != "Linux" ]]; then
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'`
fi
if [ "${PID}" != "" ];
then
for p in ${PID}
do
echo "start ${APP_NAME} ( pid =" ${p} ")"
done
fi
}
stop() {
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'`
if [[ ${OS_NAME} != "Linux" ]]; then
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'`
fi
if [ "${PID}" != "" ];
then
for ps in ${PID}
do
echo "kill -SIGINT ${APP_NAME} ( pid =" ${ps} ")"
kill -2 ${ps}
done
fi
}
term() {
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'`
if [[ ${OS_NAME} != "Linux" ]]; then
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'`
fi
if [ "${PID}" != "" ];
then
for ps in ${PID}
do
echo "kill -9 ${APP_NAME} ( pid =" ${ps} ")"
kill -9 ${ps}
done
fi
}
list() {
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s\n", $1, $2, $9, $10)}'`
if [[ ${OS_NAME} != "Linux" ]]; then
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s,%s\n", $1, $4, $6, $7, $8)}'`
fi
if [ "${PID}" != "" ];
then
echo "list ${APP_NAME}"
if [[ ${OS_NAME} == "Linux" ]]; then
echo "index: user, pid, start, duration"
else
echo "index: PID, WINPID, UID, STIME, COMMAND"
fi
idx=0
for ps in ${PID}
do
echo "${idx}: ${ps}"
((idx ++))
done
fi
}
opt=$1
case C"$opt" in
Cstart)
start
;;
Cstop)
stop
;;
Cterm)
term
;;
Crestart)
term
start
;;
Clist)
list
;;
C*)
usage
;;
esac
# getty application configure script
# ******************************************************
# DESC : application environment variable
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:29
# FILE : app.properties
# ******************************************************
TARGET_EXEC_NAME="echo_client"
BUILD_PACKAGE="app"
TARGET_CONF_FILE="conf/config.toml"
TARGET_LOG_CONF_FILE="conf/log.xml"
#!/usr/bin/env bash
# ******************************************************
# DESC : build script
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:28
# FILE : build.sh
# ******************************************************
rm -rf target/
PROJECT_HOME=`pwd`
TARGET_FOLDER=${PROJECT_HOME}/target/${GOOS}
TARGET_SBIN_NAME=${TARGET_EXEC_NAME}
version=`cat app/version.go | grep Version | awk -F '=' '{print $2}' | awk -F '"' '{print $2}'`
if [[ ${GOOS} == "windows" ]]; then
TARGET_SBIN_NAME=${TARGET_SBIN_NAME}.exe
fi
TARGET_NAME=${TARGET_FOLDER}/${TARGET_SBIN_NAME}
if [[ $PROFILE = "test" ]]; then
# GFLAGS=-gcflags "-N -l" -race -x -v # -x会把go build的详细过程输出
# GFLAGS=-gcflags "-N -l" -race -v
# GFLAGS="-gcflags \"-N -l\" -v"
cd ${BUILD_PACKAGE} && go build -gcflags "-N -l" -x -v -i -o ${TARGET_NAME} && cd -
else
# -s去掉符号表(然后panic时候的stack trace就没有任何文件名/行号信息了,这个等价于普通C/C++程序被strip的效果),
# -w去掉DWARF调试信息,得到的程序就不能用gdb调试了。-s和-w也可以分开使用,一般来说如果不打算用gdb调试,
# -w基本没啥损失。-s的损失就有点大了。
cd ${BUILD_PACKAGE} && go build -ldflags "-w" -x -v -i -o ${TARGET_NAME} && cd -
fi
TAR_NAME=${TARGET_EXEC_NAME}-${version}-`date "+%Y%m%d-%H%M"`-${PROFILE}
mkdir -p ${TARGET_FOLDER}/${TAR_NAME}
SBIN_DIR=${TARGET_FOLDER}/${TAR_NAME}/sbin
BIN_DIR=${TARGET_FOLDER}/${TAR_NAME}
CONF_DIR=${TARGET_FOLDER}/${TAR_NAME}/conf
mkdir -p ${SBIN_DIR}
mkdir -p ${CONF_DIR}
mv ${TARGET_NAME} ${SBIN_DIR}
cp -r assembly/bin ${BIN_DIR}
# modify APPLICATION_NAME
sed -i "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/*
# modify TARGET_CONF_FILE
sed -i "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/*
# modify TARGET_LOG_CONF_FILE
sed -i "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/*
cp -r profiles/${PROFILE}/* ${CONF_DIR}
cd ${TARGET_FOLDER}
tar czf ${TAR_NAME}.tar.gz ${TAR_NAME}/*
#!/usr/bin/env bash
# ******************************************************
# DESC : build script for test env
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : LGPL V3
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:34
# FILE : test.sh
# ******************************************************
set -e
export GOOS=linux
export GOARCH=amd64
PROFILE=test
PROJECT_HOME=`pwd`
if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then
. ${PROJECT_HOME}/assembly/common/app.properties
fi
if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then
. ${PROJECT_HOME}/assembly/common/build.sh
fi
#!/usr/bin/env bash
# ******************************************************
# DESC : build script for test env
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:34
# FILE : test.sh
# ******************************************************
set -e
export GOOS=windows
export GOARCH=amd64
PROFILE=test
PROJECT_HOME=`pwd`
if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then
. ${PROJECT_HOME}/assembly/common/app.properties
fi
if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then
. ${PROJECT_HOME}/assembly/common/build.sh
fi
# toml configure file
# toml中key的首字母可以小写,但是对应的golang中的struct成员首字母必须大写
AppName = "ECHO-CLIENT"
# host
LocalHost = "127.0.0.1"
# server
ServerHost = "192.168.35.1"
ServerPort = 10000
ProfilePort = 10080
# connection pool
# 连接池连接数目
ConnectionNum = 2
# 当连接失败或者连接断开时,连接池中重连的间隔时间
ConnectInterval = "5s"
# session
# client与server之间连接的心跳周期
HeartbeatPeriod = "10s"
# client与server之间连接的超时时间
SessionTimeout = "20s"
# client
# client echo request string
EchoString = "Hello, getty!"
# 发送echo请求次数
EchoTimes = 10000
# app fail fast
FailFastTimeout = "3s"
# tcp
[GettySessionParam]
TcpNoDelay = true
TcpKeepAlive = true
TcpRBufSize = 262144
TcpWBufSize = 65536
PkgRQSize = 512
PkgWQSize = 256
TcpReadTimeout = "1s"
TcpWriteTimeout = "5s"
WaitTimeout = "1s"
SessionName = "echo-client"
<logging>
<filter enabled="false">
<tag>stdout</tag>
<type>console</type>
<!-- level is (:?FINEST|FINE|DEBUG|TRACE|INFO|WARNING|ERROR) -->
<level>DEBUG</level>
</filter>
<filter enabled="false">
<tag>debug_file</tag>
<type>file</type>
<level>DEBUG</level>
<property name="filename">logs/debug.log</property>
<property name="format">[%D %T] [%L] [%S] %M</property>
<property name="rotate">true</property> <!-- true enables log rotation, otherwise append -->
<property name="maxsize">0M</property> <!-- \d+[KMG]? Suffixes are in terms of 2**10 -->
<property name="maxlines">0K</property> <!-- \d+[KMG]? Suffixes are in terms of thousands -->
<property name="daily">true</property> <!-- Automatically rotates when a log message is written after midnight -->
</filter>
<filter enabled="true">
<tag>info_file</tag>
<type>file</type>
<level>INFO</level>
<property name="filename">logs/info.log</property>
<!--
%T - Time (15:04:05 MST)
%t - Time (15:04)
%D - Date (2006/01/02)
%d - Date (01/02/06)
%L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT)
%S - Source
%M - Message
It ignores unknown format strings (and removes them)
Recommended: "[%D %T] [%L] (%S) %M"
-->
<property name="format">[%D %T] [%L] [%S] %M</property>
<property name="rotate">true</property> <!-- true enables log rotation, otherwise append -->
<property name="maxsize">0M</property> <!-- \d+[KMG]? Suffixes are in terms of 2**10 -->
<property name="maxlines">0K</property> <!-- \d+[KMG]? Suffixes are in terms of thousands -->
<property name="daily">true</property> <!-- Automatically rotates when a log message is written after midnight -->
</filter>
<filter enabled="true">
<tag>warn_file</tag>
<type>file</type>
<level>WARNING</level>
<property name="filename">logs/warn.log</property>
<property name="format">[%D %T] [%L] [%S] %M</property>
<property name="rotate">true</property> <!-- true enables log rotation, otherwise append -->
<property name="maxsize">0M</property> <!-- \d+[KMG]? Suffixes are in terms of 2**10 -->
<property name="maxlines">0K</property> <!-- \d+[KMG]? Suffixes are in terms of thousands -->
<property name="daily">true</property> <!-- Automatically rotates when a log message is written after midnight -->
</filter>
<filter enabled="true">
<tag>error_file</tag>
<type>file</type>
<level>ERROR</level>
<property name="filename">logs/error.log</property>
<property name="format">[%D %T] [%L] [%S] %M</property>
<property name="rotate">true</property> <!-- true enables log rotation, otherwise append -->
<property name="maxsize">0M</property> <!-- \d+[KMG]? Suffixes are in terms of 2**10 -->
<property name="maxlines">0K</property> <!-- \d+[KMG]? Suffixes are in terms of thousands -->
<property name="daily">true</property> <!-- Automatically rotates when a log message is written after midnight -->
</filter>
</logging>
/******************************************************
# DESC : env var & configure
# MAINTAINER : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-06 16:53
# FILE : config.go
******************************************************/
package main
import (
"fmt"
"os"
"path"
"time"
)
import (
// "github.com/AlexStocks/gocolor"
log "github.com/AlexStocks/log4go"
config "github.com/koding/multiconfig"
)
const (
APP_CONF_FILE string = "APP_CONF_FILE"
APP_LOG_CONF_FILE string = "APP_LOG_CONF_FILE"
)
var (
conf *Config
)
type (
GettySessionParam struct {
TcpNoDelay bool `default:"true"`
TcpKeepAlive bool `default:"true"`
TcpRBufSize int `default:"262144"`
TcpWBufSize int `default:"65536"`
PkgRQSize int `default:"1024"`
PkgWQSize int `default:"1024"`
TcpReadTimeout string `default:"1s"`
tcpReadTimeout time.Duration
TcpWriteTimeout string `default:"5s"`
tcpWriteTimeout time.Duration
WaitTimeout string `default:"7s"`
waitTimeout time.Duration
SessionName string `default:"echo-client"`
}
// Config holds supported types by the multiconfig package
Config struct {
// local address
AppName string `default:"echo-server"`
Host string `default:"127.0.0.1"`
Ports []string `default:["10000"]`
Paths []string `default:["/echo"]`
ProfilePort int `default:"10086"`
// session
SessionTimeout string `default:"60s"`
sessionTimeout time.Duration
SessionNumber int `default:"1000"`
// app
FailFastTimeout string `default:"5s"`
failFastTimeout time.Duration
// session tcp parameters
GettySessionParam GettySessionParam `required:"true"`
}
)
func initConf() {
var (
err error
confFile string
)
// configure
confFile = os.Getenv(APP_CONF_FILE)
if confFile == "" {
panic(fmt.Sprintf("application configure file name is nil"))
return // I know it is of no usage. Just Err Protection.
}
if path.Ext(confFile) != ".toml" {
panic(fmt.Sprintf("application configure file name{%v} suffix must be .toml", confFile))
return
}
conf = new(Config)
config.MustLoadWithPath(confFile, conf)
conf.sessionTimeout, err = time.ParseDuration(conf.SessionTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(SessionTimeout{%#v}) = error{%v}", conf.SessionTimeout, err))
return
}
conf.failFastTimeout, err = time.ParseDuration(conf.FailFastTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(FailFastTimeout{%#v}) = error{%v}", conf.FailFastTimeout, err))
return
}
conf.GettySessionParam.tcpReadTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpReadTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(TcpReadTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpReadTimeout, err))
return
}
conf.GettySessionParam.tcpWriteTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpWriteTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(TcpWriteTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpWriteTimeout, err))
return
}
conf.GettySessionParam.waitTimeout, err = time.ParseDuration(conf.GettySessionParam.WaitTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(WaitTimeout{%#v}) = error{%v}", conf.GettySessionParam.WaitTimeout, err))
return
}
// gocolor.Info("config{%#v}\n", conf)
// log
confFile = os.Getenv(APP_LOG_CONF_FILE)
if confFile == "" {
panic(fmt.Sprintf("log configure file name is nil"))
return
}
if path.Ext(confFile) != ".xml" {
panic(fmt.Sprintf("log configure file name{%v} suffix must be .xml", confFile))
return
}
log.LoadConfiguration(confFile)
log.Info("config{%#v}", conf)
return
}
/******************************************************
# DESC : echo package
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-08-22 17:44
# FILE : utils.go
******************************************************/
package main
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"unsafe"
)
import (
log "github.com/AlexStocks/log4go"
)
////////////////////////////////////////////
// echo command
////////////////////////////////////////////
type echoCommand uint32
const (
heartbeatCmd = iota
echoCmd
)
var echoCommandStrings = [...]string{
"heartbeat",
"echo",
}
func (c echoCommand) String() string {
return echoCommandStrings[c]
}
////////////////////////////////////////////
// EchoPkgHandler
////////////////////////////////////////////
const (
echoPkgMagic = 0x20160905
maxEchoStringLen = 0xff
echoHeartbeatRequestString = "ping"
echoHeartbeatResponseString = "pong"
echoMessage = "Hello, getty!"
)
var (
ErrNotEnoughSteam = errors.New("packet stream is not enough")
ErrTooLargePackage = errors.New("package length is exceed the echo package's legal maximum length.")
ErrIllegalMagic = errors.New("package magic is not right.")
)
var (
echoPkgHeaderLen int
)
func init() {
echoPkgHeaderLen = (int)((uint)(unsafe.Sizeof(EchoPkgHeader{})))
}
type EchoPkgHeader struct {
Magic uint32
LogID uint32 // log id
Sequence uint32 // request/response sequence
ServiceID uint32 // service id
Command uint32 // operation command code
Code int32 // error code
Len uint16 // body length
_ uint16
_ int32 // reserved, maybe used as package md5 checksum
}
type EchoPackage struct {
H EchoPkgHeader
B string
}
func (this EchoPackage) String() string {
return fmt.Sprintf("log id:%d, sequence:%d, command:%s, echo string:%s",
this.H.LogID, this.H.Sequence, (echoCommand(this.H.Command)).String(), this.B)
}
func (this EchoPackage) Marshal() (*bytes.Buffer, error) {
var (
err error
buf *bytes.Buffer
)
buf = &bytes.Buffer{}
err = binary.Write(buf, binary.LittleEndian, this.H)
if err != nil {
return nil, err
}
buf.WriteByte((byte)(len(this.B)))
buf.WriteString(this.B)
return buf, nil
}
func (this *EchoPackage) Unmarshal(buf *bytes.Buffer) (int, error) {
var (
err error
len byte
)
if buf.Len() < echoPkgHeaderLen {
return 0, ErrNotEnoughSteam
}
// header
err = binary.Read(buf, binary.LittleEndian, &(this.H))
if err != nil {
return 0, err
}
if this.H.Magic != echoPkgMagic {
log.Error("@this.H.Magic{%x}, right magic{%x}", this.H.Magic, echoPkgMagic)
return 0, ErrIllegalMagic
}
if buf.Len() < (int)(this.H.Len) {
return 0, ErrNotEnoughSteam
}
// 防止恶意客户端把这个字段设置过大导致服务端死等或者服务端在准备对应的缓冲区时内存崩溃
if maxEchoStringLen < this.H.Len {
return 0, ErrTooLargePackage
}
len, err = buf.ReadByte()
if err != nil {
return 0, nil
}
this.B = (string)(buf.Next((int)(len)))
return (int)(this.H.Len) + 1 + echoPkgHeaderLen, nil
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment