Go语言系列(九)- Socket编程和Redis

Socket编程

一、socket编程概述

  什么是socket编程? socket编程是计算机PC机器上2个程序通过一个双向的通信连接实现数据的交互,这个连接的一端就是一个socket。socket的翻译意思上还有个插座的概念,其实,也可以很形象的比喻为插座插上去了就有通电了(网络通了)。socket编程其实作为UNIX系统的进程间通信机制,通常称为“套接字”,用来描述IP地址和端口的集合,在unix系统下是一个通信的句柄(文件描述符,因为UNIX下所有都是文件)。

UNIX socket编程的流程大概如下:

  • 服务端:socket(),bind(),listen(),accept(),read()/write(),close()
  • 服务端:socket(), connect(),read()/write(),close()

二、Go语言socket编程库

go语言的基础包中,网络包net包含了很多网络I/O,TCP/IP,UDP,域名解析,Unix 域套接字等。

虽然提供的都是直接面对很底层的网络I/O访问,但是主要的TCP通信的接口也是封装得比较简单,好用,包括有:

  • 客户端连接使用的Dial方法
  • 服务端进行监听使用的Listen方法
  • 服务端进行接受链接的Accept方法
  • 封装了连接对象Conn类型

三、客户端和服务端

1. 服务器处理流程

  • a. 监听端口
  • b. 接收客户端的链接
  • c. 创建goroutine,处理该链接

2.  客户端处理流程

  • a. 建立与服务器的链接
  • b. 进行数据收发
  • c. 关闭链接

3. 服务端代码

package main

import (
	"fmt"
	"net"
)

func main() {
	fmt.Println("start server...")
	listen, err := net.Listen("tcp", "0.0.0.0: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)
		n, err := conn.Read(buf)
		if err != nil {
			fmt.Println("read err:", err)
			return
		}

		fmt.Printf(string(buf[0:n]))
	}
}

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")
		if trimmedInput == "Q" {
			return
		}
		_, err = conn.Write([]byte(trimmedInput))
		if err != nil {
			return
		}
	}
}

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:keep-alive\r\n"
	// msg += "User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36\r\n"
	msg += "\r\n\r\n"

	//io.WriteString(os.Stdout, msg)
	n, err := io.WriteString(conn, msg)
	if err != nil {
		fmt.Println("write string failed, ", err)
		return
	}
	fmt.Println("send to baidu.com bytes:", n)
	buf := make([]byte, 4096)
	for {
		count, err := conn.Read(buf)
		fmt.Println("count:", count, "err:", err)
		if err != nil {
			break
		}
		fmt.Println(string(buf[0:count]))
	}
}

 Redis

一、Redis简介

  redis是个开源的高性能的key-value的内存数据库,可以把它当成远程的数据结构。支持的value类型非常多,比如string、list(链表)、set(集合)、hash表等等。redis性能非常高,单机能够达到15w qps,通常适合做缓存。

1. 特点

  • 支持更多数据类型 
    和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set 有序集合)和hash(哈希类型)。[1]
  • 支持复杂操作 
    这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis支持各种不同方式的排序。[2]
  • 支持主从同步。 
    与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。从盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。

2.  常见使用场景

  • 高并发下数据缓存。 比如在某个场景下,大量日志同时写入数据库会给服务器带来巨大压力,这时可以先将数据写入redis中,再由redis写入数据库,减轻同时写入压力。
  • 热点信息快速显示。假设现在有一个新闻首页,需要快速显示各栏目前20条热点新闻,如果直接查询数据库,在大量用户同时访问下,会消耗极大数量的数据库请求。这时就可以用redis来优化,在新闻录入的时候将标题、时间和来源写入redis中,客户端访问时,可以从内存中一次性取出当天热单新闻列表,极大地提高请求速度和节约了服务器开销。
  • 保存会话信息。可以将登录后用户信息缓存入redis并同时设置key过期时间,这样后台api过滤请求时,就可以从内存中读取用户信息,而且redis的过期机制,天然支持用户身份有效期校验,用起来十分方便。
  • 统计计数。比如系统中常见一个功能是限制同一用户固定时间段内的登录次数或者所有请求次数,这时就可以以用户id为key,次数值为value,将计数信息缓存起来,并且有INCRBY命令原生支持。
  • 其他。Redis的应用场景十分广发,队列、发布订阅、统计分析等等,可以看看其他文章的介绍说明。

  Redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。Redis的官网地址,非常好记,是redis.io。目前,Vmware在资助着Redis项目的开发和维护。

二、Redis的主要数据结构

  Redis主要有五种基本数据结构,满足了绝大多数缓存结构的需要,如果你在使用一种结构存储时感觉别扭时,很有可能是选错了存储结构,可以考虑一下其他结构的正确实现。

  • String ,可以是字符串、整数和浮点数。如果是序列化数据,并涉及到修改操作的话,不推荐用string,可以考虑用Hash
  • Hash, key-value 对象,可以存放对象数据,比如用户信息之类。
  • List,有序数据集合,元素可以重复,用LPUSHLPOPRPUSHRPOP等指令组合可以实现栈和队列操作。
  • Set,无序集合,元素唯一。
  • Sorted Set,Sort的有序版,可以设定Score值来决定元素排序,适合用户排名这样的业务场景。

二、Redis的使用

1. redigo

  redigo是GO语言的一个redis客户端实现。项目位于https://github.com/garyburd/redigo

2. 安装redigo

redigo没有其他依赖项,可以直接通过go get进行安装

go get github.com/garyburd/redigo/redis

3. 连接redis

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()
}

4. 建立连接池

  Redigo Pool 结构维护一个 Redis 连接池。应用程序调用 Get 方法从池中获取连接,并使用连接的 Close 方法将连接的资源返回到池中。一般我们在系统初始化时声明一个全局连接池,然后在需要操作redis时获得连接,执行指令。

pool := &redis.Pool{
        MaxIdle:     3, /*最大的空闲连接数*/
        MaxActive:   8, /*最大的激活连接数*/
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", "链接地址,例如127.0.0.1:6379", redis.DialPassword("密码"))
            if err != nil {
                return nil, err
            }
            return c, nil
        },
}
c:=pool.Get()
defer c.Close()

package main

import (
    "fmt"

    "github.com/garyburd/redigo/redis"
)

var pool *redis.Pool

func init() {
    pool = &redis.Pool{
        MaxIdle:     16, /* 最大的空闲连接数 */
        MaxActive:   0,  /* 最大的激活连接数 */
        IdleTimeout: 300,
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", "localhost:6379", redis.DialPassword("0000"))
            if err != nil {
                return nil, err
            }
            return c, nil
        },
    }
}

func main() {

    c := pool.Get()
    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)
    pool.Close()
}

redis连接池实例

5.  执行指令

  查看源码,发现Conn 接口有一个执行 Redis 命令的通用方法:

//gomodule/redigo/redis/redis.go

// Conn represents a connection to a Redis server.
type Conn interface {
    // Close closes the connection.
    Close() error

    // Err returns a non-nil value when the connection is not usable.
    Err() error

    // Do sends a command to the server and returns the received reply.
    Do(commandName string, args ...interface{}) (reply interface{}, err error)

    // Send writes the command to the client‘s output buffer.
    Send(commandName string, args ...interface{}) error

    // Flush flushes the output buffer to the Redis server.
    Flush() error

    // Receive receives a single reply from the Redis server
    Receive() (reply interface{}, err error)
}

http://redis.io/commands 中的 Redis 命令参考列出了可用的命令。do的参数和redis-cli命令参数格式一致,比如SET key value EX 360 对应函数调用为Do("SET", "key", "value","EX",360),常用的命令示例有:  

c := pool.Get()
defer c.Close()
//存值,
_, err := c.Do("SET", "key", "value")
//设置过期时间
_, err := c.Do("SET", "key", "value","EX",360)
//存int
_, err := c.Do("SET", "key", 2)

//取值
v,err:=redis.String(c.Do("GET","key"))
bytes, err := redis.Bytes(c.Do("GET", "key"))

package main

import (
    "fmt"

    "github.com/garyburd/redigo/redis"
)

func main() {
    var p *int
    var a int
    p = &a
    *p = 0

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

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

mset操作

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)
    if err != nil {
        fmt.Println(err)
        return
    }

    r, err := redis.Int(c.Do("HGet", "books", "abc"))
    if err != nil {
        fmt.Println("get abc failed,", err)
        return
    }

    fmt.Println(r)
}

hset操作

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

list队列操作

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)
    if err != nil {
        fmt.Println(err)
        return
    }
}

设置过期时间

原文地址:https://www.cnblogs.com/zhangyafei/p/10993160.html

时间: 2024-10-11 03:25:16

Go语言系列(九)- Socket编程和Redis的相关文章

android开发系列之socket编程

上周在项目遇到一个接口需求就是通讯系列必须是socket,所以在这篇博客里面我想谈谈自己在socket编程的时候遇到的一些问题. 其实在android里面实现一个socket通讯是非常简单的,我们只需要在代码里面实现一个Socket对象,同时在该对象里面传进一个ip/port,同时设置一些超时时间就可以了.代码如下: public class SocketThread extends Thread { private String ip = "192.168.129.1"; priva

Java基础复习笔记系列 九 网络编程

Java基础复习笔记系列之 网络编程 1. 2.

JAVA学习笔记(五十九)- Socket编程

客户端 import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; /* * 客户端 */ public class Client { public static void main(String[] args) throws IOExcep

Go语言TCP/UDP Socket编程

1. TCP编程 TCPClient // TCPClient project main.go package main import ( "fmt" "net" "os" ) func main() { var buf [512]byte if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0]) os.Exit(1) }

winsock教程- windows下的socket编程(c语言实现)

winsock教程- windows下的socket编程(c语言实现) 使用winsock进行socket 编程 这是一个学习windows下socket编程(c语言)的快速指南.这是因为一下代码片段只能运行在windows下.windows API中的socket编程部分叫做winsock. 你电脑上做出的任何网络通信背后基本上都有socket,它是一个网络的基本组成部分.举个例子说当你在浏览器键入www.google.com的时候,socket连接到google.com并且取回那个页面然后才

Go语言TCP Socket编程

Golang的主要 设计目标之一就是面向大规模后端服务程序,网络通信这块是服务端 程序必不可少也是至关重要的一部分.在日常应用中,我们也可以看到Go中的net以及其subdirectories下的包均是"高频+刚需",而TCP socket则是网络编程的主流,即便您没有直接使用到net中有关TCP Socket方面的接口,但net/http总是用到了吧,http底层依旧是用tcp socket实现的. 网络编程方面,我们最常用的就是tcp socket编程了,在posix标准出来后,s

C语言SOCKET编程指南

1.介绍 Socket 编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措?等等… 好在我已经将这些事完成了,我将和所有人共享我的知识了.如果你了解 C语言并想穿过网络编程的沼泽,那么你来对地方了. 2.读者对象 这个文档是一个指南,而不是参考书.如果你刚开始 socket 编程并想找一本入门书,那么你是我的读者.但这不是一本完全的 socket 编程书. 3.平台和编译器

Socket编程(c语言示例)

转自:http://blog.csdn.net/dxpqxb/article/details/8166423 前言 Socket可以看成在两个程序进行通讯连接中的一个端点,是连接应用程序和网络驱动程序的桥梁,Socket在应用程序中创建,通过绑定与网络驱动建立 关系.此后,应用程序送给Socket的数据,由Socket交网络驱动程序向网络上发送出去.计算机从网络上收到与该Socket绑定IP地址和端口号 相关的数据后,由网络驱动程序交给Socket,应用程序便可从该Socket中提取接收到得数据

Windows下C语言的Socket编程例子(TCP和UDP)

一.  <TCP> server端: 1 #include "stdafx.h" 2 #include <stdio.h> 3 #include <winsock2.h> 4 5 #pragma comment(lib,"ws2_32.lib") 6 7 int main(int argc, char* argv[]) 8 { 9 //初始化WSA 10 WORD sockVersion = MAKEWORD(2,2); 11 W