Skip to content

Instantly share code, notes, and snippets.

Created September 19, 2017 11:21
Show Gist options
  • Save anonymous/8246c2ec615fc101f567ce536f5d284a to your computer and use it in GitHub Desktop.
Save anonymous/8246c2ec615fc101f567ce536f5d284a to your computer and use it in GitHub Desktop.
package client
import (
"bufio"
"errors"
"log"
"net"
"strings"
"time"
)
const (
STX = '\x02'
ETX = '\x03'
DELIM = "/"
MaxMsgLen = 20
// The following types of operations are simplified
// Client -------REQUEST------> Server
TypeA = "ClientToServerRequest"
// Client <-------RESPONSE----- Server
TypeB = "ServerToClientResponse"
// Client <------REQUEST-------- Server
TypeC = "ServerToClientRequest"
// Client -------RESPONSE------> Server
TypeD = "ClientToServerResponse"
)
type Client struct {
addr string
conn net.Conn
reader *bufio.Reader
bCh chan string
cCh chan string
}
func New(addr string) *Client {
return &Client{
addr: addr,
bCh: make(chan string),
cCh: make(chan string),
}
}
func (client *Client) Connect() (err error) {
conn, err := net.Dial("tcp", client.addr)
if err != nil {
return err
}
client.conn = conn
client.reader = bufio.NewReader(conn)
go client.readLoop()
go client.respondToServer()
return err
}
// Send msg to server
func (client *Client) Send(msg string) (ids []string, err error) {
// Send is a client-to-server request
msgType := TypeA
// max length of a message is only 20 so we need to split it into parts
msgParts := getMsgParts(msg)
ids = make([]string, 0, len(msgParts))
for i := 0; i < len(msgParts); i++ {
packet := string(STX) + msgType + DELIM + msgParts[i] + string(ETX)
if _, err = client.conn.Write([]byte(packet)); err != nil {
return ids, err
}
select {
case id := <-client.bCh:
ids = append(ids, id)
case <-time.After(time.Second * 5):
return ids, errors.New("timeout")
}
}
return ids, nil
}
// read from the TCP connection.
// determine the type and send the data to the appropriate channel.
func (client *Client) readLoop() {
for {
rData, err := client.reader.ReadString(byte(ETX))
if err != nil {
continue
}
msgType, msg := parseRead(rData)
switch msgType {
case TypeB:
// server-to-client response
// this is the response to Send(msg)
client.bCh <- msg
case TypeC:
// server-to-client request
client.cCh <- msg
default:
log.Printf("unknown message type: %s", msgType)
}
}
}
func (client *Client) respondToServer() {
for {
select {
case requestFromServer := <-client.cCh:
log.Printf("Request from server: %s\n", requestFromServer)
// respond to the server request
packet := string(STX) + TypeD + DELIM + "response from client" + string(ETX)
client.conn.Write([]byte(packet))
default:
}
}
}
// splits the message into a slice of strings.
func getMsgParts(message string) []string {
parts := make([]string, 0)
for start, end := 0, MaxMsgLen; len(message) > start; start, end = start+MaxMsgLen, end+MaxMsgLen {
if len(message) < end {
end = len(message)
}
part := message[start:end]
parts = append(parts, part)
}
return parts
}
// returns the message type and the actual message
func parseRead(data string) (msgType string, msg string) {
removedStxEtx := strings.TrimFunc(data, func(c rune) bool {
return c == STX || c == ETX
})
fields := strings.Split(removedStxEtx, DELIM)
msgType = fields[0]
msg = fields[1]
return msgType, msg
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment