golang -- TCP伺服器(2)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

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}



相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.