Go 基础学习 Part6 网络编程基础

71 阅读6分钟

Socket编程

Socket编程,也称为套接字编程,是一种网络通信的编程方式,提供了一种在不同主机之间进行数据通信的方式。

有两种常见的通信模式:客户端-服务器模式点对点模式
基于TCP的Socket通信:通常是客户端-服务器模型。服务器首先进行监听,等待客户端的请求。当客户端发起请求时,双方进行连接确认。一旦连接建立,客户端和服务器就可以开始发送和接收数据。数据传输完成后,双方断开连接。

Socket编程广泛应用于各种网络应用中,如Web服务器/客户端、远程登录、文件传输等。无论是应用于本地局域网还是互联网,都必须使用Socket编程来编写网络程序。

Dial 函数

go中对Socket编程进行了抽象和封装,无论我们期望使用什么协议建立什么形式的连接,都只需要调用net.Dial()即可。

原型func Dial(net, addr string) (Conn, error)
net:网络协议名称
addr:IP地址或域名,端口号以:跟随
连接成功则返回连接对象;失败则返回error

支持:tcp、tcp4(仅限IPv4)、tcp6(仅限IPv6)、udp、udp4(仅限IPv4)、udp6(仅限IPv6)、ip、ip4(仅限IPv4)和ip6(仅限IPv6)。常见调用:

// TCP
conn, err := net.Dial("tcp", "127.0.0.0:8888")
// UDP
conn, err := net.Dial("udp", "127.0.0.0:8888")
//ICMP(协议名称)
conn, err := net.Dial("ip4:icmp", "127.0.0.0:8888")
// or(协议编号)
conn, err := net.Dial("ip4:1", "127.0.0.0:8888")

发送数据:使用conn的Write()成员方法
接收数据:使用conn的Read()成员方法

net包还还包含一些工具函数:
验证IP地址有效性func net.ParseIP(ip)

实现TCP通信

TCP/IP 即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层通信协议,因为是面向连接的协议,数据像水流一样传输,会存在黏包问题。

服务端:

package main

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

func main() {
	// 监听本地端口
	listener, err := net.Listen("tcp", "localhost:8088")
	if err != nil {
		fmt.Println("Error listening:", err.Error())
		os.Exit(1)
	}
        // 关闭监听
	defer listener.Close()
	fmt.Println("Listening on :8080")
	for {
		// 接受连接
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("Error accepting: ", err.Error())
			os.Exit(1)
		}
		// 处理连接
		go handleRequest(conn)
	}
}

func handleRequest(conn net.Conn) {
	defer conn.Close()
	// 读取客户端发送的数据
	reader := bufio.NewReader(conn)
	message, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println("Error reading:", err.Error())
		return
	}
	fmt.Print("Message received: ", string(message))
	// 发送响应给客户端
	_, err = conn.Write([]byte("Hello client!\n"))
	if err != nil {
		fmt.Println("Error writing:", err.Error())
		return
	}
}

客户端:

package main

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

func main() {
	// 连接到服务器
	conn, err := net.Dial("tcp", "localhost:8088")
	if err != nil {
		fmt.Println("Error connecting:", err.Error())
		os.Exit(1)
	}
        // 关闭链接
	defer conn.Close()
	fmt.Println("Connected to server")

	// 发送数据到服务器
	_, err = conn.Write([]byte("Hello server!My name is Lily ~\n"))
	if err != nil {
		fmt.Println("Error writing:", err.Error())
		os.Exit(1)
	}

	// 读取服务器响应
	reader := bufio.NewReader(conn)
	message, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println("Error reading:", err.Error())
		os.Exit(1)
	}
	fmt.Print("Message from server: ", string(message))
}

TCP粘包 指的是,在传输过程中,多条数据粘到一起去了,没有单独一条条发送。 主要原因:

  1. 应用程序写入数据的速度快于发送数据的速度;
  2. Nagle算法:会合并相连的小数据包,再一次性发送,以提升网络传输效率。
  3. 多个应用程序利用相同的TCP连接并发发送数据,因为TCP本身是流式协议,无法识别边界,所以多个应用程序同时发送数据包到同一个Socket连接上时,这些数据包有可能在接收端粘连在一起。
  4. 接收端接收不及时造成。

解决方法:

  1. 使用消息定界符
  2. 使用消息长度
  3. 使用固定长度消息
  4. 使用分隔符

实现UDP编程

UDP协议中文名称是用户数据报协议,是OSI参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好。

服务端:

package main

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

func main() {
	// 解析一个UDP地址, 包含UDP地址信息。
	udpAddr, err := net.ResolveUDPAddr("udp", "localhost:8088")
	if err != nil {
		fmt.Println("Error resolving UDP address:", err)
		os.Exit(1)
	}
	// 监听
	listen, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		fmt.Println("listen failed, err:", err)
		os.Exit(1)
	}
	defer listen.Close()
	fmt.Println("Listening on", udpAddr)
	// 缓冲区用于接收数据
	buffer := make([]byte, 1024)
	for {
		// 从UDP连接读取数据
		n, addr, _ := listen.ReadFromUDP(buffer)
		if err != nil {
			fmt.Println("Error reading from UDP:", err)
			continue
		}
                // 响应返回
		fmt.Printf("Received from %s: %s\n", addr.String(), string(buffer[:n]))
		_, err := listen.WriteToUDP([]byte("Message received"), addr)
		if err != nil {
			fmt.Println("Error writing to UDP:", err)
			continue
		}
	}
}

客户端

package main

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

func main() {
	// 解析一个UDP地址, 包含UDP地址信息。
	udpAddr, err := net.ResolveUDPAddr("udp", "localhost:8088")
	if err != nil {
		fmt.Println("Error resolving UDP address:", err)
		os.Exit(1)
	}
	// 建立连接
	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		fmt.Println("连接服务端失败,err:", err)
		os.Exit(1)
	}
	defer conn.Close()

	sendData := []byte("Hello server")
	_, err = conn.Write(sendData) // 发送数据
	if err != nil {
		fmt.Println("发送数据失败,err:", err)
		os.Exit(1)
	}

	data := make([]byte, 4096)
	n, remoteAddr, err := conn.ReadFromUDP(data) // 接收数据
	if err != nil {
		fmt.Println("接收数据失败,err:", err)
		os.Exit(1)
	}
	fmt.Printf("received:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}

HTTP编程

HTTP 客户端

Go语言标准库内建提供了net/http包,涵盖了HTTP客户端和服务端的具体实现。可以直接使用HTTP中的GET和POST请求数据。

基本方法
http.Get():请求一个资源,传入一个URL
http.Post():以Post方式发送数据,三个参数 URL、资源类型、数据比特流
http.PostForm():实现标准编码格式为application/x-www-form-urlencoded的表单提交。
http.Head():表明只请求目标 URL 的头部信息,即 HTTP Header 而不返回 HTTP Body。
(*http.Client).Do():如果想要发起的请求设定一些自定义的Http Header字段,可以用该方法。

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)

HTTP 服务端

HTTP请求

net/http包提供http.ListenAndServe(),可以在指定地址进行监听,开启一个HTTP。用于在指定TCP网络地址进行监听,然后调用服务端处理程序来处理传入的连接请求。
原型:func ListenAndServee(addr string, handler Handler) err addr:监听地址
handler: 服务端处理程序,通常为空。

HTTPS请求

net/http 包还提供 http.ListenAndServeTLS() 方法,用于处理 HTTPS 连接请求。
原型:func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error
certFile:对应SSL证书存放路径
keyFile:对应证书私钥文件路径。

🌰:

package main

import (
	"fmt"
	"net/http"
)

// 定义处理函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello, World!")
}

func main() {
	// 将处理函数绑定到路由上
	http.HandleFunc("/hello", helloHandler)

	// 启动服务器,监听8080端口
	fmt.Println("Server is running on http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		// 处理错误
		fmt.Println("Error starting server:", err)
		return
	}
}

HTTP客户端

使用net/http包中的http.Client结构体。
http.Get():请求一个资源
http.Post():以POST的方式发送数据,参数:【URL、数据资源类型(MIMEType)、 数据比特流([]byte)】
http.PostForm():实现了标准编码格式为application/x-www-form-urlencoded的表单提交。
http.Head():表明只请求目标 URL 的头部信息,即 HTTP Header 而不返回 HTTPBody。
(*http.Client).Do():发起的HTTP 请求可以设定一些自定义的 Http Header 字段。

🌰:

package main

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

func main() {
	// 创建一个HTTP GET请求
	resp, err := http.Get("http://localhost:8080")
	if err != nil {
		// 处理错误
		fmt.Println("Error fetching URL:", err)
		return
	}
	defer resp.Body.Close() // 确保关闭响应体

	// 读取响应体
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// 处理错误
		fmt.Println("Error reading response body:", err)
		return
	}

	// 打印响应体
	fmt.Println(string(body))
}