zoukankan      html  css  js  c++  java
  • tcp编程、socket编程、redis

    tcp编程(需要建立连接,三次握手,四次挥手,然后发送信息流,数据包是有序的)

    udp编程(知道IP、端口直接发送数据,数据包可能是无序的)

    1、客户端和服务器

    socket编程

    1.服务端的处理流程
      a.监听端口
      b.接收客户端的链接
      c.创建goroutine,处理该链接

    2.客户端的处理流程
      a.建立与服务端的链接
      b.进行数据收发
      c.关闭链接

    3.服务端代码

    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))
        }
    }
    View Code

    4.客户端代码

    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
            }
        }
    }
    View Code

    5.发送http请求

    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]))
        }
    }
    View Code

    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

    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)
    }
    View Code

    4、Hash

    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)
    }
    View Code

    5、批量Set

    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)
        }
    }
    View Code

    6、过期时间

    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
        }
    }
    View Code

    7、队列操作

    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)
    }
    View Code

    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
    
    }
    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)
    
    }
    View Code

    socket练习:

    client/main.go

    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
    }
    main.go

    server/

    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")
    }
    main.go
    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
    }
    server.go
    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
    }
    conn_mgr.go

     

  • 相关阅读:
    浅谈Python常用英文单词
    python web框架 Django的APP以及目录介绍 2
    Python中的enumerate函数
    python web框架 django wsgi 理论
    python web框架 django 工程 创建 目录介绍
    python web框架 django工程的创建
    mysql c 终止 mysql输入语句模式
    前端 html input标签 placeholder属性 标签上显示内容
    img 标签注意 默认img标签,有一个1px的边框 img{ border: 0; }
    前端 html input标签 disable 属性
  • 原文地址:https://www.cnblogs.com/domestique/p/8414731.html
Copyright © 2011-2022 走看看