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")
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\n", err)
return
}
err = processRequest(conn, body, cmd)
if err != nil {
fmt.Printf("processRequest[%v] failed, err:%v\n", 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\n", 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]\n", cmd)
err = errors.New("unsupport cmd")
return
}
return
}
func processLogin(conn net.Conn, body []byte) (err error) {
fmt.Printf("begin process login request\n")
var loginRequest proto.LoginRequest
err = json.Unmarshal(body, &loginRequest)
if err != nil {
fmt.Printf("Unmarshal failed[%v]\n", err)
return
}
fmt.Printf(" process login request:%+v\n", 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]\n", err)
return
}
fmt.Printf(" write login response %+v\n", 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\n")
var messageReq proto.MessageRequest
err = json.Unmarshal(body, &messageReq)
if err != nil {
fmt.Printf("Unmarshal failed[%v]\n", 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\n", 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\n", 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\n")
case <-ticker.C:
fmt.Printf("add msg timeout\n")
err = errors.New("add msg timeout")
}
return
}
conn_mgr.go