Go語言8-socket和redis
SOCKET 編程
在Go里為我們提供了net包。
下面這篇貌似是官方文檔的翻譯:
https://blog.csdn.net/chenbaoke/article/details/42782571
上面的轉載,上面的頁面在IE下瀏覽貌似有點問題:
https://studygolang.com/articles/3600
Package net provides a portable interface for network I/O, including TCP/IP, UDP, domain name resolution, and Unix domain SOCKETs.
net包對于網絡I/O提供了便攜式接口,包括TCP/IP,UDP,域名解析以及Unix Socket。
Although the package provides access to low-level networking primitives, most clients will need only the basic interface provided by the Dial, Listen, and Accept functions and the associated Conn and Listener interfaces. The crypto/tls package uses the same interfaces and similar Dial and Listen functions.
盡管net包提供了大量訪問底層的接口,但是大多數情況下,客戶端僅僅只需要最基本的接口,例如Dial,LIsten,Accepte以及分配的conn連接和listener接口。 crypto/tls包使用相同的接口以及類似的Dial和Listen函數。
服務端
服務端的處理流程:
監聽端口
接收客戶端的連接
創建goroutine,處理連接
服務端代碼:
package?main import?( ????"fmt" ????"net" ) func?main()?{ ????fmt.Println("準備開啟Server...") ????listen,?err?:=?net.Listen("tcp",?"0.0.0.0:8080") ????if?err?!=?nil?{ ????????fmt.Println("監聽端口ERROR:",?err) ????????return ????} ????for?{ ????????conn,?err?:=?listen.Accept() ????????if?err?!=?nil?{ ????????????fmt.Println("接收連接ERROR:",?err) ????????} ????????go?process(conn) ????} } func?process(conn?net.Conn)?{ ????defer?conn.Close() ????for?{ ????????buf?:=?make([]byte,?512) ????????n,?err?:=?conn.Read(buf) ????????fmt.Println(n)??//?這個應該是讀取到的數據的長度 ????????if?err?!=?nil?{ ????????????fmt.Println("讀取數據ERROR:",?err) ????????????return ????????} ????????fmt.Println("READ:",?string(buf)) ????} }
測試,暫時還沒有客戶端,可以先用windows的telnet工具來測試一下:
>telnet?127.0.0.1?8080
進入telnet后按任意鍵盤,server端會做出反應,但是效果不是很友好。
修改一下服務端的process函數如下:
func?process(conn?net.Conn)?{ ????defer?conn.Close() ????for?{ ????????buf?:=?make([]byte,?10)??//?這次一次只收10個 ????????_,?err?:=?conn.Read(buf)??//?接收的長度就不看了 ????????if?err?!=?nil?{ ????????????fmt.Println("讀取數據ERROR:",?err) ????????????return ????????} ????????fmt.Printf("%v\n",?buf)??//?查看buf的字符類型 ????} }
conn.Read用于讀取收到的數據,把數據存到變量buf里。buf切片里的類型應該是字符類型默認就是0,從輸出里看到其他后面都是0。
conn.Read 返回的第一個參數是接收的長度,那么可以對buf進行切片,只把收到的數據打印出來:
package?main import?( ????"fmt" ????"net" ) func?main()?{ ????fmt.Println("準備開啟Server...") ????listen,?err?:=?net.Listen("tcp",?"0.0.0.0:8080") ????if?err?!=?nil?{ ????????fmt.Println("監聽端口ERROR:",?err) ????????return ????} ????for?{ ????????conn,?err?:=?listen.Accept() ????????if?err?!=?nil?{ ????????????fmt.Println("接收連接ERROR:",?err) ????????} ????????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("讀取數據ERROR:",?err) ????????????return ????????} ????????fmt.Printf(string(buf[0:n]))??//?接收了n個字符,就只打印前n個 ????} }
上面的代碼,在telnet連接后,鍵盤輸入任何內容,都會在server端打印出來。這里也能傳中文,但是接收到的的是亂碼,這個主要是telnet的問題(有編碼的問題,一個中文字符占2個長度,如果是utf-8是長度3。而且會把中文的2段編碼拆開發出去。不深究了。),后面用上客戶端就好了。
客戶端
客戶端的處理流程:
建立與服務端的連接
進行數據收發
關閉連接
客戶端代碼:
package?main import?( ????"bufio" ????"fmt" ????"net" ????"os" ????"strings" ) func?main()?{ ????conn,?err?:=?net.Dial("tcp",?"127.0.0.1:8080") ????if?err?!=?nil?{ ????????fmt.Println("建立連接ERROR:",?err) ????????return ????} ????fmt.Println("建立連接成功") ????defer?conn.Close()??//?這里一定記得要關閉 ????inputReader?:=?bufio.NewReader(os.Stdin) ????for?{ ????????input,?err?:=?inputReader.ReadString('\n') ????????input?=?strings.TrimSpace(input) ????????if?err?!=?nil?{ ????????????fmt.Println("終端輸入ERROR:",?err) ????????} ????????if?input?==?"Q"?{ ????????????fmt.Println("退出...") ????????????return ????????} ????????_,?err?=?conn.Write([]byte(input))??//?第一個參數是發送的字符數 ????????if?err?!=?nil?{ ????????????fmt.Println("發送數據ERROR:",?err) ????????????return ????????} ????} }
發送http請求
這里需要一點基礎,你得知道Web服務的本質。
這里只用HTTP/1.1來做個示例,默認:Connection:keep-alive,示例里設置為:Connection:close。接收的數據的內容會有區別,Connection:close收到的數據會比較簡單(Connection:keep-alive應該是比較新的標準,默認是返回這樣的格式)。
發送http請求的代碼:
package?main import?( ????"fmt" ????"io" ????"os" ????"net" ) func?main()?{ ????conn,?err?:=?net.Dial("tcp",?"edu.51cto.com:80") ????if?err?!=?nil?{ ????????fmt.Println("創建連接ERROR:",?err) ????????return ????} ????defer?conn.Close() ????//?設置請求頭 ????msg?:=?"GET?/?HTTP/1.1\r\n" ????msg?+=?"Host:edu.51cto.com\r\n" ????msg?+=?"Connection:close\r\n" ????msg?+=?"\r\n"??//?請求頭結束 ????_,?err?=?io.WriteString(conn,?msg)??//?發送請求 ????if?err?!=?nil?{ ????????fmt.Println("發送數據ERROR:",?err) ????????return ????} ????buf?:=?make([]byte,?1024) ????fmt.Println("接收數據...") ????for?{ ????????n,?err?:=?conn.Read(buf) ????????fmt.Println(string(buf[0:n])) ????????if?err?!=?nil?{ ????????????if?err?==?io.EOF{ ????????????????fmt.Println("接收完畢...") ????????????????break ????????????}?else?{ ????????????????fmt.Println("接收數據ERROR:",?err) ????????????????os.Exit(0) ????????????} ????????} ????} }
Redis
本篇主要是講go如何使用Redis,就是一些簡單的代碼示例。關于Redis的基礎知識可以看下面這篇的第一章節。
Redis安裝和基礎知識:http://blog.51cto.com/steed/2057706
Redis是一個開源的高性能的key-value的內存數據庫,可以把它當成遠程的數據結構。
支持的value類型很多,比如:string、list(鏈表)、set(集合)、hash表等
Redis的性能非常高,單機能夠達到15w qps,通常用來做緩存。
使用第三方開源的redis庫:https://github.com/gomodule/redigo
安裝第三方庫:
go?get?github.com/gomodule/redigo/redis
連接 Redis
package?main import?( ????"fmt" ????"github.com/gomodule/redigo/redis" ) func?main()?{ ????conn,?err?:=?redis.Dial("tcp",?"192.168.3.108:6379") ????if?err?!=?nil?{ ????????fmt.Println("連接Redis?ERROR:",?err) ????????return ????} ????fmt.Println("連接Redis?成功:",?conn) ????defer?conn.Close() }
Set 和 Get
上面的連接沒問題的,接下來就可以通過Redis存(Set)取(Get)數據了。這里還有個坑,
正常啟動Redis的命令:
$?redis-server
redis默認是以保護模式啟動的,只能本機連。本機以外可能連不上,或者只能連不能改。就不要那么麻煩再去整配置文件了,這里以學習測試為主,推薦這么啟動:
$?redis-server?--protected-mode?no
現在Redis可以用了,使用Set和Get進行存取的代碼:
package?main import?( ????"fmt" ????"github.com/gomodule/redigo/redis" ) func?main()?{ ????conn,?err?:=?redis.Dial("tcp",?"192.168.3.108:6379") ????if?err?!=?nil?{ ????????fmt.Println("連接Redis?ERROR:",?err) ????????return ????} ????fmt.Println("連接Redis?成功:",?conn) ????defer?conn.Close() ????resSet,?err?:=?conn.Do("Set",?"age",?18)??//?這里存字符串或數值到Redis里都是一樣的 ????if?err?!=?nil?{ ????????fmt.Println("Set?Redis?ERROR:",?err) ????????return ????} ????fmt.Println("Redis?Set:",?resSet) ????resGet,?err?:=?redis.Int(conn.Do("Get",?"age")) ????if?err?!=?nil?{ ????????fmt.Println("Get?Redis?ERROR:",?err) ????????return ????} ????fmt.Println("Redis?Get:",?resGet) } /*?執行結果 PS?H:\Go\src\go_dev\day9\redis\connect>?go?run?main.go 連接Redis?成功:?&{{0?0}?0?
批量Set
批量Set是redis原生就支持的功能,這里就是調用新的方法 MSet 和 MGet ,進行批量的操作:
package?main import?( ????"fmt" ????"github.com/gomodule/redigo/redis" ) func?main()?{ ????conn,?err?:=?redis.Dial("tcp",?"192.168.3.108:6379") ????if?err?!=?nil?{ ????????fmt.Println("連接Redis?ERROR:",?err) ????????return ????} ????fmt.Println("連接Redis?成功:",?conn) ????defer?conn.Close() ????resSet,?err?:=?conn.Do("MSet",?"k1",?"v1",?"k2",?"v2",?"k3",?"v3") ????if?err?!=?nil?{ ????????fmt.Println("Set?Redis?ERROR:",?err) ????????return ????} ????fmt.Println("Redis?MSet:",?resSet) ????resGet,?err?:=?redis.Strings(conn.Do("MGet",?"k1",?"k2",?"k3")) ????if?err?!=?nil?{ ????????fmt.Println("Get?Redis?ERROR:",?err) ????????return ????} ????fmt.Println("Redis?MGet:",?resGet) ????for?_,?v?:=?range?resGet?{ ????????fmt.Println(v) ????} }
Hash 操作
package?main import?( ????"fmt" ????"github.com/gomodule/redigo/redis" ) func?main()?{ ????conn,?err?:=?redis.Dial("tcp",?"192.168.3.108:6379") ????if?err?!=?nil?{ ????????fmt.Println("連接Redis?ERROR:",?err) ????????return ????} ????fmt.Println("連接Redis?成功:",?conn) ????defer?conn.Close() ????resSet,?err?:=?conn.Do("HSet",?"student",?"age",?18) ????if?err?!=?nil?{ ????????fmt.Println("Set?Redis?ERROR:",?err) ????????return ????} ????fmt.Println("Redis?Set:",?resSet) ????resGet,?err?:=?redis.Int(conn.Do("HGet",?"student",?"age")) ????if?err?!=?nil?{ ????????fmt.Println("Get?Redis?ERROR:",?err) ????????return ????} ????fmt.Println("Redis?Get:",?resGet) }
設置超時時間
超時時間是對key進行設置的。默認是沒有超時時間的,永不過期。這里的示例是在設置好key-value之后再對key設置超時時間:
package?main import?( ????"fmt" ????"github.com/gomodule/redigo/redis" ) func?main()?{ ????conn,?err?:=?redis.Dial("tcp",?"192.168.3.108:6379") ????if?err?!=?nil?{ ????????fmt.Println("連接Redis?ERROR:",?err) ????????return ????} ????fmt.Println("連接Redis?成功:",?conn) ????defer?conn.Close() ????//?假設之前已經設置了name這個key值,設置后name會在10秒后過期 ????resSet,?err?:=?conn.Do("expire",?"name",?10) ????if?err?!=?nil?{ ????????fmt.Println("Set?Redis?ERROR:",?err) ????????return ????} ????fmt.Println("Redis?Set:",?resSet) ????if?int(resSet.(int64))?==?1?{ ????????fmt.Println("設置超時時間成功") ????} }
在Set的時候,就可以加一個參數,其中就有超時時間。這里的例子是對已有的key重新設置一個超時時間。如果key存在,則返回1,如果key不存在,Redis會返回0。
隊列操作
package?main import?( ????"fmt" ????"github.com/gomodule/redigo/redis" ) func?main()?{ ????conn,?err?:=?redis.Dial("tcp",?"192.168.3.108:6379") ????if?err?!=?nil?{ ????????fmt.Println("連接Redis?ERROR:",?err) ????????return ????} ????fmt.Println("連接Redis?成功:",?conn) ????defer?conn.Close() ????resPush,?err?:=?conn.Do("lpush",?"names",?"Clark",?"Lois",?"Kara") ????if?err?!=?nil?{ ????????fmt.Println("Push?Redis?ERROR:",?err) ????????return ????} ????fmt.Println("Redis?MSet:",?resPush) ????for?{ ????????resPop,?err?:=?redis.String(conn.Do("lpop",?"names")) ????????if?err?!=?nil?{ ????????????if?err?==?redis.ErrNil?{ ????????????????fmt.Println("隊列已經取完:",?err) ????????????????break ????????????}?else?{ ????????????????fmt.Println("Pop?Redis?ERROR:",?err) ????????????????return ????????????} ????????} ????????fmt.Println("Redis?lpop:",?resPop) ????} }
使用連接池
使用場景:
對于一些大對象,或者初始化過程較長的可復用的對象,我們如果每次都new對象出來,那么意味著會耗費大量的時間。我們可以將這些對象緩存起來,當接口調用完畢后,不是銷毀對象,當下次使用的時候,直接從對象池中拿出來即可。
使用連接池操作Redis的步驟:
首先創建連接池,可以在init函數里創建。
每次要連接的時候,就從池里獲取一個連接。記得defer關閉連接。
獲取到連接后的操作方法就一樣了
代碼示例:
package?main import?( ????"fmt" ????"github.com/gomodule/redigo/redis" ) var?pool?redis.Pool func?init()?{ ????pool?=?redis.Pool?{ ????????MaxIdle:?8, ????????MaxActive:?0, ????????IdleTimeout:?300, ????????Dial:?func()?(redis.Conn,?error)?{ ????????????return?redis.Dial("tcp",?"192.168.3.108:6379") ????????}, ????} } func?main()?{ ????conn?:=?pool.Get()??//?從池里獲取一個連接使用 ????defer?conn.Close()??//?還是記得要關閉連接 ????resCon,?err?:=?conn.Do("Set",?"school",?"SHHS") ????if?err?!=?nil?{ ????????fmt.Println("Redis?Set?ERROR:",?err) ????????return ????} ????fmt.Println(resCon)??//?返回值是OK沒什么用 ????resGet,?err?:=?redis.String(conn.Do("Get",?"school")) ????if?err?!=?nil?{ ????????fmt.Println("Redis?Get?ERROR:",?err) ????????return ????} ????fmt.Println(resGet) ????pool.Close()??//?關閉pool }
編程語言
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。