Created
September 19, 2017 11:21
-
-
Save anonymous/8246c2ec615fc101f567ce536f5d284a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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