Go语言的TCP和HTTP网络服务基础

目录

【TCP Socket 编程模型】

Socket读操作

【HTTP网络服务】

HTTP客户端

HTTP服务端


TCP/IP 网络模型实现了两种传输层协议:TCP 和 UDP,其中TCP 是面向连接的流协议,为通信的两端提供稳定可靠的数据传输服务;UDP 提供了一种无需建立连接就可以发送数据包的方法。实现网络编程,不仅可以基于应用层协议的HTTP,也可以直接基于传输层暴露给用户的网络编程接口:Socket(套接字)。socket是一种 IPC 方法。

IPC 是 Inter-Process Communication 的缩写,可以被翻译为进程间通信,主要定义的是多个进程之间相互通信的方法。这些方法主要包括:系统信号(signal)、管道(pipe)、套接字 (socket)、文件锁(file lock)、消息队列(message queue)、信号量(semaphore)等。

关于TCP和UDP以及HTTP的相关术语可以参考:HTTP协议中的响应码和实体数据

【TCP Socket 编程模型】

网络 I/O 模型指的是 应用线程与操作系统内核之间的交互模式,常用的有 阻塞 I/O(Blocking)、非阻塞 I/O(Non-Blocking)、I/O 多路复用(I/O Multiplexing)。

  • 阻塞I/O模型:内核一直等到全部数据就绪才返回给发起系统调用的应用线程。一个线程仅能处理一个网络连接上的数据通信,即便连接上没有数据,线程也只能阻塞在对 Socket 的读操作上。虽然这个模型对应用整体来说是低效的,但却是最容易实现和使用的,所以各大平台在默认情况下都将 Socket 设置为阻塞的。
  • 非阻塞I/O模型:内核查看数据就绪状态后,即便没有就绪也立即返回错误给发起系统调用的应用线程。位于用户空间的 I/O 请求发起者会通过轮询的方式,去一次次发起 I/O请求,直到读到所需的数据为止。不过这样的轮询会浪费很多CPU计算资源,因此非阻塞 I/O 模型单独应用于实际生产的比例并不高。
  • I/O多路复用模型:一个应用线程可以同时处理多个 Socket,并且由内核实现可读/可写事件的通知,避免了非阻塞模型中轮询导致的CPU计算资源浪费的问题。

Go语言的socket服务端程序通常采用一个 Goroutine 处理一个连接,主要关键词是Listen和Accept,实现方式如下:

package main

import (
	"fmt"
	"net"
)

func handleConn(c net.Conn) {
	defer c.Close()
	for {
		//...
	}
}

func main() {
	//使用net包的Listen函数绑定服务器端口8888,并将它转换为监听状态
	l, err := net.Listen("tcp", ":8888")
	if err != nil {
		fmt.Println("listen error:", err)
		return
	}
	for {
		//Listen返回成功后,这个服务会进入一个循环,调用net.Listener 的 Accept 方法接收新客户端连接
		//在没有新连接的时候,这个服务会阻塞在 Accept 调用上,直到有客户端连接上来,Accept 方法将返回一个 net.Conn 实例,用于和新连上的客户端进行通信。
		c, err := l.Accept()
		if err != nil {
			fmt.Println("accept error:", err)
			break
		}

		//这个服务程序启动了一个新 Goroutine,并将 net.Conn 传给这个 Goroutine,这样这个Goroutine 就专门负责处理与这个客户端的通信了。
		go handleConn(c)
	}
}

运行上面的代码后,使用  netstat -an|grep 8888 可以查看端口的监听情况:

当服务端按照上面的Listen + Accept结构成功启动后,客户端就可以使用 net.Dial net.DialTimeout 向服务端发起建立连接的请求。net.Dial函数会接受两个参数,分别名为network和address,都是string类型的。

参数network常用的可选值一共有 9 个,分别代表了程序底层创建的 socket 实例可使用的不同通信协议:

  • "tcp":代表 TCP 协议,其基于的 IP 协议的版本根据参数address的值自适应。
  • "tcp4":代表基于 IP 协议第四版的 TCP 协议。
  • "tcp6":代表基于 IP 协议第六版的 TCP 协议。
  • "udp":代表 UDP 协议,其基于的 IP 协议的版本根据参数address的值自适应。
  • "udp4":代表基于 IP 协议第四版的 UDP 协议。
  • "udp6":代表基于 IP 协议第六版的 UDP 协议。
  • "unix":代表 Unix 通信域下的一种内部 socket 协议,以 SOCK_STREAM 为 socket 类型。
  • "unixgram":代表 Unix 通信域下的一种内部 socket 协议,以 SOCK_DGRAM 为 socket 类型。
  • "unixpacket":代表 Unix 通信域下的一种内部 socket 协议,以 SOCK_SEQPACKET 为 socket 类型。
//Dial 函数向服务端发起 TCP 连接,这个函数会一直阻塞,直到连接成功或失败后,才会返回。
conn, err := net.Dial("tcp", "localhost:8888")
//DialTimeout 带有超时机制,如果连接耗时大于超时时间,这个函数会返回超时错误。
conn, err := net.DialTimeout("tcp", "localhost:8888", 2 * time.Second)

客户端建立连接的几种特殊情况:

  • 如果传给Dial的服务端地址是网络不可达,或者服务地址中端口对应的服务并没有启动,端口未被监听(Listen),Dial会立即返回这样的错误:dial error: dial tcp :8888: getsockopt: connection refused
  • 当对方服务器很忙,瞬间有大量客户端尝试向服务端建立连接时,服务端可能会出现 listen backlog 队列满,接收连接(accept)不及时的情况,这就会导致客户端的Dial调用阻塞,直
    到服务端进行一次 accept,从 backlog 队列中腾出一个槽位,客户端的 Dial 才会返回成功。如果服务端一直不执行accept操作,那么客户端会阻塞大约1分钟左右才会返回超时错误:dial error: dial tcp :8888: getsockopt: operation timed out
  • 如果网络延迟较大,TCP的三次握手过程会经历各种丢包,时间消耗也会更长,这种情况下Dial函数会阻塞。如果经过长时间阻塞后依旧无法建立连接,那么Dial也会返回 getsockopt: operation timed out的错误。

当客户端调用 Dial 成功,就会在客户端与服务端之间建立起一条通信通道,双方通过各自获得的 Socket可以向对方发送数据包,也可以接收来自对方的数据包,双方都会为已建立的连接分配一个发送缓冲区和一个接收缓冲区。

Socket读操作

开发人员只需要采用 Goroutine + 阻塞 I/O 模型,就可以满足大部分场景需求。Dial 连接成功后,会返回一个 net.Conn 接口类型的变量值,这个接口变量的底层类型为一个 *TCPConn,TCPConn“继承”了conn类型的Read和Write方法,后续通过Dial函数返回值调用的Read和Write方法都是net.conn 的方法,它们分别代表了对 socket 的读和写。

//$GOROOT/src/net/tcpsock.go
type TCPConn struct {
    conn //这里的conn是一个非导出类型,封装了底层的 socket
}

 Go 从 socket 读取数据的几种场景:

  • Socket 中无数据:连接建立后,如果客户端未发送数据,服务端会阻塞在 Socket 的读操作上,执行这个操作的 Goroutine 也会被挂起。Go 运行时会监视这个 Socket,直到它有数据读事件才会重新调度这个 Socket 对应的 Goroutine 完成读操作。
  • Socket 中有部分数据:如果 Socket 中有部分数据就绪,且数据数量小于一次读操作期望读出的数据长度,那么读操作将会成功读出这部分数据并返回,而不是等待期望长度数据全部读取后再返回。
  • Socket 中有足够数据:如果连接上有数据,且数据长度大于等于一次Read操作期望读出的数据长度,那么Read将会成功读出这部分数据并返回。
  • 设置读操作超时:有些场合对 socket 的读操作的阻塞时间有严格限制的,但由于 Go 使用的是阻塞 I/O 模型,如果没有可读数据,Read 操作会一直阻塞在对 Socket 的读操作上。这时可以通过 net.Conn 提供的 SetReadDeadline 方法,设置读操作的超时时间,当超时后仍然没有数据可读的情况下,Read 操作会解除阻塞并返回超时错误,这就给 Read 方法的调用者提供了处理其他业务逻辑的机会。

 下面代码是结合 SetReadDeadline 设置的服务端一般处理逻辑:

func handleConn(c net.Conn) {
	defer c.Close()
	for {
		//从连接中读取数据
		var buf = make([]byte, 128)
		//SetReadDeadline 方法接收一个绝对时间作为超时的 deadline,一旦设置了,
		//那么无论后续的Read操作是否超时,后面与这个socket有关的所有读操作都会返回超时失败错误。
		//如果要取消超时设置,可以使用 SetReadDeadline(time.Time{})
		c.SetReadDeadline(time.Now().Add(time.Second))
		n, err := c.Read(buf)
		if err != nil {
			fmt.Printf("conn read %d bytes, error: %s", n, err)
			if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
				// 进行其他业务逻辑的处理
				continue
			}
			return
		}
		fmt.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))
	}
}

【HTTP网络服务】

HTTP客户端

HTTP 协议是基于 TCP/IP 协议的,如果只是访问基于 HTTP 协议的网络服务,那么使用net/http代码包中的程序实体会更加便捷。比如使用 http.Get函数获取一个HTTP请求的返回信息,在调用它的时候只需要传给它一个 URL 就可以了:

func testGet() {
	url1 := "https://www.baidu.com"
	//http.Get函数会返回两个结果值:第一个结果值的类型是*http.Response,表示网络服务传回来的响应内容的结构化表示;
	//第二个结果值是error类型的,表示在创建和发送HTTP请求以及接收和解析HTTP响应的过程中可能发生的错误。
	resp1, err := http.Get(url1)
	if err != nil {
		fmt.Printf("请求发送失败: %v\n", err)
	}
	defer resp1.Body.Close()
	line1 := resp1.Proto + " " + resp1.Status
	fmt.Printf("返回内容:%s\n", line1) //HTTP/1.1 200 OK
}
func main() {
	testGet()
}

HTTP服务端

http.Server类型是基于 HTTP 协议的服务端,其中ListenAndServe方法的功能是:监听一个基于 TCP 协议的网络地址,并对接收到的 HTTP 请求进行处理。该方法会一直执行,直到有严重的错误发生或者被外界关掉。

func httpServer1() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "用户名: %s", r.URL.Path[1:])
	})
	http.ListenAndServe(":8080", nil)
}
func main() {
	httpServer1()
}

启动后,浏览器输入 http://localhost:8080/zhangsan

源代码:https://gitee.com/rxbook/go-demo-2023/tree/master/basic/go04/net

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/31767.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[MySQL]不就是SQL语句

前言 本期主要的学习目标是SQl语句中的DDL和DML实现对数据库的操作和增删改功能,学习完本章节之后需要对SQL语句手到擒来。 1.SQL语句基本介绍 SQL(Structured Query Language)是一种用于管理关系型数据库的编程语言。它允许用户在数据库中存…

双因素身份验证在远程访问中的重要性

在快速发展的数字环境中,远程访问计算机和其他设备已成为企业运营的必要条件。无论是在家庭办公室运营的小型初创公司,还是团队分散在全球各地的跨国公司,远程访问解决方案都能保证工作效率和连接性,能够跨越距离和时间的阻碍。 …

7Z045 引脚功能详解

本文针对7Z045芯片,详细讲解硬件设计需要注意的技术点,可以作为设计和检查时候的参考文件。问了方便实用,按照Bank顺序排列,包含配置Bank、HR Bank、HP Bank、GTX Bank、供电引脚等。 参考文档包括: ds191-XC7Z030-X…

怎么计算 flex-shrink 的缩放尺寸

计算公式: 子元素的宽度 - (子元素的宽度的总和 - 父盒子的宽度) * (某个元素的flex-shrink / flex-shrink总和) 面试问题是这样的下面 left 和 right 的宽度分别是多少 * {padding: 0;margin: 0;}.container {width: 500px;height: 300px;display: flex;}.left {width: 500px…

红日靶场(一)外网到内网速通

红日靶场(一) 下载地址:http://vulnstack.qiyuanxuetang.net/vuln/detail/2/ win7:双网卡机器 win2003:域内机器 win2008域控 web阶段 访问目标机器 先进行一波信息收集,扫一下端口和目录 扫到phpmyadmin,还有一堆…

【资料分享】Xilinx Zynq-7010/7020工业核心板规格书(双核ARM Cortex-A9 + FPGA,主频766MHz)

1 核心板简介 创龙科技SOM-TLZ7x是一款基于Xilinx Zynq-7000系列XC7Z010/XC7Z020高性能低功耗处理器设计的异构多核SoC工业核心板,处理器集成PS端双核ARM Cortex-A9 PL端Artix-7架构28nm可编程逻辑资源,通过工业级B2B连接器引出千兆网口、USB、CAN、UA…

Triton教程 --- 动态批处理

Triton教程 — 动态批处理 Triton系列教程: 快速开始利用Triton部署你自己的模型Triton架构模型仓库存储代理模型设置优化动态批处理 Triton 提供了动态批处理功能,将多个请求组合在一起执行同一模型以提供更大的吞吐量。 默认情况下,只有当每个输入在…

【开源与项目实战:开源实战】81 | 开源实战三(上):借Google Guava学习发现和开发通用功能模块

上几节课,我们拿 Unix 这个超级大型开源软件的开发作为引子,从代码设计编写和研发管理两个角度,讲了如何应对大型复杂项目的开发。接下来,我们再讲一下 Google 开源的 Java 开发库 Google Guava。 Google Guava 是一个非常成功、…

io.netty学习(十一)Reactor 模型

目录 前言 传统服务的设计模型 NIO 分发模型 Reactor 模型 1、Reactor 处理请求的流程 2、Reactor 三种角色 单Reactor 单线程模型 1、消息处理流程 2、缺点 单Reactor 多线程模型 1、消息处理流程 2、缺点 主从Reactor 多线程模型 主从Reactor 多线程模型示例 1…

CTF-Show密码学:ZIP文件密码破解【暴力破解】

萌新 隐写23 题目内容: 文件的主人喜欢用生日做密码,而且还是个90后。 一、已知条件 在这个题目中,我们有以下已知条件: 文件的主人喜欢用生日做密码 - 这个条件告诉我们,密码可能是一个八位的纯数字密码&#xff0c…

云原生之深入解析如何正确计算Kubernetes容器CPU使用率

一、简介说明 使用 Prometheus 配置 kubernetes 环境中 Container 的 CPU 使用率时,会经常遇到 CPU 使用超出 100%,现在来分析一下: container_spec_cpu_period:当对容器进行 CPU 限制时,CFS 调度的时间窗口&#xff…

[架构之路-214]- UML-类图图解、详解、结构化、本质化讲解

目录 一、什么是类 1.1 概述 1.2 UML中类的表示 1.3 接口 1.4 抽象类 1.5 模板类 二、什么类图 2.1 概述 2.2 类关系 三、UML类图 3.1 结构关系 3.1.1 完全一体:继承关系 (类与类耦合度最高,类与类之间最强的关系) …

计算机基础知识

参考链接:https://blog.csdn.net/ChineseSoftware/article/details/123176978 https://www.cnblogs.com/8023-CHD/p/11067141.html https://blog.csdn.net/qq_42033567/article/details/108088514 http与https的区别 HTTP 的URL以http:// 开头,而HTTPS…

【Matlab】语音信号分析与处理实验报告

一、目的 使用Matlab分析与设计实验,理解与掌握以下知识点: 1、信号的采样、频谱混叠 2、信号的频谱分析 3、信号的幅度调制与解调方法 4、理想滤波器的时域和频域特性 5、数字滤波器的设计与实现 二、内容 1、录制一段个人的语音信号 2、采用合适的频…

Unity光照贴图的切换,实现黑夜和白天效果

有这么一个需求,不能使用实时光来进行动态控制光照开关,但是又要实现白天和黑夜的效果,我的场景中有大概十几个点光源和平行光 实现步骤: 一、模型原模原样复制到另一个场景中(因为贴图只能存在于当前的场景文件夹&am…

支付宝沙箱支付详细教程(IDEA版)—2023最新版

😇作者介绍:一个有梦想、有理想、有目标的,且渴望能够学有所成的追梦人。 🎆学习格言:不读书的人,思想就会停止。——狄德罗 ⛪️个人主页:进入博主主页 🗼专栏系列:无 &#x1f33c…

Redis数据库操作

Redis 命令参考 — Redis 命令参考http://doc.redisfans.com/ 1、Redis,远程词典服务器,是一个基于内存的键值型NoSQL数据库 特征: 键值型,支持多种不同数据结构,功能丰富 单线程,每个命令具备原子性 …

Docker的安装部署以及配置的操作流程(图文)

Docker的安装以及配置流程(图文) Docker一、配置域名解析二、CentOS Docker 安装1. 查询已安装的docker2. 安装必要的一些系统工具3. 添加软件源(阿里云)信息4. 更新并安装Docker-CE5. 查看docker 的版本6. 关闭运行的防火墙7. 开…

h5手写签名示例

前言 业务中需要用户进行签字&#xff0c;如何让用户在手机端进行签字&#xff1f; 示例如下 代码已分享至Gitee: https://gitee.com/lengcz/qianming 原示例&#xff1a; https://www.jq22.com/jquery-info13488 H5实现手写签字 创建一个html页面 <!DOCTYPE html> …

Wireshark抓包分析(ARP TCP DNS HTTP)

目录 一、ARP 二、DNS 三、TCP TCP的总过程&#xff1a; ​TCP三次握手&#xff1a; TCP四次挥手&#xff1a; 四、HTTP 一、ARP 1.ARP&#xff08;Address Resolution Protocol&#xff09;&#xff0c;是根据IP地址获取物理地址的一个TCP/IP协议。 我们要抓ARP 同网段内…
最新文章