HTTP网络编程

98 阅读9分钟

Socket编程

package main

import (
    "fmt"
    "net"
)

func main() {
    //使用net.Dial()函数建立一个TCP连接,连接到example.com的80端口
    conn, err := net.Dial("tcp", "example.com:80")
    if err != nil {
        fmt.Println("Error connecting:", err)
        return
    }

    //通过连接发送一个HTTP GET请求,请求路径是"/",表示请求网站根路径
    //请求使用HTTP 1.0版本,后面添加一个空行表示请求头结束
    fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")

    // 从连接中读取服务器的响应
    buf := make([]byte, 1024)
    //在一个循环里不断读取从连接返回的数据,直到读取到0字节或发生错误
    for {
        //将读取的数据存入buf字节数组,然后转换为字符串打印输出
        n, err := conn.Read(buf)
        if n == 0 || err != nil {
            break
        }
        //读取的数据就是服务器返回的HTTP响应,打印输出可以看到响应的内容
        fmt.Print(string(buf[0:n]))
    }
}
  • net.Listen:用于监听一个网络地址,并返回一个net.Listener对象,可以用来接受连接请求。
  • net.Accept:用于接受一个连接请求,并返回一个net.Conn对象,可以用来进行数据通信。
  • net.DialTimeout:类似于net.Dial,但是可以设置一个连接超时时间。
  • net.ResolveTCPAddr:用于解析TCP网络地址,返回一个TCP地址对象。

TCP编程

服务器

package main

import (
	"fmt"
	"net"
)

func main() {
        //调用net.Listen()在端口8080监听TCP连接
	ln, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println("Error listening:", err.Error())
		return
	}
	defer ln.Close()

	fmt.Println("Server listening on port 8080")
        //在循环中调用ln.Accept()等待并接受来自客户端的连接
	for {
		conn, err := ln.Accept()
		if err != nil {
			fmt.Println("Error accepting connection:", err.Error())
			continue
		}
                //对每个连接启动一个goroutine来handleRequest
		go handleRequest(conn)
	}
}

func handleRequest(conn net.Conn) {
	buf := make([]byte, 1024)
        //在handleRequest里,读取客户端发送的数据保存到buf,并打印输出
	_, err := conn.Read(buf)
	if err != nil {
		fmt.Println("Error reading:", err.Error())
	}
	fmt.Println("Received message:", string(buf))
        //向客户端回复一条信息"Hello from server!"
	conn.Write([]byte("Hello from server!"))
	conn.Close()
}
  • net.Listen() 监听指定端口的连接请求
  • ln.Accept() 接受客户端连接,返回连接对象
  • conn.Read() 从连接读取数据
  • conn.Write() 通过连接回复数据
  • go handleRequest(conn) 并发处理连接请求

客户端

package main

import (
	"fmt"
	"net"
)

func main() {
        //使用net.Dial()连接本地8080端口的TCP服务器
	conn, err := net.Dial("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Println("Error connecting:", err.Error())
		return
	}
	defer conn.Close()

	message := "Hello from client!"
        //通过conn.Write()发送一条消息"Hello from client!"给服务器
	_, err = conn.Write([]byte(message))
	if err != nil {
		fmt.Println("Error sending message:", err.Error())
		return
	}

	buf := make([]byte, 1024)
        //使用conn.Read()读取来自服务器的回复数据
	_, err = conn.Read(buf)
	if err != nil {
		fmt.Println("Error reading:", err.Error())
		return
	}
	fmt.Println("Received message:", string(buf))
}
  • net.Dial() 建立连接
  • conn.Write() 发送数据
  • conn.Read() 接收数据

UDP编程

服务器

package main

import (
	"fmt"
	"net"
)

var (
	serverAddr = "localhost:8080"
)

func main() {
	//使用net.ResolveUDPAddr解析UDP服务器地址
	addr, err := net.ResolveUDPAddr("udp", serverAddr)
	if err != nil {
		fmt.Println(err)
		return
	}

	//使用net.ListenUDP监听指定地址的UDP连接
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()

	fmt.Println("Server started on", serverAddr)

	for {
		// 接收请求
		buf := make([]byte, 1024)
                //在循环中通过conn.ReadFromUDP读取来自客户端的数据
		n, clientAddr, err := conn.ReadFromUDP(buf)
		if err != nil {
			fmt.Println(err)
			continue
		}
                //对收到的数据打印输出长度、客户端地址等信息
		fmt.Printf("Received %d bytes from %s: %s\n", n, clientAddr, string(buf[:n]))

		//使用conn.WriteToUDP将收到的数据原封不动返回给客户端
		_, err = conn.WriteToUDP(buf[:n], clientAddr)
		if err != nil {
			fmt.Println(err)
			continue
		}
                //打印输出回复的数据长度和客户端地址
		fmt.Printf("Sent %d bytes to %s\n", n, clientAddr)
	}
}
  • net.ResolveUDPAddr解析UDP地址
  • net.ListenUDP监听UDP连接
  • conn.ReadFromUDP读取客户端数据
  • conn.WriteToUDP回复客户端数据

客户端

package main

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

var (
	serverAddr = "localhost:8080"
)

func main() {
	//解析目标服务器地址
	addr, err := net.ResolveUDPAddr("udp", serverAddr)
	if err != nil {
		fmt.Println(err)
		return
	}

	//使用net.DialUDP建立UDP连接
	conn, err := net.DialUDP("udp", nil, addr)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()

	msg := []byte("Hello, server!")
        //通过conn.Write发送数据给服务器
	_, err = conn.Write(msg)
	if err != nil {
		fmt.Println(err)
		return
	}

	buf := make([]byte, 1024)
        //使用conn.SetReadDeadline设置读取超时
	conn.SetReadDeadline(time.Now().Add(time.Second * 5))
        //通过conn.ReadFromUDP读取服务器响应
	n, _, err := conn.ReadFromUDP(buf)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("Received:", string(buf[:n]))
}

  • net.ResolveUDPAddr解析服务器地址
  • net.DialUDP建立UDP连接
  • conn.Write发送数据
  • conn.SetReadDeadline设置超时
  • conn.ReadFromUDP读取响应

URL编程

解析URL

package main

import (
	"fmt"
	"net/url"
)

func main() {
	//使用url.Parse()解析一个URL字符串,得到一个url.URL类型的结构体实例
	u, err := url.Parse("https://www.example.com/search?q=golang#top")
	if err != nil {
		fmt.Println(err)
		return
	}

	//印url.URL结构体的各个字段
        //Scheme: 协议类型,http或https
	fmt.Println("Scheme:", u.Scheme)
        //Host: 主机名,如www.example.com
	fmt.Println("Host:", u.Host)
        //Path: 路径,如/search
	fmt.Println("Path:", u.Path)
        //Query(): 查询参数,返回值类型是url.Values,需要调用才会解析
	fmt.Println("Query:", u.Query())
        //ragment: 片段标识符
	fmt.Println("Fragment:", u.Fragment)
}
  • url.Parse():解析URL字符串
  • url.URL:表示一个解析后的URL结构

构建URL

package main

import (
	"fmt"
	"net/url"
)

func main() {
	//创建一个新的url.URL空结构体指针
	u := &url.URL{
        //设置Scheme、Host、Path字段为指定值
		Scheme: "https",
		Host:   "www.example.com",
		Path:   "/search",
	}
        //调用u.Query()获取url.Values类型的查询参数map
	q := u.Query()
        //使用q.Set()设置查询参数key和value
	q.Set("q", "golang")
        //使用q.Encode()编码查询参数,并赋值到u.RawQuery
	u.RawQuery = q.Encode()

	//使用u.String()输出构建好的URL字符串
	fmt.Println(u.String())
}
  • 创建url.URL空结构体
  • 设置Scheme、Host、Path
  • 获取url.Values类型的查询参数map
  • 使用Set()设置参数
  • 使用Encode()编码参数到RawQuery
  • 生成URL字符串

解析查询参数

package main

import (
	"fmt"
	"net/url"
)

func main() {
	// 使用url.ParseQuery()解析查询字符串,返回url.Values类型map
	values, err := url.ParseQuery("q=golang&sort=recent&limit=10")
	if err != nil {
		fmt.Println(err)
		return
	}

	//url.Values实际上是一个map[string][]string类型
        //通过Get()方法可以获取某个key对应的第一个value
	q := values.Get("q")
	sort := values.Get("sort")
	limit := values.Get("limit")

	fmt.Println("q:", q)
	fmt.Println("sort:", sort)
	fmt.Println("limit:", limit)
}
  • 调用url.ParseQuery()解析
  • 获取url.Values类型map
  • 使用Get()获取某个参数值

编码和解码

package main

import (
	"fmt"
	"net/url"
)

func main() {
	//使用url.QueryEscape()对字符串进行URL编码。它会将字符串中不安全的字符转换为%XX格式的编码
	encoded := url.QueryEscape("https://www.example.com/search?q=golang&sort=recent")
	fmt.Println("Encoded:", encoded)

	//使用url.QueryUnescape()对编码后的字符串进行解码
	decoded, err := url.QueryUnescape(encoded)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("Decoded:", decoded)
}

Http编程

简单例子

package main

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

func main() {
        //使用http.Get()发送GET请求到指定URL
	resp, err := http.Get("http://www.example.com/")
	if err != nil {
		fmt.Println(err)
		return
	}
        //使用defer保证响应体在函数结束时关闭
	defer resp.Body.Close()
        
        //使用ioutil.ReadAll()读取响应体中的所有数据
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
        //将响应体转换为string并打印输出
	fmt.Println(string(body))
}
  • http.Get(): 发送HTTP GET请求
  • http.Response: HTTP响应对象
  • resp.Body: 响应体,用于读取数据
  • ioutil.ReadAll(): 读取所有响应数据
  • defer: 函数结束时执行关闭操作

客户端

package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
        //构造请求URL和需要POST的数据
	url := "http://www.example.com/login"
	data := []byte(`{"username": "admin", "password": "password"}`)
        
        //使用http.NewRequest()创建一个http.Request对象,指定请求方法为POST,请求URL和body
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
	if err != nil {
		fmt.Println(err)
		return
	}
        
        //使用req.Header.Set()设置请求头的Content-Type为application/json
	req.Header.Set("Content-Type", "application/json")
        
        //使用http.Client发起请求,获取响应
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
		return
	}
        
        //使用defer确保响应体关闭
	defer resp.Body.Close()
        
        //读取响应体中的数据并打印输出
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(string(body))
}
  • http.NewRequest(): 构造请求
  • http.Request: 请求对象
  • http.Client: 发起请求的客户端
  • resp.Body: 响应体,需要关闭
  • ioutil.ReadAll(): 读取响应数据

服务器

package main

import (
	"fmt"
	"net/http"
)

func main() {
        //http.HandleFunc注册处理根路径"/"的函数,接收请求和写入响应
        //处理函数接收http.ResponseWriter和*http.Request参数,用于读取请求和写入响应
        //处理函数通过http.ResponseWriter设置header和写入body来构造响
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, World!")
	})
        
        //http.ListenAndServe启动服务器, nil表示使用默认的mux路由器
	http.ListenAndServe(":8080", nil)
}
  • http.HandleFunc 函数用于注册处理函数,接收两个参数:路径pattern和处理函数
  • http.ResponseWriter 用于构造HTTP响应,可以设置header和写入body
  • http.Request 请求对象,包含请求方法、URL、header等信息
  • http.ListenAndServe 启动HTTP服务器监听指定端口

Rpc编程

net/rpc

服务端

package main

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

//实现rpc服务必须定义结构体类型
//结构体的方法就是rpc暴露的方法

// 算数运算结构体
// 结构体的方法就是rpc暴露的方法
type Arith struct {
}

// 算数运算请求结构体
//client调用时需要传入该结构体实例
type ArithRequest struct {
	A int
	B int
}

// 算数运算响应结构体
//server调用后需要返回该结构体实例
type ArithResponse struct {
	Pro int // 乘积
	Quo int // 商
	Rem int // 余数
}

//rpc服务的方法必须满足func(t *T, args *Args, reply *Reply) error签名
// 乘法运算方法
func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error {
	res.Pro = req.A * req.B
	return nil
}

// 除法运算方法
func (this *Arith) Divide(req ArithRequest, res *ArithResponse) error {
	if req.B == 0 {
		return errors.New("divide by zero")
	}
	res.Quo = req.A / req.B
	res.Rem = req.A % req.B
	return nil
}

func main() {
        //用于注册RPC服务实例,这里注册了Arith结构体实例
	rpc.Register(new(Arith)) // 注册rpc服务
        
        //选择使用HTTP作为RPC载体协议
        //默认是gob编码,也可以选择其他编码方式,如JSON
	rpc.HandleHTTP()
        
        //监听指定的网络地址
	lis, err := net.Listen("tcp", "127.0.0.1:8090")
	if err != nil {
		log.Fatalln("fatal error: ", err)
	}

	fmt.Fprintf(os.Stdout, "%s", "start connection")
        
        //使用HTTP服务器提供RPC服务
        //使用传入的listener监听并处理请求
	http.Serve(lis, nil)
}

客户端

package main

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

// 定义算术运算请求结构体ArithRequest和响应结构体ArithResponse
// 算数运算请求结构体
type ArithRequest struct {
	A int
	B int
}

// 算数运算响应结构体
type ArithResponse struct {
	Pro int // 乘积
	Quo int // 商
	Rem int // 余数
}

func main() {
        //使用rpc.DialHTTP连接RPC服务器
	conn, err := rpc.DialHTTP("tcp", "127.0.0.1:8090")
	if err != nil {
		log.Fatalln("dailing error: ", err)
	}
        
        //构造ArithRequest请求参数
	req := ArithRequest{9, 2}
	var res ArithResponse
        
        //调用conn.Call发起RPC请求,调用Multiply方法进行乘法运算
	err = conn.Call("Arith.Multiply", req, &res) // 乘法运算
	if err != nil {
		log.Fatalln("arith error: ", err)
	}
	fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)
        
        //调用Divide方法进行除法运算
	err = conn.Call("Arith.Divide", req, &res)
	if err != nil {
		log.Fatalln("arith error: ", err)
	}
	fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
}

net/rpc/jsonrpc

服务端

package main

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

// 算数运算结构体
type Arith struct {
}

// 算数运算请求结构体
type ArithRequest struct {
	A int
	B int
}

// 算数运算响应结构体
type ArithResponse struct {
	Pro int // 乘积
	Quo int // 商
	Rem int // 余数
}

// 乘法运算方法
func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error {
	res.Pro = req.A * req.B
	return nil
}

// 除法运算方法
func (this *Arith) Divide(req ArithRequest, res *ArithResponse) error {
	if req.B == 0 {
		return errors.New("divide by zero")
	}
	res.Quo = req.A / req.B
	res.Rem = req.A % req.B
	return nil
}

func main() {
	rpc.Register(new(Arith)) // 注册rpc服务

	lis, err := net.Listen("tcp", "127.0.0.1:8090")
	if err != nil {
		log.Fatalln("fatal error: ", err)
	}

	fmt.Fprintf(os.Stdout, "%s", "start connection")

	for {
		conn, err := lis.Accept() // 接收客户端连接请求
		if err != nil {
			continue
		}

		go func(conn net.Conn) { // 并发处理客户端请求
			fmt.Fprintf(os.Stdout, "%s", "new client in coming\n")
			jsonrpc.ServeConn(conn)
		}(conn)
	}
}

这个代码实现了一个使用JSON-RPC协议的RPC服务,主要不同点是:

  • 使用jsonrpc.ServeConn处理每个客户端连接,取代了http.Serve。
  • jsonrpc.ServeConn底层采用JSON编码,和默认的gob编码不同。
  • 每个客户端连接在新goroutine中处理,实现并发。
  • 注册服务和方法定义与普通RPC相同。

客户端

package main

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

// 算数运算请求结构体
type ArithRequest struct {
	A int
	B int
}

// 算数运算响应结构体
type ArithResponse struct {
	Pro int // 乘积
	Quo int // 商
	Rem int // 余数
}

func main() {
	conn, err := jsonrpc.Dial("tcp", "127.0.0.1:8090")
	if err != nil {
		log.Fatalln("dailing error: ", err)
	}

	req := ArithRequest{9, 2}
	var res ArithResponse

	err = conn.Call("Arith.Multiply", req, &res) // 乘法运算
	if err != nil {
		log.Fatalln("arith error: ", err)
	}
	fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)

	err = conn.Call("Arith.Divide", req, &res)
	if err != nil {
		log.Fatalln("arith error: ", err)
	}
	fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
}

和默认的RPC不同点在于:

  • 使用jsonrpc.Dial连接
  • 底层采用JSON编码
  • 方法名和参数需要符合JSON-RPC规范