Go 网络编程

本文介绍了Socket编程的基础概念,包括TCP/IP中的套接字、TCPSocket和UTPSocket的区别,以及如何在TCP和UDP服务端/客户端进行编程。此外,还涵盖了HTTP和WebSocket的通信原理与示例,展示了从基础套接字到高级Web实时通信的演进。

Socket 编程

Socket套接字是TCP/IP网络中API,有很多函数例程程序员创建TCP/IP网络程序,应用程序通过套接字向网络发送请求,接收请求

Socket图解

Socket是用户层和传输层中间的抽象层

Socket如何通信

三元组(ip地址 端口 协议) 在网络中确定一个进程

Socket基础知识

Socket有两种: TCP Socket和UTP Socket

TCP编程 

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    LocalAddr() Addr
    RemoteAddr() Addr
    SetDeadline(t time.Time) error
    SetReadDeadline(t time.Time) error
    SetWriteDeadline(t time.Time) error
}

// Conn是一个通用的面向流网络连接。
//多个程序可以同时调用一个Conn上的方法。
type Conn interface {
	// Read从连接读取数据。
	//可以设置读取超时并在固定的时间限制后返回错误;参见SetDeadline和SetReadDeadline。
	Read(b []byte) (n int, err error)

	// Write向连接写入数据。
	//可以设置写入超时并在固定的时间限制后返回错误;参见SetDeadline和SetWriteDeadline。
	Write(b []byte) (n int, err error)

	//关闭连接。
	//被阻塞的读写操作将被解除阻塞并返回错误。
	Close() error

	// LocalAddr返回本地网络地址(如果已知)。
	LocalAddr() Addr

	// RemoteAddr返回远程网络地址,如果已知。
	RemoteAddr() Addr

	// SetDeadline sets the read and write deadlines associated
	// with the connection. It is equivalent to calling both
	// SetReadDeadline and SetWriteDeadline.
	//
	// A deadline is an absolute time after which I/O operations
	// fail instead of blocking. The deadline applies to all future
	// and pending I/O, not just the immediately following call to
	// Read or Write. After a deadline has been exceeded, the
	// connection can be refreshed by setting a deadline in the future.
	//
	// If the deadline is exceeded a call to Read or Write or to other
	// I/O methods will return an error that wraps os.ErrDeadlineExceeded.
	// This can be tested using errors.Is(err, os.ErrDeadlineExceeded).
	// The error's Timeout method will return true, but note that there
	// are other possible errors for which the Timeout method will
	// return true even if the deadline has not been exceeded.
	//
	// An idle timeout can be implemented by repeatedly extending
	// the deadline after successful Read or Write calls.
	//
	// A zero value for t means I/O operations will not time out.
	SetDeadline(t time.Time) error

	// SetReadDeadline为将来的Read调用和当前阻塞的Read调用设置截止日期。
	//如果t为零,表示Read不会超时。
	SetReadDeadline(t time.Time) error

	// SetWriteDeadline设置未来Write调用和当前阻塞的Write调用的截止日期。
	//即使写超时,也可能返回n > 0,表示
    //部分数据写入成功。
	//如果t为零,表示写入操作不会超时。
	SetWriteDeadline(t time.Time) error
}

TCP服务端 



/*

TCP服务端程序的处理流程:
1. 监听端口
2. 接收客户端请求建立链接
3. 创建goroutine处理链接。

conn.Write()
conn.Close()
conn.Read()

func ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error)
	net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only)、TCP(IPv6-only)或者
	TCP(IPv4,IPv6的任意一个)
	laddr表示本机地址,一般设置为nil
	raddr表示远程的服务地址
func (l *TCPListener) Accept() (Conn, error)

可以通过net包来创建一个服务器端程序,在服务器端我们需要绑定服务到指定的非激活端口,并监听此
端口,当有客户端请求到达的时候可以接收到来自客户端连接的请求。net包中有相应功能的函数,函数
*/

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	var buf [128]byte
	for {
		n, err := reader.Read(buf[:])
		if err != nil {
			fmt.Println("读取失败")
			break
		}
		recvStr := string(buf[:n])
		fmt.Println("收到client端发来的数据:", recvStr)
		conn.Write([]byte(recvStr)) //发送数据

	}
}
func main() {
	// 指定要监听的地址和端口
	address := "127.0.0.1:20000"

	// 创建 TCPAddr 结构体,指定地址和端口
	tcpAddr, err := net.ResolveTCPAddr("tcp", address)
	if err != nil {
		fmt.Println("Error resolving address:", err)
		return
	}
	// 开始监听指定地址和端口的 TCP 连接
	listener, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		fmt.Println("Error listening:", err)
		return
	}
	for {
		conn, err := listener.Accept() //建立连接
		if err != nil {
			fmt.Println("连接错误")
			continue
		}
		go process(conn) //启动一个go程处理连接

	}

}

 TCP客户端


/*
一个TCP客户端进行TCP通信的流程如下:
1. 建立与服务端的链接
2. 进行数据收发
3. 关闭链接

func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
	net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only)、TCP(IPv6-only)或者
	TCP(IPv4,IPv6的任意一个)
	laddr表示本机地址,一般设置为nil
	raddr表示远程的服务地址

Go语言中通过net包中的 DialTCP 函数来建立一个TCP连接,并返回一个 TCPConn 类型的对象,当连接
建立时服务器端也创建一个同类型的对象,此时客户端和服务器段通过各自拥有的 TCPConn 对象来进行
数据交换。一般而言,客户端通过 TCPConn 对象将请求信息发送到服务器端,读取服务器端响应的信
息。服务器端读取并解析来自客户端的请求,并返回应答信息,这个连接只有当任一端关闭了连接之后
才失效,不然这连接可以一直在使用。建立连接的函数定义如下:
*/
func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("err :", err)
		return
	}
	defer conn.Close() // 关闭连接
	inputReader := bufio.NewReader(os.Stdin)
	for {
		input, _ := inputReader.ReadString('\n') //读取用户输入
		inputInfo := strings.Trim(input, "\r\n") //用于去除字符串回车和换行符。
		if strings.ToUpper(inputInfo) == "Q" {   // 如果输入q就退出
			return
		}
		_, err = conn.Write([]byte(inputInfo)) // 发送数据
		if err != nil {
			return
		}
		buf := [512]byte{} //读取数据
		n, err := conn.Read(buf[:])
		if err != nil {
			fmt.Println("读取失败")
			return
		}
		fmt.Println(string(buf[:n]))
	}
}

DialTimeout() 

/*

func DialTimeout(network, address string, timeout time.Duration) (Conn, error)
设置建立连接的超时时间,客户端和服务器端都适用,当超过设置时间时,连接自动关闭。
 */

UDP编程

UDP协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System
Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。
Go语言包中处理UDP Socket和TCP Socket不同的地方就是在服务器端处理多个客户端请求数据包的方式不同,UDP缺少了对客户端连接请求的Accept函数。其他基本几乎一模一样,只有TCP换成了UDP而已。UDP的几个主要函数如下所示:

 UDP服务端

package main

import (
	"fmt"
	"net"
)

func main() {
	listen, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	defer listen.Close()
	for {
		var data [1024]byte
		n, addr, err := listen.ReadFromUDP(data[:]) // 接收数据
		if err != nil {
			fmt.Println("read udp failed, err:", err)
			continue
		}
		fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
		_, err = listen.WriteToUDP(data[:n], addr) // 发送数据
		if err != nil {
			fmt.Println("write to udp failed, err:", err)
			continue
		}
	}
}

   UDP客户端

// UDP/client/client.go
// UDP client端
package main

import (
	"fmt"
	"net"
)

// UDP 客户端
func main() {
	socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("连接服务端失败,err:", err)
		return
	}
	defer socket.Close()
	sendData := []byte("Hello server")
	_, err = socket.Write(sendData) // 发送数据
	if err != nil {
		fmt.Println("发送数据失败,err:", err)
		return
	}
	data := make([]byte, 4096)
	n, remoteAddr, err := socket.ReadFromUDP(data) // 接收数据
	if err != nil {
		fmt.Println("接收数据失败,err:", err)
		return
	}
	fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}

HTTP编程

HTTP协议

超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,它详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。
HTTP协议通常承载于TCP协议之上。Go语言通过引入net/http包来实现http网络访问,并提供HTTP客户端和服务端的实现。

HTTP服务端

package main

import (
	"fmt"
	"net/http"
)

/*
ListenAndServe使用指定的监听地址和处理器启动一个HTTP服务端。处理器参数通常是nil,这表示采
用包变量DefaultServeMux作为处理器。
Handle和HandleFunc函数可以向DefaultServeMux添加处理器。

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

*/
func main() {
	http.HandleFunc("/go", myHandler)
	err := http.ListenAndServe("127.0.0.1:8000", nil)
	if err != nil {
		fmt.Println("服务端启动失败")
	}
}

// handler函数
func myHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Println(r.RemoteAddr, "连接成功")
	// 请求方式:GET POST DELETE PUT UPDATE
	fmt.Println("method:", r.Method)
	// /go
	fmt.Println("url:", r.URL.Path)
	fmt.Println("header:", r.Header)
	fmt.Println("body:", r.Body)
	// 回复
	w.Write([]byte("我爱包子!!!"))
}

WebSoket编程

WebSocket是HTML5的重要特性,它实现了基于浏览器的远程socket,它使浏览器和服务器可以进行全双工通信,许多浏览器(Firefox、Google Chrome和Safari)都已对此做了支持。
在WebSocket出现之前,为了实现即时通信,采用的技术都是“轮询”,即在特定的时间间隔内,由浏览器对服务器发出HTTP Request,服务器在收到请求后,返回最新的数据给浏览器刷新,“轮询”使得浏览器需要对服务器不断发出请求,这样会占用大量带宽。
WebSocket采用了一些特殊的报头,使得浏览器和服务器只需要做一个握手的动作,就可以在浏览器和服务器之间建立一条连接通道。且此连接会保持在活动状态,你可以使用JavaScript来向连接写入或从中接收数据,就像在使用一个常规的TCP Socket一样。它解决了Web实时化的问题,相比传统HTTP有如下好处:

一个Web客户端只建立一个TCP连接
Websocket服务端可以推送(push)数据到web客户端.
有更加轻量级的头,减少数据传送量

websoket原理 

WebSocket的目的就是解决网络传输中的双向通信

WebSocket URL的起始输入是ws://或是wss://(在SSL上)。下图展示了WebSocket的通信过程,一个带有特定报头的HTTP握手被发送到了服务器端,接着在服务器端或是客户端就可以通过JavaScript来使用某种套接口(socket),这一套接口可被用来通过事件句柄异步地接收数据。

WebSocket是一种在单个TCP连接上进行全双工通信的协议。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSoket服务端



/*
		websocket.Message.Receive()
		websocket.Message.Send()

func (cd Codec) Receive(ws *Conn, v interface{}) (err error)
func (cd Codec) Send(ws *Conn, v interface{}) (err error)

当客户端将用户输入的信息Send之后,服务器端通过Receive接收到了相应信息,然后通过Send发送了应答信息。
*/
func Echo(ws *websocket.Conn) {
	var err error
	for {

		var reply string
		if err = websocket.Message.Receive(ws, &reply); err != nil { //接收信息
			fmt.Println("Can't receive")
			break
		}
		fmt.Println("Received back from client: " + reply)
		msg := "Received: " + reply
		fmt.Println("Sending to client: " + msg)
		if err = websocket.Message.Send(ws, msg); err != nil {
			fmt.Println("Can't send")
			break
		}
	}

}
func main() {
	http.Handle("/", websocket.Handler(Echo)) //这里校验请求头中的Origin字段
	if err := http.ListenAndServe("127.0.0.1:1234", nil); err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值