這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
tcp伺服器
包括日誌,定時處理,廣播,逾時
map寫添加了鎖(讀不用鎖)
添加瞭解碼器
刪除了addr-buf映射,添加刪除鎖
mark:今天聽大神所要處理系統中斷EINTR, 以後做簡單處理EINTR--retry
mark:用struct封裝addr, net.Listener, exit(是否斷開)等資訊..最重要的是使用:
br := bufio.NewReader(conn), bw := bufio.NewWriter(conn)來取代讀迴圈,這樣就可以需要的時候再讀/寫
https://github.com/zhangpeihao/gortmp/blob/master/server.go
package mainimport ( "bytes" "encoding/binary" "fmt" "log" "net" "os" "strconv" "strings" "sync" "time")func main() { tcpStart(8889)}/* 定義相關鎖*/var ( connMkMutex sync.Mutex connDelMutex sync.Mutex)/* 定義logger*/var logger *log.Logger/* 初始化log*/func initLog(logfile *os.File) { // logger = log.New(logfile, "log:", log.Ldate|log.Ltime) logger = log.New(logfile, "prefix ", 0)}/* 處理log*/func doLog(args ...interface{}) { str := time.Now().Format("2006-01-02 15:04:05") var logData string var temp string for _, arg := range args { switch val := arg.(type) { case int: temp = strconv.Itoa(val) case string: temp = val } if len(temp) > 64 { // 限制只列印前64個字元 logData = temp[:64] } else { logData = temp } str = str + " " + logData } logger.Println(str)}/* 定義socket conn 映射*/var clisConnMap map[string]*net.TCPConn/* 初始化socket conn 映射*/func initClisConnMap() { clisConnMap = make(map[string]*net.TCPConn)}/* 建立socket conn 映射*/func mkClisConn(key string, conn *net.TCPConn) { connMkMutex.Lock() defer connMkMutex.Unlock() clisConnMap[key] = conn}/* 刪除socket conn 映射*/func delClisConn(key string) { connDelMutex.Lock() defer connDelMutex.Unlock() delete(clisConnMap, key)}/* 定義解碼器*/type Unpacker struct { // 頭(xy)2bytes + 標識1byte + 包長度2bytes + data // 當然了,頭不可能是xy,這裡舉例子,而且一般還需要轉義 _buf []byte}func (unpacker *Unpacker) feed(data []byte) { unpacker._buf = append(unpacker._buf, data...)}func (unpacker *Unpacker) unpack() (flag byte, msg []byte) { str := string(unpacker._buf) for { if len(str) < 5 { break } else { _, head, data := Partition(str, "xy") if len(head) == 0 { // 沒有頭 if str[len(str)-1] == byte(120) { // 120 => 'x' unpacker._buf = []byte{byte(120)} } else { unpacker._buf = []byte{} } break } buf := bytes.NewReader([]byte(data)) msg = make([]byte, buf.Len()) var dataLen uint16 binary.Read(buf, binary.LittleEndian, &flag) binary.Read(buf, binary.LittleEndian, &dataLen) fmt.Println("DEC:", flag, dataLen) if buf.Len() < int(dataLen) { break } binary.Read(buf, binary.LittleEndian, &msg) unpacker._buf = unpacker._buf[2+1+2+dataLen:] } } return}/* 啟動服務*/func tcpStart(port int) { initLog(os.Stderr) initClisConnMap() doLog("tcpStart:") host := ":" + strconv.Itoa(port) tcpAddr, err := net.ResolveTCPAddr("tcp4", host) checkError(err) listener, err := net.ListenTCP("tcp", tcpAddr) checkError(err) for { conn, err := listener.AcceptTCP() if err != nil { continue } go handleClient(conn) }}/* socket conn*/func handleClient(conn *net.TCPConn) { // ****這裡是初始化串連處理 addr := conn.RemoteAddr().String() doLog("handleClient:", addr) connectionMade(conn) request := make([]byte, 128) defer conn.Close() buf := make([]byte, 0) for { // ****這裡是讀迴圈處理 readLoopHandled(conn) read_len, err := conn.Read(request) if err != nil { // 這裡沒使用checkError因為不退出,只是break出去 doLog("ERR:", "read err", err.Error()) break } if read_len == 0 { // 在gprs時資料不能通過這個判斷是否中斷連線,要通過心跳包 doLog("ERR:", "connection already closed by client") break } else { // request[:read_len]處理 buf = append(buf, request[:read_len]...) doLog("<=", addr, string(request[:read_len])) dataReceived(conn, &buf) request = make([]byte, 128) // clear last read content } } // ****這裡是串連斷開處理 connectionLost(conn)}/* 串連初始處理(ed)*/func connectionMade(conn *net.TCPConn) { //初始化串連這個函數被調用 // ****建立conn映射 addr := conn.RemoteAddr().String() ip := strings.Split(addr, ":")[0] mkClisConn(ip, conn) doLog("connectionMade:", addr) // ****定時處理(心跳等) go loopingCall(conn)}/* 讀迴圈處理(ed)*/func readLoopHandled(conn *net.TCPConn) { //當進入迴圈讀資料這個函數被調用, 主要用於設定逾時(好重新整理設定逾時) // *****設定逾時 (要寫在for迴圈裡) setReadTimeout(conn, 10*time.Minute)}/* 用戶端串連發送來的訊息處理(ed)*/func dataReceived(conn *net.TCPConn, pBuf *[]byte) { //一般情況可以用pBuf參數,但是如果有分包粘包的情況就必須使用clisBufMap的buf //clisBufMap的buf不斷增大,不管是否使用都應該處理 //addr := conn.RemoteAddr().String() doLog("*pBuf:", string(*pBuf)) //sendData(clisConnMap["192.168.6.234"], []byte("xxx")) sendData(conn, []byte("echo"))}/* 串連斷開(ed)*/func connectionLost(conn *net.TCPConn) { //串連斷開這個函數被調用 addr := conn.RemoteAddr().String() ip := strings.Split(addr, ":")[0] delClisConn(ip) // 刪除關閉的串連對應的clisMap項 doLog("connectionLost:", addr)}/* 發送資料*/func sendData(conn *net.TCPConn, data []byte) (n int, err error) { addr := conn.RemoteAddr().String() n, err = conn.Write(data) if err == nil { doLog("=>", addr, string(data)) } return}/* 廣播資料*/func broadcast(tclisMap map[string]*net.TCPConn, data []byte) { for _, conn := range tclisMap { sendData(conn, data) }}/* 定時處理&延時處理*/func loopingCall(conn *net.TCPConn) { pingTicker := time.NewTicker(30 * time.Second) // 定時 testAfter := time.After(5 * time.Second) // 延時 for { select { case <-pingTicker.C: //發送心跳 _, err := sendData(conn, []byte("PING")) if err != nil { pingTicker.Stop() return } case <-testAfter: doLog("testAfter:") } }}/* 設定讀資料逾時*/func setReadTimeout(conn *net.TCPConn, t time.Duration) { conn.SetReadDeadline(time.Now().Add(t))}/* 錯誤處理*/func checkError(err error) { if err != nil { doLog("ERR:", err.Error()) os.Exit(1) }}func Partition(s string, sep string) (head string, retSep string, tail string) { // Partition(s, sep) -> (head, sep, tail) index := strings.Index(s, sep) if index == -1 { head = s retSep = "" tail = "" } else { head = s[:index] retSep = sep tail = s[len(head)+len(sep):] } return}