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. 服务端代码

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. "net"
  6. )
  7.  
  8. func main() {
  9. fmt.Println("start server...")
  10. listen, err := net.Listen("tcp", "0.0.0.0:50000")
  11. if err != nil {
  12. fmt.Println("listen failed, err:", err)
  13. return
  14. }
  15. for {
  16. conn, err := listen.Accept()
  17. if err != nil {
  18. fmt.Println("accept failed, err:", err)
  19. continue
  20. }
  21. go process(conn)
  22. }
  23. }
  24. func process(conn net.Conn) {
  25. defer conn.Close()
  26. for {
  27. buf := make([]byte, 512)
  28. n, err := conn.Read(buf)
  29. if err != nil {
  30. fmt.Println("read err:", err)
  31. return
  32. }
  33.  
  34. fmt.Printf(string(buf[0:n]))
  35. }
  36. }

4. 客户端代码

  1. package main
  2.  
  3. import (
  4. "bufio"
  5. "fmt"
  6. "net"
  7. "os"
  8. "strings"
  9. )
  10.  
  11. func main() {
  12.  
  13. conn, err := net.Dial("tcp", "localhost:50000")
  14. if err != nil {
  15. fmt.Println("Error dialing", err.Error())
  16. return
  17. }
  18.  
  19. defer conn.Close()
  20. inputReader := bufio.NewReader(os.Stdin)
  21. for {
  22. input, _ := inputReader.ReadString('\n')
  23. trimmedInput := strings.Trim(input, "\r\n")
  24. if trimmedInput == "Q" {
  25. return
  26. }
  27. _, err = conn.Write([]byte(trimmedInput))
  28. if err != nil {
  29. return
  30. }
  31. }
  32. }

5. 发送http请求

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. "io"
  6. "net"
  7. )
  8.  
  9. func main() {
  10.  
  11. conn, err := net.Dial("tcp", "www.baidu.com:80")
  12. if err != nil {
  13. fmt.Println("Error dialing", err.Error())
  14. return
  15. }
  16. defer conn.Close()
  17. msg := "GET / HTTP/1.1\r\n"
  18. msg += "Host:www.baidu.com\r\n"
  19. msg += "Connection:keep-alive\r\n"
  20. // 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"
  21. msg += "\r\n\r\n"
  22.  
  23. //io.WriteString(os.Stdout, msg)
  24. n, err := io.WriteString(conn, msg)
  25. if err != nil {
  26. fmt.Println("write string failed, ", err)
  27. return
  28. }
  29. fmt.Println("send to baidu.com bytes:", n)
  30. buf := make([]byte, 4096)
  31. for {
  32. count, err := conn.Read(buf)
  33. fmt.Println("count:", count, "err:", err)
  34. if err != nil {
  35. break
  36. }
  37. fmt.Println(string(buf[0:count]))
  38. }
  39. }

 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进行安装

  1. go get github.com/garyburd/redigo/redis

3. 连接redis

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. "github.com/garyburd/redigo/redis"
  6. )
  7.  
  8. func main() {
  9. c, err := redis.Dial("tcp", "localhost:6379")
  10. if err != nil {
  11. fmt.Println("conn redis failed,", err)
  12. return
  13. }
  14.  
  15. defer c.Close()
  16. }

4. 建立连接池

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

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

  1. package main
  2.  
  3. import (
  4. "fmt"
  5.  
  6. "github.com/garyburd/redigo/redis"
  7. )
  8.  
  9. var pool *redis.Pool
  10.  
  11. func init() {
  12. pool = &redis.Pool{
  13. MaxIdle: 16, /* 最大的空闲连接数 */
  14. MaxActive: 0, /* 最大的激活连接数 */
  15. IdleTimeout: 300,
  16. Dial: func() (redis.Conn, error) {
  17. c, err := redis.Dial("tcp", "localhost:6379", redis.DialPassword("0000"))
  18. if err != nil {
  19. return nil, err
  20. }
  21. return c, nil
  22. },
  23. }
  24. }
  25.  
  26. func main() {
  27.  
  28. c := pool.Get()
  29. defer c.Close()
  30.  
  31. _, err := c.Do("Set", "abc", 100)
  32. if err != nil {
  33. fmt.Println(err)
  34. return
  35. }
  36.  
  37. r, err := redis.Int(c.Do("Get", "abc"))
  38. if err != nil {
  39. fmt.Println("get abc failed,", err)
  40. return
  41. }
  42.  
  43. fmt.Println(r)
  44. pool.Close()
  45. }

redis连接池实例

5.  执行指令

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

  1. //gomodule/redigo/redis/redis.go
  2.  
  3. // Conn represents a connection to a Redis server.
  4. type Conn interface {
  5. // Close closes the connection.
  6. Close() error
  7.  
  8. // Err returns a non-nil value when the connection is not usable.
  9. Err() error
  10.  
  11. // Do sends a command to the server and returns the received reply.
  12. Do(commandName string, args ...interface{}) (reply interface{}, err error)
  13.  
  14. // Send writes the command to the client's output buffer.
  15. Send(commandName string, args ...interface{}) error
  16.  
  17. // Flush flushes the output buffer to the Redis server.
  18. Flush() error
  19.  
  20. // Receive receives a single reply from the Redis server
  21. Receive() (reply interface{}, err error)
  22. }

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

  1. c := pool.Get()
  2. defer c.Close()
  3. //存值,
  4. _, err := c.Do("SET", "key", "value")
  5. //设置过期时间
  6. _, err := c.Do("SET", "key", "value""EX",360)
  7. //存int
  8. _, err := c.Do("SET", "key", 2)
  9.  
  10. //取值
  11. v,err:=redis.String(c.Do("GET","key"))
  12. bytes, err := redis.Bytes(c.Do("GET", "key"))

  1. package main
  2.  
  3. import (
  4. "fmt"
  5.  
  6. "github.com/garyburd/redigo/redis"
  7. )
  8.  
  9. func main() {
  10. var p *int
  11. var a int
  12. p = &a
  13. *p = 0
  14.  
  15. c, err := redis.Dial("tcp", "localhost:6379")
  16. if err != nil {
  17. fmt.Println("conn redis failed,", err)
  18. return
  19. }
  20.  
  21. defer c.Close()
  22. _, err = c.Do("Set", "abc", 100)
  23. if err != nil {
  24. fmt.Println(err)
  25. return
  26. }
  27.  
  28. r, err := redis.Int(c.Do("Get", "abc"))
  29. if err != nil {
  30. fmt.Println("get abc failed,", err)
  31. return
  32. }
  33.  
  34. fmt.Println(r)
  35. }

set操作

  1. package main
  2.  
  3. import (
  4. "fmt"
  5.  
  6. "github.com/garyburd/redigo/redis"
  7. )
  8.  
  9. func main() {
  10. c, err := redis.Dial("tcp", "localhost:6379")
  11. if err != nil {
  12. fmt.Println("conn redis failed,", err)
  13. return
  14. }
  15.  
  16. defer c.Close()
  17. _, err = c.Do("MSet", "abc", 100, "efg", 300)
  18. if err != nil {
  19. fmt.Println(err)
  20. return
  21. }
  22.  
  23. r, err := redis.Ints(c.Do("MGet", "abc", "efg"))
  24. if err != nil {
  25. fmt.Println("get abc failed,", err)
  26. return
  27. }
  28.  
  29. for _, v := range r {
  30. fmt.Println(v)
  31. }
  32. }

mset操作

  1. package main
  2.  
  3. import (
  4. "fmt"
  5.  
  6. "github.com/garyburd/redigo/redis"
  7. )
  8.  
  9. func main() {
  10. c, err := redis.Dial("tcp", "localhost:6379")
  11. if err != nil {
  12. fmt.Println("conn redis failed,", err)
  13. return
  14. }
  15.  
  16. defer c.Close()
  17. _, err = c.Do("HSet", "books", "abc", 100)
  18. if err != nil {
  19. fmt.Println(err)
  20. return
  21. }
  22.  
  23. r, err := redis.Int(c.Do("HGet", "books", "abc"))
  24. if err != nil {
  25. fmt.Println("get abc failed,", err)
  26. return
  27. }
  28.  
  29. fmt.Println(r)
  30. }

hset操作

  1. package main
  2.  
  3. import (
  4. "fmt"
  5.  
  6. "github.com/garyburd/redigo/redis"
  7. )
  8.  
  9. func main() {
  10. c, err := redis.Dial("tcp", "localhost:6379")
  11. if err != nil {
  12. fmt.Println("conn redis failed,", err)
  13. return
  14. }
  15.  
  16. defer c.Close()
  17. _, err = c.Do("lpush", "book_list", "abc", "ceg", 300)
  18. if err != nil {
  19. fmt.Println(err)
  20. return
  21. }
  22.  
  23. r, err := redis.String(c.Do("lpop", "book_list"))
  24. if err != nil {
  25. fmt.Println("get abc failed,", err)
  26. return
  27. }
  28.  
  29. fmt.Println(r)
  30. }

list队列操作

  1. package main
  2.  
  3. import (
  4. "fmt"
  5.  
  6. "github.com/garyburd/redigo/redis"
  7. )
  8.  
  9. func main() {
  10. c, err := redis.Dial("tcp", "localhost:6379")
  11. if err != nil {
  12. fmt.Println("conn redis failed,", err)
  13. return
  14. }
  15.  
  16. defer c.Close()
  17. _, err = c.Do("expire", "abc", 10)
  18. if err != nil {
  19. fmt.Println(err)
  20. return
  21. }
  22. }

设置过期时间

Go语言系列- Socket编程和Redis的更多相关文章

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

    原文:Windows下C语言的Socket编程例子(TCP和UDP) 刚刚学windows编程,所以想写学习笔记,这是一个简单的Socket程序例子,开发环境是vc6: 首先是TCP server端: ...

  2. <转>Go语言TCP Socket编程

    授权转载: Tony Bai 原文连接: https://tonybai.com/2015/11/17/tcp-programming-in-golang/ Golang的主要 设计目标之一就是面向大 ...

  3. Go语言TCP Socket编程

      Golang的主要 设计目标之一就是面向大规模后端服务程序,网络通信这块是服务端 程序必不可少也是至关重要的一部分.在日常应用中,我们也可以看到Go中的net以及其subdirectories下的 ...

  4. boost.asio系列——socket编程

    asio的主要用途还是用于socket编程,本文就以一个tcp的daytimer服务为例简单的演示一下如何实现同步和异步的tcp socket编程. 客户端 客户端的代码如下: #include &l ...

  5. Python语言系列-09-socket编程

    简介 软件开发的架构 1.C/S架构(client-server) 2.B/S架构 (browser-server) 网络基础概念 网络三要素: 1.ip 2.port 3.通信协议:TCP.UDP ...

  6. Go语言系列- http编程和mysql

    http编程 一.Http协议  1. 什么是协议? 协议,是指通信的双方,在通信流程或内容格式上,共同遵守的标准.  2. 什么是http协议? http协议,是互联网中最常见的网络通信标准.  3 ...

  7. [转]C语言SOCKET编程指南

    1.介绍 Socket编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措?等 ...

  8. C语言SOCKET编程指南

    1.介绍 Socket 编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措? ...

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

    winsock教程- windows下的socket编程(c语言实现) 使用winsock进行socket 编程     这是一个学习windows下socket编程(c语言)的快速指南.这是因为一下 ...

随机推荐

  1. 【JavaWeb】【Eclipse】使用Eclipse创建我的第一个网页

    使用Eclipse创建我的第一个网页 哔哩哔哩 萌狼蓝天 你可以直接点击Finish,也可以点击Next,到下面这个界面后,勾选生成web.xml然后Finish(你不这样做就会没有Web.xml文件 ...

  2. Nginx区分PC和手机

    目录 一.简介 二.配置 nginx识别手机端跳转到wap pc端跳转移动端 一.简介 有时候需要当手机访问PC站页面时自动跳转到对应的手机站页面. 二.配置 nginx识别手机端跳转到wap 即手机 ...

  3. hooks中,useState异步问题解决方案

    问题描述: 在hooks中,修改状态的是通过useState返回的修改函数实现的.它的功能类似于class组件中的this.setState().而且,这两种方式都是异步的.可是this.setSta ...

  4. 利用 clip-path 实现动态区域裁剪

    背景 今天逛 CodePen,看到了这样一个非常有意思的效果: CodePen Demo -- Material Design Menu By Bennett Feely 这个效果还是有一些值得探讨学 ...

  5. 转:苹果iphone APP界面设计尺寸官方版

    苹果iphone APP界面设计尺寸官方版

  6. socket网络编程基础模块

    更多功能 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0) 参数一:地址簇 socket.AF_INET IPv4(默认) socket. ...

  7. linux test使用

    文件 文件是否存在 test -f 判断文件是否存在 test -d 目录是否存在 test -e 文件名是否存在 通过echo $? 来得知test后的结果 test -f sh && ...

  8. SpringBoot 封装异步执行任务简单demo

    ThreadPoolConfig.java import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.concurrent.B ...

  9. 【LeetCode】536. Construct Binary Tree from String 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 统计字符串出现的次数 日期 题目地址:https:// ...

  10. 【九度OJ】题目1118:数制转换 解题报告

    [九度OJ]题目1118:数制转换 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1118 题目描述: 求任意两个不同进制非 ...