0%

说明

  • 本文主要对json内容的读取

实例

  • data.json文件内容
1
{"code": 1, "msg":"success", "data":[{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]}
  • main.go代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"

"github.com/tidwall/gjson"
)

func strGjson() {
// go get -u github.com/tidwall/gjson
jsonstr := `{"code": 1, "msg":"success", "data":[{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]}`
results := gjson.GetMany(jsonstr, "code", "msg", "data")
fmt.Println(results) //[1 success [{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]]
data := results[2]
fmt.Println(data) //[{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]
if data.Exists() {
re := data.Array()
for _, v := range re {
fmt.Println(v) // {"id": 1, "name":"西瓜"}
fmt.Println(v.Get("name"))
}
}

}

func readByteJson() {
f, err := os.Open("hello_work\\data.json")
if err != nil {
fmt.Println("open file eror:", err)
return
}
defer f.Close()
// 不写结构体,直接用这种方式定义
formData := make(map[string]interface{})
// 解码json
json.NewDecoder(f).Decode(&formData)
// 第一次循环,取map值
for key, value := range formData {
fmt.Println("key:", key, " => value :", value)
// 第二次循环,取数组的值
if _, ok := value.([]interface{}); ok {
for k, v := range value.([]interface{}) {
fmt.Println("key:", k, " => value :", v)
// 第三次循环,取数组中map的值
for j, s := range v.(map[string]interface{}) {
fmt.Printf("%s: %v\n", j, s)

}
}
}
}

}
func byteGjson() {
// go get -u github.com/tidwall/gjson
f, err := os.Open("hello_work\\data.json")
if err != nil {
fmt.Println("open file eror:", err)
return
}
defer f.Close()
// 读取json内容,类型为[]byte
byteValue, _ := ioutil.ReadAll(f)
data := gjson.GetBytes(byteValue, "data")
code := gjson.GetBytes(byteValue, "code")
fmt.Println(data) // [{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]
fmt.Println(code)
if data.Exists() {
re := data.Array()
for _, v := range re {
fmt.Println(v) // {"id": 1, "name":"西瓜"}
fmt.Println(v.Get("name"))
}
}

}

func main() {
// readJson()
// byteGjson()
strGjson()
}

socket

  • Socket又称“套接字”,应用程序通常通过“套接字”向网络发出请求或者应答网络请求

  • 常用的Socket类型有两种:流式Socket和数据报式Socket,流式是一种面向连接的Socket,针对于面向连接的TCP服务应用,数据报式Socket是一种无连接的Socket,针对于无连接的UDP服务应用

  • TCP:比较靠谱,面向连接,比较慢

  • UDP:不是太靠谱,比较快

  • 举个例子:TCP就像货到付款的快递,送到家还必须见到你人才算一整套流程。UDP就像某快递快递柜一扔就走管你收到收不到,一般直播用UDP。

TCP编程

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# hello_work\tcp\server.go
package main

import (
"fmt"
"net"
)

func process(conn net.Conn) {
defer conn.Close()
for {
var buff [128]byte
// 将tcp连接读取到的数据读取到byte数组中, 返回读取到的byte的数目
n, err := conn.Read(buff[:])
if err != nil {
// 从客户端读取数据的过程中发生错误
fmt.Println("read from client failed, err:", err)
break
}
recvStr := string(buff[:n])
fmt.Println("收到client端发来的数据", recvStr)
// conn.Write([]byte(recvStr)) // 发送数据
conn.Write([]byte("这是服务端返回的数据")) // 发送数据

}
}

func main() {
// 监听当前的tcp连接
listen, err := net.Listen("tcp", "127.0.0.1:2000")
if err != nil {
fmt.Println("liston failed, error:", err)
return
}
for {
conn, err := listen.Accept() //建立tcp连接
fmt.Print("建立tcp连接")
if err != nil {
fmt.Println("aceept failed,error", err)
continue
}
// 启动一个新的线程
go process(conn)
}
}

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

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

func main() {
// 连接tcp服务器
conn, err := net.Dial("tcp", "127.0.0.1:2000")
if err != nil {
fmt.Println("err:", err)
return
}
fmt.Println("连接tcp服务成功")
defer conn.Close()
// 获取一个标准输入的*Reader结构体指针类型的变
inputReader := bufio.NewReader(os.Stdin)
for {
// 读取用户输入
input, _ := inputReader.ReadString('\n')
// 去掉\r\n
inputInfo := strings.Trim(input, "\r\n")
// 用户输入q,就退出
if strings.ToUpper(inputInfo) == "Q" {
return
}
// 发送数据到tcp服务端
_, err := conn.Write([]byte(inputInfo))
if err != nil {
fmt.Println("send data is worng,error:", err)
return
}
buff := [512]byte{}
// var buff [512]byte
// 读取服务端发送的数据
n, err := conn.Read(buff[:])
if err != nil {
fmt.Println("recv failed, err:", err)
return
}
fmt.Println("客户端接受服务端的数据为:", string(buff[:n]))
}
}

运行

  • 分别运行go run hello_work/tcp/server.gogo run hello_work/tcp/client.go

  • 客户端输入数据后,服务端返回数据

1
2
3
4
5
6
7
8
9
10
PS E:\proj\gowork\hello_work\tcp> go run .\server.go
建立tcp连接收到client端发来的数据 22
收到client端发来的数据 333

PS E:\proj\gowork> go run .\hello_work\tcp\client.go
连接tcp服务成功
22
客户端接受服务端的数据为: 这是服务端返回的数据
333
客户端接受服务端的数据为: 这

http

  • HTTP协议通常承载于TCP协议之上
  • 下面的实例包含两部分,一部分是最简单的http服务器和客户端,另外一部分是展示如何使用json传递消息

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# hello_work/http/server.go
package main

import (
"fmt"
"net/http"
)

func myhandler(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.RemoteAddr, "连接成功")
fmt.Println("method:", r.Method)
fmt.Println("url:", r.URL)
fmt.Println("header:", r.Header)
fmt.Println("body:", r.Body)
// 服务器回复内容
w.Write([]byte("hello word!"))
}

func main() {
// 监控客户端发送的请求网址后缀为/go,并调用自定义回调函数
http.HandleFunc("/go", myhandler)
// http.HandleFunc("/to", myhandler1)
http.ListenAndServe("127.0.0.1:8000", nil)
}

  • 启动server
1
go run hello_work/http/server.go

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# hello_work/http/client
package main

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

func main() {
resp, err := http.Get("http://127.0.0.1:8000/go")
if err != nil {
fmt.Println("连接服务器失败,error", err)
return
}
defer resp.Body.Close()
fmt.Println("code:", resp.StatusCode)
buf := make([]byte, 1024)
for {
// 接受服务器信息
n, err := resp.Body.Read(buf)
if err != nil && err != io.EOF {
fmt.Println("读取服务器数据失败,error:", err)
return
}
fmt.Println("读取服务器数据成功")
res := string(buf[:n])
fmt.Println(res)
break
}

}

  • 运行客户端
1
2
3
4
5
PS E:\proj\gowork\hello_work> go run .\http\client.go
code: 200
读取服务器数据成功
hello word!
PS E:\proj\gowork\hello_work>
  • 实际上的场景肯定复杂的多,比如常见的客户端和服务端都是用json交互,如下面代码

server1

  • 服务器代码进行优化,当请问为post时,接受客户端的json参数,响应结果也是以json字符串进行返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# hello_work/http/server.go
package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
)

type Data struct {
Name string
Age int
}

// 定义返回给客户端的结构体
type Ret struct {
Code int
Param string
Msg string
Data []Data
}

func myHandlerJson(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
err := r.ParseForm()
// 接受客户端传过来的json
formData := make(map[string]interface{})
if err != nil {
fmt.Println("读取客户端的数据失败,error:", err)
return
}
// 调用json包的解析,解析请求body
json.NewDecoder(r.Body).Decode(&formData)
// 打印客户端传过来的json值
for key, value := range formData {
fmt.Println("key:", key, " => value :", value)
}

data := Data{Name: "li", Age: 18}
ret := new(Ret)
ret.Code = 0
ret.Msg = "success"
ret.Param = "1"
ret.Data = append(ret.Data, data)
ret.Data = append(ret.Data, data)
ret.Data = append(ret.Data, data)
// 将结构体转换为切片
ret_json, _ := json.Marshal(ret)
// 传递参数给客户端
io.WriteString(w, string(ret_json))

}
}

func myhandler(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.RemoteAddr, "连接成功")
fmt.Println("method:", r.Method)
fmt.Println("url:", r.URL)
fmt.Println("header:", r.Header)
fmt.Println("body:", r.Body)
// 服务器回复内容
w.Write([]byte("hello word!"))
}

func main() {
// 监控客户端发送的请求网址后缀为/go,并调用自定义回调函数
http.HandleFunc("/go", myhandler)
http.HandleFunc("/bar", myHandlerJson)
http.ListenAndServe("127.0.0.1:8000", nil)
}

client1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#  hello_work/http/client
package main

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)

type Cmd struct {
ReqType int
username string
pwd string
}

func start(s string) {
if strings.ToUpper(s) == "POST" {
contentType := "application/json;charset=utf-8"
url := "http://127.0.0.1:8000/bar"

// 定义传送给服务器的json
cmd := Cmd{ReqType: 1, Username: "test1", Pwd: "123456"}
// 把结构体转换为json
b, err := json.Marshal(cmd)
if err != nil {
fmt.Println("json格式错误,error:", err)
return
}
// 把json参数转换为bytes,传给服务端
body := bytes.NewBuffer(b)
resp, err := http.Post(url, contentType, body)
if err != nil {
fmt.Println("http request is fail,error:", err)
}
defer resp.Body.Close()

// content, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// fmt.Println("Read failed:", err)
// return
// }
// 不写结构体,直接用这种方式定义
formData := make(map[string]interface{})
json.NewDecoder(resp.Body).Decode(&formData)

// jsonErr := json.Unmarshal(content, &formData) // 解码服务端传递的json
// if jsonErr != nil {
// fmt.Println(jsonErr)
// }
// 数据结构体
//map[Code:0 Data:[map[Age:18 Name:li] map[Age:18 Name:li] map[Age:18 Name:li]] Msg:success Param:1]
fmt.Println("读取服务器数据成功", formData)

for key, value := range formData {
fmt.Printf("%s: %v\n", key, value)
if key == "Data" {
// 判断value是否是一个切片,再使用for循环遍历切片中的每个元素
if _, ok := value.([]interface{}); ok {
for _, v := range value.([]interface{}) {
// 循环取map中的值
for j, s := range v.(map[string]interface{}) {
fmt.Printf("%s: %v\n", j, s)

}

}
}

}

}

} else {

resp, err := http.Get("http://127.0.0.1:8000/go")
if err != nil {
fmt.Println("连接服务器失败,error", err)
return
}
defer resp.Body.Close()
fmt.Println("code:", resp.StatusCode)
buf := make([]byte, 1024)
for {
// 接受服务器信息
n, err := resp.Body.Read(buf)
if err != nil && err != io.EOF {
fmt.Println("读取服务器数据失败,error:", err)
return
}
fmt.Println("读取服务器数据成功")
res := string(buf[:n])
fmt.Println(res)
break
}
}
}

func main() {

start("post")

}

  • 运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
S E:\proj\gowork> go run .\hello_work\http\client.go
读取服务器数据成功 map[Code:0 Data:[map[Age:18 Name:li] map[Age:18 Name:li] map[Age:18 Name:li]] Msg:success Param:1]
Code: 0
Param: 1
Msg: success
Data: [map[Age:18 Name:li] map[Age:18 Name:li] map[Age:18 Name:li]]
Name: li
Age: 18
Name: li
Age: 18
Name: li
Age: 18

PS E:\proj\gowork> go run .\hello_work\http\server.go
key: ReqType => value : 1
key: Username => value : test1
key: Pwd => value : 123456
key: ReqType => value : 1

总结

  • 在处理json这种数据结构时,感觉go还是比较复杂,还是python用起来更舒服

  • 网络编程

环境搭建

  • 打开官网,下载win平台下的msi安装文件,环境变量设置E:\app\Go\bin

  • 本地新建目录,E:\proj\gowork作为工作空间

  • 初始化项目

1
2
3
4
5
6
7
8
9
cd E:\proj\gowork
mkdir hello_work
cd hello_work
go mod init example.com/hello_work

$ go mod init example.com/hello_work
go: creating new go.mod: module example.com/hello_work
go: to add module requirements and sums:
go mod tidy

go mod tidy 执行后,把当前项目中不需要用的依赖文件删除,也就是go.mod中添加的依赖文件

  • 当前最新的工作目录如下
1
2
3
4
Administrator@WIN-5TF67LA12I4 MINGW64 /e/proj/gowork
$ ls
go.mod hello_work/

  • 下载并打开Visual Studio Code,作为开发go的工具,扩展中安装GO
  • 按快捷键ctrl+shift+x,打开扩展,安装GO和chinese

image-20230525113031586

  • 安装go tools的依赖插件
1
2
go env -w GO111MODULE=on
go env -w GOPROXY=https://proxy.golang.com.cn,direct
  • Ctrl+Shift+P 然后搜索>Go:Install/Update Tools 然后勾选全部项目即可。

image-20230525114744401

实践

  • vscode 打开E:\proj\gowork目录
  • 在终端中拉取远程的依赖项目代码:go get github.com/georgehao/gomodtestc
  • 拉取远程代码成功后,go.mod文件中生产的代码如下
1
2
3
4
5
6
7
8
module example.com/hello_work

go 1.20

require (
github.com/georgehao/gomodtestc v1.0.1 // indirect
)

源码分析

image-20230531170143420

  • hello_wrok/main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"

"example.com/hello_work/hello_work/util" // 引用本地其他目录下的文件
"github.com/georgehao/gomodtestc" // 引用远程项目地址
)

func main() {
fmt.Println(gomodtestc.PrintStr("Hello", 100)) // 调用远程项目的代码
Print_hello() // 相同文件夹内的函数直接调用,来自于代码hello.go
util.Print_work() //util是包名
}

在编写代码中,比如输入Print_hello,import自动导入依赖包

  • hello_work/hello.go
1
2
3
4
5
6
7
8
package main

import "fmt"

func Print_hello() {
fmt.Println("Print_hello!")
}

  • hello_work/util/work.go
1
2
3
4
5
6
7
8
9
10
11
12
package util

import "fmt"

func init() {
fmt.Println("imp-init() come here.")
}

func Print_work() {
fmt.Println("Hello!")
}

  • 执行命令,如下go run 执行了两个go文件,因为不这样执行无法调用到hello.go中的函数,理论上根目录下只有一个main.go函数,其他函数写到其他目录下
1
2
3
4
5
PS E:\proj\gowork> go run .\hello_work\main.go .\hello_work\hello.go
imp-init() come here.
project C Hello_100
Print_hello!
Hello!

基础语法

总结

常用组件汇总

  • 看如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from tkinter import *
from tkinter import messagebox

root = Tk()
root.title('我的gui程序')
root.geometry('500x300+100+200')
# 500宽度 300高度 距屏幕左侧100像素 顶部200像素
bt = Button(root)
bt['text'] = '点我'
bt.pack()


def click(e):
print(e)
messagebox.showinfo('message', 'give flower') # 提示框

bt.bind('<Button-1>', click) # 绑定点击事件
root.mainloop() # 进入事件循环
  • 查看root = Tk(),查看源码,发现其实就是实例化class Tk(Misc, Wm),发现注释中写明了是Base class,经过搜索发现了6个基类,这6个基类包含了常见的组件

image-20230517161613275

  • 组件汇总如下

image-20230517161741836

image-20230517161833878

面向对象写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from tkinter import *
from tkinter import messagebox


class Application(Frame):
"""
继承Frame容器类
"""
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()

self.createWidget()

def createWidget(self):
# 创建组件
self.btn01 = Button(self)
self.btn01['text'] = '点击送花'
self.btn01.pack()
self.btn01['command'] = self.songhua
# 创建一个退出按钮
self.btnQuit = Button(self, text='退出', command=root.destroy)
self.btnQuit.pack()

def songhua(self):
messagebox.showinfo('送花', '送你99朵玫瑰花')


root = Tk()
root.geometry('400x100+200+300')
root.title('一个经典的GUI程序类的测试')
app = Application(master=root)
root.mainloop()

常见组件用法

label

  • 主要用于显示文本信息,也可以显示图像,常见属性如下

  • width,height
    用于指定区域大小,如果显示是文本,则以单个英文字符大小为单位(一个汉字宽度占 2 个字符位置,高度和英文字符一样);如果显示是图像,则以像素为单位。默认值是根据具体显示的内容动态调整。

  • font
    指定字体和字体大小,如:font =(font_name,size)

  • image
    显示在 Label 上的图像,目前 tkinter 只支持 gif 格式。

  • fgbg
    fg(foreground):前景色、bg(background):背景色

  • justify
    针对多行文字的对齐,可设置 justify 属性,可选值”left” “center” “right”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from tkinter import *


class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.createWidget()

def createWidget(self):
# 创建组件
self.label01 = Label(self, text='关关雎鸠', width=10, height=2,
bg='black', fg='white')
self.label01.pack() # self.label01.pack(side=TOP)
self.label02 = Label(self, text='hebut', width=10, height=2,
bg='blue', fg='white', font=('黑体', 30))
self.label02.pack()
# 显示图像
global photo
# photo = PhotoImage(file='pic01.gif')
# self.label03 = Label(self, image=photo)
# self.label03.pack()
# 显示多行文本
self.label04 = Label(self, text='hebut\n关关雎鸠', borderwidth=1, relief='groove', justify='right')
self.label04.pack()


if __name__ == '__main__':
root = Tk()
root.geometry('400x500+200+300')
app = Application(master=root)
root.mainloop()

上述例子中,注意pack()的用法,是tkinter中一种布局管理的方法,文章后面介绍了三种布局

image-20230518093353160

options

我们可以通过 Options 设置组件的属性,从而控制组件的各种状态。比如:宽度、高度、颜色、位置等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.createWidget()

def createWidget(self):
fred = Label(self, text='什么?', fg="red", bg="blue")
fred["fg"] = "red"
fred["bg"] = "blue"
fred.config(fg="white", bg="blue")
fred.pack()

self.btn01 = Button(self, text="点击送花,变色")
# self.btn01.pack()
self.btn01["command"] = self.click()
def click(self):
messagebox.showinfo("送花","送你99朵花")
self.btn01.config(fg="white", bg="black")
print(self.btn01.config()) # 查看 Options 选项的方法

print(self.btn01.config()) 可查看到options中的常用方法

Button-Entry

  • Button(按钮)用来执行用户的单击操作。Button 可以包含文本,也可以包含图像。按钮被单击后会自动调用对应事件绑定的方法。
  • Entry,是单行文本框
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from tkinter import *
from tkinter import messagebox


class Application(Frame):
def __init__(self, master=None): # super()代表的是父类的定义,而不是父类对象
super().__init__(master)
self.master = master
self.pack()
self.createWidget()

def createWidget(self):
# 创建登录界面的组件
self.label01 = Label(self, text="用户名")
self.label01.pack()

# StringVar 变量绑定到指定的组件。
# StringVar 变量的值发生变化,组件内容也变化;
# 组件内容发生变化,StringVar 变量的值也发生变化。 v1 = StringVar()
v1 = StringVar()
self.entry01 = Entry(self, textvariable=v1)
self.entry01.pack()
v1.set("admin")
print(v1.get())
print(self.entry01.get())

# 创建密码框
self.label02 = Label(self, text="密码")
self.label02.pack()

v2 = StringVar()
self.entry02 = Entry(self, textvariable=v2, show="*")
self.entry02.pack()

Button(self, text="登陆", command=self.login).pack()

def login(self):
username = self.entry01.get()
pwd = self.entry02.get()
print("去数据库比对用户名和密码!")
print("用户名:" + username)
print("密码:" + pwd)
if username == "关关雎鸠" and pwd == "123456":
messagebox.showinfo("学习系统", "登录成功!欢迎开始学习!")
else:
messagebox.showinfo("学习系统", "登录失败!用户名或密码错误!")


if __name__ == '__main__':
root = Tk()
root.geometry("400x130+200+300")
app = Application(master=root)
root.mainloop()

  • 常见的组件比如Text多行文本框、Radiobutton 单选按钮、Checkbutton 复选按钮、canvas 画布等不做演示

布局管理

tkinter 提供了三种管理器:pack、grid、place

grid 布局管理器

grid 表格布局,采用表格结构组织组件。子组件的位置由行和列的单元格来确定,并且可以跨行和跨列,从而实现复杂的布局。

image-20230518094805637

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 测试 Grid 布局管理器的基本用法,使用面向对象的方式
from tkinter import *

class Application(Frame):
def __init__(self, master=None):
# super()代表的是父类的定义,而不是父类对象
super().__init__(master)
self.master = master
self.pack()
self.createWidget()

def createWidget(self):
# 通过 grid 布局实现登录界面
Label(self,text="用户名").grid(row=0,column=0)
Entry(self).grid(row=0,column=1)
Label(self,text="用户名为手机号").grid(row=0,column=2)
Label(self, text="密码").grid(row=1, column=0)
Entry(self, show="*").grid(row=1, column=1)
Button(self, text="登录").grid(row=2, column=1, sticky=EW)
Button(self, text="取消").grid(row=2, column=2, sticky=EW)

if __name__ == '__main__':
root = Tk()
root.geometry("400x90+200+300")
app = Application(master=root)
root.mainloop()

image-20230518100007136

pack布局

  • 三种布局中最简单的
  • pack 按照组件的创建顺序将子组件添加到父组件中,按照垂直或者水平的方向自然排布。如果不指定任何选项,默认在父组件中自顶向下垂直添加组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 测试 pack 布局管理
from tkinter import *

root = Tk()
root.geometry("700x220")
# Frame 是一个矩形区域,就是用来防止其他子组件
f1 = Frame(root)
f1.pack()
f2 = Frame(root)
f2.pack()
btnText = ("流行风", "中国风", "日本风", "重金属", "轻音乐")
for txt in btnText:
Button(f1, text=txt).pack(side="left", padx="10")
for i in range(1, 20):
Button(f2, width=5, height=10, bg="black" if i % 2 == 0 else "white").pack(side="left")
root.mainloop()

  • 常用方法

image-20230518113530055

place 布局管理器

place 布局管理器可以通过坐标精确控制组件的位置,适用于一些布局更加灵活的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from tkinter import *

root= Tk()
root.geometry("500x300")
root.title("布局管理 place")
root["bg"]="white"


f1= Frame(root,width=200,height=200,bg="green")
f1.place(x=30,y=30)
Button(root,text="hebut").place(relx=0.5,rely=0,x=100,y=200,relwidth=0.2,relheight=0.2)
Button(f1,text="programmer").place(relx=0.6,rely=0.7)
Button(f1,text="关关雎鸠").place(relx=0.2,rely=0.2)

root.mainloop()

image-20230518114625901

打包

  • 源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

from tkinter import *
from tkinter import messagebox
import requests

class Application(Frame):
def __init__(self, master=None): # super()代表的是父类的定义,而不是父类对象
super().__init__(master)
self.master = master
self.pack()
self.createWidget()

def createWidget(self):
# 创建登录界面的组件
self.label01 = Label(self, text="用户名")
self.label01.pack()

# StringVar 变量绑定到指定的组件。
# StringVar 变量的值发生变化,组件内容也变化;
# 组件内容发生变化,StringVar 变量的值也发生变化。 v1 = StringVar()
v1 = StringVar()
self.entry01 = Entry(self, textvariable=v1)
self.entry01.pack()
v1.set("admin")
print(v1.get())
print(self.entry01.get())

# 创建密码框
self.label02 = Label(self, text="密码")
self.label02.pack()

v2 = StringVar()
self.entry02 = Entry(self, textvariable=v2, show="*")
self.entry02.pack()

Button(self, text="登陆", command=self.login).pack()

def login(self):
username = self.entry01.get()
pwd = self.entry02.get()
print("去数据库比对用户名和密码!")
print("用户名:" + username)
print("密码:" + pwd)
if username == "admin" and pwd == "123456":
messagebox.showinfo("学习系统", "登录成功!欢迎开始学习!")
res = requests.get("http://www.baidu.com")
print(res.status_code)
else:
messagebox.showinfo("学习系统", "登录失败!用户名或密码错误!")


if __name__ == '__main__':
root = Tk()
root.geometry("400x130+200+300")
app = Application(master=root)
root.mainloop()

  • 安装打包依赖文件
1
pip install pyinstaller  -i https://pypi.tuna.tsinghua.edu.cn/simple
  • 打包
1
D:\project\studyTK>pyinstaller -F main.py
  • 执行命令完成后,在dist目录就发现了打包好的exe文件,运行即可

image-20230518151216662

迭代器

  • 内部含有_iter_方法和_next_方法都是迭代器
    • 可迭代对象 都可以通过内置函数 iter转换为迭代器
    • 内置函数 next 进行迭代操作,当所有数据迭代完毕后,再使用 next 迭代,会抛出异常 StopIteration
  • 使用for循环容器取值的都是可迭代的

容器就是将多个元素在一起的单元,并且,是可以迭代的,比如列表,字典,元组,字符串

  • 如下代码
1
2
3
4
5
6
7
8
# 将列表转换为一个迭代器
iter_li = iter([11,22)
# 通过next对迭代器进行迭代操作,每次可以迭代出来一个数据
s1 = next(iter_li)
print('s1:',s1)
s2 = next(iter_li) # s1 :11
print('s2:',s2) # s2 :22
s3 = next(iter_li) # StopIteration 错误

生成器

  • 是一种特殊的迭代器,具备迭代器所有的特性。但其相比与迭代器,占用的内存更少

  • 他本质上是一个函数,只不过函数的return改为了yield语句,正常的for循环语句,会不停的遍历容器的对象,然后返回(如果有return的话),当我们用yield取而代之

  • 每当函数执行时遇到yield时,他会记住此时的一个位置并挂起,等到使用__next__操作时,才由上一次挂起的位置继续执行。生成器不仅“记住”了它的数据状态,生成还记住了程序执行的位置

  • python 中定义生成器,一共有两种方式,一种是生成器表达式,另一种是生成器函数。

生成器表达式

生成器表达式的语法其实就是把列表推导式的中括号改成小括号,如下:

1
2
3
4
5
6
7
gen_ =(item for item in range(2))
print(gen_)
s1 = next(gen_)
print(s1)
next(gen_)
next(gen_) # 报错StopIteration

生成器函数

在函数中使用 yield关键字可以定义一个生成器函数

1
2
3
4
5
6
7
8
9
10
def func():
for i in range(1):
yield i

gen_list = func()
# 调用函数
gen_lsit = func()
s2 = next(gen_list)
print(s2)
next(gen_list) # 报错StopIteration

自定义类生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from collections import Iterable
from collections import Iterator
import time


class Classmate(object):
"""定义一个同学类"""

def __init__(self):
self.name = list()
self.name_num = 0

def add(self, name):
self.name.append(name)

def __iter__(self):
return self # 返回本身

def __next__(self):
if self.name_num < len(self.name):
ret = self.name[self.name_num]
self.name_num += 1
return ret

# 抛出异常,当循环后自动结束
else:
print("-------StopIteration---")
raise StopIteration


class1 = Classmate()
class1.add("张三")
class1.add("李四")
class1.add("王五")

print("判断是否是可迭代的对象:", isinstance(class1, Iterable))

print("判断是否是迭代器:", isinstance(class1, Iterator))

for name in class1:
print(name)
time.sleep(1)

# 结果为
判断是否是可迭代的对象: True
判断是否是迭代器: True
张三
李四
王五
-------StopIteration---

总结两者的区别

  • 生成器就是迭代器,反之则不是
  • 迭代器,在创建的时候已经生成(比如上面迭代器的列子),在需要的时候再去操作迭代器加载元素到内存中
  • 而生成器,本质是一个可以暂时挂起的函数,等到需要时再继续执行去生成元素,这样子,使得生成器比迭代器更省内存。

Dependency-Check

  • 使用 “存在已知漏洞的组件” 已经成为OWASP TOP 10的漏洞之一了,他是一个开源的程序,主要用于识别项目依赖项并检查是否存在已知的,公开披露的漏洞,目前支持Java.NETRubyNode.jsPython等语言。
  • 主要功能是对jar依赖包进行扫描。他的简单工作原理是依靠强大的库,与被扫jar依赖包进行比对,输出jar包详情。所以该工具只能扫描出已经公布的,无法扫描0day

工作原理

  • Dependency-Check工作的方式是通过分析器对文件进行扫描搜集信息,搜集到的信息被叫做迹象。
  • 这边共搜集3种迹象,分时是vendor(供应商),product(产品)和version(版本)。例如,jarAnalyzer将从jar文件包中的Mainfestpom.xml和包名进行信息搜集,然后把各种搜集到的源放到一个或者多个迹象表里。
  • 通过搜集到的迹象和CPE条目(NVD 美国国家通用数据库、CVE数据索引)进行匹配,分析器匹配到了就会给个标志发送到报告。
  • Dependency-Check 目前不使用hash识别文件,因为第三方依赖从源码中的hash值构建通常不会匹配官方发布版本的hash。后续版本中可能会增加一些hash来匹配一些常用的第三方库,例如Spring, Struts等。

使用

  • 本地环境:win10 x64

  • 打开官网,点击command Line下载文件,并解压

image-20230324164847298

  • 进入到bin目录
1
D:\app\dependency-check-8.2.1-release\dependency-check\bin>
  • 常用参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  --advancedHelp              Print the advanced help message.
--enableExperimental Enables the experimental analyzers.
--exclude <pattern> 指定一个排除模式。这个选项可以多次指定,它接受Ant风格的排除”
-f,--format <format> The report format (HTML, XML, CSV, JSON,
JUNIT, SARIF, JENKINS, or ALL). The
default is HTML. Multiple format
parameters can be specified.
--failOnCVSS <score> 指定如果识别到高于指定级别的CVSS评分,是否应该使构建失败。
默认值是11;因为CVSS评分是0-10,所以默认情况下构建永远不会失败

-h,--help Print this message.
--junitFailOnCVSS <score> 指定在生成junit报告时被视为失败的CVSS评分。默认值是0
-l,--log <file> The file path to write verbose logging
information.
-n,--noupdate 禁用NVD-CVE,hosted-suppressions和RetireJS数据的自动更新
-o,--out <path> 指定报告目录.
--prettyPrint When specified the JSON and XML report
formats will be pretty printed.
--project <name> The name of the project being scanned.
-s,--scan <path> 指定被扫描的jar文件目录
recommended to quote the argument value.
--suppression <file> The file path to the suppression XML file.
This can be specified more then once to
utilize multiple suppression files
-v,--version Print the version information.

  • CVE
  • CVSS评分标准: 漏洞的最终得分最大为10,最小为0。得分710的漏洞通常被认为比较严重,得分在46.9之间的是中级漏洞,0~3.9的则是低级漏洞。
  • 扫描本地jar,远程更新这里的漏洞数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dependency-check.bat --disableRetireJS --disableNodeJS -s  D:\project\checkjar  -o D:\project\report

[INFO] NVD CVE requires several updates; this could take a couple of minutes.
[INFO] Download Started for NVD CVE - 2002
[INFO] Download Complete for NVD CVE - 2002 (4454 ms)
[INFO] Processing Started for NVD CVE - 2002
[INFO] Download Started for NVD CVE - 2003
[INFO] Processing Complete for NVD CVE - 2002 (6277 ms)
[INFO] Download Complete for NVD CVE - 2003 (2566 ms)
[INFO] Processing Started for NVD CVE - 2003
[INFO] Processing Complete for NVD CVE - 2003 (1376 ms)
...
INFO] Finished Dependency Bundling Analyzer (0 seconds)
[INFO] Finished Unused Suppression Rule Analyzer (0 seconds)
[INFO] Analysis Complete (8 seconds)
[INFO] Writing report to: D:\project\report\dependency-check-report.html

报告分析

  • 查看这里的测试报告
1
D:\project\report\dependency-check-report.html
  • 比如log4j-core-2.16.0.jar 发现两个中等漏洞

image-20230325101328304

  • 当配置使用JDBC AppenderJNDI LDAP数据源URI时,Apache Log4j2版本2.0-beta72.17.0(不包括安全修复版本2.3.2和2.12.4)容易受到远程代码执行(RCE)攻击,如果攻击者控制了目标LDAP服务器。这个问题通过将JNDI数据源名称限制为java协议在Log4j2版本2.17.1,2.12.42.3.2中得到修复

image-20230325101710088

离线审计

  • 这里没有实践
  • 当然了,如果你是离线审计,也可以将NVD库搭建到本地,这样就会更加的方便,我们可以在本地搭建一个NVD库来提高更新效率,具体可以参考这里
  • 具体命令如下,其中cveUrlModifiedcveUrlModified指定本地NVD
1
2
3
4
dependency-check.bat
--cveUrlModified 本地nvd库的url/nvdcve-1.1-modified.json.gz
--cveUrlBase本地nvd库的url/nvdcve-1.1-2020.json.gz
--project test -s D:\checkjar\ -o D:\report\

集成

  • 此处没有实践,转载这里

与maven集成

  • Dependency-check-maven非常易于使用,可以作为独立插件使用,也可以作为maven site的一部分使用。该插件需要使用Maven 3.1或更高版本,第一次执行时,可能需要20分钟或更长时间,因为它会从NIST托管的国家漏洞数据库下载漏洞数据到本地备份库。第一次批量下载后,只要插件每七天至少执行一次,本地漏洞库就会自动更新,更新只需几秒钟。
  • 集成很简单,只需要在项目的pom文件中增加maven配置即可。

用法一 在target目录中创建dependency-check-report.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>4.0.2</version>
<configuration>
<autoUpdate>true</autoUpdate>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>

用法二 在maven site中创建聚合性的报告

1
2
3
4
5
6
7
8
9
10
11
12
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>4.0.2</version>
<reportSets>
<reportSet>
<reports>
<report>aggregate</report>
</reports>
</reportSet>
</reportSets>
</plugin>

用法三 设置当风险指数(CVSS)大于等于8时(CVSS分数为0-10)则项目编译失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>4.0.2</version>
<configuration>
<failBuildOnCVSS>8</failBuildOnCVSS>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>

用法四 仅更新NVD(漏洞库)数据,而不执行检查

1
2
3
4
5
6
7
8
9
10
11
12
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>4.0.2</version>
<executions>
<execution>
<goals>
<goal>update-only</goal>
</goals>
</execution>
</executions>
</plugin>

更多配置信息,可以根据实际情况在官网查找:参考链接

与Jenkins集成

  • Jenkins中需要安装插件:Static Analysis UtilitiesDependency-Check
    该插件具有执行依赖关系分析和构建后查看检查结果的功能。
    执行依赖分析配置:
    image-20230325103550702

  • 查看检查分析结果配置:

image-20230325103620374

SonarQube 7.x集成

  • 与代码质量管理平台SonarQube 7.x以上的版本集成

  • 将插件(jar文件)复制到$SONAR_INSTALL_DIR/extensions/plugins并重新启动SonarQube
    但需要添加以下配置:

1
2
3
4
sonar.dependencyCheck.reportPath = ${WORKSPACE}/dependency-check-report.xml
##以Jenkins为例报告.xml路径
sonar.dependencyCheck.htmlReportPath = ${WORKSPACE}/dependency-check-report.html
##以Jenkins为例报告.html路径
  • 问题严重性分数设定:
1
2
3
4
sonar.dependencyCheck.severity.blocker = 9.0
sonar.dependencyCheck.severity.critical = 7.0
sonar.dependencyCheck.severity.major = 4.0
sonar.dependencyCheck.severity.minor = 0.0

报告查看

image-20230325103731194

image-20230325103754549

其他

  • 扫描python
  • 测试报告不好看,有能力的话,可以基于开源的代码进行修改

说明

本机环境,python3.7 ,win10 64

操作sqlite

  • 新建一个空的后缀名文件sample.db

  • 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import time
import sqlite3

#打开数据库
def open_db():
#创建SQLite数据库
con=sqlite3.connect(r"D:\project\pythonSql\sample.db")
#创建表book:包含3列,id(主键,学号),name,tel
# con.execute("create table if not exists book(id primary key,name,tel)")
con.execute("create table if not exists book(id INTEGER PRIMARY KEY, name TEXT, tel TEXT, timer TEXT);")

#创建游标对象
cur=con.cursor()
# 涉及到修改,删除等入库的操作必须用con.commit()提交事务最终完成
# cur 主要是进行查询
return con,cur

#查询全部信息
def show_all_db():
print("******通讯录现有数据******")
cur_1=open_db()[1]
cur_1.execute("select id,name,tel from book")
for row in cur_1:
print(row)
print(row[0])



#向数据库中添加内容
def add_db(name,tel):
print("******数据添加功能******")
cur_1=open_db()
# cur_1[1].execute("insert into book(id,name,tel) values(?,?,?)",(id,name,tel))
cur_1[1].execute("insert into book(name,tel) values(?,?)",(name,tel))
cur_1[0].commit()
print("******数据添加成功******")

#删除数据库中的内容
def delete_db(del_id):
print("******数据删除功能******")
cur_1=open_db()
cur_1[1].execute("delete from book where id="+del_id)
cur_1[0].commit()
print("******数据删除成功******")
show_all_db()
#关闭游标对象
cur_1[1].close()

#修改数据库中的内容
def alter_db(id,name,tel):
print("******数据修改功能******")
cur_1=open_db()
#更新数据使用 SQL 语句中的 update
cur_1[1].execute("update book set name = ? ,tel = ? where id ="+id,(name,tel))
#游标事务提交
cur_1[0].commit()
show_all_db()
cur_1[1].close()

#查询数据
def query_data(id):

print("******数据查询功能******")
cur_1=open_db()
cur_1[1].execute("select id,name,tel from book where id ="+id)
print("******查询结果如下******")
for row in cur_1[1]:
print(row)
cur_1[1].close()



if __name__=="__main__":
# add_db("你好", "185111")
# show_all_db()
# query_data("1")
# alter_db("1", "我好", "hehe")
# query_data("1")
# delete_db("1")
show_all_db()

  • 使用工具打开sqlitespy打开sqlite文件

image-20221209174201531

操作mysql

  • 本地搭建的mysql环境,省略
  • 使用HeidiSQL,连接数据库,创建表

image-20221209174414824

  • 设置自增主键

image-20221209174525798

  • 操作代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import pymysql

dbinfo = {
"host":"127.0.0.1",
"user":"root",
"password":"123456",
"port":3306
}

class DB:
def __init__(self,dbinfo):
# self.db 只要修改表,必须用self.db.commit()进行事务提交,如果报错可以用self.db.rollback()进行回滚
self.db = pymysql.connect(cursorclass = pymysql.cursors.DictCursor,
**dbinfo)
# self.cursor 用来查询
self.cursor = self.db.cursor()

def select(self,sql):
self.cursor.execute(sql)
result = self.cursor.fetchall()
return result

def execute(self,sql):

self.cursor.execute(sql)
self.db.commit()

# self.db.rollback()
# try:
# self.cursor.execute(sql)
# self.db.commit()
# except:
# self.db.rollback()

def close(self):
self.cursor.close()
self.db.close()

if __name__ == '__main__':
db = DB(dbinfo)
#查询
sql = "SELECT * from test.class"
result = db.select(sql)
print(result)

#新增
# sql2 = "INSERT into test.class(name) VALUES ('五年一班')"
# db.execute(sql2)
# sql = "SELECT * from test.class"
# result = db.select(sql)
# print(result)

# 修改
# sql2 = "update test.class set name='综艺一般' where id=6"
# db.execute(sql2)
# sql = "SELECT * from test.class"
# result = db.select(sql)
# print(result)

# # 删除
# sql2 = "delete from test.class where id=5"
# db.execute(sql2)
# sql = "SELECT * from test.class"
# result = db.select(sql)
# print(result)

Django mysql

  • django安装pip install Django

  • 创建项目及应用

1
2
3
4
5
# 创建应用
D:\project>django-admin startproject pysql
D:\project>cd pysql
# 创建应用下的一个项目
D:\project\mysite> python manage.py startapp myapi
  • django 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# pysql\pysql\setting.py
import pymysql
pymysql.version_info = (1, 4, 13, "final", 0) #指定版本
pymysql.install_as_MySQLdb()



INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapi' # 注册应用
]


// 设置默认数据库为mysql
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'test', # 数据库名字
'USER': 'root', # 帐号
'PASSWORD': '123456', # 密码
'HOST': '127.0.0.1', # IP
'PORT': '3306', # 端口
}
}
  • 设置model中的表字段
1
2
3
4
5
6
7
8
9
10
11
12
#pysql\myapi\model.py
from django.db import models


class class1(models.Model):
id = models.IntegerField().primary_key
name = models.CharField(max_length=128, unique=True)
tel = models.CharField(max_length=256)
c_time = models.DateTimeField(auto_now_add=True)

def __str__(self):
return self.name
  • 让model中的表结构在数据库中生成
1
2
python manage.py makemigrations # 让model生效
python manage.py migrate # 生成表
  • 编写views中代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# pysql/myapi/views.py
import json

from django.core.exceptions import ObjectDoesNotExist
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt

from myapi.models import *

def query_all(request):
res = Class1.objects.all()
resp = []
for i in res:
resp.append({"id": i.id, "name": i.name, "tel": i.tel})
return JsonResponse({"code": 1, "msg": "success", "data": resp})

@csrf_exempt
def delete(request):
data = json.loads(request.body)
id = data.get("id")
if not id:
return JsonResponse({"code": -1, "msg": "id must be fill"})
try:
entry = Class1.objects.get(id=id)
if not entry:
return JsonResponse({"code": -1, "msg": "no effect row"})
entry.delete()
return JsonResponse({"code": 1, "msg": "success"})

except ObjectDoesNotExist:
return JsonResponse({"code": -1, "msg": "no effect row"})

@csrf_exempt
def update(request):
data = json.loads(request.body)
name = data.get('name').strip()
tel = data.get('tel').strip()
ids = data.get("id")
if not name and not ids:
return JsonResponse({"code": -1, "msg": "error"})
try:
entry = Class1.objects.get(id=ids)
if not entry:
return JsonResponse({"code": -1, "msg": "no effect row"})
entry.tel = tel
entry.name = name
entry.save()

return JsonResponse({"code": 1, "msg": "success"})

except ObjectDoesNotExist:
return JsonResponse({"code": -1, "msg": "no effect row"})


def get_class(request, id):
try:
# entry = Class1.objects.filter(id=id)
entry = Class1.objects.get(id=id)
if not entry:
result = {'code': -1, 'msg': 'data is null'}
return JsonResponse(result)
return JsonResponse({"code": 1, "msg": "success", "data": {"name": entry.name, "tel": entry.tel}})

except ObjectDoesNotExist:
result = {'code': -1, 'msg': 'no effect row'}
return JsonResponse(result)

@csrf_exempt
def add(request):
data = json.loads(request.body)
name = data.get("name")
tel = data.get("tel")
if not name or not tel:
res = {'code': -1, 'msg': 'name,tel must be fill'}
return JsonResponse(res)
Class1(name=name, tel=tel).save()
res = {'code': 1, 'msg': 'success'}
return JsonResponse(res)


  • 把views中的对外接口引用到url中
1
2
3
4
5
6
7
8
9
10
11
12
13
# pysql/myapi/urls.py

from django.conf.urls import url
from django.urls import path
from myapi import views

urlpatterns = [
url(r'^query_all/', views.query_all),
url(r'^delete/', views.delete),
url(r'^update/', views.update),
url(r'^add/', views.add),
path('get_class/<int:id>/', views.get_class)
]

把myapi应用的路由url引用到pysql项目中

1
2
3
4
5
6
7
8
9
10
11
12
13
#pysql/pysql/urls.py
from django.conf.urls import url
from django.urls import path

from myapi import views

urlpatterns = [
url(r'^query_all/', views.query_all),
url(r'^delete/', views.delete),
url(r'^update/', views.update),
url(r'^add/', views.add),
path('get_class/<int:id>/', views.get_class)
]
  • 运行Django
1
2
3
4
5
6
7
8
9
10
11
D:\project\pysql>python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
December 09, 2022 - 19:15:56
Django version 3.1.3, using settings 'pysql.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.


  • 模拟前端访问后台接口代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

import requests

resp = requests.post("http://127.0.0.1:8000/myapi/add/", json={"name": "admin1", "tel": "123456"})
print(resp.text)
resp1 = requests.post("http://127.0.0.1:8000/myapi/update/", json={"id": 1,"name": "test", "tel": "7999"})
print(resp1.text)
resp2= requests.post("http://127.0.0.1:8000/myapi/update/", json={"id": 1,"name": "test", "tel": "1111"})
print(resp2.text)
resp3 = requests.post("http://127.0.0.1:8000/myapi/delete/", json={"id": 1})
print(resp3.text)

resp4 = requests.get("http://127.0.0.1:8000/myapi/get_class/1/")
print(resp4.text)

resp4 = requests.get("http://127.0.0.1:8000/myapi/query_all")
print(resp4.text)

  • 得到结果为:
1
2
D:\app\Python37\python.exe D:/project/pysql/test1.py
{"code": 1, "msg": "success", "data": [{"id": 5, "name": "admin1", "tel": "123456"}]}

Django sqlite

  • 由于接入mysql,配置已经改的差不多,只要修改,setting.py的默认数据库引用
1
2
3
4
5
6
7
8
# pysql/pysql/setting.py

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
  • 在pysql文件夹下新建一个空文件db.sqlite3
  • 让model的结构迁移到sqlite3数据库中
1
2
python manage.py makemigrations # 让model生效
python manage.py migrate # 生成表
  • 再次运行django
1
D:\project\pysql>python manage.py runserver
  • 模拟前端访问后台接口代码
1
2
3
4
import requests

resp = requests.post("http://127.0.0.1:8000/myapi/add/", json={"name": "admin1", "tel": "123456"})
print(resp.text)

image-20221209200159234

概述

  • 本文来自这里

  • mitmproxy 相比Charles、fiddler的优点在于,它可以命令行方式或脚本的方式进行mock mitmproxy不仅可以像Charles那样抓包,还可以对请求数据进行二次开发,进入高度二次定制

安装

  • windows下安装,我本机的python版本为3.7
1
pip install mitmproxy
  • 完成后,系统将拥有 mitmproxymitmdumpmitmweb 三个命令,由于 mitmproxy 命令不支持在 windows 系统中运行(这没关系,不用担心),我们可以拿 mitmdump 测试一下安装是否成功,执行:
1
2
3
4
5
6
7
8
C:\Users\Administrator>mitmdump --version
Mitmproxy: 5.3.0
Python: 3.7.9
OpenSSL: OpenSSL 1.1.1h 22 Sep 2020
Platform: Windows-10-10.0.19041-SP0

C:\Users\Administrator>

  • 要启动 mitmproxymitmproxymitmdumpmitmweb 这三个命令中的任意一个即可,这三个命令功能一致,且都可以加载自定义脚本,唯一的区别是交互界面的不同。

生命周期

原文中介绍了HTTPTCP,Websocket 的生命周期,因为用http比较多,只需要用到 http_connectrequestresponse 三个事件就能完成大多数需求了。

实战

  • 启动mitmproxy
1
mitmweb
  • cmd中启动chrome(启动前,需要先关闭所有chrome)
1
"C:\Users\Administrator\AppData\Local\Google\Chrome\Application\chrome.exe " --proxy-server=127.0.0.1:8080 --ignore-certificate-errors

新增三个代码文件分别为:

  • counter.py,这里代码比较简单,就是发送请求前打印信息
1
2
3
4
5
6
7
8
9
10
11
12
13
# counter.py

import mitmproxy.http
from mitmproxy import ctx


class Counter:
def __init__(self):
self.num = 0

def request(self, flow: mitmproxy.http.HTTPFlow):
self.num = self.num + 1
ctx.log.info("We've seen %d flows" % self.num)
  • joker.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import mitmproxy.http
from mitmproxy import ctx, http


class Joker:
def request(self, flow: mitmproxy.http.HTTPFlow):
"""
对百度的搜索发送请求进行拦截,对wd参数修改为360搜索
:param flow:
:return:
"""
if flow.request.host != "www.baidu.com" or not flow.request.path.startswith("/s"):
return
# for i in flow.request.query.keys():
# print(i)
# 抓取请求参数可以看到wd传的是百度搜索的具体内容
if "wd" not in flow.request.query.keys():
ctx.log.warn("can not get search word from %s" % flow.request.pretty_url)
return

ctx.log.info("catch search word: %s" % flow.request.query.get("wd"))
flow.request.query.set_all("wd", ["360搜索"])

def response(self, flow: mitmproxy.http.HTTPFlow):
"""
点击百度搜索中的so搜索后,把当期网页标题修改
:param flow:
:return:
"""
if flow.request.host != "www.so.com":
return

text = flow.response.get_text()
# 网页标题修改
text = text.replace("搜索", "请使用谷歌")
flow.response.set_text(text)

def http_connect(self, flow: mitmproxy.http.HTTPFlow):
"""
连接的请求为google,则把状态码改为404
:param flow:
:return:
"""

if flow.request.host == "www.google.com":
flow.response = http.HTTPResponse.make(404)
# 打印是否设置成功
print(flow.response.status_code)
  • addons.py,这里代码比较简单,只是把自定义事件加载进来
1
2
3
4
5
6
7
import counter
import joker

addons = [
counter.Counter(),
joker.Joker(),
]
  • 运行mitmproxy
1
mitmweb -s addons.py
  • 输入你好,因为搜索关键字被替换为了so搜索,所以结果查到了so搜索

image-20221014183654649

  • 点击360搜索首页,标题被修改了

image-20221014184000637

image-20221014184051730

playwright

优点

  • Selenium需要通过WebDriver操作浏览器;Playwright通过开发者工具与浏览器交互,安装简洁,不需要安装各种Driver。

  • Playwright几乎支持所有语言,且不依赖于各种Driver,通过调用内置浏览器所以启动速度更快。

  • Selenium基于HTTP协议(单向通讯),Playwright基于Websocket(双向通讯)可自动获取浏览器实际情况。

  • 比如使用selenium时,操作元素需要对每个元素进行智能查询等待等,而Playwright为自动等待:

    • 等待元素出现(定位元素时,自动等待30s,时间可以自定义,单位毫秒)
    • 等待事件发生
  • Playwright速度比selenium快很多,还支持异步方式

  • 支持使用API的方式发送请求

限制

  • 不支持旧版Edge和IE11。Playwright不支持传统的Microsoft Edge或IE11,支持新的Microsoft Edge (在Chromium上)。
  • 在真实移动设备上测试: Playwright使用桌面浏览器来模拟移动设备。

安装

1
2
3
4
5
6
#升级pip
pip install --upgrade pip
#安装playwright模块
pip install playwright
#安装主流浏览器依赖,时间可能较久
playwright install

测试

  • 录制代码,输入下面的命令,启动一个浏览器,一个代码记录器,然后再浏览器的所有步骤都自动记录到了代码记录器中
1
python -m playwright codegen

image-20221011105539960

  • 录制代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from playwright.sync_api import Playwright, sync_playwright, expect
def run(playwright: Playwright) -> None:
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("https://www.baidu.com/")
page.locator("input[name=\"wd\"]").click()
page.locator("input[name=\"wd\"]").fill("python")
page.get_by_role("button", name="百度一下").click()
page.wait_for_url("https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=python&fenlei=256&rsv_pq=0xc3da98d700012600&rsv_t=a363ozUooWOMdrOI3S3PH3JauszohenVsQYNmRX6SyweDX91MOi0p89Sb4HG&rqlang=en&rsv_enter=0&rsv_dl=tb&rsv_sug3=6&rsv_sug1=1&rsv_sug7=100&rsv_btype=i&inputT=1846&rsv_sug4=1847&rsv_jmp=fail")
# ---------------------
context.close()
browser.close()
with sync_playwright() as playwright:
run(playwright)

通过以上代码可以了解到:

  • playwright支持同步和异步两种使用方法

  • 不需要为每个浏览器下载webdriver

  • 相比selenium多了一层context抽象

  • 支持无头浏览器,且较为推荐(headless默认值为True)

  • 可以使用传统定位方式(CSS,XPATH等),也有自定义的新的定位方式(如文字定位)

  • 没有使用selenium的先定位元素,再进行操作的方式,而是在操作方法中传入了元素定位,定位和操作同时进行(其实也playwright也提供了单独的定位方法,作为可选)

  • 很多方法使用了with的上下文语法

  • 当然更多的人愿意在Pycharm中手写用例

playwright基本概念

PlayWright的核心概念包括:

Browser

  • 一个Browser是一个Chromium, Firefox 或 WebKit(plarywright支持的三种浏览器)的实例plarywright脚本通常以启动浏览器实例开始,以关闭浏览器结束。浏览器实例可以在headless(没有 GUI)或head模式下启动。Browser实例创建:
1
2
3
4
5
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
browser.close()
  • 启动browser实例是比较耗费资源的,plarywright做的就是如何通过一个browser实例最大化多个BrowserContext的性能。
  • API:Browser

BrowserContext

  • 一个BrowserContex就像是一个独立的匿名模式会话(session),非常轻量,但是又完全隔离。

  • (译者注:每个browser实例可有多个BrowserContex,且完全隔离。比如可以在两个BrowserContext中登录两个不同的账号,也可以在两个 context 中使用不同的代理。 )

  • context创建:

1
2
browser = playwright.chromium.launch()
context = browser.new_context()
  • context还可用于模拟涉及移动设备、权限、区域设置和配色方案的多页面场景,如移动端context创建:
1
2
3
4
5
6
7
8
9
10
11
12
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
iphone_11 = p.devices['iPhone 11 Pro']
browser = p.webkit.launch(headless=False)
context = browser.new_context(
**iphone_11,
locale='de-DE',
geolocation={ 'longitude': 12.492507, 'latitude': 41.889938 },
permissions=['geolocation']
)
browser.close()

API:

Page 和 Frame

  • 一个BrowserContext可以有多个page,每个page代表一个tab或者一个弹窗。page用于导航到URL并与page内的内容交互。创建page:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
page = context.new_page()

# Navigate explicitly, similar to entering a URL in the browser.
page.goto('http://example.com')
# Fill an input.
page.fill('#search', 'query')

# Navigate implicitly by clicking a link.
page.click('#submit')
# Expect a new url.
print(page.url)

# Page can navigate from the script - this will be picked up by Playwright.
# window.location.href = 'https://example.com'
  • 一个page可以有多个frame对象,但只有一个主frame,所有page-level的操作(比如click),都是作用在主frame上的。page的其他frame会打上iframe HTML标签,这些frame可以在内部操作实现访问。
1
2
3
4
5
6
7
8
9
10
11
12
# 通过name属性获取frame
frame = page.frame('frame-login')

# 通过URL获取frame
frame = page.frame(url=r'.*domain.*')

# 通过其他选择器(selector)获取frame
frame_element_handle = page.query_selector('.frame-class')
frame = frame_element_handle.content_frame()

# 与frame交互
frame.fill('#username-input', 'John')
  • 在录制模式下,会自动识别是否是frame内的操作,不好定位frame时,那么可以使用录制模式来找。

API:

Selector

  • playwright可以通过 CSS selector, XPath selector, HTML 属性(比如 id, data-test-id)或者是文本内容定位元素。

  • 除了xpath selector外,所有selector默认都是指向shadow DOM,如果要指向常规DOM,可使用*:light。不过通常不需要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Using data-test-id= selector engine
page.click('data-test-id=foo')

# CSS and XPath selector engines are automatically detected
page.click('div')
page.click('//html/body/div')

# Find node by text substring
page.click('text=Hello w')

# Explicit CSS and XPath notation
page.click('css=div')
page.click('xpath=//html/body/div')

# Only search light DOM, outside WebComponent shadow DOM:
page.click('css:light=div')

# 不同的selector可组合使用,用 >>连接
# Click an element with text 'Sign Up' inside of a #free-month-promo.
page.click('#free-month-promo >> text=Sign Up')

# Capture textContent of a section that contains an element with text 'Selectors'.
section_text = page.eval_on_selector('*css=section >> text=Selectors', 'e => e.textContent')

详细:

Element selectors | Playwright Python

Auto-waiting

  • playwright在执行操作之前对元素执行一系列可操作性检查,以确保这些行动按预期运行。它会自动等待(auto-wait)所有相关检查通过,然后才执行请求的操作。如果所需的检查未在给定的范围内通过timeout,则操作将失败并显示TimeoutError

  • 如 page.click(selector, **kwargs) 和 page.fill(selector, value, **kwargs) 这样的操作会执行auto-wait ,等待元素变成可见(visible)和 可操作( actionable)。例如,click将会:

    • 等待selectorx选定元素出现在 DOM 中

    • 待它变得可见(visible):有非空的边界框且没有 visibility:hidden

    • 等待它停止移动:例如,等待 css 过渡(css transition)完成

    • 将元素滚动到视图中

    • 等待它在动作点接收点事件:例如,等待元素不被其他元素遮挡

    • 如果在上述任何检查期间元素被分离,则重试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Playwright waits for #search element to be in the DOM
page.fill('#search', 'query')

# Playwright waits for element to stop animating
# and accept clicks.
page.click('#search')

#也可显示执行等待动作

# Wait for #search to appear in the DOM.
page.wait_for_selector('#search', state='attached')
# Wait for #promo to become visible, for example with `visibility:visible`.
page.wait_for_selector('#promo')

# Wait for #details to become hidden, for example with `display:none`.
page.wait_for_selector('#details', state='hidden')
# Wait for #promo to be removed from the DOM.
page.wait_for_selector('#promo', state='detached')

Execution context

  • API page.evaluate(expression, **kwargs) 可以用来运行web页面中的 JavaScript函数,并将结果返回到plarywright环境中。浏览器的全局变量,如 windowdocument, 可用于 evaluate。
1
2
3
4
5
6
7
8
href = page.evaluate('() => document.location.href')

# if the result is a Promise or if the function is asynchronous evaluate will automatically wait until it's resolved

status = page.evaluate("""async () => {
response = fetch(location.href)
return response.status
}""")

Evaluation Argument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
result = page.evaluate("([x, y]) => Promise.resolve(x * y)", [7, 8])
print(result) # prints "56"


print(page.evaluate("1 + 2")) # prints "3"
x = 10
print(page.evaluate(f"1 + {x}")) # prints "11"


body_handle = page.query_selector("body")
html = page.evaluate("([body, suffix]) => body.innerHTML + suffix", [body_handle, "hello"])
body_handle.dispose()


# A primitive value.
page.evaluate('num => num', 42)

# An array.
page.evaluate('array => array.length', [1, 2, 3])

# An object.
page.evaluate('object => object.foo', { 'foo': 'bar' })

# A single handle.
button = page.query_selector('button')
page.evaluate('button => button.textContent', button)

# Alternative notation using elementHandle.evaluate.
button.evaluate('(button, from) => button.textContent.substring(from)', 5)

# Object with multiple handles.
button1 = page.query_selector('.button1')
button2 = page.query_selector('.button2')
page.evaluate("""o => o.button1.textContent + o.button2.textContent""",
{ 'button1': button1, 'button2': button2 })

# Object destructuring works. Note that property names must match
# between the destructured object and the argument.
# Also note the required parenthesis.
page.evaluate("""
({ button1, button2 }) => button1.textContent + button2.textContent""",
{ 'button1': button1, 'button2': button2 })

# Array works as well. Arbitrary names can be used for destructuring.
# Note the required parenthesis.
page.evaluate("""
([b1, b2]) => b1.textContent + b2.textContent""",
[button1, button2])

# Any non-cyclic mix of serializables and handles works.
page.evaluate("""
x => x.button1.textContent + x.list[0].textContent + String(x.foo)""",
{ 'button1': button1, 'list': [button2], 'foo': None })

结合pytest

  • testcas\conftest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import pytest
from playwright.sync_api import sync_playwright
from py._xmlgen import html


@pytest.fixture()
def browser():
playwrigh = sync_playwright().start()
browser = playwrigh.chromium.launch(headless=False)

# 返回数据
yield browser

# 实现用例后置
browser.close()
playwrigh.stop()


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
report.description = str(item.function.__doc__)
report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape") #

def pytest_html_results_table_header(cells):
cells.insert(1, html.th('用例名称'))
cells.insert(2, html.th('Test_nodeid'))
cells.pop(2)


def pytest_html_results_table_row(report, cells):
cells.insert(1, html.td(report.description))
cells.insert(2, html.td(report.nodeid))
cells.pop(2)


def pytest_html_results_table_html(report, data):
if report.passed:
del data[:]
data.append(html.div('通过的用例未捕获日志输出.', class_='empty log'))


def pytest_html_report_title(report):
report.title = "pytest示例项目测试报告"


  • testcase\test1.py,page.request.get可以直接发送请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import pytest


class TestClassName:
@pytest.mark.usefixtures("browser")
def test_func_name1(self, browser):
context = browser.new_context()
page = context.new_page()
# 发送http请求
resp = page.request.get("http://www.kuaidi100.com/query?type=")
print(resp.text())
page.goto("https://www.baidu.com/")
assert page.title() == "百度一下,你就知道"

page.locator("input[name=\"wd\"]").click()
page.locator("input[name=\"wd\"]").fill("python")
page.get_by_role("button", name="百度一下").click()
context.close()

@pytest.mark.usefixtures("browser")
def test_func_name1_1(self, browser):
context = browser.new_context()
page = context.new_page()
page.goto("https://www.baidu.com/")
assert page.title() == "百度一下,你就知道1"
page.locator("input[name=\"wd\"]").click()
page.locator("input[name=\"wd\"]").fill("python")
page.get_by_role("button", name="百度一下").click()
context.close()


  • 执行用例
1
2
3
4
5
6
# 批量运行用例
pytest -s testcase\ --html=report.html --self-contained-html --capture=sys

# 多线程运行用例
pip install pytest-multithreading -i https://pypi.douban.com/simple
pytest -s testcase/ --th 10 --html=report.html --self-contained-html --capture=sys
  • 查看执行结果

image-20221011152104439

检查元素可见性

  • 在元素定位过程中,经常出现元素出现了,但是实际定位不到,这时候可以检查dom元素的可见性
1
2
3
4
5
6
7
8
9
10
def find_el(page, el, timeout=10000):
try:
# 等待元素出现到dom中
element = page.wait_for_selector(el, state="attached", timeout=timeout)
# 等待元素可见
element.wait_for_element_state("visible", timeout=timeout)
return element
except Exception as e:
pass
return None

其他

  • 如何集成到CI上待实践
  • 关于多机并行,可以多进程去启动,也可以在CI上新建几个节点去执行

ab压力测试中,发现你一次最多只能启动1024个线程

  • 默认情况下,一个线程的栈要预留1M的内存空间
    而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程
  • 但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小。你也可以通过连接时修改默认栈大小,将其改的比较小,这样就可以多开一些线程。如将默认栈的大小改成512K,这样理论上最多就可以开4096个线程。即使物理内存再大,一个进程中可以起的线程总要受到2GB这个内存空间的限制。
  • 比方说你的机器装了64GB物理内存,但每个进程的内存空间还是4GB,其中用户态可用的还是2GB。
    • 内核态. CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序
    • 用户态:.只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取
  • 如果是同一台机器内的话,能起多少线程也是受内存限制的。每个线程对象都要站用非页面内存,而非页面内存也是有限的,当非页面内存被耗尽时,也就无法创建线程了。
  • 如果物理内存非常大,同一台机器内可以跑的线程数目的限制值会越来越大。在Windows下写个程序,一个进程Fork出2000个左右线程就会异常退出了,为什么?这个问题的产生是因为windows32位系统,一个进程所能使用的最大虚拟内存为2G,而一个线程的默认线程栈StackSize为1024K(1M),这样当线程数量逼近2000时,2000*1024K=2G(大约),内存资源就相当于耗尽

影响最大线程大小的因素

  • Java虚拟机本身
    • Xms 初始堆大小
    • Xmx 最大堆大小
    • Xss 每个线程的堆栈大小
  • 系统限制
    • /proc/sys/kernel/pid_max
    • /proc/sys/kernel/thread-max
    • max_user_process(ulimit -u)
    • /proc/sys/vm/max_map_count

其他突破线程问题

  • 多进程-启动多线程
  • 使用异步请求
    • 无论是使用多进程-多线程,还是异步请求,最主要的影响还是电脑本身的配置

下面是具体配置

Windows

httpd.exe -l 会看见 mpm_winnt.c windows默认执行 mpm_winnt_module方式 (暂未找到修改成其他方式的方法)

1.httpd.conf 文件去掉 Include conf/extra/httpd-mpm.conf 前面的#

2.修改extra/httpd-mpm.conf 最下面 或 查找 mpm_winnt_module 修改ThreadsPerChildMaxRequestsPerChild

1
2
3
4
5
6
7
#每个子进程建立的线程数
ThreadsPerChild 1 默认150

#指令设置每个子进程在其生存期内允许伺服的最大请求数量。
#到达MaxRequestsPerChild的限制后,子进程将会结束。
#如果MaxRequestsPerChild为"0",子进程将永远不会结束。
MaxRequestsPerChild 10 默认0

根据自己网站并发数量设置:

  • ThreadsPerChild 设置 网站平均在线人数
  • MaxRequestsPerChild 设置最高在线人数的值

Linux

  • ps -ef | grep httpd | wc -l 查看当前 httpd进程数

  • apachectl -l 会看见 prefork.c Linux默认执行 mpm_prefork_module

  • httpd.conf 文件去掉 Include conf/extra/httpd-mpm.conf 前面的#

  • 修改extra/httpd-mpm.conf 最上面 或 查找 mpm_prefork_module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
StartServers          5 #默认启动线程数

#指令设置空闲子进程的最小数量。
#所谓空闲子进程是指没有正在处理请求的子进程。
#如果当前空闲子进程数少于MinSpareServers ,
#那么Apache将以最大每秒一个的速度产生新的子进程。
#只有在非常繁忙机器上才需要调整这个参数。将此参数设的太大通常是一个坏主意。
MinSpareServers 5 #

#指令设置空闲子进程的最大数量。
#所谓空闲子进程是指没有正在处理请求的子进程。
#如果当前有超过MaxSpareServers数量的空闲子进程,
#那么父进程将杀死多余的子进程。
#只有在非常繁忙机器上才需要调整这个参数。
#将此参数设的太大通常是一个坏主意。
#如果你将该指令的值设置为比MinSpareServers小,
#Apache将会自动将其修改成"MinSpareServers+1"。
MaxSpareServers 10 #
MaxClients 150 #apache可以同时处理的请求
MaxRequestsPerChild 0 #如windows MaxRequestsPerChild

其他参考