Go Web:处理请求
处理请求
Request和Response
http Requset和Response的内容包括以下几项:
- Request or response line
- Zero or more headers
- An empty line, followed by …
- … an optional message body
例如一个http Request:
GET /Protocols/rfc2616/rfc2616.html HTTP/1.1
Host: www.w3.org
User-Agent: Mozilla/5.0
(empty line)
如果是POST方法,在empty line后还包含请求体。
一个http Response:
HTTP/1.1 200 OK
Content-type: text/html
Content-length: 24204
(empty line)
and then 24,204 bytes of HTML code
go http包分为两种角色:http Client和http Server。http Client可以发送请求,比如写爬虫程序时语言扮演的角色就是http Client;http Server用来提供web服务,可以处理http请求并响应。
对于Request,作为http客户端(如编写爬虫类工具)常需要关注的是URL和User-Agent以及其它几个Header;作为http服务端(web服务端,处理请求)常需要关注的几项是:
URL
Header
Body
Form,、PostForm、MultipartForm
以下是完整的Request结构以及相关的函数、方法:混个眼熟就好了
type Request struct {
Method string
URL *url.URL
Header Header
Body io.ReadCloser
GetBody func() (io.ReadCloser, error) // Server: x, Cleint: √
ContentLength int64
TransferEncoding []string
Close bool // Server: x, Cleint: √
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
Trailer Header
RemoteAddr string
RequestURI string // x
TLS *tls.ConnectionState
Cancel <-chan struct{} // x
Response *Response // x
}
func NewRequest(method, url string, body io.Reader) (*Request, error)
func ReadRequest(b *bufio.Reader) (*Request, error)
func (r *Request) AddCookie(c *Cookie)
func (r *Request) BasicAuth() (username, password string, ok bool)
func (r *Request) Context() context.Context
func (r *Request) Cookie(name string) (*Cookie, error)
func (r *Request) Cookies() []*Cookie
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)
func (r *Request) FormValue(key string) string
func (r *Request) MultipartReader() (*multipart.Reader, error)
func (r *Request) ParseForm() error
func (r *Request) ParseMultipartForm(maxMemory int64) error
func (r *Request) PostFormValue(key string) string
func (r *Request) ProtoAtLeast(major, minor int) bool
func (r *Request) Referer() string
func (r *Request) SetBasicAuth(username, password string)
func (r *Request) UserAgent() string
func (r *Request) WithContext(ctx context.Context) *Request
func (r *Request) Write(w io.Writer) error
func (r *Request) WriteProxy(w io.Writer) error
注意有哪些字段和方法,字段的详细说明见go doc http.Request
。上面打了"x"的表示不需要了解的或者废弃的。
有一个特殊的字段Trailer
,它是Header类型的,显然它存放的是一个个请求header,它表示请求发送完成之后再发送的额外的header。对于Server来说,读取了request.Body之后才会读取Trailer。很少有浏览器支持HTTP Trailer功能。
以下是完整的Response结构以及相关的函数、方法:混个眼熟就好了
type Response struct {
Status string // e.g. "200 OK"
StatusCode int // e.g. 200
Proto string // e.g. "HTTP/1.0"
ProtoMajor int // e.g. 1
ProtoMinor int // e.g. 0
Header Header
Body io.ReadCloser
ContentLength int64
TransferEncoding []string
Close bool
Uncompressed bool
Trailer Header
Request *Request
TLS *tls.ConnectionState
}
func Get(url string) (resp *Response, err error)
func Head(url string) (resp *Response, err error)
func Post(url string, contentType string, body io.Reader) (resp *Response, err error)
func PostForm(url string, data url.Values) (resp *Response, err error)
func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)
func (r *Response) Cookies() []*Cookie
func (r *Response) Location() (*url.URL, error)
func (r *Response) ProtoAtLeast(major, minor int) bool
func (r *Response) Write(w io.Writer) error
其实有些直接从字面意思看就知道了。
URL
内容太多,见:Go Web:URLs
Http Header
Request和Response结构中都有Header字段,Header是一个map结构。
type Header map[string][]string
A Header represents the key-value pairs in an HTTP header.
func (h Header) Add(key, value string)
func (h Header) Del(key string)
func (h Header) Get(key string) string
func (h Header) Set(key, value string)
func (h Header) Write(w io.Writer) error
func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error
key是Header字段名,value是Header字段的值,同个字段多个值放在string的slice中。
Add()、Del()、Get()、Set()意义都很明确。
Write()是将Header写进Writer中,比如从网络连接中发送出去。WriteSubSet()和Write()类似,但可以指定exclude[headerkey]==true
排除不写的字段。
下面是一个示例:
package main
import (
"fmt"
"net/http"
)
func headers(w http.ResponseWriter, r *http.Request) {
for key := range r.Header {
fmt.Fprintf(w, "%s: %s\n", key, r.Header[key])
}
fmt.Fprintf(w, "--------------\n")
fmt.Fprintf(w, "the key: %s\n", r.Header["Accept-Encoding"])
fmt.Fprintf(w, "the key: %s\n", r.Header.Get("Accept-Encoding"))
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/headers", headers)
server.ListenAndServe()
}
浏览器中访问http://127.0.0.1:8080/headers
的结果:
Connection: [keep-alive]
Cache-Control: [max-age=0]
Upgrade-Insecure-Requests: [1]
User-Agent: [Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36]
Accept: [text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8]
Accept-Encoding: [gzip, deflate, br]
Accept-Language: [zh-CN,zh;q=0.9,en;q=0.8]
--------------
the key: [gzip, deflate, br]
the key: gzip, deflate, br
Http Body
Request和Response结构中都有Body字段,它们都是io.ReadCloser接口类型。从名字可以看出,io.ReadCloser由两个接口组成:Reader和Closer,意味着它实现了Reader接口的Read()方法,也实现了Closer接口的Close()方法。这意味着Body的实例可以调用Read()方法,也可以调用Close()方法。
例如,下面写一个handler,从请求中读取Body并输出:
package main
import (
"fmt"
"net/http"
)
func body(w http.ResponseWriter, r *http.Request) {
len := r.ContentLength
body := make([]byte, len)
r.Body.Read(body)
fmt.Fprintf(w, "%s\n", string(body))
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/body", body)
server.ListenAndServe()
}
因为使用HTTP Get方法的Request没有Body,所以这里使用curl的"-d"选项来构造一个POST请求,并发送Request Body:
$ curl -id "name=lognshuai&age=23" 127.0.0.1:8080/body
HTTP/1.1 200 OK
Date: Mon, 26 Nov 2018 09:04:40 GMT
Content-Length: 22
Content-Type: text/plain; charset=utf-8
name=lognshuai&age=23
Go和HTML Form
在Request结构中,有3个和form有关的字段:
// Form字段包含了解析后的form数据,包括URL的query、POST/PUT提交的form数据
// 该字段只有在调用了ParseForm()之后才有数据
Form url.Values
// PostForm字段不包含URL的query,只包括POST/PATCH/PUT提交的form数据
// 该字段只有在调用了ParseForm()之后才有数据
PostForm url.Values // Go 1.1
// MultipartForm字段包含multipart form的数据
// 该字段只有在调用了ParseMultipartForm()之后才有数据
MultipartForm *multipart.Form
所以,一般的逻辑是:
- 先调用ParseForm()或ParseMultipartForm()解析请求中的数据
- 按需访问Request结构中的Form、PostForm或MultipartForm字段
除了先解析再访问字段的方式,还可以直接使用Request的方法:
- FormValue(key)
- PostFormValue(key)
稍后解释这两个方法。
取Form和PostForm字段
给定一个html文件,这个html文件里是form表单:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Go Web</title>
</head>
<body>
<form action=http://127.0.0.1:8080/process?name=xiaofang&boyfriend=longshuai
method="post" enctype="application/x-www-form-urlencoded">
<input type="text" name="name" value="longshuai"/>
<input type="text" name="age" value="23"/>
<input type="submit"/>
</form>
</body>
</html>
在这个form里,action指定了要访问的url,其中path=process,query包含name和boyfriend两个key。除此之外,form表单的input属性里,也定义了name和age两个key,由于method为post,这两个key是作为request body发送的,且因为enctype指定为application/x-www-form-urlencoded
,这两个key会按照URL编码的格式进行组织。
下面是web handler的代码:
package main
import (
"fmt"
"net/http"
)
func form(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Fprintf(w, "%s\n", r.Form)
fmt.Fprintf(w, "%s\n", r.PostForm)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", form)
server.ListenAndServe()
}
上面先使用ParseForm()方法解析Form,再访问Request中的Form字段和PostForm字段。
打开前面的Html文件,点击"提交"后,将输出:
map[name:[longshuai xiaofang] age:[23] boyfriend:[longshuai]]
map[name:[longshuai] age:[23]]
如果这时,将application/x-www-form-urlencoded
改成multipart/form-data
,再点击提交,将输出:
map[name:[xiaofang] boyfriend:[longshuai]]
map[]
显然,使用multipart/form-data
编码form的时候,编码的内容没有放进Form和PostForm字段中,或者说编码的结果没法放进这两个字段中。
取MultipartForm字段
要取MultipartForm字段的数据,先使用ParseMultipartForm()方法解析Form,解析时会读取所有数据,但需要指定保存在内存中的最大字节数,剩余的字节数会保存在临时磁盘文件中。
package main
import (
"fmt"
"net/http"
)
func form(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(1024)
fmt.Fprintf(w,"%s\n",r.Form)
fmt.Fprintf(w,"%s\n",r.PostForm)
fmt.Fprintf(w,"%s\n",r.MultipartForm)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", form)
server.ListenAndServe()
}
将html文件的enctype改为multipart/form-data
后,重新点开html文件,将输出:
map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]]
map[name:[longshuai] age:[23]]
&{map[name:[longshuai] age:[23]] map[]}
前两行结果意味着ParseMultipartForm()方法也调用了ParseForm()方法,使得除了设置MultipartForm字段,也会设置Form字段和PostForm字段。
注意上面的第三行,返回的是一个struct,这个struct中有两个map,第一个map是来自form的key/value,第二个map为空,这个见后面的File。
最后还需注意的是,enctype=multipart/form-data
和enctype=application/x-www-form-urlencoded
时,Request.Form字段中key的保存顺序是不一致的:
// application/x-www-form-urlencoded
map[name:[longshuai xiaofang] age:[23] boyfriend:[longshuai]]
// multipart/form-data
map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]]
FormValue()和PostFormValue()
前面都是先调用ParseForm()或ParseMultipartForm()解析Form后再调用Request中对应字段的。还可以直接调用FormValue()或PostFormValue()方法。
- FormValue(key)
- PostFormValue(key)
这两个方法在需要时会自动调用ParseForm()或ParseMultipartForm(),所以使用这两个方法取Form数据的时候,可以不用显式解析Form。
FormValue()返回form数据和url query组合后的第一个值。要取得完整的值,还是需要访问Request.Form或Request.PostForm字段。但因为FormValue()已经解析过Form了,所以无需再显式调用ParseForm()再访问request中Form相关字段。
PostFormValue()返回form数据的第一个值,因为它只能访问form数据,所以忽略URL的query部分。
先看FormValue()方法的使用。注意,下面调用了FormValue()之后没有调用ParseForm()和ParseMultipartForm()解析Form,就可以直接访问Request中和Form相关的3个字段。
package main
import (
"fmt"
"net/http"
)
func form(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w,"%s\n",r.FormValue("name"))
fmt.Fprintf(w,"%s\n",r.FormValue("age"))
fmt.Fprintf(w,"%s\n",r.FormValue("boyfriend"))
fmt.Fprintf(w,"%s\n",r.Form)
fmt.Fprintf(w,"%s\n",r.PostForm)
fmt.Fprintf(w,"%s\n",r.MultipartForm)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", form)
server.ListenAndServe()
}
当enctype=multipart/form-data
时,会自动调用ParseMultipartForm(),输出结果:
xiaofang
23
longshuai
map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]]
map[name:[longshuai] age:[23]]
&{map[name:[longshuai] age:[23]] map[]}
当enctype=application/x-www-form-urlencoded
时,会自动调用ParseForm(),输出结果:
longshuai
23
longshuai
map[name:[longshuai xiaofang] age:[23] boyfriend:[longshuai]]
map[name:[longshuai] age:[23]]
%!s(*multipart.Form=<nil>)
仍然注意,因为两种enctype导致的Request.Form存储key时的顺序不一致,使得访问有多个值的key得到的结果不一致。
再看PostFormValue()方法的使用。
package main
import (
"fmt"
"net/http"
)
func form(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w,"%s\n",r.PostFormValue("name"))
fmt.Fprintf(w,"%s\n",r.PostFormValue("age"))
fmt.Fprintf(w,"%s\n",r.PostFormValue("boyfriend"))
fmt.Fprintf(w,"%s\n",r.Form)
fmt.Fprintf(w,"%s\n",r.PostForm)
fmt.Fprintf(w,"%s\n",r.MultipartForm)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", form)
server.ListenAndServe()
}
当enctype=multipart/form-data
时,会自动调用ParseMultipartForm(),输出结果:
longshuai
23
map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]]
map[name:[longshuai] age:[23]]
&{map[name:[longshuai] age:[23]] map[]}
当enctype=application/x-www-form-urlencoded
时,会自动调用ParseForm(),输出结果:
longshuai
23
map[age:[23] boyfriend:[longshuai] name:[longshuai xiaofang]]
map[name:[longshuai] age:[23]]
%!s(*multipart.Form=<nil>)
注意,由于PostFormValue()方法只能访问form数据,上面调用了PostFormValue()后,无法使用PostFormValue()访问URL中的query的Key/value,尽管request中的字段都合理设置了。
Files
multipart/form-data
最常用的场景可能是上传文件,比如在form中使用file标签。以下是html文件:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Go Web Programming</title>
</head>
<body>
<form action=http://127.0.0.1:8080/process?name=xiaofang&boyfriend=longshuai
method="post" enctype="multipart/form-data">
<input type="text" name="name" value="longshuai"/>
<input type="text" name="age" value="23"/>
<input type="file" name="file_to_upload">
<input type="submit"/>
</form>
</body>
</html>
下面是服务端接收上传文件数据的代码:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func form(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(1024)
fileHeader := r.MultipartForm.File["file_to_upload"][0]
file, err := fileHeader.Open()
if err == nil {
dataFromFile, err := ioutil.ReadAll(file)
if err == nil {
fmt.Fprintf(w, "%s\n", dataFromFile)
}
}
fmt.Fprintf(w, "%s\n", r.MultipartForm)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", form)
server.ListenAndServe()
}
上面先调用ParseMultipartForm()解析multipart form,然后访问request的MultipartForm字段,这个字段的类型是*multipart.Form
,该类型定义在mime/multipart/formdata.go文件中:
$ go doc multipart.Form
package multipart // import "mime/multipart"
type Form struct {
Value map[string][]string
File map[string][]*FileHeader
}
Form类型表示解析后的multipart form,字段File和Value都是map类型的,其中File的map value是*FileHeader
类型:
$ go doc multipart.fileheader
package multipart // import "mime/multipart"
type FileHeader struct {
Filename string
Header textproto.MIMEHeader
Size int64
// Has unexported fields.
}
A FileHeader describes a file part of a multipart request.
func (fh *FileHeader) Open() (File, error)
它实现了Open()方法,所以可以直接调用Open()来打开multipart.Form的File部分。即:
fileHeader := r.MultipartForm.File["file_to_upload"][0]
file, err := fileHeader.Open()
然后读取这段数据,响应给客户端。注意,有了File后,request.MultipartForm字段的第二个map就有了值,第二个map对应的就是multipart.Form.File的内容。
整个返回结果如下:
FormFile()
类似于FormValue()和PostFormValue()方法的便捷,读取multipart.Form也有快捷方式:
$ go doc http.formfile
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)
FormFile returns the first file for the provided form key. FormFile calls
ParseMultipartForm and ParseForm if necessary.
FormFile()方法会在需要的时候自动调用parseMultipartForm()或ParseForm()。注意它的返回值。因为第一个返回值为multipart.File
,说明至少实现了io.Reader接口,可以直接读取这个文件。
修改上一节的示例:
func form(w http.ResponseWriter, r *http.Request) {
file, _, err := r.FormFile("file_to_upload")
if err != nil {
panic(err)
}
dataFromFile, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
fmt.Fprintf(w, "%s\n", dataFromFile)
}
ResponseWriter
ResponseWriter接口用于发送响应数据、响应header。它有3个方法:
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}
A ResponseWriter interface is used by an HTTP handler to construct an HTTP
response.
A ResponseWriter may not be used after the Handler.ServeHTTP method has
returned.
Header()用于构造response header,构造好的header会在稍后自动被WriteHeader()发送出去。比如设置一个Location字段:
w.Header().Set("Location", "http://google.com")
Write()用于发送响应数据,例如发送html格式的数据,json格式的数据等。
str := `<html>
<head><title>Go Web Programming</title></head>
<body><h1>Hello World</h1></body>
</html>`
w.Write([]byte(str))
WriteHeader(int)可以接一个数值HTTP状态码,同时它会将构造好的Header自动发送出去。如果不显式调用WriteHeader(),会自动隐式调用并发送200 OK。
下面是一个示例:
package main
import (
"fmt"
"encoding/json"
"net/http"
)
func commonWrite(w http.ResponseWriter, r *http.Request) {
str := `<html>
<head>
<title>Go Web</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>`
w.Write([]byte(str))
}
func writeHeader(w http.ResponseWriter,r *http.Request){
w.WriteHeader(501)
fmt.Fprintln(w,"not implemented service")
}
func header(w http.ResponseWriter,r *http.Request){
w.Header().Set("Location","http://www.baidu.com")
w.WriteHeader(302)
}
type User struct {
Name string
Friends []string
}
func jsonWrite(w http.ResponseWriter, r *http.Request) {
var user = &User{
Name: "longshuai",
Friends: []string{"personA", "personB", "personC"},
}
w.Header().Set("Content-Type", "application/json")
jsonData, _ := json.Marshal(user)
w.Write(jsonData)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/commonwrite", commonWrite)
http.HandleFunc("/writeheader", writeHeader)
http.HandleFunc("/header", header)
http.HandleFunc("/jsonwrite", jsonWrite)
server.ListenAndServe()
}
commonWrite()这个handler用于输出带html格式的数据。访问结果:
writeheader()这个handler用于显式发送501状态码。访问结果:
$ curl -i 127.0.0.1:8080/writeheader
HTTP/1.1 501 Not Implemented
Date: Tue, 27 Nov 2018 03:36:57 GMT
Content-Length: 24
Content-Type: text/plain; charset=utf-8
not implemented service
header()这个handler用于设置响应的Header,这里设置了302重定向,客户端收到302状态码后会找Location字段的值,然后重定向到http://www.baidu.com
。
jsonWrite()这个handler用于发送json数据,所以发送之前先设置了Content-Type: application/json
。
Go Web:处理请求的更多相关文章
- 深入web的请求过程
一.深入web的请求过程 1.1.B/S网络架构概述 · 从前端到后端,都基于应用层协议HTTP来交互数据.一个请求就对应了一个操作,完成操作之后就断开了连接.基于这样的特点可以用来满足海量的用户的操 ...
- 传统Web应用请求和响应特点【显示当前时间】
(1)请求:浏览器以HTTP协议的方式提交请求到服务器 (2)响应:服务器以HTTP协议的方式响应内容到浏览器 注意:HTTP是WEB大众化非安全协议 HTTPS是WEB安全协议,是基于HTTP协议的 ...
- web页面请求历程
web页面请求历程 1)准备DHCP,UDP,IP和以太网 客户端要访问www.google.com的网站. 首先客户端要与网络相接,没有IP地址地址就不能做什么事情,所以客户端采取的一个网络相关的动 ...
- SpringBoot web获取请求数据【转】
SpringBoot web获取请求数据 一个网站最基本的功能就是匹配请求,获取请求数据,处理请求(业务处理),请求响应,我们今天来看SpringBoot中怎么获取请求数据. 文章包含的内容如下: 获 ...
- Web应用请求和响应 HTTP相关
(1)请求:浏览器以HTTP协议的方式提交请求到服务器 (2)响应:服务器以HTTP协议的方式响应内容到浏览器 注意:HTTP是WEB大众化非安全协议 HTTPS是WEB安全协议,是基于HTTP协议的 ...
- Java Web之请求和响应
Servlet最主要作用就是处理客户端请求并作出回应,为此,针对每次请求,Web容器在调用service()之前都会创建两个对象,分别是HttpServletRequest和HttpServletRe ...
- Flask web开发 请求拦截和预处理
我们在开发WEB应用时,往往会需要对所有的url请求进行拦截,做些预处理,比如权限处理.日志等统一处理. 本文介绍一下Flask中的处理机制.我们通过一个简单的例子来说明. 1.编写一个简单应用 ru ...
- 从插上网线到web页面请求,究竟发生了哪些过程?(计算机网络篇)
一.说在前头 好久没有更新博客了,汗颜,最近忙于各种实验与报告,但是还是要抽时间总结一下学的东西.欢迎转载,但是要注明出处哦(=^ ^=). 最近学了计算机网络,正好总结一下.本篇博客的大部分是跟计算 ...
- C# 神奇的Web services 请求超时问题 排查分析
服务器上有两个接口,一个是Web Services(asmx文件)接口,一个是MVC API (普通的GET请求接口) 神奇的事情是这样的,只要我使用WebRequest请求两次,再使用Web Ser ...
- 如何测量并报告ASP.NET Core Web API请求的响应时间
介绍 大家都知道性能是API的流行语.而相应时间则是API性能的一个重要并且可测量的参数.在本文中,我们将了解如何使用代码来测量API的响应时间,然后将响应时间数据返回到客户端. 作者:依乐祝 原文地 ...
随机推荐
- Unity自动生成AnimatorController
上一篇写了如何自动切割动画,这一篇写如何自动生成AnimatorController. 之前网上查了很多资料,看的一直很蒙,看不懂是怎么回事的,这里我先给大家明确几个概念: 画的不好,大家将就着看,写 ...
- MySQL数据库(一)
一:MySQL的简单介绍 MySQL属于关系型数据库,数据是以行和列的形式去存储(表),表中的每一行叫一条记录,表中的每一列叫一个字段,表和表之间的逻辑关联叫关系. 二:MySQL的基本操作 (一)M ...
- SVN服务端和客户端的安装与搭建
版权声明:本文为博主原创文章,转载请注明原文出处. https://blog.csdn.net/zzfenglin/article/details/50931462 SVN简介 SVN全名Subver ...
- python网络编程 双人多人聊天
在学习网路编程时,我们首先要考虑的是其中的逻辑,我们借助打电话的形式来了解网络编程的过程, 我们打电话时属于呼叫方,接电话的属于被呼叫方,那么被呼叫方一直保持在待机状态,等待主呼叫方 呼叫,只有在被呼 ...
- ubuntu16.04下安装g2o
根本不需要编译源码直接一行命令就可以 sudo apt-get install libpcl-dev 如果没有安装pcl_viewer就再加一行命令 sudo apt-get install pcl- ...
- Linux下使用openVPN连接到某个内网
推荐一个网站(比较全的介绍关于openvpn的客户端与服务端的配置) 点击我 此处我介绍我配置openvpn客户端连接的坑 我的机器为kali linux apt-get install openvp ...
- NGUI 摇奖滚轮
效果图: 优缺点: 优点: 1.一条曲线完美解决旋转问题 2. 解决了超速的问题,现在速度再快也不会乱了 3.快速停止的时候进行了进度区分,后面的会等前面的停了再停 缺点: 1.停止节奏上会有细微差距 ...
- JS自定义鼠标右击菜单
自定义鼠标右击菜单要素: 禁止页面默认右击事件 设置右击菜单的样式以及菜单出现的位置(通过捕获鼠标点击位置来确定菜单的位置) 鼠标在指定控件(区域)上右击时显示菜单(默认菜单隐藏,点击鼠标右键时显示) ...
- maven 本地仓库无法更新到最新版本的jar包
maven 本地仓库无法更新到最新版本的jar包 描述:maven 本地仓库无法更新最新版的jar包导致项目一直报错 解决:去jar包版本所在目录,删除掉所有红框内文件,重新用ide导入
- day_3各种数据类型与各种运算符
首先我们复习一下昨天的内容 1:语言的分类: --有三种 机器语言,汇编语言,高级语言 运行的效率是机器语言最高 开发效率 是高级语言最高 2:计算机由五大部分组成:控制器+运算器+存储器+inpu ...