Go语言的网络编程

一、Socket编程

很多游戏服务都是采用Socket来编写服务器端的,因为HTTP协议相对而言比较耗费性能。

对于底层网络应用开发者而言,几乎所有网络编程都是Socket,因为大部分底层网络的编程都离不开Socket编程。HTTP编程、Web开发、IM通信、视频流传输的底层都是Socket编程。

1、什么是Socket

Socket编程主要是面向OSI模型的第三层和第四层协议。

Socket类型只有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。

流式Socket是一种面向连接的Socket,针对面向连接的TCP服务应用。

数据报式Socket是一种无连接的Socket,对应无连接的UDP服务应用。

1)Socket如何通信

网络层的“IP地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。

利用三大要素(IP地址、协议、端口)就可以标识网络的进程了,网络中需要互相通信的进程,就可以利用这个标志在它们之间进行交互。

Socket有两种:TCP Socket和UDP Socket。

2)Go语言支持的IP类型

在Go语言的net包中定义了很多类型、函数和方法用于网络编程,其中IP的定义如下:

type IP []byte

代码如下:

package main

import (
	"fmt"
	"net"
	"os"
)

func main() {
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, "用法: %s IP地址
", os.Args[0])
		os.Exit(1)
	}
	name := os.Args[1]
	addr := net.ParseIP(name)
	if addr == nil {
		fmt.Println("无效的IP地址")
	} else {
		fmt.Println("IP地址是", addr.String())
	}
	os.Exit(0)
}

运行结果如下:

用法: /var/folders/3x/txs8lfzs6s13njkkrvjmnz2c0000gn/T/go-build3894546141/b001/exe/main IP地址
exit status 1

2、Dial()函数

Dial()函数的原型如下:

func Dial(net, addr string)(Conn, error)

其中net参数是网络协议的名字,addr参数是IP地址或域名,而端口号以“:”的形式跟随在地址或域名的后面,端口号可选。如果连接成功,返回连接对象,否则返回error。

在成功建立连接后,就可以进行数据的发送和接收。在发送数据时,使用conn的Write()成员方法,在接收数据时使用Read()方法。

3、ICMP示例

代码如下:

package main

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"os"
)

func checkSum(msg []byte) uint16 {
	sum := 0
	for n := 1; n < len(msg)-1; n += 2 {
		sum += int(msg[n])*256 + int(msg[n+1])
	}
	sum = (sum >> 16) + (sum & 0xffff)
	sum += (sum >> 16)
	var answer uint16 = uint16(^sum)
	return answer
}

func checkError(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "错误:%s", err.Error())
		os.Exit(1)
	}
}

func readFully(conn net.Conn) ([]byte, error) {
	defer conn.Close()
	result := bytes.NewBuffer(nil)
	var buf [512]byte
	for {
		n, err := conn.Read(buf[0:])
		result.Write(buf[0:n])
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
	}
	return result.Bytes(), nil
}

func main() {
	if len(os.Args) != 2 {
		fmt.Println("用法:", os.Args[0], "hostname")
		os.Exit(1)
	}
	service := os.Args[1]

	conn, err := net.Dial("ip4:icmp", service)
	checkError(err)

	var msg [512]byte
	msg[0] = 8
	msg[1] = 0
	msg[2] = 0
	msg[3] = 0
	msg[4] = 0
	msg[5] = 13
	msg[6] = 0
	msg[7] = 37
	len := 8
	check := checkSum(msg[0:len])
	msg[2] = byte(check >> 8)
	msg[3] = byte(check & 255)

	_, err = conn.Write(msg[0:len])
	checkError(err)

	_, err = conn.Read(msg[0:])
	checkError(err)

	fmt.Println("取得响应")
	if msg[5] == 13 {
		fmt.Println("Identifier匹配")
	}
	if msg[7] == 37 {
		fmt.Println("Sequence匹配")
	}
	os.Exit(0)
}

运行结果如下:

用法: /var/folders/3x/txs8lfzs6s13njkkrvjmnz2c0000gn/T/go-build3151093604/b001/exe/main hostname
exit status 1

4、TCP Socket

当知道如何通过网络端口访问一个服务时能够做什么呢?作为客户端来说,可以通过向远端某台机器的某个网络端口发送一个请求,然后得到在机器的此端口上监听的服务反馈的信息。作为服务器端,需要把服务绑定到某个指定端口,并且在此端口上监听,当有客户端来访问时能够读取信息并且写入反馈信息。

在Go语言的net包中有一个类型TCPConn,这个类型可以用来作为客户端和服务端交互的通道,它有两个主要的函数:

func (c *TCPConn) Write(b []byte) (n int, err os.Error)

func (c *TCPConn) Read(b []byte) (n int, err os.Error)

TCPConn可以用在客户端和服务器端读写数据。

需要知道一个TCPAddr类型,它表示一个TCP的地址信息,它的定义如下:

type TCPAddr struct{

        IP IP

        Port int

Zone string

}

在Go语言中通过ResolveTCPAddr获取一个TCPAddr:

func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)

net参数是TCP4、TCP6、TCP中的任意一个,分别表示TCP(IPv4-only),TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个)。

addr表示域名或者IP地址。

1)TCP客户端

建立连接的函数定义如下:

func DialTCP(net string, laddr, raddr *TCPAddr)(c *TCPConn, err os.Error)

net参数是TCP4、TCP6、TCP中的任意一个。

laddr表示本机地址,一般设置为nil。

raddr表示远程服务地址

代码如下:

package main

import (
	"fmt"
	"io/ioutil"
	"net"
	"os"
)

func checkError(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "错误: %s", err.Error())
		os.Exit(1)
	}
}
func main() {
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, "用法: %s host:port", os.Args[0])
		os.Exit(1)
	}
	service := os.Args[1]
	tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
	checkError(err)
	conn, err := net.DialTCP("tcp", nil, tcpAddr)
	checkError(err)
	_, err = conn.Write([]byte("HEAD / HTTP/1.0

"))
	checkError(err)
	result, err := ioutil.ReadAll(conn)
	checkError(err)
	fmt.Println(string(result))
	os.Exit(0)
}

运行结果如下:

用法: /var/folders/3x/txs8lfzs6s13njkkrvjmnz2c0000gn/T/go-build3298507250/b001/exe/main host:portexit status 1

2)TCP服务器端

函数定义如下:

func ListenTCP(net string, laddr *TCPAddr)(1 *TCPListener, err os.Error)
func (1 *TCPListener) Accept() (c Conn, err os.Error)

代码如下:

package main

import (
	"fmt"
	"log"
	"net"
	"time"
)

func echo(conn *net.TCPConn) {
	tick := time.Tick(5 * time.Second)
	for now := range tick {
		n, err := conn.Write([]byte(now.String()))
		if err != nil {
			log.Println(err)
			conn.Close()
			return
		}
		fmt.Printf("send %d bytes to %s
", n, conn.RemoteAddr())
	}
}
func main() {
	address := net.TCPAddr{
		IP:   net.ParseIP("127.0.0.1"),
		Port: 8000,
	}
	listener, err := net.ListenTCP("tcp4", &address)
	if err != nil {
		log.Fatal(err)
	}
	for {
		conn, err := listener.AcceptTCP()
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println("远程地址:", conn.RemoteAddr())
		go echo(conn)
	}
}

运行结果如下:(出不来结果)

3)控制TCP连接

func DialTimeout(net, addr string, timeout time.Duration) (Conn, error)

设置建立连接的超时时间,客户端和服务器端都适用,当超过设置时间后,连接自动关闭:

func (c *TCPConn) SetReadDeadline(t time.Time) error

func (c *TCPConn) SetWriteDeadline(t time.Time) error

用来设置写入/读取一个连接的超时时间。当超过设置时间后,连接自动关闭:

func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error

5、UDP Socket

func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error)

func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error)

func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error)

func (c *UDPConn) ReadFormUDP(b []byte) (n int, addr *UDPAddr, err os.Error)

func (c *UDPConn) WriteToUDP (b []byte, addr *UDPAddr) (n int, err os.Error)

一个UDP的客户端,代码如下:

package main

import (
	"fmt"
	"net"
	"os"
)

func checkError(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "错误", err.Error())
		os.Exit(1)
	}
}

func main() {
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, "用法:%s host:port", os.Args[0])
		os.Exit(1)
	}
	service := os.Args[1]
	udpAddr, err := net.ResolveUDPAddr("udp4", service)
	checkError(err)
	conn, err := net.DialUDP("udp", nil, udpAddr)
	checkError(err)
	_, err = conn.Write([]byte("anything"))
	checkError(err)
	var buf [512]byte
	n, err := conn.Read(buf[0:])
	checkError(err)
	fmt.Println(string(buf[0:n]))
	os.Exit(0)
}

运行结果 如下:

用法:C:UsersA-XIAO~1AppDataLocalTempgo-build3311049071001exemain.exe host:portexit status 1

UDP服务端,代码如下:

package main

import (
	"fmt"
	"net"
	"os"
	"time"
)

func handleClient(conn *net.UDPConn) {
	var buf [512]byte
	_, addr, err := conn.ReadFromUDP(buf[0:])
	if err != nil {
		return
	}
	daytime := time.Now().String()
	conn.WriteToUDP([]byte(daytime), addr)
}

func checkError(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "错误", err.Error())
		os.Exit(1)
	}
}

func main() {
	service := ":1200"
	udpAddr, err := net.ResolveUDPAddr("udp4", service)
	checkError(err)
	conn, err := net.ListenUDP("udp", udpAddr)
	checkError(err)
	for {
		handleClient(conn)
	}
}

运行结果 如下:

错误%!(EXTRA string=listen udp :1200: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.)exit status 1

二、HTTP编程

Go语言标准库内建提供了net/http包,涵盖了HTTP客户端和服务器端的具体实现。

1、HTTP客户端

1)基本用法

func (c *Client) Get(url string) (r *Response, err error)

func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err error)

func (c *Client) PostForm(url string, data url.Values) (r *Response, err error)

func (c *Client) Head(url string) (r *Response, err error)

func (c *Client) Do(req *Request) (resp *Response, err error)

2)封装

自定义http.Client

自定义http.Transport

2、HTTP服务器端

处理HTTP请求

func ListenAndServe(addr string, handler Handler) error

第一个参数addr即监听地址;第二个参数表示服务器端处理程序 ,通常为空。

处理HTTPS请求

func ListenAndServeTLS(addr string, certFile string, keyFile string, hander Handler) error

cerFile对应SSL证书文件存放路径,keyFile对应证书私钥文件路径。

三、RPC编程

RPC就是想实现函数调用模式的网络化。

运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下 10步。

(1)调用客户端句柄,执行传送参数。

(2)调用本地系统内核发送网络消息。

(3)消息传送到远程主机。

(4)服务器句柄得到消息并取得参数。

(5)执行远程过程。

(6)执行的过程将结果返回服务器句柄。

(7)服务器句柄返回结果,调用远程系统内核。

(8)消息传回本地主机。

(9)客户句柄由内核接收消息。

(10)客户接收句柄返回的数据。

1、Go RPC

Go RPC的函数只有符合下面的条件才能被 远程访问,不然会被 忽略,详细的要求如下:

函数必须是导出的(首字母大写)。

必须有两个导出类型的参数。

第一个参数是接收的参数,第二个参数是返回给客户端的参数,第二个参数是必须 是指针类型。

函数还要有一个返回值error。

func (t *T) MethodName(argType T1, replyType *T2) error

2、HTTP RPC

代码如下:

package main

import (
	"error"
	"fmt"
	"net/http"
	"net/rpc"
)

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}

type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
	*reply = args.A * args.B
	return nil
}

func (t *Arith) Divide(args *Args, quo *Quotient) error {
	if args.B == 0 {
		return error.New("除数不能为零。")
	}
	quo.Quo = args.A / args.A
	quo.Rem = args.A % args.B
	return nil
}

func main() {
	arith := new(Arith)
	rpc.Register(arith)
	rpc.HandleHTTP()

	err := http.ListenAndServe(":1234", nil)
	if err != nil {
		fmt.Println(err.Error())
	}
}

运行结果如下:

CreateFile main1.go: The system cannot find the file specified.

代码如下:

package main

import (
	"fmt"
	"log"
	"net/rpc"
	"os"
)

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}

func main() {
	if len(os.Args) != 2 {
		fmt.Println("用法:", os.Args[0], "server")
		os.Exit(1)
	}
	serverAddress := os.Args[1]

	client, err := rpc.DialHTTP("tcp", serverAddress+":1234")
	if err != nil {
		log.Fatal("Dial错误:", err)
	}

	args := Args{17, 8}
	var reply int
	err = client.Call("Arith.Multiply", args, &reply)
	if err != nil {
		log.Fatal("arith错误:", err)
	}
	fmt.Printf("Arith:%d*%d=%d
", args.A, Args.B, reply)

	var quot Quotient
	err = client.Call("Arith.Divide", args, &quot)
	if err != nil {
		log.Fatal("arith错误:", err)
	}
	fmt.Printf("Arith:%d/%d=%d 余 %d
", args.A, Args.B, quot.Quo, quot.Rem)
}

运行结果如下:

用法: C:UsersA-XIAO~1AppDataLocalTempgo-build1502604559001exemain.exe server
exit status 1

书上说:把上面的服务器端和客户端的代码分别编译,先把服务器端开启,再开户客户端,输入代码,就会输出如下信息:

$ ./http_c localhost

Arith:17*8=136

Arith:17/8=2 余 1

3、TCP RPC

代码如下:

package main

import (
	"errors"
	"fmt"
	"net"
	"net/rpc"
	"os"
)

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}

type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
	*reply = args.A * args.B
	return nil
}

func (t *Arith) Divide(args *Args, quo *Quotient) error {
	if args.B == 0 {
		return errors.New("除数不能为零")
	}
	quo.Quo = args.A / args.B
	quo.Rem = args.A % args.B
	return nil
}

func main() {
	arith := new(Arith)
	rpc.Register(arith)
	tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
	checkError(err)
	listener, err := net.ListenTCP("tcp", tcpAddr)
	checkError(err)
	for {
		conn, err := listener.Accept()
		if err != nil {
			continue
		}
		rpc.ServeConn(conn)
	}
}
func checkError(err error) {
	if err != nil {
		fmt.Println("错误:", err.Error())
		os.Exit(1)
	}
}

运行结果如下:

代码如下:

package main

import (
	"fmt"
	"log"
	"net/rpc"
	"os"
)

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}

func main() {
	if len(os.Args) != 2 {
		fmt.Println("用法:", os.Args[0], "server:port")
		os.Exit(1)
	}
	service := os.Args[1]
	client, err := rpc.Dial("tcp", service)
	if err != nil {
		log.Fatal("Dial错误:", err)
	}
	args := Args{17, 8}
	var reply int
	err = client.Call("Arith.Multiply", args, &reply)
	if err != nil {
		log.Fatal("arith错误:", err)
	}
	fmt.Printf("Arith:%d*%d=%d\n", args.A, args.B, reply)

	var quot Quotient
	err = client.Call("Arith.Divide", args, &quot)
	if err != nil {
		log.Fatal("arith错误:", err)
	}
	fmt.Printf("Arith:%d/%d=%d 余 %d\n", args.A, args.B, quot.Quo, quot.Rem)
}

运行结果如下:

用法: C:\Users\A-XIAO~1\AppData\Local\Temp\go-build3660339729\b001\exe\main.exe server:port
exit status 1

4、JSON RPC

服务器端代码如下:

package main

import (
	"errors"
	"fmt"
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"
	"os"
)

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}

type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
	*reply = args.A * args.B
	return nil
}

func (t *Arith) Divide(args *Args, quo *Quotient) error {
	if args.B == 0 {
		return errors.New("除数不能为零")
	}
	quo.Quo = args.A / args.B
	quo.Rem = args.A % args.B
	return nil
}

func main() {
	arith := new(Arith)
	rpc.Register(arith)
	tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
	checkError(err)

	listener, err := net.ListenTCP("tcp", tcpAddr)
	checkError(err)

	for {
		conn, err := listener.Accept()
		if err != nil {
			continue
		}
		jsonrpc.ServeConn(conn)
	}
}

func checkError(err error) {
	if err != nil {
		fmt.Println("错误:", err.Error())
		os.Exit(1)
	}
}

运行结果如下:

客户端的代码如下:

package main

import (
	"fmt"
	"log"
	"net/rpc/jsonrpc"
	"os"
)

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}

func main() {
	if len(os.Args) != 2 {
		fmt.Println("用法:", os.Args[0], "server:port")
		log.Fatal(1)
	}
	service := os.Args[1]
	client, err := jsonrpc.Dial("tcp", service)
	if err != nil {
		log.Fatal("Dial错误:", err)
	}
	args := Args{17, 8}
	var reply int
	err = client.Call("Arith.Multiply", args, &reply)
	if err != nil {
		log.Fatal("arith错误:", err)
	}
	fmt.Printf("Arith:%d*%d=%d\n", args.A, args.B, reply)
	var quot Quotient
	err = client.Call("Arith.Divide", args, &quot)
	if err != nil {
		log.Fatal("arith错误:", err)
	}
	fmt.Printf("Arith:%d/%d=%d 余 %d\n", args.A, args.B, quot.Quo, quot.Rem)
}

运行结果如下:

用法: /var/folders/3x/txs8lfzs6s13njkkrvjmnz2c0000gn/T/go-build1075837643/b001/exe/main server:port
2022/03/02 20:11:23 1
exit status 1

5、RPC接口

type ClientCodec interface{

        WriteRequest(*Request, interface{}) error

        ReadResponseHeader(*Response) error

        ReadResponseBody(interface{}) error

        Close() error

}

type ServerCodec interface{

        ReadRequestHeader(*Request) error

        ReadRequestBody(interface{}) error

        WriteResponse(*Response, interface{}) error

        Close() error

}

四、数据库

http://go-database.sql.org

1、database/sql接口

1)sql.Register

2)driver.Driver

3) driver.Conn

4) driver.Stmt

5) driver.Tx

6) driver.Execer

7) driver.Result

8) driver.Rows

9) driver.RowsAffected

10) driver.Value

11) driver.ValueConverter

12) driver.Valuer

2、使用MySQL数据库

https://github.com/go-sql-driver/mysql 支持database/sql

https://github.com/ziutek/mymysql 支持database/sql

https://github.com/Philio/GoMySQL 不支持database/sql

CREATE TABLE 'userinfor' (

        'uid' INT(10) NOT NULL AUTO_INCREMENT,

        'username' VARCHAR(64) NULL DEFAULT NULL,

        'departname' VARCHAR(64) NULL DEFAULT NULL,

        ’created‘ DATE NULL DEFAULT NULL,

        PRIMARY KEY ('uid')

);

CREATE TABLE 'userdetail' (

        'uid' INT(10) NOT NULL DEFAULT '0',

        'intro' TEXT NULL,

        'profile' TEXT NULL,

        PRIMARY KEY ('uid')

)

代码如下:

package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
)



func main() {
	db,err:=sql.Open("mysql","zuolan:zuolan@/test?charset=utf8")
	checkErr(err)

    //插入数据
	stm,err:=db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
	checkErr(err)

	res,err:=stmt.Exec("张三","研发部门","2017-09-09")
	checkErr(err)

	id,err:=res.LastInsertId()
	checkErr(err)

	fmt.Println(id)
    //更数数据
	stmt,err=db.Prepare("update userinfo set username=? where uid=?")
	checkErr(err)

	res,err=stmt.Exec("zuolanupdate",id)
	checkErr(err)

	affect,err:=res.RowsAffected()
	checkErr(err)

	fmt.Println(affect)

    //查询数据
	rows,err:=db.Query("SELECT*FROM userinfo")
	checkErr(err)

	for rows.Next(){
		var uid int
		var username string
		var department string
		var created string
		err=rows.Scan(&uid,&usernae,&department,&created)
		checkErr(err)
		fmt.Println(uid)
		fmt.Println(username)
		fmt.Println(department)
		fmt.Println(created)
	}

    //删除数据
	stmt,err=db.Prepare("delete from userinfo where uid=?")
	checkErr(err)

	res,err=stmt.Exec(id)
	checkErr(err)

	affect,err=res.RowsAffected()
	checkErr(err)

	fmt.Println(affect)

	db.Close()
}

func checkErr(err error){
	if err!=nil{
		panic(err)
	}
}

运行结果如下:

2022/03/05 20:03:02 open test/test2/: no such file or directory
exit status 1

sql.Open()函数用来打开一个注册过的数据库驱动,go-sql-driver中注册了MySQL这个数据库驱动,第二个参数是DNS(Data Source Name),它是go-sql-driver定义的一些数据库连接和配置信息。它支持如下格式:

user@unix(/path/to/socket)/dbname?charset=utf8

user:password@tcp(localhost:5555)/dbname?charset=utf8

user:password@/dbname

user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname

db.Prepare()函数用来返回准备要执行的SQL操作,然后返回准备完毕的执行状态。

db.Query()函数用来直接执行SQL返回Rows结果。

stmt.Exec()函数用来执行stmt准备好的SQL语句。

3、使用SQLite数据库

https://github.com/mattn/go-sqlite3        支持database/sql接口,基于cgo

https://github.com/feyeleanor/gosqlite3        不支持database/sql接口,基于cgo

https://github.com/phf/go-sqlite3        不支持database/sql接口,基于cgo

示例的数据库表结构相应的建表SQL如下:

CREATE TABLE 'userinfo' (

        'uid' INTEGER PRIMARY KEY AUTOINCREMENT,

        'username' VARCHAR(64) NULL,

        'departname' VARCHAR(64) NULL,

        ’created‘ DATE NULL,

        PRIMARY KEY ('uid')

);

CREATE TABLE 'userdeatail' (

        'uid' INT(10) NULL,

        'intro' TEXT NULL,

        'profile' TEXT NULL,

        PRIMARY KEY ('uid')

)

代码如下:

package main

import (
	"database/sql"
	"fmt"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

func main() {
	db, err := sql.Open("sqlite3", "./demo.db")
	checkErr(err)

	//插入数据
	stmt, err := db.Prepare("INSERT INTO userinfo(username,departname,created) values(?,?,?)")
	checkErr(err)

	res, err := stmt.Exec("张三", "研发部门", "2017-09-09")
	checkErr(err)

	id, err := res.LastInsertId()
	checkErr(err)

	fmt.Println(id)
	//更新数据
	stmt,err = db.Prepare("update userinfo set username=? where uid=?")
	checkErr(err)

	res, err = stmt.Exec("zuolanupdate", id)
	checkErr(err)

	affect, err := res.RowsAffected()
	checkErr(err)

	fmt.Println(affect)

	//查询数据
	rows, err := db.Query("SELECT*FROM userinfo")
	checkErr(err)

	for rows.Next() {
		var uid int
		var username string
		var department string
		var created time.Time
		err = rows.Scan(&uid, &usernae, &department, &created)
		checkErr(err)
		fmt.Println(uid)
		fmt.Println(username)
		fmt.Println(department)
		fmt.Println(created)
	}

	//删除数据
	stmt, err = db.Prepare("delete from userinfo where uid=?")
	checkErr(err)

	res, err = stmt.Exec(id)
	checkErr(err)

	affect, err = res.RowsAffected()
	checkErr(err)

	fmt.Println(affect)

	db.Close()
}

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}

运行结果如下:

main.go:8:2: cannot find package "github.com/mattn/go-sqlite3" in any of:
	/usr/local/go/src/github.com/mattn/go-sqlite3 (from $GOROOT)
	/Users/douxiaobo/go/src/github.com/mattn/go-sqlite3 (from $GOPATH)

4、使用PostgreSQL数据库存

github.com/lib/pq

github.com/jbarham/gopgsqldriver

github.com/lxn/go-pgsql

CREATE TABLE userinfo 

(

        uid serial NOT NULL,

        username character varying(100) NOT NULL,

        departname character varying(500) NOT NULL,

        Created date,

        CONSTRAINT userinfo_pkey PRIMARY KEY (uid)

)

WITH (OIDS=FALSE);

CREATE TABLE 'userdeatail'

(

        uid integer,

        intro character varying(180),

        profile character varying(180)

)

WITH (OIDS=FALSE);

代码如下:

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/lib/pq"
)

func main() {
	db, err := sql.Open("postgres", "user=zuolan password=zuolan dbname=test sslmode=disable")
	checkErr(err)

	//插入数据
	stmt, err := db.Prepare("INSERT INTO userinfo(username,departname,created) values($1,$2,$3) RETURNING uid")
	checkErr(err)

	res, err := stmt.Exec("张三", "研发部门", "2017-09-09")
	checkErr(err)

	//pg不支持这个函数,因为它没有类似MySQL的自增ID
	//id, err := res.LastInsertId()
	//checkErr(err)
	//fmt.Println(id)

	var lastInsertId int
	err = db.QueryRow("INSERT INTO userinfo(username,departname,created) values($1,$2,$3) returning uid;", "zuolan", "研发部门", "2017-09-09").Scan(&lastInsertId)
	checkErr(err)
	fmt.println("最后插入id =", lastInsertId)

	//更新数据
	stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2")
	checkErr(err)

	res, err = stmt.Exec("zuolanupdate", id)
	checkErr(err)

	affect, err := res.RowsAffected()
	checkErr(err)

	fmt.Println(affect)

	//查询数据
	rows, err := db.Query("SELECT * FROM userinfo")
	checkErr(err)

	for rows.Next() {
		var uid int
		var username string
		var department string
		var created string
		err = rows.Scan(&uid, &username, &department, &created)
		checkErr(err)
		fmt.Println(uid)
		fmt.Println(username)
		fmt.Println(department)
		fmt.Println(created)
	}

	//删除数据
	stmt, err = db.Prepare("delete from userinfo where uid=$1")
	checkErr(err)

	res, err = stmt.Exec(1)
	checkErr(err)

	affect, err = res.RowsAffected()
	checkErr(err)

	fmt.Println(affect)

	db.Close()
}

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}

运行结果如下:

main.go:7:2: cannot find package "github.com/lib/pq" in any of:
	/usr/local/go/src/github.com/lib/pq (from $GOROOT)
	/Users/douxiaobo/go/src/github.com/lib/pq (from $GOPATH)

5、NoSQL数据库操作

1)Redis

Go语言支持Redis的驱动如下:

https://github.com/garyburd/redigo

https://github.com/go-redis/redis

https://github.com/hoisie/redis

https://github.com/alphazero/Go-Redis

https://github.com/simonz05/godis

2)MongoDB

Go语言支持MongoDB最好的驱动就是mgo(http://labix.org/mgo)。安装mgo的命令为:

go get gopkg.in/mgo.v2

五、Go语言使用Cookie

1、设置Cookie

Go语言中通过net/http包中的SetCookie来设置Cookie:

http.SetCookie(w ResponseWriter, Cookie *Cookie)

2、读取Cookie

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值