学习目标:
本篇要达到的目的,能为后续复习提供极大便利。
(第3遍复习)
(为本篇基础核心内容)
1、什么是网络通信?
网络通信是指不同设备(如计算机、手机、服务器等)通过计算机网络进行数据交换和信息传递的过程。其核心目标是实现设备之间的互联互通,让数据能够准确、高效地从发送端传输到接收端。
2、网络通信的核心组成部分
3、网络通信的工作原理
第三次复习:
(更清晰的认识到了协议)
(嘿嘿,收获满满)
TCP(传输控制协议)、UDP(用户数据报协议)都属于 TCP/IP 协议族 中的传输层协议
IP 地址是网络世界的 “身份证”,用于跨区域 “找到人”;
MAC 地址是网络世界的 “门牌号”,用于在具体 “房间”(局域网)内 “敲开门”。
两者缺一不可,共同实现从 “全球定位” 到 “最后一公里交付” 的完整通信流程。
4、网络通信的主要类型
5、关键网络协议
基础概念
第一次复习:
如果要我解释socket,他就像一门面,封装着各种函数。
是一个接口API,正着说可能会让人误解。
反着说,socket既不是某种协议,也不是id+端口号的集合,
而是操控协议与这个地址结合的工具。
他封装着一组函数,通过接口的性质,进行通信。超级方便的哦。
(想用即拿)
第二次复习:
Socket 通信基于客户端 - 服务器(Client - Server)模型
第三次复习:
小傻🥚,我就知道,你对这里有疑惑,你迟早会去查出来的:API是啥?
API(程序编程接口)是什么?有什么用?
API是软件用来和外部程序 进行数据交换 的一个渠道,
就像一个神明允许凡人借用他的力量。
通过API,外部程序可以访问软件的功能,而不需要了解其实现的细节。
API的使用非常简单,就像在电脑中访问一个文件,只需要知道他的地址(url)
然后提交一些数据作为输入,文件运行完毕后,就会给使用者返回一个结果。
API的使用大大降低了程序开发的难度。
restful风格 就是 API的一个典型案例,因为是依靠http协议进行传输,所以又叫做web api。
(这也是为啥,能实现跨平台配合...为啥能实现不同语言之间的配合...)
与函数对比:(浓缩一下)
函数 → 库
模块 → 服务 → API
解释第一次复习给出的答案:
Socket 是一种网络编程 API,通过指定 IP 地址、端口号和协议类型(如 TCP 或 UDP),
调用其提供的函数实现网络通信。
小demo
根据本图,写相应的代码:
server
package main
import (
"fmt"
"net"
"strings"
)
type User struct {
Username string
Othername string
Msg string
ServerMsg string
}
var (
user = new(User)
userMap = make(map[string]net.Conn)
)
func main() {
// 地址
addr, _ := net.ResolveTCPAddr("tcp4", "localhost:8889")
lis, _ := net.ListenTCP("tcp4", addr)
// 循环接收连接
for {
conn, _ := lis.Accept()
go func() {
for {
b := make([]byte, 1024)
count, _ := conn.Read(b)
array := strings.Split(string(b[:count]), "-")
user.Username = array[0]
user.Othername = array[1]
user.Msg = array[2]
user.ServerMsg = array[3]
// 加入对方
userMap[user.Username] = conn
if v, ok := userMap[user.Othername]; ok && v != nil { // 存在 且 不为空
n, err := v.Write([]byte(fmt.Sprintf("%s-%s-%s-%s", user.Username, user.Othername, user.Msg, user.ServerMsg)))
// 关闭连接
if n <= 0 || err != nil {
fmt.Println("无效格式")
delete(userMap, user.Othername)
conn.Close()
return
} else {
fmt.Println("发送成功")
}
} else {
user.ServerMsg = "对方不在线"
conn.Write([]byte(fmt.Sprintf("%s-%s-%s-%s", user.Username, user.Othername, user.Msg, user.ServerMsg)))
}
void
}
}()
}
}
client
package main
import (
"fmt"
"net"
"os"
"strings"
"sync"
)
type User struct {
Username string
Othername string
Msg string
ServerMsg string
}
var (
user = new(User)
wg sync.WaitGroup
)
func main() {
wg.Add(1)
fmt.Println("请输入你的账号")
fmt.Scanln(&user.Username)
fmt.Println("请输入你要给谁发送消息")
fmt.Scanln(&user.Othername)
addr, _ := net.ResolveTCPAddr("tcp4", "localhost:8889")
conn, _ := net.DialTCP("tcp4", nil, addr)
// 发送
go func() {
for {
fmt.Println("请输入,你要发给谁?仅仅只提示一次")
fmt.Scanln(&user.Msg)
if user.Msg == "exit" {
conn.Close()
wg.Done()
os.Exit(0)
}
n, _ := conn.Write([]byte(fmt.Sprintf("%s-%s-%s-%s", user.Username, user.Othername, user.Msg, user.ServerMsg)))
fmt.Println(n, "发送成功")
}
}()
// 接收
go func() {
for {
rb := make([]byte, 1024)
c, _ := conn.Read(rb)
user2 := new(User)
array := strings.Split(string(rb[:c]), "-")
if len(array) < 3 {
fmt.Println("无效格式:", array)
break
}
user2.Username = array[0]
user2.Othername = array[1]
user2.Msg = array[2]
user2.ServerMsg = array[3]
if user2.ServerMsg != "" {
fmt.Println("\t\t\t服务器消息:", user2.ServerMsg)
} else {
fmt.Println(user2.Username, ":", user2.Msg)
}
}
}()
wg.Wait()
}
对数据库的操作:
开始之前的基操
create database goWeb;
use goWeb;
create table people(
id int primary key auto_increment,
name varchar(20),
address varchar(100)
);
desc people;
select * from people;
增:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
/*
数据库的连接是一个非常有趣的玩意
[user[:password]@][net[(addr)]]/dbname[?param1=value1¶m2=value2...]
还有一个奇怪的点,就是必须要导入_ "github.com/go-sql-driver/mysql"
因为,他中的init的函数,是sql与go之间的桥梁,起到注册作用register
但是,我没有理解透,感觉好难受
*/
func main() {
// 1、打开链接
db, err := sql.Open("mysql", "root:1234@tcp(localhost:3306)/goweb")
db.Ping()
defer func() {
if db != nil {
db.Close()
}
}()
if err != nil {
fmt.Println("数据库连接错误", err)
}
// 2、预处理SQL
// ?表示占位符
stmt, err := db.Prepare("insert into people values(default,?,?)")
if err != nil {
fmt.Println("预处理失败:", err)
}
defer func() {
if stmt != nil {
stmt.Close()
}
}()
r, _ := stmt.Exec("张三", "上海")
// 3、获取结果
count, _ := r.RowsAffected()
fmt.Println("修改了:", count)
// 获取最后修改的主键
fmt.Println(r.LastInsertId())
}
删:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 连接
db, err := sql.Open("mysql", "root:1234@tcp(localhost:3306)/goWeb")
if err != nil {
fmt.Println("连接失败", err)
}
defer db.Close()
// 预处理
stmt, err := db.Prepare("delete from people where id = 2")
if err != nil {
fmt.Println("预处理失败:", err)
}
defer stmt.Close()
r, _ := stmt.Exec()
// 预处理
count, _ := r.RowsAffected()
if count > 0 {
fmt.Println("删除成功")
} else {
fmt.Println("负责删除失败")
}
}
改:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
/*
这里有一个很有趣的事情,如果修改没变化,则修改失败
*/
func main() {
// 连接
db, err := sql.Open("mysql", "root:1234@tcp(localhost:3306)/goWeb")
db.Ping()
if err != nil {
fmt.Println("失败啦", err)
}
defer db.Close()
// 预处理
stmt, err := db.Prepare("update people set name = ?,address = ? where id=3 ;")
if err != nil {
fmt.Println("预处理失败:", err)
}
defer stmt.Close()
r, _ := stmt.Exec("朝阳", "新乡")
// 查看修改情况
count, _ := r.RowsAffected()
if count > 0 {
fmt.Println("修改成功")
} else {
fmt.Println("修改失败")
}
}
查:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
/*
写完之后,没啥感受,只是在想,这玩意咋都长一个样,背背方法就过去了,
可是好像了解了解底层呐
*/
func main() {
// 连接
db, err := sql.Open("mysql", "root:1234@tcp(localhost:3306)/goWeb")
if err != nil {
fmt.Println("连接失败", err)
}
defer db.Close()
// 预处理
stmt, err := db.Prepare("select * from people")
if err != nil {
fmt.Println("预处理失败:", err)
}
defer func() {
if stmt != nil {
stmt.Close()
}
}()
// 获取
rows, err := stmt.Query()
if err != nil {
fmt.Println("获取值出错", err)
}
for rows.Next() {
var id int
var name, address string
rows.Scan(&id, &name, &address)
fmt.Println(id, " ", name, " ", address)
}
defer rows.Close() // 关闭结果集
}
第一次复习: 当你再次回来看时,希望这个能加深你的理解:以下三个函数,的区别 Handler 接口 定义 HTTP 处理逻辑的规范(要求实现 ServeHTTP 方法) 所有 HTTP 处理程序必须直接或间接实现此接口 Handle 函数 将一个 Handler 实例绑定到 URL 路径模式(pattern) 参数 handler 必须是 Handler 接口的实现 HandleFunc 函数 便捷方式:将一个函数包装为 Handler 接口的实现,并绑定到 URL 路径模式 本质是 Handle(pattern, HandlerFunc(handler)),简化了手动实现接口的步骤 拓展,实现了handler接口的对象实例,都能被Handle调用。 第三次复习:
单控制器
package main
import (
"net/http"
)
/*
何其抽象,这只是一个但控制器
其实就是用结构体,实现一个端口
*/
type MyHander struct {
}
func (m *MyHander) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("返回的数据"))
}
// 单控制器
func main() {
m := MyHander{}
server := http.Server{
Addr: "localhost:8081",
Handler: &m, // 一旦绑定在这里,无论访问什么路径,结果都是这个
}
server.ListenAndServe()
//if err := server.ListenAndServe(); err != nil {
// fmt.Printf("服务器启动失败: %v\n", err) // 打印具体错误(如端口冲突)
//}
}
多控制器
package main
import "net/http"
/*
好抽象的呢,既然是重写函数,却要重写的一模一样,抽象啦
简直气死我了
捋一捋思路,发现就是
1、先建立服务器
2、注册路由
3、监听函数
*/
/*
func first(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "first")
}
func second(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "second")
}
func main() {
// 多控制函数
server := http.Server{
Addr: "localhost:8081",
}
// 注册路由
http.HandleFunc("/first", first)
http.HandleFunc("/second", second)
server.ListenAndServe()
}
*/
// 第二套就是绑定结构体
/*
其实多控制器,用结构体,我觉得有点累赘和臃肿
首先重写多个结构体,实现接口,然后将每个结构体,依次绑定到服务器上。
与其用Handle绑定,不如直接用HandleFunc直接绑定
但控制器,就是绑定一个url,多控制器就是绑定多个url。
*/
type Handle struct {
}
func (m *Handle) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("一号"))
}
type Handler struct{}
func (m *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("二号"))
}
func main() {
h1 := Handle{}
h2 := Handler{}
// 重写结构体
server := http.Server{
Addr: "localhost:8081",
}
http.Handle("/first", &h1)
http.Handle("/second", &h2)
// 监听
server.ListenAndServe()
}
请求头
package main
import (
"fmt"
"net/http"
)
func param(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "第一个")
// 请求头
//fmt.Fprintln(w, r.Header)
// 为了便于读写代码
var acc []string = r.Header["Accept"]
for _, n := range acc {
fmt.Fprintln(w, "Accepth内容", n)
}
}
func main() {
// 建立服务器
server := http.Server{
Addr: "localhost:8081",
}
// 绑定url
http.HandleFunc("/param", param)
// 开启监听模式
server.ListenAndServe()
}
请求参数
(可在url 或 请求体中)
package main
import (
"fmt"
"net/http"
)
/*
这里有个有意思的事情,是要用ParseForm去解析表单,才能用r.Form查到
因为,需要ParseForm更新并放置于了Form中
*/
func param(w http.ResponseWriter, r *http.Request) { // 我的名字叫做解析
// 对请求头解析
h := r.Header // header是一个map类型,选中key值后,返回的是一个string类型的切片
fmt.Fprintln(w, h["Accept-Encoding"][0])
// 必须先解析成form
r.ParseForm()
fmt.Fprintln(w, r.Form)
}
func main() {
// 建立服务器
server := http.Server{
Addr: "localhost:8081",
}
http.HandleFunc("/param", param)
// 开始作为服务端监听
server.ListenAndServe()
}
main
package main
import (
"fmt"
"html/template"
"net/http"
)
/*
第一遍学习时:
这个解析模板,我有点不理解
不是啊,哥们,这有点抽象
第一遍复习:
其实,我很无奈,因为我的目录与课程目录不同
倒逼我去理解,某些函数的作用
开始时,我最苦恼的是,StripPerfex与FileServer的作用。
原来他的作用,就是解析。FileServer起一个拼接的作用,一旦出现 /static/ 拼接的作用,就开始显现
url中
如Handle的作用
*/
// 与其绑定的url操作
func welcome(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("GoWebDevelopment/basis/htmlTest/view/index.html")
if err != nil {
fmt.Println("出错了: ", err)
}
t.Execute(w, nil)
}
func main() {
// 服务器
server := http.Server{
Addr: "localhost:8081",
}
http.HandleFunc("/1", welcome)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("GoWebDevelopment/basis/htmlTest/static"))))
// 开启监听
server.ListenAndServe()
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="/static/js/index.js"></script>
</head>
<body>
你好啊,哥们
<button onClick="myClick()"></button>
</body>
</html>
js
function myClick(){
alert("您点击了按钮")
}
难道看到这里你不好奇吗?
模板--数据,难道你不好奇吗,中间是怎么传输数据的?中间的具体过程,以及底层实现
第一次复习:我当然知道,不就是reflect嘛,但是我不希望就此止步于此
第二次复习:采用的都是链式调用(使用方法的精髓)
第三遍复习:我发现一个非常有趣的事,每次访问 URL 时,服务器才按需加载模板文件并返回。
因此模板可随时修改,无需重启服务,修改内容下次访问即生效,实现动态更新。
数据->模板
1、向模板传递参数
2、向模板传递结构体
3、向模板传递map
函数->模板
main
package main
import (
"fmt"
"html/template"
"net/http"
"time"
)
/*
说实话走到这里有一个非常抽象的事情,就是模板时间,你所设置的模板时间必须与这个一模一样
这个是模板时间的整体性:"2006-01-02 15:04:05"
其实最抽象的是FuncMap这个绑定的函数,我在这里错了好久。
起因却是因为key-value出了问题。
html文件中,用的函数名,就是key值,我之前写的却是
fm := template.FuncMap{"mt": GetTime}--“mt”
但是,html中绑定的确是fm,这完全是混洗了概念的
如果不明白上述说的啥,请让ai回溯
*/
func GetTime(t time.Time) string {
return t.Format("2006-01-02")
}
func welcome(w http.ResponseWriter, r *http.Request) {
curtime := time.Date(2018, 1, 2, 3, 4, 5, 0, time.Local)
// time.Format:大致意思,就是你给他一个格式,他按照你给的格式编辑。
// 解析成合适的函数
fm := template.FuncMap{"fm": GetTime}
// 绑定
t := template.New("index.html").Funcs(fm)
t, err := t.ParseFiles("GoWebDevelopment/basis/htmlTest1/PassFunction/view/index.html")
if err != nil {
fmt.Println("调用失败:", err)
}
t.Execute(w, curtime) // 这个是暂时的
}
// 打着这个旗号
func main() {
// 创建服务器
server := http.Server{
Addr: "localhost:8082",
}
http.HandleFunc("/2", welcome)
server.ListenAndServe()
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是是北京时间:{{.}}
<br>
今天是{{.Year}}年<br>
格式化输出就是{{.Format "2006-01-02"}}
<br>{{fm .}}
</body>
</html>
if使用
二元比较(隶属于if)
if..else..if...else
range
main
package main
import (
"fmt"
"html/template"
"net/http"
)
/*
1、测试变量-$
2、测试 if and if else
3、测试 range
*/
func welcome(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("GoWebDevelopment/basis/action/view/index.html")
if err != nil {
fmt.Println("解析出错: ", err)
}
// []string
varInt := []int{1, 2, 3, 4, 5}
t.Execute(w, varInt)
}
func main() {
server := http.Server{
Addr: "localhost:8081",
}
http.HandleFunc("/1", welcome)
server.ListenAndServe()
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--第一目,变量-->
{{$n:=100}}
<!-- 第二目,if-else -->
{{if gt $n 101}}
你好呀<br>
{{else}}
好遗憾,他没看到呢<br>
{{end}}
<!-- 第三目,遍历 -->
{{range .}}
{{.}}<br> <!-- 遍历内部参数 -->
{{end}}
</body>
</html>
若我没猜错,你一定会回来看的。
用我复习3遍的经验告诉你,你可以直接看代码
或许你看着他们特别的复杂,其实除了主main函数 三个html函数,都依靠着各自的后背 head index(被定义为了layout) end 以index为主体,通过define定义,以template为连接。将他们链接在一起。
main
package main
import (
"fmt"
"html/template"
"net/http"
)
func welcome(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("GoWebDevelopment/basis/Nesting/view/index.html",
"GoWebDevelopment/basis/Nesting/view/head.html", "GoWebDevelopment/basis/Nesting/view/end.html")
if err != nil {
fmt.Println("这里出错啦:", err)
}
t.ExecuteTemplate(w, "layout", nil)
}
func main() {
// 设置服务器
server := http.Server{Addr: "localhost:8081"}
// 开启
http.HandleFunc("/", welcome)
// 开启监听
server.ListenAndServe()
}
head
如果爆红,请不要紧张,编辑器的bug,不怪咱
{{define "head"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
我是头部
</body>
</html>
{{end}}
index
{{define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{template "head"}}<br>
你好<br>
{{template "end"}}<br>
</body>
</html>
{{end}}
end
{{define "end"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这里是结尾呦
</body>
</html>
{{end}}
在这里,html表单中的enctype是主角,
我个人感觉,后端只需要接收传递过来的信息,附带加工即可。
html-form表单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<!--
这是一非常有趣的表单,
form的作用,就是提取表单
action:将表单提取到指定url
method代表应用method方法
input--type:类型,name:input提交内容的名字
-->
<!--文件上传upload,表达提交-->
<form action="upload" enctype="multipart/form-data" method="post">
用户名:<input type="text" name="username"><br>
上传照片:<input type="file" name="photo"><br>
<input type="submit" value="注册">
</form>
</body>
</html>
main
package main
import (
"fmt"
"html/template"
"io/ioutil"
"net/http"
"strings"
)
/*
第一遍感悟:
从->r.Request接收数据
FormValue接收值-到-FormFile接收文件,
文件又有File(粗略)与FileHeader(详细)两个方向解析
其实到最后,还有一个奇葩的问题,就是form没数据,硬传,定报错,所以要用err
第二遍感悟:
先从http中获取名字(FormValue),在获取文件接口(FormFile),转为2进制(WriteFile),存入本地
*/
func welcome(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("GoWebDevelopment/basis/upload/view/index.html")
if err != nil {
fmt.Println("解析模板出错", err)
}
t.Execute(w, nil)
}
func upload(w http.ResponseWriter, r *http.Request) {
fileName := r.FormValue("name")
fmt.Fprintln(w, fileName)
// 检查文件上传错误(关键修正)
file, fileHeader, err := r.FormFile("photo")
if err != nil {
fmt.Fprintln(w, "错误:未上传文件或请求不合法")
return // 终止函数,避免后续空指针操作
}
defer file.Close() // 及时关闭文件流,释放资源
b, err := ioutil.ReadAll(file) // 建议改为 io.ReadAll(file)(ioutil 已弃用)
if err != nil {
fmt.Fprintln(w, "错误:读取文件内容失败", err)
return
}
// 获取后缀
suffix := fileHeader.Filename[strings.LastIndex(fileHeader.Filename, "."):]
// 建议改为 os.WriteFile(ioutil 已弃用)
err = ioutil.WriteFile("D:\\workspace_go\\practice\\GoWebDevelopment\\basis\\upload\\file"+fileName+suffix, b, 0777)
if err != nil {
fmt.Fprintln(w, "错误:保存文件失败", err)
return
}
t, err := template.ParseFiles("GoWebDevelopment/basis/upload/view/success.html")
if err != nil {
fmt.Fprintln(w, "错误:解析模板失败", err)
return
}
t.Execute(w, nil)
}
func main() {
server := http.Server{
Addr: "localhost:8081",
}
http.HandleFunc("/1", welcome)
http.HandleFunc("/upload", upload)
server.ListenAndServe()
}
基础概念
MIME
MIME类似标签,多用途互联网邮件扩展类型,是一种用于标识文档、文件或字节流的性质和格式的标准
main函数
package main
import (
"fmt"
"html/template"
"net/http"
"os"
)
/*
关于请求下载,是一件非常有趣的事情
老样子,启动服务器,开启监听,绑定路由,用一个页面将基础内容展示出来
其次才是新东西,在html中,设置a标签。href设置成请求下载url,启动download路由
获取filename参数。开始申请本地文件,通过os.ReadAll转化为2进制,传递到客户端
并通过设置客户端标头,Head()...用set改变各种参数content-type与Disposition
是客户端下载下来
*/
func welcome(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("GoWebDevelopment/basis/download/view/index.html")
t.Execute(w, nil)
}
func download(w http.ResponseWriter, r *http.Request) { // 人家这个函数,只是接收的一个请求而已
filename := r.FormValue("filename")
// 获取值
b, err := os.ReadFile("GoWebDevelopment/basis/upload/" + filename)
if err != nil {
fmt.Fprintf(w, "下载失败:", err)
return
}
h := w.Header()
h.Set("Content-Type", "application/octet-stream")
h.Set("Content-Disposition", "attachment;filename="+filename)
w.Write(b)
/*
我知道,以后看到这里的时候,你一定会有疑惑,(w.Write()与Fprintln(w,)的区别)
没事,我替你解决:
若需要精确控制输出内容(如二进制数据、JSON、无额外字符的文本),选 w.Write([]byte);
若需要快速拼接并输出文本(如调试信息、多变量组合输出),选 fmt.Fprintln(w, ...)。
*/
}
func main() {
server := http.Server{
Addr: "localhost:8081",
}
http.HandleFunc("/1", welcome)
http.HandleFunc("/download", download)
server.ListenAndServe()
}
index函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>下载链接</title>
</head>
<body>
<!--前提是,我可没有这个文件-->
<a href="download?filename=file.png">点击我下载</a>
</body>
</html>
main
package main
import (
"encoding/json"
"fmt"
"html/template"
"net/http"
)
/*
我幸运的孩子呐,如果你下载jquery不幸运落坑,这时我建议你,Ctrl+S试试
*/
type User struct {
Name string
Age int
}
// 显示主页面
func welcome(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("GoWebDevelopment/basis/ajax/view/index.html")
t.Execute(w, nil)
}
// 响应ajax请求
func showUser(w http.ResponseWriter, r *http.Request) {
us := make([]User, 0)
us = append(us, User{"张三", 12})
us = append(us, User{"李四", 13})
us = append(us, User{"王五", 14})
b, _ := json.Marshal(us) // 转化成了二进制
w.Header().Set("Content-Type", "application/json;charset=utf-8")
fmt.Fprintln(w, string(b))
/*
我知道,很多天后,你肯定会出现疑惑!为什么要用string(b),而不直接用b呢??
哈哈,我来教你:
这是符合 Content-Type: application/json 的正确输出方式,客户端(如前端 Ajax)可以直接解析这个 JSON 字符串。
其他底层的,是与前端接轨的,我一个后端的,暂时不研究
*/
}
func main() {
// 启动服务器
server := http.Server{
Addr: ":8888",
}
// 处理静态资源
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("GoWebDevelopment/basis/ajax/static/"))))
http.HandleFunc("/1", welcome)
http.HandleFunc("/showUser", showUser)
// 同步监听
server.ListenAndServe()
}
html
html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户数据展示</title>
<script type="text/javascript" src="/static/jquery-3.7.1.min.js"></script>
<script type="text/javascript">
$(function () {
$("button").click(function () {
$.ajax({
url: "/showUser",
type: "GET",
data: {}, // 若后端需要参数(如分页),可在此添加 {page: 1}
success: function (data) {
var res = "";
// 遍历服务器返回的 data 数组(假设 data 是 [{Name: "张三", Age: 20}, ...])
for (let i = 0; i < data.length; i++) {
// 修正:使用 data[i] 而非 resKey[i]
res += "<tr>";
res += "<td>" + data[i].Name + "</td>"; // 拼接姓名
res += "<td>" + data[i].Age + "</td>"; // 拼接年龄
res += "</tr>";
}
// 将拼接好的行插入到 tbody#mybody 中
$("#mybody").html(res);
},
error: function (xhr, status, error) {
// 错误提示
alert("数据加载失败!错误:" + error);
}
});
});
});
</script>
</head>
<body>
<button>加载数据</button>
<!-- 修正:<tbody> 移到 <table> 内部 -->
<table border="1">
<!-- 表头用 <thead> 包裹(可选但推荐) -->
<thead>
<tr>
<td>姓名</td>
<td>年龄</td>
</tr>
</thead>
<!-- 表体用 <tbody id="mybody"> 包裹 -->
<tbody id="mybody">
<!-- 数据行将通过 AJAX 动态插入到这里 -->
</tbody>
</table>
</body>
</html>
非常有趣的小玩意
解释:
这个是关键:
这里,我建议,最好的学习方式,是看看菜鸟文档 :: 我的笔记 ::
自己的小小测试,掌握到这里就足够了,当然要结合菜鸟看看基础知识点
::可以自己尝试写一个邮箱,用来测试自己::
package main
import (
"fmt"
"regexp"
)
/*
第一次复习的时候,巩固知识点,收获
1、动态编译(Compile)
2、静态编译(MustCompile)
1、匹配(MatchString)、查找(FindAllString)、分割(Split)、替换(ReplaceAllString)
*/
func main() {
// ^与$ 两者结合起来的用法。\D的用法,反斜杠``转义的用法
flag, _ := regexp.MatchString(`^\D+$`, "abs")
fmt.Println(flag)
/*
创建一个regexp对象,然后调用方法
*/
r := regexp.MustCompile(`\d`)
flag = r.MatchString("fsaf")
fmt.Println(flag)
// 返回所有切片,-1返回所有,1返回第一个,2返回前两个,3返回前三个
str := r.FindAllString("234", -1)
fmt.Println(str)
// 按照规则切割,没有的话,返回空。n==0返回空,n<0返回所有,n>0返回对应个数+剩余个数
str = r.Split("d12jkj231dd", -1)
fmt.Println(str[0], str)
// 就是起到一个替换的作用
st := r.ReplaceAllString("d1w2k3k3", "美女")
fmt.Println(st)
}
基础概念:
Cookie 是一种客户端存储技术,用于解决 HTTP 协议无状态的问题。HTTP 协议本身不会记录用户的任何操作状态,而 Cookie 可以让服务器在客户端存储少量数据(以键值对形式),当客户端再次访问服务器时,会将这些 Cookie 携带在请求中发送给服务器,从而实现会话跟踪、用户身份识别等功能。 在 Go 语言中, net/http 包提供了对 Cookie 的支持,通过 http.Cookie 结构体来表示一个 Cookie,其常见字段如下:
设置(set)、获取(get) Cookie
package main
import (
"fmt"
"html/template"
"net/http"
"net/url"
)
/*
为了写这个,真是命运多舛呐
cookie本来即使一个很好写的东西
无非就是创建cookie,然后通过响应传回去
并入到,请求标头中
通过request接受这个信息
!!! 但前提有一个重要的原因是,必须编译与解码!你可以把url.QueryFiles的作用是将信息转换成%+16进制
*/
func welcome(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("GoWebDevelopment/basis/Cookie/setAndGetCookie/view/index.html")
t.Execute(w, nil)
}
func setCookie(w http.ResponseWriter, r *http.Request) {
encodeValue := url.QueryEscape("成功了")
c := http.Cookie{Name: "name", Value: encodeValue, Path: "/"}
http.SetCookie(w, &c)
t, _ := template.ParseFiles("GoWebDevelopment/basis/Cookie/setAndGetCookie/view/index.html")
t.Execute(w, nil)
}
func getCookie(w http.ResponseWriter, r *http.Request) {
res := r.Cookies()
for n, v := range res {
str, _ := url.QueryUnescape(v.Value)
fmt.Println(n, ":", str)
}
t, _ := template.ParseFiles("GoWebDevelopment/basis/Cookie/setAndGetCookie/view/index.html")
t.Execute(w, res)
}
func main() {
server := http.Server{
Addr: ":8888",
}
http.HandleFunc("/1", welcome)
http.HandleFunc("/setCookie", setCookie)
http.HandleFunc("/getCookie", getCookie)
server.ListenAndServe()
}
拓展:(HttpOnly、Path、MaxAge、Expires、Domain)
package main
import (
"net/http"
"time"
)
func serv(w http.ResponseWriter, r *http.Response) {
// 验证httpOnly
// true不允许获得,false允许js脚本获得
c1 := http.Cookie{Name: "myname", Value: "myvalue", HttpOnly: true}
// /abc/ 代表能访问的路径,必须以/abc/以跟路径
c2 := http.Cookie{Name: "myname", Value: "myvalue", Path: "/abc/"}
// 设置存货时间
c3 := http.Cookie{Name: "myname", Value: "myvalue", MaxAge: 10}
t := time.Now() // 获取现在时间
c4 := http.Cookie{Name: "myname", Value: "myvalue", Expires: t}
// 必须以这个指定域名结尾,才可使用
c5 := http.Cookie{Name: "myname", Value: "myvalue", Domain: ".bjsxt.com"}
}
func main() {
server := http.Server{
Addr: ":8888",
}
http.HandleFunc("/1", serv)
server.ListenAndServe()
}
核心流程:
角色 | 现实类比 | 在 Web 中的作用 |
---|---|---|
客户端 | 去餐厅吃饭的 “顾客” | 主动向服务器(餐厅)发送请求(点单),并接收响应(用餐);存储 Cookie(如 “会员卡号”) |
服务器 | 提供服务的 “餐厅” | 接收请求(处理点单),返回响应(提供食物);设置 Cookie(如发放 “会员凭证”) |
URL | 餐厅的 “地址” | 客户端通过 URL 找到服务器(餐厅位置),并指定具体资源(如 “餐厅的汉堡套餐”) |
Cookie | 餐厅发放的 “会员凭证” | 服务器(餐厅)发给客户端(顾客)的凭证,客户端下次访问时携带(证明会员身份) |
简而言之,第三个库实现了一个路由
他只是一种风格,而不是一种协议。 换句话说,他只是将http协议应用的更规范。它基于 HTTP 协议对资源操作进行规范设计
交换机就是把数据包发送到正确的位置
交换机相当于邮递员,根据数据包中的目标mac地址,找到它对应的物理端口。
一台交换机有很多个端口。他们都有自己的编号
计算机的网卡通过网线连接到交换机的端口上。这个端口就是一个确定的物理位置。我们只要知道某个网卡的mac地址在哪个端口上,我们就能正确的把数据包发给它。所以在交换机中,有一张端口与mac地址的影射关系表,我们称之为mac地址表。交换机维护这张映射关系表。想要与某个mac地址通信时,只需要来查询一下,这个mac地址在哪个端口上,然后从对应的端口发送出去就可以了。
每一包数据都会有两个mac地址,一个是发送方的mac地址成为源mac,另一个是接收方的mac地址称为目标mac,交换机受到一包数据后,首先要把这包数据的源mac与接收端口进行绑定,然后交换机要根据目标mac查找,从哪个端口把数据包发送出去,这时候就会出现两种情况。第一种是mac地址表中查询到了关联的端口,则直接从关联端口发出。第二种情况是mac地址表中没有查询到关联端口。则向除了接收端口之外的所有端口群发,这种行为称之为泛洪,如果目标mac地址在这个网络中则它一定能受到群发的数据包,如此运行一段时间后,通过交换机的mac地址表,就可以找到网络中的所有网卡设备。由此可见,交换机只会关心数据包中的mac地址,而不会关心ip地址,mac地址在TCP/IP协议中处于第二层数据链路层,所以交换机通常也被称为二层设备。
路由器有两种接口,一种是LAN口,一种是WAN口,LAN口可以有多个用来接家庭网络设备。比如,台式机,手机,笔记本,其中手机和笔记本是通过wifi连接到路由器的设备,也相当于连接到了LAN口上。WAN口只有一个,用来接入运用商网络,以连接到互联网中。如果把路由器的WAN口忽略,只用LAN口,其实路由器就是一台交换机。
子网如何划分?
IP地址和子网掩码按位相与。
“与”的意思就是1与几就是几,而0与几都是0.
我们常用的子网掩码255.255.2555.0 前3个字节也就是前24位全为1,后8位全为0。所以按位相与的结果。
一定是这个IP地址的前三个字节不变,而最后一个字节是0.
比如192.168.1.10与255.255.255.0=192.168.1.0
我们把IP地址与子网掩码相与之后的结果是相同的两个IP认为是在同一个子网中,也就是说IP为192.168.1.10.子网掩码位255.255.255.0的这张网卡与另一个192.168.1.X的网卡一定是在同一个子网之中。
因为子网掩码都是连续的1和连续的0.所以我们通常用1的数量来表示子网掩码。
比如:255.255.255.0.就是24.
我们用IP/子网掩码来表示一个网络。
比如:192.168.1.0/24表示的网络中拥有255个IP地址。
所以如果想扩大子网中IP地址的数量。我们只需要把子网掩码调小,如果想减少网中IP地址的数量,我们只需要把子网掩码调大就可以了。
子网的意义:
因为TCP/IP协议规定,不同子网之间是不可以直接通信的,如果要通信需要通过网关来进行转发。(实现了对应的功能就算网关)
网关上有两张网卡。分别配置了属于两个子网的IP地址。可以在两个网络之间转发数据包。这样我们就拥有了一个连接了两个子网的网络。
比如:子网1中的计算机A发送数据包时,首先计算机A会根据目标IP判断是否跟自己属于同一个子网。如果是同一个子网则直接从网卡发出。如果不是同一个子网,则需要把数据包的目标mac地址改为网关mac,然后发送给网关。网关拿到这一包数据后再通过路由表查询到这一包数据属于子网2,网关修改目标mac地址为计算机B的mac地址。修改原mac为自己的mac.然后从子网2的网卡发出。
(ip判断“身份时用的”,mac转发地址时用的)
以上出现了多次根据目标IP判断数据包因该如何发送的行为,我们就称之为路由。
路由器有一个WAN口接入互联网。多个LAN口接入本地网络。它们就分别属于两个不同的子网。
所以从内网访问互联网就是跨网络的行为。这时候就需要路由器来担任网关的角色。它的行为就叫路由。
图1 路由器
每天个家庭至少有1个路由器来连接网络。
在结尾处,我知道,你可能还是会有一点点小小的疑惑:
我来解答: 交换机和路由器通常配合使用,共同构建完整的网络架构: 1、家庭网络示例:
2、企业网络示例
交换机像个桥梁。 路由器像个交通枢纽。
三次握手是建立连接的过程。
服务端回复完syn+ack之后就建立连接,这是为了防止因为已失效的请求报文,突然又传到服务器引起错误。
第一次复习:怎么总感觉,四次握手,像一对热恋的人一样呀?分开了,还恋恋不舍😄
处于连接状态的客户端和服务端,都可以发起关闭连接请求,此时需要四次挥手来进行连接关闭。
为什么客户端需要等待超时时间?
这是为了保证服务端已收到ack包。
为什么要四次挥手?
注意:我们上面写的ack和ACK,不是同一个概念:
借鉴博客:
1、我自己的笔记
2、菜鸟文档
4、TCP详解
::有道云笔记点击入口::
如果有帮助、记得点赞+收藏呐(〃 ̄︶ ̄)人( ̄︶ ̄〃)