tcp编程socket编程

Posted 关灯吃面

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tcp编程socket编程相关的知识,希望对你有一定的参考价值。

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(\'\\n\') //从终端读取一行数据
        trimmedInput := strings.Trim(input, "\\r\\n")//去掉字符串\\r\\n
        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\\r\\n"
    msg += "Host: www.baidu.com\\r\\n"
    msg += "Connection: close\\r\\n"
    msg += "\\r\\n\\r\\n"
    _, 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\\n", 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\\n", 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\\n", err)
        return
    }

    msg := <-recvMsg
    loginResp, ok := msg.(*proto.LoginResponse)
    if !ok {
        fmt.Printf("unexpect msg:%T, %+v\\n", msg, msg)
        return
    }

    if loginResp.Errno != 0 {
        fmt.Printf("login failed, err:%v\\n", loginResp.Message)
        return
    }

    fmt.Printf("login succ\\n")
    for {
        var data string
        reader := bufio.NewReader(os.Stdin)  //在shell输入
        data, err := reader.ReadString(\'\\n\') //从shell读取一行数据
        if err != nil {
            continue
        }

        err = sendMessage(conn, data) //开始发消息
        if err != nil {
            fmt.Printf("send message failed, err:%v\\n", 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\\n", err)
        return
    }

    err = proto.WritePacket(conn, proto.CmdSendMessageRequest, body) //将数据包发送给服务端
    if err != nil {
        fmt.Printf("send to server failed, err:%v\\n", 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\\n", err)
        return
    }

    err = proto.WritePacket(conn, proto.CmdLoginRequest, body)
    if err != nil {
        fmt.Printf("send to server failed, err:%v\\n", 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\\n", 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]\\n", 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\\n", 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\\n", err)
        return
    }

    if messageResp.Errno != 0 {
        fmt.Printf("消息发送失败:%v\\n", 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\\n", err)
        return
    }

    fmt.Printf("%s:\\n   %s\\n\\n", 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\\n", addr, err)
        return
    }

    return
}

func main() {

    clientMgr = NewClientMgr(200)
    fmt.Printf("start server...\\n"以上是关于tcp编程socket编程的主要内容,如果未能解决你的问题,请参考以下文章

Linux:TCP Socket编程(代码实战)

iOS socket网络编程

tcp编程socket编程

网络编程TCP/UDP(socket编程)

网络编程TCP/UDP(socket编程)

Python—网络编程之tcp编程