├── README.md ├── message ├── iso8583 │ └── field │ │ ├── field.go │ │ ├── bitmap.go │ │ └── spec.go ├── simple │ └── simple.go └── tlv │ └── tlv.go ├── go.mod ├── server ├── handler │ └── handler.go ├── request │ └── request.go ├── server.go ├── manager │ ├── manager.go │ └── epoll.go └── connect │ └── conn.go ├── encoding ├── encode.go ├── a.go ├── binary.go ├── ascii.go ├── hex.go ├── gbk.go ├── bcd.go └── z_ascii_table.md ├── go.sum └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | # go-socket 2 | 3 | go语言开发的轻量化物联网后台常用的socket server,包括连接管理,消息处理器,常用编码转换器等。 4 | -------------------------------------------------------------------------------- /message/iso8583/field/field.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/18 03:29 3 | * @Author: yt.yin 4 | */ 5 | 6 | package field 7 | 8 | -------------------------------------------------------------------------------- /message/iso8583/field/bitmap.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/18 03:28 3 | * @Author: yt.yin 4 | */ 5 | 6 | package field 7 | 8 | 9 | -------------------------------------------------------------------------------- /message/iso8583/field/spec.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/18 03:27 3 | * @Author: yt.yin 4 | */ 5 | 6 | package field 7 | 8 | 9 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/goworkeryyt/go-socket 2 | 3 | go 1.17 4 | 5 | require ( 6 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 7 | golang.org/x/text v0.3.7 8 | ) 9 | -------------------------------------------------------------------------------- /server/handler/handler.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/21 11:43 3 | * @Author: yt.yin 4 | */ 5 | 6 | // Package handler 处理消息业务的接口 7 | package handler 8 | 9 | // MsgHandlerI 消息管理接口 10 | type MsgHandlerI interface{ 11 | 12 | } 13 | 14 | -------------------------------------------------------------------------------- /encoding/encode.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/18 12:09 3 | * @Author: yt.yin 4 | */ 5 | 6 | package encoding 7 | 8 | type Encoder interface { 9 | // Encode 编码 10 | Encode([]byte) ([]byte, error) 11 | 12 | // Decode 解码 13 | Decode([]byte) (data []byte, err error) 14 | } 15 | -------------------------------------------------------------------------------- /encoding/a.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/17 18:02 3 | * @Author: yt.yin 4 | */ 5 | 6 | package encoding 7 | 8 | // 定义包内的一个解码组,涵盖定义的常见的解码器 9 | type encodeGroup struct{ 10 | // ASCII 编码器 11 | AsciiEncoder 12 | // BCD 编码器 13 | BcdEncoder 14 | // 二进制编码器 15 | BinaryEncoder 16 | // HEX 编码器 17 | HexEncoder 18 | } 19 | 20 | // EncodeGroup 对外统一开放一个解码组 21 | var EncodeGroup = new(encodeGroup) 22 | -------------------------------------------------------------------------------- /server/request/request.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/17 15:19 3 | * @Author: yt.yin 4 | */ 5 | 6 | package request 7 | 8 | import "github.com/goworkeryyt/go-socket/server/connect" 9 | 10 | // RequestI 客户端请求的连接信息和数据 11 | type RequestI interface{ 12 | 13 | // Conn 客户端请求的连接信息 14 | Conn() connect.ConnI 15 | 16 | // Data 获取当前请求的数据 17 | Data() []byte 18 | 19 | // MsgID 获取请求的消息ID 20 | MsgID() uint32 21 | } 22 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/18 14:44 3 | * @Author: yt.yin 4 | */ 5 | 6 | package server 7 | 8 | import "github.com/goworkeryyt/go-socket/server/manager" 9 | 10 | // ServerI 定义服务端的接口 11 | type ServerI interface { 12 | 13 | // Start 启动服务 14 | Start() 15 | 16 | // Stop 停止服务 17 | Stop() 18 | 19 | // Run 启动业务 20 | Run() 21 | 22 | // ConnManager 获取连接管理 23 | ConnManager() manager.ManagerI 24 | 25 | } 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= 2 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 3 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 4 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 5 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /server/manager/manager.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/18 14:39 3 | * @Author: yt.yin 4 | */ 5 | 6 | package manager 7 | 8 | import "github.com/goworkeryyt/go-socket/server/connect" 9 | 10 | // 定义连接管理器的接口 11 | 12 | // ManagerI TCP 连接管理,添加、删除、通过以恶搞连接ID获得连接对象,当前连接数、清空全部连接等方法 13 | type ManagerI interface{ 14 | 15 | // Add 添加链接 16 | Add(conn connect.ConnI) error 17 | 18 | // Remove 删除连接 19 | Remove(conn connect.ConnI) error 20 | 21 | // Conn 根据ConnID获取Tcp连接 22 | Conn(connID int) (connect.ConnI, error) 23 | 24 | // Size 获取当前连接数 25 | Size() int 26 | 27 | // Vacuum 清空连接 28 | Vacuum() 29 | } 30 | -------------------------------------------------------------------------------- /encoding/binary.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/17 20:48 3 | * @Author: yt.yin 4 | */ 5 | 6 | package encoding 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | ) 12 | 13 | type BinaryEncoder struct{} 14 | 15 | // ByteToBinStr 将byte 以8个bit位的形式展示 16 | func(e *BinaryEncoder) ByteToBinStr(b byte) string { 17 | return fmt.Sprintf("%08b", b) 18 | } 19 | 20 | // BytesToBinStr 将byte数组转成8个bit位一组的字符串 21 | func (e *BinaryEncoder) BytesToBinStr(bs []byte) string { 22 | if len(bs) <= 0 { 23 | return "" 24 | } 25 | buf := bytes.NewBuffer([]byte{}) 26 | for _, v := range bs { 27 | buf.WriteString(fmt.Sprintf("%08b", v)) 28 | } 29 | return buf.String() 30 | } 31 | 32 | // BytesToBinStrWithSplit 将byte数组转8个bit一组的字符串并且带分割符 33 | func (e *BinaryEncoder) BytesToBinStrWithSplit(bs []byte,split string) string { 34 | length := len(bs) 35 | if length <= 0 { 36 | return "" 37 | } 38 | buf := bytes.NewBuffer([]byte{}) 39 | for i := 0; i < length-1; i++ { 40 | v := bs[i] 41 | buf.WriteString(fmt.Sprintf("%08b", v)) 42 | buf.WriteString(split) 43 | } 44 | buf.WriteString(fmt.Sprintf("%08b",bs[length-1])) 45 | return buf.String() 46 | } -------------------------------------------------------------------------------- /encoding/ascii.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/17 18:01 3 | * @Author: yt.yin 4 | */ 5 | 6 | package encoding 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | type AsciiEncoder struct{} 13 | 14 | // Encode ASCII 编码 15 | func (a AsciiEncoder) Encode(data []byte) ([]byte, error) { 16 | bs := make([]byte,len(data)) 17 | for _, d := range data { 18 | if d > 127 { 19 | return nil, fmt.Errorf("无效的 ASCII 字符:'%s'", string(d)) 20 | } 21 | bs = append(bs,d) 22 | } 23 | return bs, nil 24 | } 25 | 26 | // Decode 指定长度 ASCII 解码 27 | func (a AsciiEncoder) Decode(data []byte) ([]byte, error) { 28 | bs := make([]byte,len(data)) 29 | for _, d := range data { 30 | if d > 127 { 31 | return nil, fmt.Errorf("无效的 ASCII 字符:'%s'", string(d)) 32 | } 33 | bs = append(bs,d) 34 | } 35 | return bs, nil 36 | } 37 | 38 | // AssignLenDecode 指定长度 ASCII 解码 39 | func (a AsciiEncoder) AssignLenDecode(data []byte, length int) ([]byte, int, error) { 40 | // 要解码的长度必须不小于原始字节数组的长度 41 | if len(data) < length { 42 | return nil, 0, fmt.Errorf("要解码的长度必须不小于原始字节数组的长度, 期望长度 %d, 实际长度 %d", length, len(data)) 43 | } 44 | data = data[:length] 45 | var out []byte 46 | for _, d := range data { 47 | if d > 127 { 48 | return nil, 0, fmt.Errorf("无效的 ASCII 字符:'%s'", string(d)) 49 | } 50 | out = append(out, d) 51 | } 52 | return out, length, nil 53 | } 54 | 55 | -------------------------------------------------------------------------------- /message/simple/simple.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/18 03:09 3 | * @Author: yt.yin 4 | */ 5 | 6 | package simple 7 | 8 | // SimpleMsgI 通用的message接口 9 | type SimpleMsgI interface { 10 | 11 | // MsgID 消息ID 12 | MsgID() uint32 13 | 14 | // DataLen 获取消息数据段长度 15 | DataLen() int 16 | 17 | // GetData 获取消息内容 18 | GetData() []byte 19 | 20 | // SetMsgID 设置消息ID 21 | SetMsgID(id uint32) 22 | 23 | // SetDataLen 设置消息数据段长度 24 | SetDataLen(length int) 25 | 26 | // SetData 设置消息内容 27 | SetData(data []byte) 28 | } 29 | 30 | // SimpleMsg 消息 31 | type SimpleMsg struct { 32 | 33 | //消息的ID 34 | ID uint32 35 | 36 | //消息的长度 37 | Len int 38 | 39 | //消息的内容 40 | Data []byte 41 | } 42 | 43 | // NewSimpleMsg new 一个message 44 | func NewSimpleMsg(ID uint32, data []byte) *SimpleMsg { 45 | return &SimpleMsg{ 46 | Len: len(data), 47 | ID: ID, 48 | Data: data, 49 | } 50 | } 51 | 52 | // MsgID 获取消息ID 53 | func (m *SimpleMsg) MsgID() uint32 { 54 | return m.ID 55 | } 56 | 57 | // DataLen 获取消息长度 58 | func (m *SimpleMsg) DataLen() int { 59 | return m.Len 60 | } 61 | 62 | // GetData 获取消息内容 63 | func (m *SimpleMsg) GetData() []byte { 64 | return m.Data 65 | } 66 | 67 | // SetMsgID 设置消息ID 68 | func (m *SimpleMsg) SetMsgID(id uint32) { 69 | m.ID = id 70 | } 71 | 72 | // SetDataLen 设置消息长度 73 | func (m *SimpleMsg) SetDataLen(length int) { 74 | m.Len = length 75 | } 76 | 77 | // SetData 设置数据 78 | func (m *SimpleMsg) SetData(data []byte) { 79 | m.Data = data 80 | } 81 | -------------------------------------------------------------------------------- /encoding/hex.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/18 02:45 3 | * @Author: yt.yin 4 | */ 5 | 6 | package encoding 7 | 8 | import ( 9 | "encoding/hex" 10 | "strings" 11 | ) 12 | 13 | // HexEncoder hex 编码器 14 | type HexEncoder struct{} 15 | 16 | // Encode 编码 []byte{0x55, 0xAA} 被转成 55AA 17 | func (h *HexEncoder) Encode (src []byte) (dst []byte, err error){ 18 | s := strings.ToUpper(hex.EncodeToString(src)) 19 | return []byte(s),nil 20 | } 21 | 22 | // Decode 解码 AABBCC 转成字节数组 []byte{0xAA, 0xBB, 0xCC} 23 | func (h *HexEncoder) Decode(src []byte) (dst []byte, err error){ 24 | return hex.DecodeString(string(src)) 25 | } 26 | 27 | // BytesToHex 字节数组转hex 28 | // []byte{0x55, 0xAA} 被转成 55AA 29 | func (e *HexEncoder) BytesToHex(data []byte) string{ 30 | return strings.ToUpper(hex.EncodeToString(data)) 31 | } 32 | 33 | // HexToBytes 将hex 字符串转成 byte数组 34 | // AABBCC 转成字节数组 []byte{0xAA, 0xBB, 0xCC} 35 | func (e *HexEncoder) HexToBytes(hexStr string) ([]byte, error) { 36 | return hex.DecodeString(hexStr) 37 | } 38 | 39 | // HexBCC 计算BCC校验码 40 | func (e *HexEncoder) HexBCC(hexStr string) string { 41 | hexToBytes, err := e.HexToBytes(hexStr) 42 | if err != nil { 43 | return "" 44 | } 45 | length := len(hexToBytes) 46 | if length < 1 { 47 | return "" 48 | } 49 | bcc := hexToBytes[0] 50 | if length > 1 { 51 | for i := 1; i < length; i++ { 52 | bcc = bcc ^ hexToBytes[i] 53 | } 54 | } 55 | return e.BytesToHex([]byte{bcc & 0xFF}) 56 | } 57 | 58 | // BytesBCC 计算 BCC 59 | func BytesBCC(bytes []byte) byte { 60 | bcc := bytes[0] 61 | if len(bytes) > 1 { 62 | for i := 1; i < len(bytes); i++ { 63 | bcc = bcc ^ bytes[i] 64 | } 65 | } 66 | return bcc & 0xFF 67 | } -------------------------------------------------------------------------------- /encoding/gbk.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/18 15:11 3 | * @Author: yt.yin 4 | */ 5 | 6 | package encoding 7 | 8 | import "golang.org/x/text/encoding/simplifiedchinese" 9 | 10 | type Charset string 11 | 12 | const ( 13 | UTF8 = Charset("UTF-8") 14 | GB18030 = Charset("GB18030") 15 | GBK = Charset("GBK") 16 | ) 17 | 18 | // GBKEncoder GBK 编码器, go 语言默认使用UTF-8编码,国内很多协议使用的编码规范为 GBK,或 GB18030 19 | type GBKEncoder struct{} 20 | 21 | // Encode 编码 utf-8 转到 gbk 22 | func (g *GBKEncoder) Encode(src []byte) ([]byte, error) { 23 | return simplifiedchinese.GBK.NewEncoder().Bytes(src) 24 | } 25 | 26 | // Decode 解码 gbk 转到 utf-8 27 | func (g *GBKEncoder) Decode(src []byte) (data []byte, err error) { 28 | return simplifiedchinese.GBK.NewEncoder().Bytes(src) 29 | } 30 | 31 | // UTF8StrToGBK UTF 字符串转 GBK 32 | func UTF8StrToGBK(str string) (string,error) { 33 | decodeBytes,err :=simplifiedchinese.GBK.NewEncoder().Bytes([]byte(str)) 34 | if err != nil { 35 | return "", err 36 | } 37 | return string(decodeBytes),nil 38 | } 39 | 40 | // GBKStrToUTF8 GBK 字符串转UTF 41 | func GBKStrToUTF8(str string) (string,error) { 42 | decodeBytes,err :=simplifiedchinese.GBK.NewDecoder().Bytes([]byte(str)) 43 | if err != nil { 44 | return "", err 45 | } 46 | return string(decodeBytes),nil 47 | } 48 | 49 | // UTF8StrToGB18030 UTF 字符串转 GB18030 50 | func UTF8StrToGB18030(str string) (string,error) { 51 | decodeBytes,err :=simplifiedchinese.GB18030.NewEncoder().Bytes([]byte(str)) 52 | if err != nil { 53 | return "", err 54 | } 55 | return string(decodeBytes),nil 56 | } 57 | 58 | // GB18030StrToUTF8 GBK 字符串转UTF 59 | func GB18030StrToUTF8(str string) (string,error) { 60 | decodeBytes,err :=simplifiedchinese.GB18030.NewDecoder().Bytes([]byte(str)) 61 | if err != nil { 62 | return "", err 63 | } 64 | return string(decodeBytes),nil 65 | } 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /message/tlv/tlv.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/18 03:14 3 | * @Author: yt.yin 4 | */ 5 | 6 | package tlv 7 | 8 | import "github.com/goworkeryyt/go-socket/message/simple" 9 | 10 | // TLVMsgI 定义TLV消息模版 11 | type TLVMsgI interface { 12 | 13 | simple.SimpleMsgI 14 | 15 | // GetTag 消息标识 16 | GetTag() []byte 17 | 18 | // GetBegin 消息头或开始,常用的有 FFFF,55AA 等 19 | GetBegin() []byte 20 | 21 | // GetEnd 消息结束符,常用的有 FF,回车符等 22 | GetEnd() []byte 23 | 24 | // GetICV 完整性校验值,常用的有BCC校验 CRC校验 25 | GetICV() []byte 26 | 27 | // SetTag 设置消息标识 28 | SetTag(tag []byte) 29 | 30 | // SetBegin 设置消息头或开始,常用的有 FFFF,55AA 等 31 | SetBegin(b []byte) 32 | 33 | // SetEnd 设置消息结束符,常用的有 FF,回车符等 34 | SetEnd(e []byte) 35 | 36 | // SetICV 设置 完整性校验值 37 | SetICV(c []byte) 38 | } 39 | 40 | // TLVMsg TLV 格式报文消息 41 | type TLVMsg struct{ 42 | 43 | simple.SimpleMsg 44 | 45 | // 报文标识 46 | Tag []byte 47 | 48 | // 消息头或开始,常用的有 FFFF,55AA 等 49 | Begin []byte 50 | 51 | // End 消息结束符,常用的有 FF,回车符等 52 | End []byte 53 | 54 | // ICV 完整性校验值,常用的有BCC校验 CRC校验 55 | ICV []byte 56 | } 57 | 58 | // NewTLVMsg new 一个message 59 | func NewTLVMsg(tag,begin,end,icv[]byte,msg simple.SimpleMsg) *TLVMsg { 60 | return &TLVMsg{ 61 | SimpleMsg: msg, 62 | Tag: tag, 63 | Begin: begin, 64 | End: end, 65 | ICV: icv, 66 | } 67 | } 68 | 69 | // GetTag 获取报文标识 70 | func (T *TLVMsg) GetTag() []byte { 71 | return T.Tag 72 | } 73 | 74 | // GetBegin 获取报文开始标识 75 | func (T *TLVMsg) GetBegin() []byte { 76 | return T.Begin 77 | } 78 | 79 | // GetEnd 获取报文结束标识 80 | func (T *TLVMsg) GetEnd() []byte { 81 | return T.End 82 | } 83 | 84 | // GetICV 获取报文校验 85 | func (T *TLVMsg) GetICV() []byte { 86 | return T.ICV 87 | } 88 | 89 | // SetTag 设置报文标识 90 | func (T *TLVMsg) SetTag(tag []byte) { 91 | T.Tag = tag 92 | } 93 | 94 | // SetBegin 设置报文开始标识 95 | func (T *TLVMsg) SetBegin(b []byte) { 96 | T.Begin = b 97 | } 98 | 99 | // SetEnd 设置报文结束标识 100 | func (T *TLVMsg) SetEnd(e []byte) { 101 | T.End = e 102 | } 103 | 104 | // SetICV 设置报文校验 105 | func (T *TLVMsg) SetICV(c []byte) { 106 | T.ICV = c 107 | } 108 | 109 | -------------------------------------------------------------------------------- /server/connect/conn.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/17 11:41 3 | * @Author: yt.yin 4 | */ 5 | 6 | package connect 7 | 8 | import ( 9 | "context" 10 | "net" 11 | "sync" 12 | 13 | "github.com/goworkeryyt/go-socket/server/handler" 14 | ) 15 | 16 | // ConnI 定义连接接口 17 | type ConnI interface { 18 | 19 | // Start 启动连接 20 | Start() 21 | 22 | // Stop 停止连接 23 | Stop() 24 | 25 | // Context 返回上下文,获取连接推出状态等 26 | Context() context.Context 27 | 28 | // Conn 获取原始的 socket 连接 29 | Conn() net.Conn 30 | 31 | // ConnID 连接ID 32 | ConnID() int 33 | 34 | // RemoteAddr 获取远程客户端地址信息 35 | RemoteAddr() net.Addr 36 | 37 | // SetMsgMsgHandler 设置消息处理器 38 | SetMsgMsgHandler() 39 | 40 | // SendMsg 发送消息给TCP客户端,无缓存 41 | SendMsg(msgId uint32, data []byte) error 42 | 43 | // SendBufMsg 发送消息给TCP客户端(有缓冲) 44 | SendBufMsg(msgID uint32, data []byte) error 45 | 46 | // SetAttr 设置链接属性 47 | SetAttr(key string, value interface{}) 48 | 49 | // Attr 获取链接属性 50 | Attr(key string) (interface{}, error) 51 | 52 | // DelAttr 移除链接属性 53 | DelAttr(key string) 54 | } 55 | 56 | // SimpleConn 简单连接实现 57 | type SimpleConn struct { 58 | 59 | // 当前的socket连接 60 | conn net.Conn 61 | 62 | // 消息处理器 63 | MsgHandler handler.MsgHandlerI 64 | 65 | // 连接的上下文 66 | ctx context.Context 67 | 68 | // 通知连接退出停止 69 | cancel context.CancelFunc 70 | 71 | // 无缓冲管道 72 | noBufferChan chan []byte 73 | 74 | // 有缓存管道 75 | BufferChan chan []byte 76 | 77 | // 读写锁 78 | sync.RWMutex 79 | 80 | // 连接属性 81 | attrs map[string]interface{} 82 | 83 | // 读写属性锁 84 | attrLock sync.Mutex 85 | 86 | // 当前连接的状态,closed 为true 连接已经处于关闭状态 87 | isClosed bool 88 | 89 | } 90 | 91 | // Start 启动连接让连接开始工作 92 | func (s *SimpleConn) Start() { 93 | panic("implement me") 94 | } 95 | 96 | // Stop 停止连接 97 | func (s *SimpleConn) Stop() { 98 | s.cancel() 99 | } 100 | 101 | func (s *SimpleConn) Context() context.Context { 102 | panic("implement me") 103 | } 104 | 105 | func (s *SimpleConn) Conn() net.Conn { 106 | panic("implement me") 107 | } 108 | 109 | func (s *SimpleConn) ConnID() int { 110 | panic("implement me") 111 | } 112 | 113 | func (s *SimpleConn) RemoteAddr() net.Addr { 114 | panic("implement me") 115 | } 116 | 117 | func (s *SimpleConn) SetMsgMsgHandler() { 118 | panic("implement me") 119 | } 120 | 121 | func (s *SimpleConn) SendMsg(msgId uint32, data []byte) error { 122 | panic("implement me") 123 | } 124 | 125 | func (s *SimpleConn) SendBufMsg(msgID uint32, data []byte) error { 126 | panic("implement me") 127 | } 128 | 129 | func (s *SimpleConn) SetAttr(key string, value interface{}) { 130 | panic("implement me") 131 | } 132 | 133 | func (s *SimpleConn) Attr(key string) (interface{}, error) { 134 | panic("implement me") 135 | } 136 | 137 | func (s *SimpleConn) DelAttr(key string) { 138 | panic("implement me") 139 | } 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /encoding/bcd.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/17 18:50 3 | * @Author: yt.yin 4 | */ 5 | 6 | package encoding 7 | 8 | import ( 9 | "bytes" 10 | "encoding/hex" 11 | "errors" 12 | "fmt" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | // BcdEncoder bcd 编码一般只针对数字 18 | type BcdEncoder struct{} 19 | 20 | // Encode 编码 21 | func (b *BcdEncoder) Encode (src []byte) (dst []byte, err error){ 22 | if len(src)%2 != 0 { 23 | src = append([]byte("0"), src...) 24 | } 25 | return bcd(src) 26 | } 27 | 28 | // Decode 解码 29 | func (b *BcdEncoder) Decode(src []byte) (dst []byte, err error){ 30 | hexStr := hex.EncodeToString(src) 31 | return []byte(hexStr),nil 32 | } 33 | 34 | // AssignLenDecode 指定长度解析 35 | func (b *BcdEncoder) AssignLenDecode(src []byte, length int) ([]byte, error){ 36 | hexStr := hex.EncodeToString(src) 37 | l := len(hexStr) 38 | if l == length{ 39 | return []byte(hexStr),nil 40 | }else if length == l-1 && strings.HasPrefix(hexStr,"0"){ 41 | return []byte(hexStr[1:]),nil 42 | } 43 | return nil,fmt.Errorf("原始字节数组长度和指定解码长度不匹配, 期望长度 %d, 实际长度 %d", length, l) 44 | } 45 | 46 | // Uint64ToBcd 64位无符号正数转bcd 47 | func(b *BcdEncoder) Uint64ToBcd(v uint64) ([]byte,error) { 48 | numStr := strconv.FormatUint(v, 10) 49 | if len(numStr) % 2 != 0 { 50 | // 左边补一个0 51 | var sb bytes.Buffer 52 | sb.WriteString("0") 53 | sb.WriteString(numStr) 54 | numStr = sb.String() 55 | } 56 | return bcd([]byte(numStr)) 57 | } 58 | 59 | // UintToBcd 无符号正数转bcd 60 | func(b *BcdEncoder) UintToBcd(v uint) ([]byte,error) { 61 | numStr := strconv.Itoa(int(v)) 62 | if len(numStr) % 2 != 0 { 63 | var sb bytes.Buffer 64 | sb.WriteString("0") 65 | sb.WriteString(numStr) 66 | numStr = sb.String() 67 | } 68 | return bcd([]byte(numStr)) 69 | } 70 | 71 | // IntToBcd int 转bcd 72 | func (b *BcdEncoder) IntToBcd(v int) ([]byte,error) { 73 | if v < 0 { 74 | return nil,errors.New("负数不能转BCD格式") 75 | } 76 | numStr := strconv.Itoa(v) 77 | if len(numStr) % 2 != 0 { 78 | var sb bytes.Buffer 79 | sb.WriteString("0") 80 | sb.WriteString(numStr) 81 | numStr = sb.String() 82 | } 83 | return bcd([]byte(numStr)) 84 | } 85 | 86 | // BcdToInt bcd 转int 87 | func (b *BcdEncoder) BcdToInt(data []byte) (int,error){ 88 | hexStr := hex.EncodeToString(data) 89 | parseUint, err := strconv.ParseUint(hexStr, 10, 32) 90 | return int(parseUint), err 91 | } 92 | 93 | // BcdToUint32 bcd转 unit32 94 | func (b *BcdEncoder) BcdToUint32(data []byte) (uint32,error) { 95 | hexStr := hex.EncodeToString(data) 96 | parseUint, err := strconv.ParseUint(hexStr, 10, 32) 97 | return uint32(parseUint), err 98 | } 99 | 100 | // BcdToUint64 bcd转 unit64 101 | func (b *BcdEncoder) BcdToUint64(data []byte) (uint64,error){ 102 | hexStr := hex.EncodeToString(data) 103 | return strconv.ParseUint(hexStr, 10, 64) 104 | } 105 | 106 | // 将 ascii 中的数字编码为 bcd(确保 len(data) % 2 == 0) 107 | func bcd(data []byte) ([]byte,error) { 108 | out := make([]byte, len(data)/2+1) 109 | n, err := hex.Decode(out, data) 110 | if err != nil { 111 | return nil, err 112 | } 113 | return out[:n],nil 114 | } -------------------------------------------------------------------------------- /server/manager/epoll.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Time: 2022/2/19 12:23 3 | * @Author: yt.yin 4 | */ 5 | 6 | package manager 7 | 8 | import ( 9 | "errors" 10 | "net" 11 | "reflect" 12 | "sync" 13 | "syscall" 14 | 15 | "github.com/goworkeryyt/go-socket/server/connect" 16 | "golang.org/x/sys/unix" 17 | ) 18 | 19 | // EpollManager 基于 linux epoll 实现连接管理 20 | type EpollManager struct{ 21 | fd int 22 | cons map[int]connect.ConnI 23 | lock *sync.RWMutex 24 | } 25 | 26 | // New 初始化连接管理池 27 | func New() (*EpollManager, error) { 28 | fd, err := unix.EpollCreate1(0) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return &EpollManager{ 33 | fd: fd, 34 | lock: &sync.RWMutex{}, 35 | cons: make(map[int]connect.ConnI), 36 | }, nil 37 | } 38 | 39 | // Add 添加连接到连接池 40 | func (e *EpollManager) Add(conn connect.ConnI) error { 41 | if conn == nil || conn.Conn() == nil{ 42 | return errors.New("连接为空") 43 | } 44 | // 获取与连接关联的文件描述符 45 | fd := tcpConnFd(conn.Conn()) 46 | err := unix.EpollCtl(e.fd, syscall.EPOLL_CTL_ADD, fd, &unix.EpollEvent{Events: unix.POLLIN | unix.POLLHUP, Fd: int32(fd)}) 47 | if err != nil { 48 | return err 49 | } 50 | e.lock.Lock() 51 | defer e.lock.Unlock() 52 | e.cons[fd] = conn 53 | return nil 54 | } 55 | 56 | // Remove 移除连接 57 | func (e *EpollManager) Remove(conn connect.ConnI) error{ 58 | if conn == nil || conn.Conn() == nil { 59 | return errors.New("连接为空") 60 | } 61 | // 移除连接前先关闭防止客户端如果是单片机会持有死链接 62 | _ = conn.Conn().Close() 63 | fd := tcpConnFd(conn.Conn()) 64 | retry: 65 | err := unix.EpollCtl(e.fd, syscall.EPOLL_CTL_DEL, fd, nil) 66 | if err != nil { 67 | if err == unix.EINTR { 68 | goto retry 69 | } 70 | return err 71 | } 72 | e.lock.Lock() 73 | defer e.lock.Unlock() 74 | delete(e.cons, fd) 75 | return nil 76 | } 77 | 78 | // Conn 根据连接的id从连接池里获取连接 79 | func (e *EpollManager) Conn(connID int) (connect.ConnI, error) { 80 | connI := e.cons[connID] 81 | if connI != nil { 82 | return connI,nil 83 | } 84 | return nil,errors.New("连接不存在") 85 | } 86 | 87 | // Size 获取连接数量 88 | func (e *EpollManager) Size() int { 89 | return len(e.cons) 90 | } 91 | 92 | // Vacuum 清空连接池 93 | func (e *EpollManager) Vacuum() { 94 | // TODO 95 | } 96 | 97 | 98 | // Wait 等待 maxEvent 为每次最多读取的事件数量,默认配置 100,最大1000 99 | func (e *EpollManager) Wait(maxEvent int) ([]net.Conn, error) { 100 | if maxEvent < 100 { 101 | maxEvent = 100 102 | }else if maxEvent > 1000 { 103 | maxEvent = 1000 104 | } 105 | events := make([]unix.EpollEvent, maxEvent) 106 | retry: 107 | n, err := unix.EpollWait(e.fd, events, maxEvent) 108 | if err != nil { 109 | // 判断是否有 EINTR错误 110 | if err == unix.EINTR { 111 | goto retry 112 | } 113 | return nil, err 114 | } 115 | e.lock.RLock() 116 | defer e.lock.RUnlock() 117 | var conns []net.Conn 118 | for i := 0; i < n; i++ { 119 | conn := e.cons[int(events[i].Fd)] 120 | conns = append(conns, conn.Conn()) 121 | } 122 | return conns, nil 123 | } 124 | 125 | // 获取连接的 fd 126 | // FD 是一个文件描述符。 net 和 os 包使用这种类型作为表示网络连接或操作系统文件的较大类型的字段。 127 | // Sysfd 系统文件描述符。 在关闭之前不可变。 128 | // 通过反射获取 系统文件描述符 129 | func tcpConnFd(conn net.Conn) int { 130 | // 通过反射获取不可见属性 conn 见 net/tcpsock.go:86 131 | tcpConn := reflect.Indirect(reflect.ValueOf(conn)).FieldByName("conn") 132 | // 通过反射获取网络文件描述符fd 见 net/net.go:171 133 | fdV := tcpConn.FieldByName("fd") 134 | // 通过反射获取 poll.FD 的值 135 | pfdV := reflect.Indirect(fdV).FieldByName("pfd") 136 | // 通过反射获取 系统文件描述符 137 | return int(pfdV.FieldByName("Sysfd").Int()) 138 | } 139 | 140 | 141 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /encoding/z_ascii_table.md: -------------------------------------------------------------------------------- 1 | # ASCII表 2 | | Bin(二进制) | Oct(八进制) | Dec(十进制) | Hex(十六进制) | 缩写/字符 | 解释 | 3 | | ----------- | ----------- | ----------- | ------------- | --------------------------- | ------------ | 4 | | 0000 0000 | 00 | 0 | 0x00 | NUL(null) | 空字符 | 5 | | 0000 0001 | 01 | 1 | 0x01 | SOH(start of headline) | 标题开始 | 6 | | 0000 0010 | 02 | 2 | 0x02 | STX (start of text) | 正文开始 | 7 | | 0000 0011 | 03 | 3 | 0x03 | ETX (end of text) | 正文结束 | 8 | | 0000 0100 | 04 | 4 | 0x04 | EOT (end of transmission) | 传输结束 | 9 | | 0000 0101 | 05 | 5 | 0x05 | ENQ (enquiry) | 请求 | 10 | | 0000 0110 | 06 | 6 | 0x06 | ACK (acknowledge) | 收到通知 | 11 | | 0000 0111 | 07 | 7 | 0x07 | BEL (bell) | 响铃 | 12 | | 0000 1000 | 010 | 8 | 0x08 | BS (backspace) | 退格 | 13 | | 0000 1001 | 011 | 9 | 0x09 | HT (horizontal tab) | 水平制表符 | 14 | | 0000 1010 | 012 | 10 | 0x0A | LF (NL line feed, new line) | 换行键 | 15 | | 0000 1011 | 013 | 11 | 0x0B | VT (vertical tab) | 垂直制表符 | 16 | | 0000 1100 | 014 | 12 | 0x0C | FF (NP form feed, new page) | 换页键 | 17 | | 0000 1101 | 015 | 13 | 0x0D | CR (carriage return) | 回车键 | 18 | | 0000 1110 | 016 | 14 | 0x0E | SO (shift out) | 不用切换 | 19 | | 0000 1111 | 017 | 15 | 0x0F | SI (shift in) | 启用切换 | 20 | | 0001 0000 | 020 | 16 | 0x10 | DLE (data link escape) | 数据链路转义 | 21 | | 0001 0001 | 021 | 17 | 0x11 | DC1 (device control 1) | 设备控制1 | 22 | | 0001 0010 | 022 | 18 | 0x12 | DC2 (device control 2) | 设备控制2 | 23 | | 0001 0011 | 023 | 19 | 0x13 | DC3 (device control 3) | 设备控制3 | 24 | | 0001 0100 | 024 | 20 | 0x14 | DC4 (device control 4) | 设备控制4 | 25 | | 0001 0101 | 025 | 21 | 0x15 | NAK (negative acknowledge) | 拒绝接收 | 26 | | 0001 0110 | 026 | 22 | 0x16 | SYN (synchronous idle) | 同步空闲 | 27 | | 0001 0111 | 027 | 23 | 0x17 | ETB (end of trans. block) | 结束传输块 | 28 | | 0001 1000 | 030 | 24 | 0x18 | CAN (cancel) | 取消 | 29 | | 0001 1001 | 031 | 25 | 0x19 | EM (end of medium) | 媒介结束 | 30 | | 0001 1010 | 032 | 26 | 0x1A | SUB (substitute) | 代替 | 31 | | 0001 1011 | 033 | 27 | 0x1B | ESC (escape) | 换码(溢出) | 32 | | 0001 1100 | 034 | 28 | 0x1C | FS (file separator) | 文件分隔符 | 33 | | 0001 1101 | 035 | 29 | 0x1D | GS (group separator) | 分组符 | 34 | | 0001 1110 | 036 | 30 | 0x1E | RS (record separator) | 记录分隔符 | 35 | | 0001 1111 | 037 | 31 | 0x1F | US (unit separator) | 单元分隔符 | 36 | | 0010 0000 | 040 | 32 | 0x20 | (space) | 空格 | 37 | | 0010 0001 | 041 | 33 | 0x21 | ! | 叹号 | 38 | | 0010 0010 | 042 | 34 | 0x22 | " | 双引号 | 39 | | 0010 0011 | 043 | 35 | 0x23 | # | 井号 | 40 | | 0010 0100 | 044 | 36 | 0x24 | $ | 美元符 | 41 | | 0010 0101 | 045 | 37 | 0x25 | % | 百分号 | 42 | | 0010 0110 | 046 | 38 | 0x26 | & | 和号 | 43 | | 0010 0111 | 047 | 39 | 0x27 | ' | 闭单引号 | 44 | | 0010 1000 | 050 | 40 | 0x28 | ( | 开括号 | 45 | | 0010 1001 | 051 | 41 | 0x29 | ) | 闭括号 | 46 | | 0010 1010 | 052 | 42 | 0x2A | * | 星号 | 47 | | 0010 1011 | 053 | 43 | 0x2B | + | 加号 | 48 | | 0010 1100 | 054 | 44 | 0x2C | , | 逗号 | 49 | | 0010 1101 | 055 | 45 | 0x2D | - | 减号/破折号 | 50 | | 0010 1110 | 056 | 46 | 0x2E | . | 句号 | 51 | | 0010 1111 | 057 | 47 | 0x2F | / | 斜杠 | 52 | | 0011 0000 | 060 | 48 | 0x30 | 0 | 字符0 | 53 | | 0011 0001 | 061 | 49 | 0x31 | 1 | 字符1 | 54 | | 0011 0010 | 062 | 50 | 0x32 | 2 | 字符2 | 55 | | 0011 0011 | 063 | 51 | 0x33 | 3 | 字符3 | 56 | | 0011 0100 | 064 | 52 | 0x34 | 4 | 字符4 | 57 | | 0011 0101 | 065 | 53 | 0x35 | 5 | 字符5 | 58 | | 0011 0110 | 066 | 54 | 0x36 | 6 | 字符6 | 59 | | 0011 0111 | 067 | 55 | 0x37 | 7 | 字符7 | 60 | | 0011 1000 | 070 | 56 | 0x38 | 8 | 字符8 | 61 | | 0011 1001 | 071 | 57 | 0x39 | 9 | 字符9 | 62 | | 0011 1010 | 072 | 58 | 0x3A | : | 冒号 | 63 | | 0011 1011 | 073 | 59 | 0x3B | ; | 分号 | 64 | | 0011 1100 | 074 | 60 | 0x3C | < | 小于 | 65 | | 0011 1101 | 075 | 61 | 0x3D | = | 等号 | 66 | | 0011 1110 | 076 | 62 | 0x3E | > | 大于 | 67 | | 0011 1111 | 077 | 63 | 0x3F | ? | 问号 | 68 | | 0100 0000 | 0100 | 64 | 0x40 | @ | 电子邮件符号 | 69 | | 0100 0001 | 0101 | 65 | 0x41 | A | 大写字母A | 70 | | 0100 0010 | 0102 | 66 | 0x42 | B | 大写字母B | 71 | | 0100 0011 | 0103 | 67 | 0x43 | C | 大写字母C | 72 | | 0100 0100 | 0104 | 68 | 0x44 | D | 大写字母D | 73 | | 0100 0101 | 0105 | 69 | 0x45 | E | 大写字母E | 74 | | 0100 0110 | 0106 | 70 | 0x46 | F | 大写字母F | 75 | | 0100 0111 | 0107 | 71 | 0x47 | G | 大写字母G | 76 | | 0100 1000 | 0110 | 72 | 0x48 | H | 大写字母H | 77 | | 0100 1001 | 0111 | 73 | 0x49 | I | 大写字母I | 78 | | 01001010 | 0112 | 74 | 0x4A | J | 大写字母J | 79 | | 0100 1011 | 0113 | 75 | 0x4B | K | 大写字母K | 80 | | 0100 1100 | 0114 | 76 | 0x4C | L | 大写字母L | 81 | | 0100 1101 | 0115 | 77 | 0x4D | M | 大写字母M | 82 | | 0100 1110 | 0116 | 78 | 0x4E | N | 大写字母N | 83 | | 0100 1111 | 0117 | 79 | 0x4F | O | 大写字母O | 84 | | 0101 0000 | 0120 | 80 | 0x50 | P | 大写字母P | 85 | | 0101 0001 | 0121 | 81 | 0x51 | Q | 大写字母Q | 86 | | 0101 0010 | 0122 | 82 | 0x52 | R | 大写字母R | 87 | | 0101 0011 | 0123 | 83 | 0x53 | S | 大写字母S | 88 | | 0101 0100 | 0124 | 84 | 0x54 | T | 大写字母T | 89 | | 0101 0101 | 0125 | 85 | 0x55 | U | 大写字母U | 90 | | 0101 0110 | 0126 | 86 | 0x56 | V | 大写字母V | 91 | | 0101 0111 | 0127 | 87 | 0x57 | W | 大写字母W | 92 | | 0101 1000 | 0130 | 88 | 0x58 | X | 大写字母X | 93 | | 0101 1001 | 0131 | 89 | 0x59 | Y | 大写字母Y | 94 | | 0101 1010 | 0132 | 90 | 0x5A | Z | 大写字母Z | 95 | | 0101 1011 | 0133 | 91 | 0x5B | [ | 开方括号 | 96 | | 0101 1100 | 0134 | 92 | 0x5C | \ | 反斜杠 | 97 | | 0101 1101 | 0135 | 93 | 0x5D | ] | 闭方括号 | 98 | | 0101 1110 | 0136 | 94 | 0x5E | ^ | 脱字符 | 99 | | 0101 1111 | 0137 | 95 | 0x5F | _ | 下划线 | 100 | | 0110 0000 | 0140 | 96 | 0x60 | ` | 开单引号 | 101 | | 0110 0001 | 0141 | 97 | 0x61 | a | 小写字母a | 102 | | 0110 0010 | 0142 | 98 | 0x62 | b | 小写字母b | 103 | | 0110 0011 | 0143 | 99 | 0x63 | c | 小写字母c | 104 | | 0110 0100 | 0144 | 100 | 0x64 | d | 小写字母d | 105 | | 0110 0101 | 0145 | 101 | 0x65 | e | 小写字母e | 106 | | 0110 0110 | 0146 | 102 | 0x66 | f | 小写字母f | 107 | | 0110 0111 | 0147 | 103 | 0x67 | g | 小写字母g | 108 | | 0110 1000 | 0150 | 104 | 0x68 | h | 小写字母h | 109 | | 0110 1001 | 0151 | 105 | 0x69 | i | 小写字母i | 110 | | 0110 1010 | 0152 | 106 | 0x6A | j | 小写字母j | 111 | | 0110 1011 | 0153 | 107 | 0x6B | k | 小写字母k | 112 | | 0110 1100 | 0154 | 108 | 0x6C | l | 小写字母l | 113 | | 0110 1101 | 0155 | 109 | 0x6D | m | 小写字母m | 114 | | 0110 1110 | 0156 | 110 | 0x6E | n | 小写字母n | 115 | | 0110 1111 | 0157 | 111 | 0x6F | o | 小写字母o | 116 | | 0111 0000 | 0160 | 112 | 0x70 | p | 小写字母p | 117 | | 0111 0001 | 0161 | 113 | 0x71 | q | 小写字母q | 118 | | 0111 0010 | 0162 | 114 | 0x72 | r | 小写字母r | 119 | | 0111 0011 | 0163 | 115 | 0x73 | s | 小写字母s | 120 | | 0111 0100 | 0164 | 116 | 0x74 | t | 小写字母t | 121 | | 0111 0101 | 0165 | 117 | 0x75 | u | 小写字母u | 122 | | 0111 0110 | 0166 | 118 | 0x76 | v | 小写字母v | 123 | | 0111 0111 | 0167 | 119 | 0x77 | w | 小写字母w | 124 | | 0111 1000 | 0170 | 120 | 0x78 | x | 小写字母x | 125 | | 0111 1001 | 0171 | 121 | 0x79 | y | 小写字母y | 126 | | 0111 1010 | 0172 | 122 | 0x7A | z | 小写字母z | 127 | | 0111 1011 | 0173 | 123 | 0x7B | { | 开花括号 | 128 | | 0111 1100 | 0174 | 124 | 0x7C | \| | 垂线 | 129 | | 0111 1101 | 0175 | 125 | 0x7D | } | 闭花括号 | 130 | | 0111 1110 | 0176 | 126 | 0x7E | ~ | 波浪号 | 131 | | 0111 1111 | 0177 | 127 | 0x7F | DEL (delete) | 删除 | 132 | 133 | --------------------------------------------------------------------------------