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
-
输出结果说明,请参考这篇博文
-
golang的client实现长连接的方式
- web Server 支持长连接。(golang默认支持长连接).client要和服务端响应之后,保持连接
- 根据需求,加大:DefaultMaxIdleConnsPerHost或设置MaxIdleConnsPerHost
- 读完Response Body再Close
-
写的不对的地方,希望可以加微信讨论一下