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

被折叠的 条评论
为什么被折叠?



