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粘包 指的是,在传输过程中,多条数据粘到一起去了,没有单独一条条发送。 主要原因:
- 应用程序写入数据的速度快于发送数据的速度;
- Nagle算法:会合并相连的小数据包,再一次性发送,以提升网络传输效率。
- 多个应用程序利用相同的TCP连接并发发送数据,因为TCP本身是流式协议,无法识别边界,所以多个应用程序同时发送数据包到同一个Socket连接上时,这些数据包有可能在接收端粘连在一起。
- 接收端接收不及时造成。
解决方法:
- 使用消息定界符
- 使用消息长度
- 使用固定长度消息
- 使用分隔符
实现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))
}