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规范