第5章 网络编程
5.1 socket编程
以往socket编程:
- 建立socket:使用socket()函数。
- 绑定socket:使用bind()函数。
- 监听:使用listen()函数,或连接使用connect()函数。
- 接受连接:使用accept()函数。
- 接收:使用recevie()函数。或者发送:使用send()函数。
Dial()函数
//TCP连接
conn,err := net.Dial("tcp","ip:port")
//UDP连接
conn.err := net.Dial("udp","ip:port")
//ICMP连接
conn.err := net.Dial("ip4:icmp","ip:port")
//发数据
conn.Write()
//收数据
conn.Read()
由于Read()和Write()方法只接收字节数组,所以只能先写入[]byte 再写入bytes.Buffer。
Read()方法读到末尾也会
TCP连接示例
func main() {
if len(os.Args != 2){
fmt.Fprint(os.Stderr,"Usage: %s host:port",os.Args[0])
os.Exit(1)
}
inetAddress := os.Args[1]
conn,err :=net.Dial("tcp",inetAddress)
result,err := readFully(conn)
checkError(err)
fmt.Print(string(result))
os.Exit(0)//中断程序,并返回自定义的int,一般0指正常退出
}
func checkError(err error) {
if err != nil{
fmt.Fprint(os.Stderr,"Fatal error:%s",err.Error())
os.Exit(1)
}
}
func readFully(conn net.Conn) {
defer conn.Close()//关连接
result := bytes.NewBuffer(nil)//双缓存,一缓存一结果
var buf [512]byte
for{//循环写入
n,err := conn.Read(buf[0:])
result.Write(buf[0:n])
if err != nil{
if err == io.EOF{//判尾
break
}
return nil,err//向上抛出异常
}
}
return result.Bytes(),nil
}
fmt.Fprintf的api:
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
Dial()是对DialTCP()、DialUDP()、DialIP()和DialUnix()的封装。
TCP连接示例2
func main() {
if(len(os.Args)!=2){
fmt.Fprint(os.Stderr,"Usage: %s host:port",os.Args[0])
os.Exit(1)
}
inetAddress := os.Args[1]
tcpAddr,err := net.ResolveTCPAddr("tcp4",inetAddress)
checkError(err)
conn,err := net.DialTCP("tcp",nil,tcpAddr)
_,err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
checkError(err)
result,err := ioutil.ReadAll(conn)//有现成的读数的函数
checkError(err)
fmt.Println(string(result))
os.Exit(0)
}
func checkError(err error) {
if err != nil{
fmt.Fprint(os.Stderr,"Fatal error:%s",err.Error())
os.Exit(1)
}
}
这个示例仅用于原理的理解,不记。
与示例1不同点:
- net.ResolveTCPAddr(),用于解析地直和端口号。
- net.DialTCP(),用于建立连接。
验证ip有效性:
func arse(IP)
创建子网掩码:
func IPv4Mask(a,b,c,d,byte) IPMask
获取默认子网掩码:
func (ip IP) DefaultMask() IPMask
根据域名查找IP代码:
func ResolveIPAddr(net, addr string) (*IPAddr,error)
func LoopupHost(name string)(cname string,addrs []string,err error)
5.2 HTTP编程
基本方法
http.Get(url string)方法等价于http.DefaultClient.Get()方法。
func main() {
resp,err := http.Get("http://example.com")
if err!= nil{
return
}
defer resp.Body.Close()
io.Copy(os.Stdout,resp.Body)//不能用fmt.Print打印,那样只能打印地址。这里用了流的
}
io.Copy的api:
func Copy(dst Writer, src Reader) (written int64, err error)
http.Post()参数:
请求url
将要postr的数据类型(MIMEType)
-
数据的比特流([]byte)
func main() {
imageDataBuf := []byte
resp,err := http.Post("http://example.com/upload","image/jpeg",&imageDataBuf)
if err !=nil{
return
}
if resp.StatusCode != http.StatusOK{
return
}
}
http.PostForm()实现标准编码格式application/x-www-form-urlencoded的表单提交。
func main() {
resp,err := http.PostForm("http://example.com/posts",url.Values{"title":{"article title"},"content":{"article body"}})
if err != nil{
return
}
io.Copy(os.Stdout,resp.StatusCode)
}
http.Head(url string)只请求目标的头部信息,不返回body。
需要定制化地发送Get()和Post()请求,如设定User-Agent,传递cookie,使用(http.Client)Do()。
func main() {
req,err := http.NewRequest("GET","http://example.com",nil)
req.Header.Add("User-Agent","Gobook Custom User-Agent")
client := &http.Client{}
resp,err := client.Do(req)
}
http.NewRequest
func NewRequest(method, url string, body io.Reader) (*Request, error)
自定义http.Client
type Client struct {
// Transport用于确定HTTP请求的创建机制。
// 如果为空,将会使用DefaultTransport
Transport RoundTripper
// CheckRedirect定义重定向策略。
// 如果CheckRedirect不为空,客户端将在跟踪HTTP重定向前调用该函数。
// 两个参数req和via分别为即将发起的请求和已经发起的所有请求,最早的 /
// / 已发起请求在最前面。 // 如果CheckRedirect返回错误,客户端将直接返回错误,不会再发起该请求。
// 如果CheckRedirect为空,Client将采用一种确认策略,将在10个连续 // 请求后终止
CheckRedirect func(req *Request, via []*Request) error
// 如果Jar为空,Cookie将不会在请求中发送,并会
// 在响应中被忽略
Jar CookieJar
}
CheckRedirect函数指定处理重定向策略。发送的请求如果状态码30x,httpClient会在遵循跳转规则之前先调用这个CheckRedirect函数。
CookieJar接口预定义了SetCookies()和Cookies()两个方法。
func main() {
client := &http.Client{
CheckRedirect: redirectPolicyFunc,
}
resp,err := client.Get("http://example.com")
req,err := http.NewRequest("GET","http://example.com",nil)
req.Header.Add("User-Agent","Our Custom User-Agent")
req.Header.Add("If-None-Match","W/TheFileEtag")
resp,err := client.Do(req)
}
自定义http.Transport
type Transport struct {
// Proxy指定用于针对特定请求返回代理的函数。
// 如果该函数返回一个非空的错误,请求将终止并返回该错误。
// 如果Proxy为空或者返回一个空的URL指针,将不使用代理
Proxy func(*Request) (*url.URL, error)
// Dial指定用于创建TCP连接的dail()函数。
// 如果Dial为空,将默认使用net.Dial()函数
Dial func(net, addr string) (c net.Conn, err error)
// TLSClientConfig指定用于tls.Client的TLS配置。
// 如果为空则使用默认配置
TLSClientConfig *tls.Config
DisableKeepAlives bool
DisableCompression bool
// 如果MaxIdleConnsPerHost为非零值,它用于控制每个host所需要
// 保持的最大空闲连接数。如果该值为空,则使用DefaultMaxIdleConnsPerHost
MaxIdleConnsPerHost int
// ...
}
- Proxy方法返回一个代理,如果未指定或者*URL为零值,则不会启动代理。
- Dial()创建tcp连接。
- TLSClientConfig为ssl连接专用,指定tls客户端配置,如果不指定则使用默认。
- DisableKeepAlives是否取消常连接,默认false,即启用常连接。
- DisableCompression是否取消压缩,默认false,即启用压缩。
- MaxIdleConnsPerHost指定最大非活跃连接数量。
其它公开的成员方法:
func(t *Transport) CloseIdleConnections() 。 该方法用于关闭所有非活跃的
连接。func(t *Transport) RegisterProtocol(scheme string, rt RoundTripper)。 该方法可用于注册并启用一个新的传输协议,比如 WebSocket 的传输协议标准(ws),或 者 FTP、File 协议等。
-
func(t *Transport) RoundTrip(req *Request) (resp *Response, err error)。 用于实现 http.RoundTripper 接口。
func main() {
tr := &http.Transport{
TLSClientConfig: &tls.Config{RootCAs:pool},
DisableCompression: true,
}
client := &http.Client{Transport:tr}
resp,err := client.Get("https:/example.com")
}
Client和Transport在执行多个goroutine的并发过程是线程安全的,但出于性能考虑,应当创建一次后反复使用。
http.RoundTripper接口
type RoundTripper interface {
// RoundTrip执行一个单一的HTTP事务,返回相应的响应信息。
// RoundTrip函数的实现不应试图去理解响应的内容。如果RoundTrip得到一个响应,
// 无论该响应的HTTP状态码如何,都应将返回的err设置为nil。非空的err
// 只意味着没有成功获取到响应。
// 类似地,RoundTrip也不应试图处理更高级别的协议,比如重定向、认证和Cookie等。
// // RoundTrip不应修改请求内容, 除非了是为了理解Body内容。每一个请求
// 的URL和Header域都应被正确初始化
RoundTrip(*Request) (*Response, error)
}
RoundTrip()方法用于执行独立的HTTP事务。
实现时不应在方法中解析HTTP响应。若响应成功,err值必须为nil,而与响应状态码无关。
类似也不要试图在RoundTrip()方法中处理协议层面的细节,如重定向,认证或是cookie等。
非必要情况下也不应该修改请求体*Request。
type CustomTransport struct {
Transport http.RoundTripper
}
func (t *CustomTransport) transport() http.RoundTripper {
if t.Transport != nil{
return t.Transport
}
return http.DefaultTransport
}
func (t *CustomTransport) RoundTrip(req *http.Request) (*http.Response,error) {
//发起http请求
//添加一些域到req.Header
return t.transport().RoundTrip(req)
}
func (t *CustomTransport)Client() *http.Client {
return &http.Client{Transport: t}//结构体直接传变量进行初始化
}
http.Client被设计成上下两层,一层是业务层:初始化HTTP Method 、目标url、请求参数、请求内容。一层是传输层细节,包括:http底层传输细节、http代理、gzip压缩、连接池管理、SSL认证等。
http服务端
处理http请求
func ListenAndServe(addr string, handler Handler) error
用于在指定的 TCP 网络地址 addr 进行监听,然后调用服务端处理程序来处理传入的连接请求。
第二个参数为服务端处理程序,通常为空。意味着调用http.DefaultServeMux进行处理,而服务端编写的业务逻辑处理http.Handle()或http.HandleFunc()默认注入到http.DefaultServeMux。
func main() {
http.Handle("/foo",fooHandler)
http.HandleFunc("/bar",func(w http.ResponseWriter,r *http.Request){
fmt.Fprintf(w,"Hello,%q",html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080",nil))
}
如果想更多地控制服务端的行为,可以自定义 http.Server。
s := &http.Server{
Addr: ":8080",
Handler: myHandler,
ReadTimeout: 10*time.Second,
WriteTimeout: 10*time.Second,
MaxHeaderBytes: 1<<20,
}
log.Fatal(s.ListenAndServe())
处下https请求
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error
certFile对应SSL证书 文件存放路径,keyFile对应证书私钥文件路径。如果证书是由证书颁发机构签署的,certFile 参数指定的路径必须是存放在服务器上的经由CA认证过的SSL证书。
5.4 JSON处理
较xml更轻量级,易于阅读的解析,跨语言,跨平台。go语言使用内置库encoding/json库处理。
type Book struct {
Title string
Authors []string
Publisher string
Price float32
}
func main() {
gobook := Book{
Title: "Go编程", //只用作数据传输的结构体直接写,不用构造函数
Authors: []string{"A", "B"}, //切片在用大括号初始化
Publisher: "turning.com",
Price: 9.9, //结构体构造,最后一个也要逗号
}
_,err := json.Marshal(gobook)//编码
if err != nil {
fmt.Print("suc")
}
}
大多数数据类型都可以转化为有效的JSON文本,但channel、complex和函数除外。
如果数据结构中含有指针,将会转化指针所指向的值。如果指针指向0值,则输入null。
go中json中数据映射:
- 布尔型不变
- 整数和浮点转为数字串
- 字串以UTF-8转为Unicode字符集的字符串。
- 数组和切片转为json里的数组,
- 结构体转为json对象,仅开头字母大小的
解码json数据
func Unmarshal(data []byte, v interface{}) error
该方法根据如下约定查找字段,如查找Foo
- 一个包含Foo的字段
- 一个名为Foo的字段
- 一个名为Foo或除了首字母其它字母不区分大小写的名为Foo的字段。
如果字段在Go目标类型中不存在则丢弃该字段。同时私有字段不受影响。
解码未知结构的json数据
解码未知结构的数据只需要输入到空接口。解码成map[string]interface{}结构。
b :=[]byte(`{
"Title": "Go编程",
"Price": 9.99
}`)
var r interface{}
_ := json.Unmarshal(b,&r)
//判断目标结构是否是符合预期
gobook,ok := r.(map[string]interface{})//类型判断
if ok{
for k,v := range gobook{//遍历哈希
switch v2 := v.(type) {//取类型
case string:
fmt.Print("is string")
case []interface{}:
fmt.Print("is an array")
default:
fmt.Print("is another type not handle yet")
}
}
}
json流式读写
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder
func main() {
dec :=json.NewDecoder(os.Stdin)
enc :=json.NewEncoder(os.Stdout)
for {
var v map[string]interface{}
if err := dec.Decode(&v); err != nil {
log.Println(err)
return
}
for k := range v {
if k != "Title" {
v[k] = nil
}
}
if err := enc.Encode(&v); err != nil {
log.Println(err)
}
}
}
json流式读写的编解码都是包装流,以对象指针为形参。