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语言)的快速指南.这是因为一下 ...
随机推荐
- 使用 OPC Browser 加载 OPC Server 监测点
1,首先第一步,要连接OPC ,创建好 OPC对象. /// <summary> /// 连接OPC /// </summary> private string OPCIP=1 ...
- Java 多线程的一次整理
一天没有出过家门,实属无聊,没事瞎写写 1. 基本概念 1.1 多进程和多线程的概念 程序是由指令和数据组成,指令要运行,数据要加载,指令被 CPU 加载运行,数据被加载到内存,指令运行时可由 CPU ...
- TCP链接请求的10种状态
一.状态显示 SYN_SENT:这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,随即进入到了SYN_SENT状态,并等待服务端的发送三次握手中的 ...
- Log4j漏洞源码分析
Log4j漏洞源码分析 这几天Log4j的问题消息满天飞,今天我们就一起来看看从源码角度看看这个漏洞是如何产生的. 大家都知道这次问题主要是由于Log4j中提供的jndi的功能. 具体涉及到的入口类是 ...
- bjdctf_2020_babyrop2
这道题是一道基本题,正因为它经典,所以需要重点记录一下. 这道题考察格式化字符串泄露canary,然后rop获得libc版本,之后拿到shell.拿到程序之后我们先检查一下保护... 开启了堆栈不可执 ...
- Windows 任务计划部署 .Net 控制台程序
Windows 搜索:任务计划程序 创建任务 添加任务名称 设置触发器:这里设置每10分钟执行一次 保存之后显示 此任务会从每天的 0:10:00 执行第一次后一直循环下去. 在操作选项卡下,选择启动 ...
- Java 常用类库一,main方法传参String[] args;获取输入Scanner ;hasNext();hasNextInt()
1. main方法传参 package com.zmd.common_class_libraries; /** 给mian方法传参测试 */ public class MainArgsTest { p ...
- 查找MySql的配置文件my.cnf所在路径
Linux系统 linux 上可以使用 mysql --help|grep my.cnf 过滤查看 [root@localhost etc]# mysql --help|grep my.cnf ord ...
- Windows c(++)获取磁盘剩余容量
头文件 #include <windows.h> #include <wtypes.h> 函数 GetDiskFreeSpaceExA 获取剩余可用空间 /// 得到盘符, 例 ...
- 【LeetCode】142. Linked List Cycle II 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 双指针 set 日期 题目地址:https://le ...