如何实现Golang http 长连接

有问必答Rg 回复了问题 • 6 人关注 • 4 个回复 • 334 次浏览 • 2 天前 • 来自相关话题

求一个golang +UEditor(ckeditor)+七牛的例子

有问必答xiayf 回复了问题 • 4 人关注 • 2 个回复 • 93 次浏览 • 23 小时前 • 来自相关话题

NSQ 源码阅读

技术讨论xiayf 回复了问题 • 2 人关注 • 1 个回复 • 92 次浏览 • 12 小时前 • 来自相关话题

beego中路由参数中的controller传入问题

有问必答sundyli 回复了问题 • 2 人关注 • 1 个回复 • 78 次浏览 • 5 天前 • 来自相关话题

数据库底层设计models层

有问必答lrita 回复了问题 • 1 人关注 • 1 个回复 • 108 次浏览 • 1 天前 • 来自相关话题

北京/10年老厂招聘GO高级工程师(营销大数据企业,年内上市)

回复

招聘应聘mikerr 发起了问题 • 2 人关注 • 0 个回复 • 230 次浏览 • 6 天前 • 来自相关话题

如何实现一个Java Class字节解析器

文章分享tinylcy 发表了文章 • 4 个评论 • 71 次浏览 • 1 天前 • 来自相关话题

最近在写一个私人项目,名字叫做SmallVMSmallVM的目的在于通过实现一个轻量级的Java虚拟机... 查看全部

最近在写一个私人项目,名字叫做SmallVMSmallVM的目的在于通过实现一个轻量级的Java虚拟机,加深对Java虚拟机的认知和理解。在Java虚拟机加载类的过程中,需要对Class文件进行解析,我曾经单独实现过一个Java版的Class字节解析器ClassAnalyzer,相比于Java版,新版(Golang版)更加健壮,思路也更加清晰。本文即阐述我实现Class字节解析器的思路。


Class文件


作为类或者接口信息的载体,每个Class文件都完整的定义了一个类。为了使Java程序可以“编写一次,处处运行”,Java虚拟机规范Class文件进行了严格的规定。构成Class文件的基本数据单位是字节,这些字节之间不存在任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,单个字节无法表示的数据由多个连续的字节来表示。


根据Java虚拟机规范,Class文件采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表。Java虚拟机规范定义了u1u2u4u8来分别表示1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者是字符串。表是由多个无符号数或者其它表作为数据项构成的复合数据类型,表用于描述有层次关系的复合结构的数据,因此整个Class文件本质上就是一张表。在SmallVMu1u2u4u8分别对应于uint8uint16uint32uint64Class文件被描述为如下结构体。


type ClassFile struct {
magic uint32
minorVersion uint16
majorVersion uint16
constantPoolCount uint16
constantPool []constantpool.ConstantInfo
accessFlags uint16
thisClass uint16
superClass uint16
interfacesCount uint16
interfaces []uint16
fieldsCount uint16
fields []FieldInfo
methodsCount uint16
methods []MethodInfo
attributesCount uint16
attributes []attribute.AttributeInfo
}

type FieldInfo struct {
accessFlags uint16
nameIndex uint16
descriptorIndex uint16
attributesCount uint16
attributes []attribute.AttributeInfo
}

type MethodInfo struct {
accessFlags uint16
nameIndex uint16
descriptorIndex uint16
attributesCount uint16
attributes []attribute.AttributeInfo
}

如何解析


组成Class文件的各个数据项中,例如魔数、Class文件的版本、访问标志、类索引和父类索引等数据项,它们在每个Class文件中都占用固定数量的字节,在解析时只需要读取相应数量的字节。除此之外,需要灵活处理的主要包括4部分:常量池、字段表集合、方法表集合和属性表集合。字段和方法都可以具备自己的属性,Class本身也有相应的属性,因此,在解析字段表集合和方法表集合的同时也包含了属性表的解析。


常量池占据了Class文件很大一部分的数据,用于存储所有的常量信息,包括数字和字符串常量、类名、接口名、字段名和方法名等。Java虚拟机规范定义了多种常量类型,每一种常量类型都有自己的结构。常量池本身是一个表,在解析时有几点需要注意。



  • 每个常量类型都通过一个u1类型的tag来标识。

  • 表头给出的常量池大小(constantPoolCount)比实际大1,例如,如果constantPoolCount等于47,那么常量池中有46项常量。

  • 常量池的索引范围从1开始,例如,如果constantPoolCount等于47,那么常量池的索引范围为1~46。设计者将第0项空出来的目的是用于表达“不引用任何一个常量池项目”。

  • 如果一个CONSTANT_Long_infoCONSTANT_Double_info结构的项在常量池中的索引为n,则常量池中下一个有效的项的索引为n+2,此时常量池中索引为n+1的项有效但必须被认为不可用。

  • CONSTANT_Utf8_info型常量的结构中包含u1类型的tagu2类型的length和由lengthu1类型组成的bytes,这length字节的连续数据是一个使用MUTF-8Modified UTF-8)编码的字符串。MUTF-8UTF-8并不兼容,主要区别有两点:一是null字符会被编码成2字节(0xC00x80);二是补充字符是按照UTF-16拆分为代理对分别编码的,相关细节可以看这里(变种UTF-8)


属性表用于描述某些场景专有的信息,Class文件、字段表和方法表都有相应的属性表集合。Java虚拟机规范定义了多种属性,SmallVM目前实现了对常用属性的解析。和常量类型的数据项不同,属性并没有一个tag来标识属性的类型,但是每个属性都包含有一个u2类型的attribute_name_indexattribute_name_index指向常量池中的一个CONSTANT_Utf8_info类型的常量,该常量包含着属性的名称。在解析属性时,SmallVM正是通过attribute_name_index指向的常量对应的属性名称来得知属性的类型。


字段表用于描述类或者接口中声明的变量,字段包括类级变量以及实例级变量。字段表的结构包含一个u2类型的access_flags、一个u2类型的name_index、一个u2类型的descriptor_index、一个u2类型的attributes_countattributes_countattribute_info类型的attributes。我们已经介绍了属性表的解析,attributes的解析方式与属性表的解析方式一致。


Class的文件方法表采用了和字段表相同的存储格式,只是access_flags对应的含义有所不同。方法表包含着一个重要的属性:Code属性。Code属性存储了Java代码编译成的字节码指令,在SmallVM中,Code对应的结构体如下所示(仅列出了类属性)。


type Code struct {
pool []constantpool.ConstantInfo
attributeNameIndex uint16
attributeLength uint32
maxStack uint16
maxLocals uint16
codeLength uint32
code []byte
exceptionTableLength uint16
exceptionTable []ExceptionInfo
attributesCount uint16
attributes []AttributeInfo
}

type ExceptionInfo struct {
startPc uint16
endPc uint16
handlerPc uint16
catchType uint16
}

Code属性中,codeLengthcode分别用于存储字节码长度和字节码指令,每条指令即一个字节(u1类型)。在虚拟机执行时,通过读取code中的一个个字节码,并将字节码翻译成相应的指令。另外,虽然codeLength是一个u4类型的值,但是实际上一个方法不允许超过65535条字节码指令。


代码实现


整个Class字节解析器的源码已放在了GitHub上,字节解析器仅仅是SmallVM的一个小模块,对应的目录为src/classfile。另外,可以参考ClassAnalyzerREADME,我以一个类的Class文件为例,对该Class文件的每个字节进行了分析,希望对大家的理解有所帮助。

升级 mac os 10.12.4 后 beego 跑不起来了

回复

有问必答uuapp 发起了问题 • 1 人关注 • 0 个回复 • 91 次浏览 • 1 天前 • 来自相关话题

web后端动态匹配模型查询

回复

有问必答moss 发起了问题 • 1 人关注 • 0 个回复 • 70 次浏览 • 1 天前 • 来自相关话题

Go 語言的錯誤訊息處理

文章分享appleboy 发表了文章 • 1 个评论 • 133 次浏览 • 4 天前 • 来自相关话题

本文轉錄自: Go 語言的錯誤訊息處理

每個語言對於錯誤訊息的處理方式都不同,在學習每個語言時... 查看全部

本文轉錄自: Go 語言的錯誤訊息處理


每個語言對於錯誤訊息的處理方式都不同,在學習每個語言時,都要先學會如何在程式內處理錯誤訊息 (Error Handler),而在 Go 語言的錯誤處理是非常簡單,本篇會用簡單的範例教大家 Go 如何處理錯誤訊息。



Go 輸出錯誤訊息


在 Go 語言內有兩種方式讓函示 (function) 可以回傳錯誤訊息,一種是透過 errors 套件或 fmt 套件,先看看 errors 套件使用方式:


package main

import (
"errors"
"fmt"
)

func isEnable(enable bool) (bool, error) {
if enable {
return false, errors.New("You can't enable this setting")
}

return true, nil
}

func main() {
if _, err := isEnable(true); err != nil {
fmt.Println(err.Error())
}
}

請先引入 errors 套件,接著透過 errors.New("message here"),就可以實現 error 錯誤訊息。接著我們打開 errors package 原始碼來看看


package errors

// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
s string
}

func (e *errorString) Error() string {
return e.s
}

可以發現 errors 套件內提供了 New 函示,讓開發者可以直接建立 error 物件,並且實現了 error interface。在 Go 語言有定義 error interface 為:


type error interface {
Error() string
}

只要任何 stuct 有實作 Error() 接口,就可以變成 error 物件。這在下面的自訂錯誤訊息會在提到。除了上面使用 errors 套件外,還可以使用 fmt 套件,將上述程式碼改成:


func isEnable(enable bool) (bool, error) {
if enable {
return false, fmt.Errorf("You can't enable this setting")
}

return true, nil
}

這樣也可以成功輸出錯誤訊息,請深入看 fmt.Errorf


// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}

你可以發現在 fmt 套件內,引用了 errors 套件,所以基本上本質是一樣的。


Go 錯誤訊息測試


在 Go 語言如何測試錯誤訊息,直接引用 testing 套件


package error

import "testing"

func TestIsMyError(t *testing.T) {
ok, err := isEnable(true)

if ok {
t.Fatal("should be false")
}

if err.Error() != "You can't enable this setting" {
t.Fatal("message error")
}
}

另外 Go 語言最常用的測試套件 Testify,可以改寫如下:


package error

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestIsEnable(t *testing.T) {
ok, err := isEnable(true)
assert.False(t, ok)
assert.NotNil(t, err)
assert.Equal(t, "You can't enable this setting", err.Error())
}

Go 自訂錯誤訊息


從上面的例子可以看到,錯誤訊息都是固定的,如果我們要動態改動錯誤訊息,就必須帶變數進去。底下我們來看看如何實現自訂錯誤訊息:


package main

import (
"fmt"
)

// MyError is an error implementation that includes a time and message.
type MyError struct {
Title string
Message string
}

func (e MyError) Error() string {
return fmt.Sprintf("%v: %v", e.Title, e.Message)
}

func main() {
err := MyError{"Error Title 1", "Error Message 1"}
fmt.Println(err)

err = MyError{
Title: "Error Title 2",
Message: "Error Message 2",
}
fmt.Println(err)
}

也可以把錯誤訊息包成 Package 方式


package error

import (
"fmt"
)

// MyError is an error implementation that includes a time and message.
type MyError struct {
Title string
Message string
}

func (e MyError) Error() string {
return fmt.Sprintf("%v: %v", e.Title, e.Message)
}

main.go 就可以直接引用 error 套件


package main

import (
"fmt"

my "github.com/go-training/training/example04/error"
)

func main() {
err := my.MyError{"Error Title 1", "Error Message 1"}
fmt.Println(err)

err = my.MyError{
Title: "Error Title 2",
Message: "Error Message 2",
}
fmt.Println(err)
}

如何測試錯誤訊息是我們自己所定義的呢?請在 error 套件內加入底下測試函示


func IsMyError(err error) bool {
_, ok := err.(MyError)
return ok
}

由於我們實作了 error 接口,只要是 Interface 就可以透過 Type assertion 來判斷此錯誤訊息是否為 MyError


package error

import "testing"

func TestIsMyError(t *testing.T) {
err := MyError{"title", "message"}

ok := IsMyError(err)

if !ok {
t.Fatal("error is not MyError")
}

if err.Error() != "title: message" {
t.Fatal("message error")
}
}

這樣在專案裡就可以實現多個錯誤訊息,寫測試時就可以直接判斷錯誤訊息為哪一種自訂格式。


結論


在 Go 裡面寫錯誤訊息真的很方便又很容易,動態訊息請自定,反之,固定訊息請直接宣告 const 就可以了。在寫套件給大家使用時,大部份都是使用固定訊息,如果是大型專案牽扯到資料庫時,通常會用動態自訂錯誤訊息比較多。


上述程式碼請參考這裡

golang interface浅谈

技术讨论themoonstone 发表了文章 • 0 个评论 • 27 次浏览 • 1 小时前 • 来自相关话题

golang 的接口设计是这门编程语言的两大亮点之一,本文仅浅谈golang接口和其它语言(以C++为例)的区别。 引用《Go语言编程》作者在书中的话,go语言中的接口是“非侵入式”的,而其它语言中的接口是“侵入式”的。因此,需要对非侵入式和侵入式分别理... 查看全部

golang 的接口设计是这门编程语言的两大亮点之一,本文仅浅谈golang接口和其它语言(以C++为例)的区别。
引用《Go语言编程》作者在书中的话,go语言中的接口是“非侵入式”的,而其它语言中的接口是“侵入式”的。因此,需要对非侵入式和侵入式分别理解才能理解清楚golang 接口的优点
首先 让我们看看C++中的接口:
在C++中,接口的实现必须要通过继承


interface IFoo{
void Bar();
}

class Foo: public IFoo
{}

IFoo *Foo=new IFoo;

在此类语言中 即使另外有一个接口IFoo2实现了与IFoo完全一样的接口方法,甚至名字相同只不过位于不同的名字空间下,编译器也会认为上面的类Foo只实现了IFoo的接口,而没有实现IFoo2的接口
此类接口称为侵入式接口。“侵入式”的主要表现是实现类必须明确表示自己实现了某个接口。在C++里面表示为需要继承。
举一个具体的实例:


#include <iostream>
using namespace std;

class Person
{
public:
Person():m_strName("###"){};
virtual void SetName(const string name)=0;
private:
string m_strName;
};

class Person2
{
public:
Person2():m_strName("###"){};
virtual void SetName(const string name)=0;
private:
string m_strName;
};

class Student:public Person
{
public:
Student():m_strName("***"){};
void SetName(const string name);
private:
string m_strName;
};

void Student::SetName(const string name){
std::cout << m_strName << std::endl;
m_strName=name;
std::cout << m_strName << std::endl;
}

int main() {
Student s;
Person *p;
p=&s;
Person2 *p2;
p2=&s;
return 0;
}

在这里 因为Student没有声明继承Person2 所以 针对 p2=$s;这段代码 会出现:“cannot convert Struden* to Person2 in assignment”这样的错误。
而在GO语言中,一个类只要实现了该接口所有的函数,就代表这个类实现了该接口


type file struct{}
func(f *File) Read(buf []byte)(n int, err error)
func(f *File) Write(buf []byte)(n int, err error)
func(f *File) Seek(off int64, err eror)(pos int, err error)
func(f *File) Close() err error

type IFile interface{
Read(buf []byte)(n int, err error)
Write(buf []byte)(n int, err error)
Seek(off int64, err eror)(pos int, err error)
Close() err error
}

type IReader interface
{
Read(buf []byte)(n int, err error)
}
type IWriter interface
{
Write(buf []byte)(n int, err error)
}
type ICloser interface
{
Close() err error
}

尽管file类并没有从这些接口继承,甚至可以不知道这些接口的存在,但是file实现了这些接口的所有方法,就可以说file类实现了这些接口,可以进行赋值
其实我们可以用一个通俗的例子来进行一下说明:
对于侵入式接口,假设你在参加高考,但不是全国统考,而是高校自主命题,此时有X大学和Y大学两所学校,他们的考试题目是完全相同的,录取分数也是完全相同的,但就算你参加了X大学的考试,达到了X大学的分数线,你也不能申请就读Y大学,要想申请Y大学,你必须再次参加Y大学的考试并达到Y大学的分数线,这里的参加考试并达到分数线就相当于C++中的继承,如果没有声明继承,就不能说明实现了某个接口
而对于非侵入式接口,同样是高考,但这次是全国统考,你考过了分数线,这个时候 你既能申请X大学,又能申请Y大学,因为你达到了这两所大学所要求的统一分数,相当于你实现了X和Y两个接口里的所有方法,所以既能调用X 又能调用Y
综上,侵入式接口和非侵入式接口区别的关键在于是否需要明确声明你的类实现了某个接口。
以上仅代表个人理解,对于不对的或者不恰当的地方,欢迎批评和指正。