golang長連接和短連接的學習


  • TCP連接示意圖

  • 長連接和短鏈接的區別

    • 客戶端和服務端響應的次數
      • 長連接:可以多次。
      • 短鏈接:一次。
    • 傳輸數據的方式
      • 長連接:連接--數據傳輸--保持連接
      • 短連接:連接--數據傳輸--關閉連接
  • 長連接和短鏈接的優缺點

    • 長連接
      • 優點
        • 省去較多的TCP建立和關閉的操作,從而節約時間。
        • 性能比較好。(因為客戶端一直和服務端保持聯系)
      • 缺點
        • 當客戶端越來越多的時候,會將服務器壓垮。
        • 連接管理難。
        • 安全性差。(因為會一直保持着連接,可能會有些無良的客戶端,隨意發送數據等)
    • 短鏈接
      • 優點
        • 服務管理簡單。存在的連接都是有效連接
      • 缺點
        • 請求頻繁,在TCP的建立和關閉操作上浪費時間
  • 長連接和短連接使用情況舉例

    • 長連接
      • 微信/qq
      • 一些游戲
    • 短連接
      • 普通的web網站
  • golang實現長連接參考代碼(實現群聊天)

    server.go

package main

import(
	"fmt"
	"net"
	"bufio"
	"errors"
)
var connSlice []*net.TCPConn

// 創建TCP長連接服務
func createTcp(){
	tcpAdd,err:= net.ResolveTCPAddr("tcp","127.0.0.1:9999")  //解析tcp服務
	if err!=nil{
		fmt.Println("net.ResolveTCPAddr error:",err)
		return
	}
	tcpListener,err:=net.ListenTCP("tcp",tcpAdd)   //監聽指定TCP服務
	if err!=nil{
		fmt.Println("net.ListenTCP error:",err)
		return
	}
	defer tcpListener.Close()
	for{
		tcpConn,err:=tcpListener.AcceptTCP() //阻塞,當有客戶端連接時,才會運行下面
		if err!=nil{
			fmt.Println("tcpListener error :",err)
			continue
		}
		fmt.Println("A client connected:",tcpConn.RemoteAddr().String())
		boradcastMessage(tcpConn.RemoteAddr().String()+"進入房間"+"\n")  //當有一個客戶端進來之時,廣播某某進入房間
		connSlice = append(connSlice,tcpConn)
		// 監聽到被訪問時,開一個協程處理
		go tcpPipe(tcpConn)
	}
}

// 對客戶端作出反應
func tcpPipe(conn *net.TCPConn){
	ipStr := conn.RemoteAddr().String()
	fmt.Println("ipStr:",ipStr)
	defer func(){
		fmt.Println("disconnected:",ipStr)
		conn.Close()
		deleteConn(conn)
		boradcastMessage(ipStr+"離開了房間"+"\n")
	}()
	reader:=bufio.NewReader(conn)
	for{
		message,err:=reader.ReadString('\n')  //讀取直到輸入中第一次發生 ‘\n’
		//因為按強制退出的時候,他就先發送換行,然后在結束
		if message == "\n"{
			return
		}
		message = ipStr+"說:"+message
		if err!=nil{
			fmt.Println("topPipe:",err)
			return
		}
		// 廣播消息
		fmt.Println(ipStr,"說:",message)
		err = boradcastMessage(message)
		if err!=nil{
			fmt.Println(err)
			return 
		}
	}
}

// 廣播數據
func boradcastMessage(message string)error{
	b := []byte(message)
	for i:=0;i<len(connSlice);i++{
		fmt.Println(connSlice[i])
		_,err := connSlice[i].Write(b)
		if err!=nil{
			fmt.Println("發送給",connSlice[i].RemoteAddr().String(),"數據失敗"+err.Error())
			continue
		}
	}
	return nil
}

// 移除已經關閉的客戶端
func deleteConn(conn *net.TCPConn)error{
	if conn==nil{
		fmt.Println("conn is nil")
		return errors.New("conn is nil")
	}
	for i:= 0;i<len(connSlice);i++{
		if(connSlice[i]==conn){
			connSlice = append(connSlice[:i],connSlice[i+1:]...)
			break
		}
	}
	return nil
}

func main(){
	fmt.Println("服務端")
	createTcp()
	// data := []string{"a","b"}
	// data = append(data[:1],data[2:]...)  //測試data[2:]...會不會因為超過范圍報錯
	// fmt.Println(data)
}
**client.go**
package main

import(
	"os"
	"fmt"
	"net"
	"bufio"
)


// 客戶端連接服務端
func createSocket(){
	tcpAdd,err := net.ResolveTCPAddr("tcp","127.0.0.1:9999")  //解析服務端TCP地址
	if err!=nil{
		fmt.Println("net.ResolveTCPAddr error:",err)
		return
	}
	conn,err := net.DialTCP("tcp",nil,tcpAdd)   //raddr是指遠程地址,laddr是指本地地址,連接服務端
	if err!=nil{
		fmt.Println("net.DailTCP error:",err)
		return
	}
	defer conn.Close()
	fmt.Println("connected")
	go onMessageRectived(conn)   //讀取服務端廣播的信息

	for {
		// 自己發送的信息
		var data string
		fmt.Scan(&data)
		if data == "quit"{
			break
		}
		b := []byte(data + "\n")
		conn.Write(b)
	}
}

// 獲取服務端發送來的信息
func onMessageRectived(conn *net.TCPConn){
	reader := bufio.NewReader(conn)
	for {
		// var data string
		msg,err := reader.ReadString('\n')  //讀取直到輸入中第一次發生 ‘\n’
		fmt.Println(msg)
		if err!=nil{
			fmt.Println("err:",err)
			os.Exit(1)    //服務端錯誤的時候,就將整個客戶端關掉
		}	
	}
}

func main(){
	fmt.Println("開啟客戶端")
	createSocket()
}
  • golang的Http長連接方式

server.go

package main

import (
	"fmt"
	"net/http"
)

func main(){
	fmt.Println("服務端")
	http.HandleFunc("/PrintHello",PrintHello)
	http.ListenAndServe(":8080",nil)
}

func PrintHello(w http.ResponseWriter,r *http.Request){
	data := "hello word"
	fmt.Println(r.RemoteAddr)
	fmt.Fprintf(w,data)
}

client.go

package main

import(
	"fmt"
	"time"
	"net/http"
	"io/ioutil"
)

func main(){
	fmt.Println("客戶端")
	for{
		go doGet(1)
		go doGet(2)
		time.Sleep(time.Second*3)
	}
}

func doGet(a int){
	res,err:=http.Get("http://localhost:8080/PrintHello")
	if err!=nil{
		fmt.Println(err)
		return
	}
	defer res.Body.Close()

	data,err := ioutil.ReadAll(res.Body)
	if err!=nil{
		fmt.Println(err)
		return
	}
	fmt.Println("接受服務端發送數據:",a,string(data))
}
  • 客戶端訪問服務端,服務端打印輸出
C:\Users\悟\Desktop\studygo\test\httpSever>go run main.go
服務端
GET
[::1]:60388
GET
[::1]:60389
GET
[::1]:60389
GET
[::1]:60388
GET
[::1]:60388
GET
[::1]:60389
GET
[::1]:60389
GET
[::1]:60388
GET
[::1]:60388
GET
[::1]:60389
GET
[::1]:60389
  • 客戶端打印結果
客戶端
接受服務端發送數據: 2 hello word
接受服務端發送數據: 1 hello word
接受服務端發送數據: 2 hello word
接受服務端發送數據: 1 hello word
接受服務端發送數據: 1 hello word
接受服務端發送數據: 2 hello word
  • https://serholiu.com/go-http-client-keepalive

  • 輸出結果說明,請參考這篇博文

  • golang的client實現長連接的方式

    • web Server 支持長連接。(golang默認支持長連接).client要和服務端響應之后,保持連接
    • 根據需求,加大:DefaultMaxIdleConnsPerHost或設置MaxIdleConnsPerHost
    • 讀完Response Body再Close
  • 寫的不對的地方,希望可以加微信討論一下


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM