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,有序数据集合,元素可以重复,用
LPUSH
、LPOP
、RPUSH
、RPOP
等指令组合可以实现栈和队列操作。 - 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
- }
- }
设置过期时间
Go语言系列- Socket编程和Redis的更多相关文章
- Windows下C语言的Socket编程例子(TCP和UDP)
原文:Windows下C语言的Socket编程例子(TCP和UDP) 刚刚学windows编程,所以想写学习笔记,这是一个简单的Socket程序例子,开发环境是vc6: 首先是TCP server端: ...
- <转>Go语言TCP Socket编程
授权转载: Tony Bai 原文连接: https://tonybai.com/2015/11/17/tcp-programming-in-golang/ Golang的主要 设计目标之一就是面向大 ...
- Go语言TCP Socket编程
Golang的主要 设计目标之一就是面向大规模后端服务程序,网络通信这块是服务端 程序必不可少也是至关重要的一部分.在日常应用中,我们也可以看到Go中的net以及其subdirectories下的 ...
- boost.asio系列——socket编程
asio的主要用途还是用于socket编程,本文就以一个tcp的daytimer服务为例简单的演示一下如何实现同步和异步的tcp socket编程. 客户端 客户端的代码如下: #include &l ...
- Python语言系列-09-socket编程
简介 软件开发的架构 1.C/S架构(client-server) 2.B/S架构 (browser-server) 网络基础概念 网络三要素: 1.ip 2.port 3.通信协议:TCP.UDP ...
- Go语言系列- http编程和mysql
http编程 一.Http协议 1. 什么是协议? 协议,是指通信的双方,在通信流程或内容格式上,共同遵守的标准. 2. 什么是http协议? http协议,是互联网中最常见的网络通信标准. 3 ...
- [转]C语言SOCKET编程指南
1.介绍 Socket编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措?等 ...
- C语言SOCKET编程指南
1.介绍 Socket 编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措? ...
- winsock教程- windows下的socket编程(c语言实现)
winsock教程- windows下的socket编程(c语言实现) 使用winsock进行socket 编程 这是一个学习windows下socket编程(c语言)的快速指南.这是因为一下 ...
随机推荐
- 【JavaWeb】【Eclipse】使用Eclipse创建我的第一个网页
使用Eclipse创建我的第一个网页 哔哩哔哩 萌狼蓝天 你可以直接点击Finish,也可以点击Next,到下面这个界面后,勾选生成web.xml然后Finish(你不这样做就会没有Web.xml文件 ...
- Nginx区分PC和手机
目录 一.简介 二.配置 nginx识别手机端跳转到wap pc端跳转移动端 一.简介 有时候需要当手机访问PC站页面时自动跳转到对应的手机站页面. 二.配置 nginx识别手机端跳转到wap 即手机 ...
- hooks中,useState异步问题解决方案
问题描述: 在hooks中,修改状态的是通过useState返回的修改函数实现的.它的功能类似于class组件中的this.setState().而且,这两种方式都是异步的.可是this.setSta ...
- 利用 clip-path 实现动态区域裁剪
背景 今天逛 CodePen,看到了这样一个非常有意思的效果: CodePen Demo -- Material Design Menu By Bennett Feely 这个效果还是有一些值得探讨学 ...
- 转:苹果iphone APP界面设计尺寸官方版
苹果iphone APP界面设计尺寸官方版
- socket网络编程基础模块
更多功能 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0) 参数一:地址簇 socket.AF_INET IPv4(默认) socket. ...
- linux test使用
文件 文件是否存在 test -f 判断文件是否存在 test -d 目录是否存在 test -e 文件名是否存在 通过echo $? 来得知test后的结果 test -f sh && ...
- SpringBoot 封装异步执行任务简单demo
ThreadPoolConfig.java import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.concurrent.B ...
- 【LeetCode】536. Construct Binary Tree from String 解题报告(C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 统计字符串出现的次数 日期 题目地址:https:// ...
- 【九度OJ】题目1118:数制转换 解题报告
[九度OJ]题目1118:数制转换 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1118 题目描述: 求任意两个不同进制非 ...