tcp编程(需要建立连接,三次握手,四次挥手,然后发送信息流,数据包是有序的)
udp编程(知道IP、端口直接发送数据,数据包可能是无序的)
1、客户端和服务器客
socket编程
1.服务端的处理流程
a.监听端口
b.接收客户端的链接
c.创建goroutine,处理该链接
2.客户端的处理流程
a.建立与服务端的链接
b.进行数据收发
c.关闭链接
3.服务端代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "fmt" "net"//导入socket的包 ) func main() { fmt.Println("start server...") listen, err := net.Listen("tcp", "0.0.0.0:50000")//监听50000端口 if err != nil { fmt.Println("listen failed, err:", err) return } for { conn, err := listen.Accept()//等待客户端连接 if err != nil { fmt.Println("accept failed, err:", err) continue } go process(conn) } } func process(conn net.Conn) { defer conn.Close() for { buf := make([]byte, 512) _, err := conn.Read(buf)//读取客户端传输过来的数据 if err != nil { fmt.Println("read err:", err) return } fmt.Println("read: ", string(buf)) } }
4.客户端代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "bufio" "fmt" "net" "os" "strings" ) func main() { conn, err := net.Dial("tcp", "localhost:50000") //建立链接 if err != nil { fmt.Println("Error dialing", err.Error()) return } defer conn.Close() inputReader := bufio.NewReader(os.Stdin) //从终端读取数据 for { input, _ := inputReader.ReadString(' ') //从终端读取一行数据 trimmedInput := strings.Trim(input, " ")//去掉字符串 if trimmedInput == "Q" { return } _, err = conn.Write([]byte(trimmedInput))//将信息发给服务端 if err != nil { return } } }
5.发送http请求
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main
import (
"fmt"
"io"
"net"
)
func main() {
conn, err := net.Dial("tcp", "www.baidu.com:80")
if err != nil {
fmt.Println("Error dialing", err.Error())
return
}
defer conn.Close()
msg := "GET / HTTP/1.1
"
msg += "Host: www.baidu.com
"
msg += "Connection: close
"
msg += "
"
_, err = io.WriteString(conn, msg)
if err != nil {
fmt.Println("write string failed, ", err)
return
}
buf := make([]byte, 4096)
for {
count, err := conn.Read(buf)
if err != nil {
break
}
fmt.Println(string(buf[0:count]))
}
}
redis
redis是个开源的高性能的key-value的内存数据库,可以把它当成远程的数据结构。
支持的value类型 非常多, 比如string、list(链表)、set(集合)、hash表等等
redis性能非常高,单机能够达到15w qps,通常适合做缓存。
PV=page view 是指页面被浏览的次数,比如你打开一网页,那么这个网站的pv就算加了一次; TPS=transactions per second 是每秒内的事务数 QPS=queries per second 是指每秒内查询次数,比如执行了select操作,相应的qps会增加。
RPS=requests per second 是指每秒请求数
1、redis使用
安装使用第三方开源的redis库: github.com/garyburd/redigo/redis go get github.com/garyburd/redigo/redis import( “github.com/garyburd/redigo/redis" )
2、连接redis
package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main() { c, err := redis.Dial("tcp", "localhost:6379") //连接redis
if err != nil { fmt.Println("conn redis failed,", err) return } defer c.Close() }
3、Set 接 口
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main() { c, err := redis.Dial("tcp", "localhost:6379") if err != nil { fmt.Println("conn redis failed,", err) return } defer c.Close() _, err = c.Do("Set", "abc", 100) //设置值 if err != nil { fmt.Println(err) return } r, err := redis.Int(c.Do("Get", "abc")) //获取设置的值 if err != nil { fmt.Println("get abc failed,", err) return } fmt.Println(r) }
4、Hash表
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main() { c, err := redis.Dial("tcp", "localhost:6379") if err != nil { fmt.Println("conn redis failed,", err) return } defer c.Close() _, err = c.Do("HSet", "books", "abc", 100) //设置hash表的名字books if err != nil { fmt.Println(err) return } r, err := redis.Int(c.Do("HGet", "books", "abc")) //因为读取出来是字符串,所以redis.Int转换成整数 if err != nil { fmt.Println("get abc failed,", err) return } fmt.Println(r) }
5、批量Set
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main() { c, err := redis.Dial("tcp", "localhost:6379") if err != nil { fmt.Println("conn redis failed,", err) return } defer c.Close() _, err = c.Do("MSet", "abc", 100, "efg", 300) //批量设置 if err != nil { fmt.Println(err) return } r, err := redis.Ints(c.Do("MGet", "abc", "efg")) //读取出来是一个切片 if err != nil { fmt.Println("get abc failed,", err) return } for _, v := range r { fmt.Println(v) } }
6、过期时间
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main() { c, err := redis.Dial("tcp", "localhost:6379") if err != nil { fmt.Println("conn redis failed,", err) return } defer c.Close() _, err = c.Do("expire", "abc", 10) //设置过期时间,abc为key值,10为超时时间,插入的时候不能设置超时时间,因为插入和设置超时时间是两个不同的接口 if err != nil { fmt.Println(err) return } }
7、队列操作
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main() { c, err := redis.Dial("tcp", "localhost:6379") if err != nil { fmt.Println("conn redis failed,", err) return } defer c.Close() _, err = c.Do("lpush", "book_list", "abc", "ceg", 300) //book_list队列的名字 if err != nil { fmt.Println(err) return } r, err := redis.String(c.Do("lpop", "book_list")) //从队列里面取值 if err != nil { fmt.Println("get abc failed,", err) return } fmt.Println(r) }
8、连接池
type Pool struct { //Dial 是创建链接的方法 Dial func() (Conn, error) //TestOnBorrow 是一个测试链接可用性的方法 TestOnBorrow func(c Conn, t time.Time) error // 最大的空闲连接数,表示即使没有redis连接时依然可以保持N个空闲的连接,而不被清除,随时处于待命状态 MaxIdle int // 最大的激活连接数,表示同时最多有N个连接 ,为0事表示没有限制 MaxActive int //最大的空闲连接等待时间,超过此时间后,空闲连接将被关闭 IdleTimeout time.Duration // 当链接数达到最大后是否阻塞,如果不的话,达到最大后返回错误 Wait bool }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "encoding/json" "flag" "fmt" "time" "github.com/garyburd/redigo/redis" ) //声明一些全局变量 var ( pool *redis.Pool redisServer = flag.String("redisServer", ":6379", "") //flag对命令参数进行解析是常见的需求 //go run main.go -redisServer "8000",启动程序是给redisServer赋值,第3个参数是使用说明 //如果执行程序不带有-fredisServer 那么flag.Sring()的第2个参数则为默认值 redisPassword = flag.String("redisPassword", "123456", "") ) //初始化一个连接池pool func newPool(server, password string) *redis.Pool { return &redis.Pool{ MaxIdle: 64, //空闲连接数 MaxActive: 1000, //活跃连接数 IdleTimeout: 240 * time.Second, //240s超时时间,连接池没有使用,会关闭 Dial: func() (redis.Conn, error) { c, err := redis.Dial("tcp", server) if err != nil { return nil, err } /* //密码权限验证 if _, err := c.Do("AUTH", password); err != nil { c.Close() return nil, err }*/ return c, err }, //从连接池获取一个链接,测试连接是否可用 TestOnBorrow: func(c redis.Conn, t time.Time) error { if time.Since(t) < time.Minute { return nil } _, err := c.Do("PING") return err }, } } type Student struct { Id int `json:"id"` Name string `json:"name"` Age int `json:"age"` Score float32 `json:"score"` } func main() { flag.Parse() pool = newPool(*redisServer, *redisPassword) conn := pool.Get() defer conn.Close() var stu Student = Student{ Id: 1000, Name: "abc", Age: 89, Score: 99.2, } data, _ := json.Marshal(stu) //redis操作结构体,需要转成json格式 v, err := conn.Do("SET", 1000, string(data)) if err != nil { fmt.Println(err) return } fmt.Println(v) ret, err := redis.String(conn.Do("GET", 1000)) if err != nil { fmt.Println(err) return } var stu01 Student json.Unmarshal([]byte(ret), &stu01) fmt.Printf("stu01:%#v ", stu01) }
socket练习:
client/main.go
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "bufio" "encoding/json" "fmt" "net" "os" "github.com/sherlockhua/goproject/day9/proto" ) var recvMsg chan interface{} func main() { conn, err := net.Dial("tcp", "192.168.14.200:18080") if err != nil { fmt.Printf("dial server failed, err:%v ", err) return } fmt.Fprintf recvMsg = make(chan interface{}, 1000) defer conn.Close() go read(conn) err = login(conn) if err != nil { fmt.Printf("login failed, err:%v ", err) return } msg := <-recvMsg loginResp, ok := msg.(*proto.LoginResponse) if !ok { fmt.Printf("unexpect msg:%T, %+v ", msg, msg) return } if loginResp.Errno != 0 { fmt.Printf("login failed, err:%v ", loginResp.Message) return } fmt.Printf("login succ ") for { var data string reader := bufio.NewReader(os.Stdin) //在shell输入 data, err := reader.ReadString(' ') //从shell读取一行数据 if err != nil { continue } err = sendMessage(conn, data) //开始发消息 if err != nil { fmt.Printf("send message failed, err:%v ", err) return } } } func sendMessage(conn net.Conn, data string) (err error) { var message proto.MessageRequest message.Message = data message.Username, _ = os.Hostname() //获取主机名 body, err := json.Marshal(message) if err != nil { fmt.Printf("marshal failed, err:%v ", err) return } err = proto.WritePacket(conn, proto.CmdSendMessageRequest, body) //将数据包发送给服务端 if err != nil { fmt.Printf("send to server failed, err:%v ", err) return } return } func login(conn net.Conn) (err error) { var loginReq proto.LoginRequest loginReq.Password = "admin" loginReq.Username = "admin" body, err := json.Marshal(loginReq) if err != nil { fmt.Printf("marshal failed, err:%v ", err) return } err = proto.WritePacket(conn, proto.CmdLoginRequest, body) if err != nil { fmt.Printf("send to server failed, err:%v ", err) return } return } func read(conn net.Conn) { for { body, cmd, err := proto.ReadPacket(conn) if err != nil { fmt.Printf("read from server failed, err:%v ", err) return } switch cmd { case proto.CmdLoginResponse: err = processLoginResponse(conn, body) case proto.CmdSendMessageResponse: err = processSendMsgResponse(conn, body) case proto.CmdBroadMessage: err = processBroadMessage(conn, body) default: fmt.Printf("unsupport cmd[%v] ", cmd) return } } } func processLoginResponse(conn net.Conn, body []byte) (err error) { //登录返回 var loginResponse proto.LoginResponse err = json.Unmarshal(body, &loginResponse) if err != nil { fmt.Printf("unmarshal failed, err:%v ", err) return } recvMsg <- &loginResponse return } func processSendMsgResponse(conn net.Conn, body []byte) (err error) { var messageResp proto.MessageResponse err = json.Unmarshal(body, &messageResp) if err != nil { fmt.Printf("unmarshal failed, err:%v ", err) return } if messageResp.Errno != 0 { fmt.Printf("消息发送失败:%v ", messageResp.Message) return } return } func processBroadMessage(conn net.Conn, body []byte) (err error) { var msg proto.BroadMessage err = json.Unmarshal(body, &msg) if err != nil { fmt.Printf("unmarshal failed, err:%v ", err) return } fmt.Printf("%s: %s ", msg.Username, msg.Message) return }
server/
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "fmt" "net" ) var ( clientMgr *ClientMgr ) func startServer(addr string) (l net.Listener, err error) { l, err = net.Listen("tcp", addr) //监听一个地址 if err != nil { fmt.Printf("listen addr:%s failed, err:%v ", addr, err) return } return } func main() { clientMgr = NewClientMgr(200) fmt.Printf("start server... ") l, err := startServer("0.0.0.0:18080") if err != nil { fmt.Println("start server failed, err:", err) return } err = runServer(l) if err != nil { fmt.Println("run server failed, err:", err) return } fmt.Println("server is exied") }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "encoding/json" "errors" "fmt" "net" "github.com/sherlockhua/goproject/day9/proto" ) func runServer(l net.Listener) (err error) { fmt.Println("run server succ") for { conn, err := l.Accept() if err != nil { fmt.Println("accept failed, err:", err) continue } clientMgr.newClientChan <- conn go process(conn) } } func process(conn net.Conn) { defer func() { clientMgr.closeChan <- conn conn.Close() }() for { body, cmd, err := proto.ReadPacket(conn) if err != nil { fmt.Printf("read from conn failed, err:%v ", err) return } err = processRequest(conn, body, cmd) if err != nil { fmt.Printf("processRequest[%v] failed, err:%v ", cmd, err) return } /* var buf []byte = make([]byte, 512) n, err := conn.Read(buf) if err != nil { fmt.Printf("read from conn failed, err:%v ", err) return } buf = buf[0:n] clientMgr.addMsg(buf) */ } } func processRequest(conn net.Conn, body []byte, cmd int32) (err error) { switch cmd { case proto.CmdLoginRequest: err = processLogin(conn, body) case proto.CmdRegisterRequest: err = processRegister(conn, body) case proto.CmdSendMessageRequest: err = processMessage(conn, body) default: fmt.Printf("unsupport cmd[%v] ", cmd) err = errors.New("unsupport cmd") return } return } func processLogin(conn net.Conn, body []byte) (err error) { fmt.Printf("begin process login request ") var loginRequest proto.LoginRequest err = json.Unmarshal(body, &loginRequest) if err != nil { fmt.Printf("Unmarshal failed[%v] ", err) return } fmt.Printf(" process login request:%+v ", loginRequest) var loginResp proto.LoginResponse loginResp.Errno = 100 loginResp.Message = "username or password not right" if loginRequest.Username == "admin" && loginRequest.Password == "admin" { loginResp.Errno = 0 loginResp.Message = "success" } data, err := json.Marshal(loginResp) if err != nil { fmt.Printf("Marshal failed[%v] ", err) return } fmt.Printf(" write login response %+v ", loginResp) return proto.WritePacket(conn, proto.CmdLoginResponse, data) //将登录响应信息传递给客户端 } func processRegister(conn net.Conn, body []byte) (err error) { return } //将消息广播出去 func processMessage(conn net.Conn, body []byte) (err error) { fmt.Printf("begin process login request ") var messageReq proto.MessageRequest err = json.Unmarshal(body, &messageReq) if err != nil { fmt.Printf("Unmarshal failed[%v] ", err) return } var broadMessage proto.BroadMessage broadMessage.Message = messageReq.Message broadMessage.Username = messageReq.Username body, err = json.Marshal(broadMessage) if err != nil { fmt.Printf("marshal failed, err:%v ", err) return } packet := &proto.Packet{ Cmd: proto.CmdBroadMessage, Body: body, } clientMgr.addMsg(packet) return }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "errors" "fmt" "net" "sync" "time" "github.com/sherlockhua/goproject/day9/proto" ) type ClientMgr struct { //clientsMap维护所有客户端连接 clientsMap map[net.Conn]int maxClientNums int //msgChan用来保存客户端发送过来的消息 msgChan chan *proto.Packet newClientChan chan net.Conn closeChan chan net.Conn lock sync.RWMutex //读写锁 } func NewClientMgr(maxClients int) *ClientMgr { mgr := &ClientMgr{ clientsMap: make(map[net.Conn]int, 1024), //连接的客户端的map maxClientNums: maxClients, //最大连接客户端数 msgChan: make(chan *proto.Packet, 1000), //消息管道 newClientChan: make(chan net.Conn, 1000), //新连接的客户端 closeChan: make(chan net.Conn, 1000), //关闭客户端连接 } go mgr.run() //遍历所有客户端发送过来的消息,并广播到所有的其他客户端 go mgr.procConn() return mgr } //监测新连接的客户端管道和需要关闭的客户端管道 func (c *ClientMgr) procConn() { for { select { case conn := <-c.newClientChan: //监测新连接的客户端的管道 c.lock.Lock() c.clientsMap[conn] = 0 //将新连接的客户端写入处于连接的客户端的map c.lock.Unlock() case conn := <-c.closeChan: //监测需要关闭的客户端的管道 c.lock.Lock() delete(c.clientsMap, conn) //从客户端连接的map中删除需要断开连接的客户端 c.lock.Unlock() } } } //遍历所有客户端发送过来的消息,并广播到所有的其他客户端 func (c *ClientMgr) run() { for msg := range c.msgChan { //遍历消息管道 c.transfer(msg) //广播消息 } } //广播消息,将消息发送给当前在线的客户端 func (c *ClientMgr) transfer(msg *proto.Packet) { c.lock.RLock() defer c.lock.RUnlock() for client, _ := range c.clientsMap { err := c.sendToClient(client, msg) //将消息发送给当前在线的客户端 if err != nil { continue } } } //发送消息给指定客户端 func (c *ClientMgr) sendToClient(client net.Conn, msg *proto.Packet) (err error) { return proto.WritePacket(client, msg.Cmd, msg.Body) /* var n int var sendBytes int msgLen := len(msg) for { n, err = client.Write(msg) if err != nil { fmt.Printf("send to client:%v failed, err:%v ", client, err) client.Close() delete(c.clientsMap, client) return } sendBytes += n if sendBytes >= msgLen { break } msg = msg[sendBytes:] } return */ } //将消息广播出去 func (c *ClientMgr) addMsg(msg *proto.Packet) (err error) { ticker := time.NewTicker(time.Millisecond * 10) //设置一个超时时间 defer ticker.Stop() select { case c.msgChan <- msg: fmt.Printf("send to chan succ ") case <-ticker.C: fmt.Printf("add msg timeout ") err = errors.New("add msg timeout") } return }