
文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载本指南源自本仓库build-web-application-with-golang第 8 章Web 服务的核心小节 ja/08.1.md系统讲解如何用 Go 标准库net包进行底层网络编程从 Socket 的本质与通信原理到 IPv4/IPv6 地址处理再到 TCP 客户端/服务端的完整实现、基于 goroutine 的并发改造、长连接读写控制以及 UDP 的无连接通信模型。读完本文你将掌握用 Go 独立编写可运行、可扩展的 Socket 服务与客户端程序的完整技术路线。Socket 是什么Socket 起源于 Unix。Unix 的基本哲学之一是一切皆文件everything is a file所有对象都遵循打开 open → 读写 write/read → 关闭 close的操作模式Socket 正是这一哲学的典型实现。网络上的 Socket 数据通信是一种特殊的 I/OSocket 本身也是一种文件描述符file descriptor。与打开文件类似Socket 也有自己的打开函数socket()它返回一个 int 类型的 Socket 描述符。此后建立连接、传输数据等一系列操作都通过这个描述符来完成。常用的 Socket 分为两类流式 SocketSOCK_STREAM面向连接的 Socket用于面向连接的 TCP 服务应用数据报 SocketSOCK_DGRAM无连接的 Socket用于无连接的 UDP 服务应用。Socket 如何通信网络上的进程之间如何通过 Socket 通信首先要解决的问题是如何唯一标识一个通信进程。在本地环境中可以用进程 PID 标识但在网络中 PID 无法通用。TCP/IP 协议族恰好已经解决了这一问题网络层的 IP 地址唯一标识网络上的主机传输层的协议 端口唯一标识主机上的应用程序进程。因此利用三元组IP 地址协议端口就可以在网络中唯一识别一个进程需要通信的进程便可以通过这一标识互相建立联系。上面这张 TCP/IP 协议分层图清楚地展示了 Socket 的位置它位于应用层与传输层之间作为Socket 应用程序接口Socket Application Interface向上为应用提供统一的编程 API向下屏蔽 TCP/UDP、IP 以及链路层的实现差异。基于 TCP/IP 协议族的应用程序通常通过应用层端口来提供服务经典实现包括 UNIX BSD 的 Socket 与 UNIX System V 的 TLI后者已被淘汰。如今几乎所有应用程序都采用 Socket网络进程通信无处不在一切皆 Socket正是由此而来。Socket 基础知识Socket 分为 TCP Socket 与 UDP Socket 两类。TCP 与 UDP 都是传输层协议要确定一个进程除了协议本身还需要 IP 地址与端口号三者共同构成唯一标识。IPv4 地址当前互联网采用的协议族是 TCP/IP其中 IP 是网络层协议也是 TCP/IP 协议族的核心协议。目前主要使用的 IP 版本是 4简称 IPv4至今已运行了 30 多年。IPv4 地址位数为 32 位理论上最多可连接 2 的 32 次方台网络设备。随着互联网近十年的高速发展IP 地址需求激增、分配日益紧张IPv4 地址已接近耗尽如今企业中保有的服务器 IP 已成为稀缺资源。地址形式示例127.0.0.1、172.122.121.111。IPv6 地址IPv6 是下一代的互联网协议为解决 IPv4 实施过程中出现的各类问题而提出。IPv6 采用 128 位地址长度可提供的地址几乎无限按保守估计足以给地球上每平方米面积分配 1000 个以上的地址。除预先解决地址枯竭问题外IPv6 还考虑了 IPv4 未能很好解决的其他问题主要包括端到端的 IP 连接peer to peer服务质量QoS安全性组播multicast移动性即插即用plug and play地址形式示例2002:c0e8:82e7:0:0:0:c0e8:82e7。Go 支持的 IP 形式Go 的net包为网络编程定义了众多类型、函数与方法其中 IP 的定义如下type IP []bytenet包中有大量操作 IP 的函数其中最常用的ParseIP(s string) IP用于将 IPv4 或 IPv6 格式的地址字符串转换为 IP 类型。下面的示例演示了它的用法package main import ( net os fmt ) func main() { if len(os.Args) ! 2 { fmt.Fprintf(os.Stderr, Usage: %s ip-addr\n, os.Args[0]) os.Exit(1) } name : os.Args[1] addr : net.ParseIP(name) if addr nil { fmt.Println(Invalid address) } else { fmt.Println(The address is , addr.String()) } os.Exit(0) }运行程序并传入 IP 地址参数即可看到对应的 IP 类型输出传入非法地址时则会提示Invalid address。需要说明的是文档写作于 Go 早期版本其中os.Error在当代 Go 中已由标准库的error接口取代下文代码统一使用error语义完全一致。TCP Socket知道如何通过端口访问网络服务后能做什么从客户端视角看向某台远程主机的某个端口发送一个请求即可获得监听该端口的服务端返回的反馈信息从服务端视角看则需要把服务绑定到指定端口并监听它在客户端访问时读取信息、写回反馈。Go 的net包提供了TCPConn类型作为客户端与服务端之间数据交换的通道它有两个核心方法func (c *TCPConn) Write(b []byte) (n int, err error) func (c *TCPConn) Read(b []byte) (n int, err error)TCPConn可被客户端或服务端任意一方用于读写数据。同时还需要TCPAddr类型来表示 TCP 地址信息type TCPAddr struct { IP IP Port int }Go 使用ResolveTCPAddr函数获取一个TCPAddrfunc ResolveTCPAddr(net, addr string) (*TCPAddr, error)net参数可取tcp4、tcp6、tcp三者之一分别表示 TCP仅 IPv4、TCP仅 IPv6与 TCPIPv4、IPv6 任意其一addr为域名或 IP 地址例如www.google.com:80或127.0.0.1:22。TCP 客户端Go 客户端通过net包的DialTCP函数建立一条 TCP 连接并返回TCPConn对象连接建立后服务端也持有同类型对象双方即可通过各自的TCPConn交换数据。通常客户端用TCPConn向服务端发送请求信息、读取服务端响应服务端读取并解析客户端请求后返回反馈。连接在任一方主动断开前一直有效。建立连接函数的定义func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err error)net参数同样可取tcp4、tcp6、tcp含义同上laddr表示本地地址一般设置为nilraddr表示远端服务器地址。下面编写一个简单示例模拟基于 HTTP 协议的客户端向 Web 服务器发起请求。需要构造一个简单的 HTTP 请求头其格式如下HEAD / HTTP/1.0\r\n\r\n服务端返回的响应信息格式大致如下HTTP/1.0 200 OK ETag: -9985996 Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT Content-Length: 18074 Connection: close Date: Sat, 28 Aug 2010 00:43:48 GMT Server: lighttpd/1.4.23客户端代码如下package main import ( fmt io/ioutil net os ) func main() { if len(os.Args) ! 2 { fmt.Fprintf(os.Stderr, Usage: %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\r\n\r\n)) checkError(err) result, err : ioutil.ReadAll(conn) checkError(err) fmt.Println(string(result)) os.Exit(0) } func checkError(err error) { if err ! nil { fmt.Fprintf(os.Stderr, Fatal error: %s, err.Error()) os.Exit(1) } }程序执行流程把用户输入参数作为service传给net.ResolveTCPAddr获得tcpAddr将tcpAddr传入DialTCP建立 TCP 连接conn通过conn向服务器发送请求信息最后用ioutil.ReadAll从conn中读取全部内容即服务器对请求的反馈信息。TCP 服务端编写完 TCP 客户端后同样可以用net包编写服务端程序。服务端需要把服务绑定到一个未被激活的指定端口并监听它在客户端请求到达时接收连接。net包提供如下函数func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err error) func (l *TCPListener) Accept() (c Conn, err error)参数含义与DialTCP相同。下面实现一个简单的时间同步服务监听 7777 端口package main import ( fmt net os time ) func main() { service : :7777 tcpAddr, err : net.ResolveTCPAddr(tcp4, service) checkError(err) listener, err : net.ListenTCP(tcp, tcpAddr) checkError(err) for { conn, err : listener.Accept() if err ! nil { continue } daytime : time.Now().String() conn.Write([]byte(daytime)) // dont care about return value conn.Close() // were finished with this client } } func checkError(err error) { if err ! nil { fmt.Fprintf(os.Stderr, Fatal error: %s, err.Error()) os.Exit(1) } }服务启动后将持续等待新客户端请求收到请求并通过Accept接受后向该请求方反馈当前时间信息。注意代码中的for循环出错时直接continue而不退出循环。这是因为服务端长期运行单个连接出错时应记录错误、让当前客户端断开而不会影响服务整体运行。用 goroutine 实现并发服务上述代码存在明显缺陷它是单任务串行执行的无法同时接收多个请求。如何改造以支持并发处理Go 提供了 goroutine 机制改造后的代码如下package main import ( fmt net os time ) func main() { service : :1200 tcpAddr, err : net.ResolveTCPAddr(tcp4, service) checkError(err) listener, err : net.ListenTCP(tcp, tcpAddr) checkError(err) for { conn, err : listener.Accept() if err ! nil { continue } go handleClient(conn) } } func handleClient(conn net.Conn) { defer conn.Close() daytime : time.Now().String() conn.Write([]byte(daytime)) // dont care about return value // were finished with this client } func checkError(err error) { if err ! nil { fmt.Fprintf(os.Stderr, Fatal error: %s, err.Error()) os.Exit(1) } }把任务处理逻辑抽离到handleClient函数中仅添加一个go关键字就实现了服务的并行处理——goroutine 的简洁与强大在此体现得淋漓尽致。这也是后续构建高性能 Socket 服务的基础模式。长连接与多请求处理上面的服务并未真正处理客户端请求内容。如果客户端要求在一条连接上发送不同请求、返回不同格式的时间且需要保持长时间连接该怎么做看下面的实现package main import ( fmt net os time strconv ) func main() { service : :1200 tcpAddr, err : net.ResolveTCPAddr(tcp4, service) checkError(err) listener, err : net.ListenTCP(tcp, tcpAddr) checkError(err) for { conn, err : listener.Accept() if err ! nil { continue } go handleClient(conn) } } func handleClient(conn net.Conn) { conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout request : make([]byte, 128) // set maximum request length to 128B to prevent flood attacks defer conn.Close() // close connection before exit for { read_len, err : conn.Read(request) if err ! nil { fmt.Println(err) break } if read_len 0 { break // connection already closed by client } else if string(request[:read_len]) timestamp { daytime : strconv.FormatInt(time.Now().Unix(), 10) conn.Write([]byte(daytime)) } else { daytime : time.Now().String() conn.Write([]byte(daytime)) } request make([]byte, 128) // clear last read content } } func checkError(err error) { if err ! nil { fmt.Fprintf(os.Stderr, Fatal error: %s, err.Error()) os.Exit(1) } }该示例用conn.Read()持续读取客户端发送的请求。由于需要保持与客户端的长连接读完一次请求后不能断开连接conn.SetReadDeadline()设置超时后若一定时间内客户端没有新请求conn会自动断开连接for循环随之退出。实现中有三点必须注意request在创建时必须限制最大长度此处为 128 字节以防止 flood attack洪泛攻击耗尽服务端内存每次请求读取并处理完毕后要重新初始化request因为conn.Read()可能把新读取的内容追加到原有内容之后不清理会导致旧数据污染判断逻辑判断请求内容时使用string(request[:read_len])而非整个缓冲区保证只比较实际读取到的字节数。TCP 连接控制net包提供了大量连接控制函数日常最常用的包括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) error设置客户端是否与服务端保持长连接keep-alive可以减少 TCP 连接握手带来的开销特别适合需要频繁交换数据的应用场景。更详细的内容可查阅 Gonet包的标准库文档。当前仓库的示例代码目录如 en/code/src/apps收录了第 15 章的应用示例第 8 章的 Socket 示例代码以本文档内给出的完整程序为准可直接复制运行验证。UDP SocketGo 中 UDP Socket 与 TCP Socket 处理的差异主要体现在服务端对多个客户端请求数据包的处理方式上UDP 缺少像Accept那样接收客户端连接请求的函数其余部分基本与 TCP 相同——把TCP换成UDP即可。UDP 的主要函数如下func ResolveUDPAddr(net, addr string) (*UDPAddr, error) func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err error) func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err error) func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err error)UDP 客户端UDP 客户端代码如下可见与 TCP 客户端仅将相关函数替换为 UDP 版本package main import ( fmt net os ) func main() { if len(os.Args) ! 2 { fmt.Fprintf(os.Stderr, Usage: %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) } func checkError(err error) { if err ! nil { fmt.Fprintf(os.Stderr, Fatal error , err.Error()) os.Exit(1) } }UDP 客户端先解析地址、建立 UDP 连接DialUDP不真正握手仅建立套接字写入任意数据后用固定 512 字节的缓冲区读取服务端回包。由于 UDP 无连接回包数据通过Read读入。UDP 服务端UDP 服务端的处理方式如下package main import ( fmt net os time ) func main() { service : :1200 udpAddr, err : net.ResolveUDPAddr(udp4, service) checkError(err) conn, err : net.ListenUDP(udp, udpAddr) checkError(err) for { handleClient(conn) } } 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, Fatal error , err.Error()) os.Exit(1) } }UDP 服务端用ListenUDP绑定端口后进入无限循环每次调用handleClient通过ReadFromUDP读取数据报同时获得来源地址addr再用WriteToUDP把当前时间写回该地址。因为没有连接概念服务端必须从数据报中取出对端地址才能把响应送回正确的客户端。小结通过上述对 TCP 与 UDP Socket 编程的描述与实现可以看到Go 对 Socket 编程提供了完善的支持使用起来非常方便。net包提供了丰富的函数利用这些函数尤其是TCPConn/UDPConn的读写方法、ResolveTCPAddr/ListenTCP/DialTCP等地址解析与连接管理函数再结合 goroutine 并发模型可以轻松编写出高性能的 Socket 应用程序。作为延伸本章后续小节还涵盖了基于 Socket 之上的更高层通信技术WebSocket 编程、REST 接口 与 RPC 实现本仓库其余语言版本如 en/08.1.md亦收录了本文的英文对照内容可供交叉参考。赞分享文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载相关推荐用 Go 编写 TCP/UDP Socket 程序从网络协议基础到并发服务实战用 Go 编写 TCP/UDP Socket 程序从网络协议基础到并发服务实战 导读 本文基于开源 Go 语言教程《Build Web Applicatio文档教程超详细volo_d2_384.sail_in1k配置文件(config.json)参数调优指南超详细volo_d2_384.sail_in1k配置文件 config.json 参数调优指南 volo_d2_384.sail_in1k是一款基于VOLO快速掌握web-basics前端面试与算法题的高效学习方法快速掌握web basics前端面试与算法题的高效学习方法 web basics是一个专注于大厂前端面试与算法题的学习项目涵盖JS基础能力、大厂场景题和面试上一篇从理论到代码PyTorch-RL中PPO算法的Clipping机制原理解析下一篇基于WebGL与MediaPipe的人体姿态搜索系统浏览器端实时动作分析与匹配解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考