This repository has been archived by the owner on May 26, 2021. It is now read-only.
forked from things-go/go-modbus
-
Notifications
You must be signed in to change notification settings - Fork 7
/
tcpserver_session.go
151 lines (138 loc) · 3.77 KB
/
tcpserver_session.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package modbus
import (
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"strings"
"time"
)
// ServerSession tcp server session
type ServerSession struct {
conn net.Conn
readTimeout time.Duration
writeTimeout time.Duration
*serverCommon
logger
}
// handler net conn
func (sf *ServerSession) running(ctx context.Context) {
var err error
var bytesRead int
sf.Debug("client(%v) -> server(%v) connected", sf.conn.RemoteAddr(), sf.conn.LocalAddr())
defer func() {
sf.conn.Close()
sf.Debug("client(%v) -> server(%v) disconnected,cause by %v", sf.conn.RemoteAddr(), sf.conn.LocalAddr(), err)
}()
raw := make([]byte, tcpAduMaxSize)
for {
select {
case <-ctx.Done():
err = errors.New("server active close")
return
default:
}
adu := raw
for rdCnt, length := 0, tcpHeaderMbapSize; rdCnt < length; {
err = sf.conn.SetReadDeadline(time.Now().Add(sf.readTimeout))
if err != nil {
return
}
bytesRead, err = io.ReadFull(sf.conn, adu[rdCnt:length])
if err != nil {
if err != io.EOF && err != io.ErrClosedPipe || strings.Contains(err.Error(), "use of closed network connection") {
return
}
if e, ok := err.(net.Error); ok && !e.Temporary() {
return
}
if bytesRead == 0 && err == io.EOF {
err = fmt.Errorf("remote client closed, %v", err)
return
}
// cnt >0 do nothing
// cnt == 0 && err != io.EOF continue do it next
}
rdCnt += bytesRead
if rdCnt >= length {
// check head ProtocolIdentifier
if binary.BigEndian.Uint16(adu[2:]) != tcpProtocolIdentifier {
rdCnt, length = 0, tcpHeaderMbapSize
continue
}
length = int(binary.BigEndian.Uint16(adu[4:])) + tcpHeaderMbapSize - 1
if rdCnt == length {
if err = sf.frameHandler(adu[:length]); err != nil {
return
}
}
}
}
}
}
// modbus 包处理
func (sf *ServerSession) frameHandler(requestAdu []byte) error {
defer func() {
if err := recover(); err != nil {
sf.Error("painc happen,%v", err)
}
}()
sf.Debug("RX Raw[% x]", requestAdu)
// got head from request adu
tcpHeader := protocolTCPHeader{
binary.BigEndian.Uint16(requestAdu[0:]),
binary.BigEndian.Uint16(requestAdu[2:]),
binary.BigEndian.Uint16(requestAdu[4:]),
requestAdu[6],
}
funcCode := requestAdu[7]
pduData := requestAdu[8:]
node, err := sf.GetNode(tcpHeader.slaveID)
if err != nil { // slave id not exit, ignore it
return nil
}
var rspPduData []byte
if handle, ok := sf.function[funcCode]; ok {
rspPduData, err = handle(node, pduData)
} else {
err = &ExceptionError{ExceptionCodeIllegalFunction}
}
if err != nil {
funcCode |= 0x80
rspPduData = []byte{err.(*ExceptionError).ExceptionCode}
}
// prepare responseAdu data,fill it
responseAdu := requestAdu[:tcpHeaderMbapSize]
binary.BigEndian.PutUint16(responseAdu[0:], tcpHeader.transactionID)
binary.BigEndian.PutUint16(responseAdu[2:], tcpHeader.protocolID)
binary.BigEndian.PutUint16(responseAdu[4:], uint16(2+len(rspPduData)))
responseAdu[6] = tcpHeader.slaveID
responseAdu = append(responseAdu, funcCode)
responseAdu = append(responseAdu, rspPduData...)
sf.Debug("TX Raw[% x]", responseAdu)
// write response
return func(b []byte) error {
for wrCnt := 0; len(b) > wrCnt; {
err = sf.conn.SetWriteDeadline(time.Now().Add(sf.writeTimeout))
if err != nil {
return fmt.Errorf("set read deadline %v", err)
}
byteCount, err := sf.conn.Write(b[wrCnt:])
if err != nil {
// See: https://github.com/golang/go/issues/4373
if err != io.EOF && err != io.ErrClosedPipe ||
strings.Contains(err.Error(), "use of closed network connection") {
return err
}
if e, ok := err.(net.Error); !ok || !e.Temporary() {
return err
}
// temporary error may be recoverable
}
wrCnt += byteCount
}
return nil
}(responseAdu)
}