TCP
一、 服务端
1. 解析地址
在TCP服务端我们需要监听一个TCP地址,因此建立服务端前我们需要生成一个正确的TCP地址,这就需要用到下面的函数了
// ResolveTCPAddr函数会输出一个TCP连接地址和一个错误信息
func ResolveTCPAddr(network, address string) (*TCPAddr, error)
// 解析IP地址
func ResolveIPAddr(net, addr string) (*IPAddr, error)
// 解析UDP地址
func ResolveUDPAddr(net, addr string) (*UDPAddr, error)
// 解析Unix地址
func ResolveUnixAddr(net, addr string) (*UnixAddr, error)
2. 监听请求
我们可以通过 Listen方法监听我们解析后的网络地址。
// 监听net类型,地址为laddr的地址
func Listen(net, laddr string) (Listener, error)
// 监听TCP地址
func ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error)
// 监听IP地址
func ListenIP(netProto string, laddr *IPAddr) (*IPConn, error)
// 监听UDP地址
func ListenMulticastUDP(net string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, error)
func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error)
// 监听Unix地址
func ListenUnixgram(net string, laddr *UnixAddr) (*UnixConn, error)
func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error)
3. 接收请求
TCPAddr 实现了两个接受请求的方法,两者代码实现其实是一样的,唯一的区别是第一种返回了一个对象,第二种返回了一个接口。
func (l *TCPListener) AcceptTCP() (*TCPConn, error)
func (l *TCPListener) Accept() (Conn, error)
其他类型也有类似的方法,具体请参考go语言标准库文档。
4. 连接配置
配置监听器超时时间
// 超过t之后监听器自动关闭,0表示不设置超时时间
func (l *TCPListener) SetDeadline(t time.Time) error
关闭监听器
// 关闭监听器
func (l *TCPListener) Close() error
二、 TCP客户端
1. 解析TCP地址
在TCP服务端我们需要监听一个TCP地址,因此建立服务端前我们需要生成一个正确的TCP地址,这就需要用到下面的函数了。
// ResolveTCPAddr函数会输出一个TCP连接地址和一个错误信息
func ResolveTCPAddr(network, address string) (*TCPAddr, error)
2. 发送连接请求
net包提供了多种连接方法
// DialIP的作用类似于IP网络的拨号
func DialIP(network string, laddr, raddr *IPAddr) (*IPConn, error)
// Dial 连接到指定网络上的地址,涵盖
func Dial(network, address string) (Conn, error)
// 这个方法只是在Dial上面设置了超时时间
func DialTimeout(network, address string, timeout time.Duration) (Conn, error)
// DialTCP 专门用来进行TCP通信的
func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
// DialUDP 专门用来进行UDP通信的
func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)
// DialUnix 专门用来进行 Unix 通信
func DialUnix(network string, laddr, raddr *UnixAddr) (*UnixConn, error)
编写服务端与客户端
server.go
package main
import (
"log"
"net"
)
func main() {
// 解析服务端监听地址
addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8000")
if err != nil {
log.Panic(err)
}
// 创建监听器
listen, err := net.ListenTCP("tcp", addr)
if err != nil {
log.Panic(err)
}
for {
// 监听客户端连接请求
conn, err := listen.AcceptTCP()
if err != nil {
continue
}
// 处理客户端请求
go handleConnectionForServer(conn)
}
}
// handleConnection 读取数据, 在这里我们可以编写自己的交互程序
func handleConnectionForServer(conn net.Conn) {
for flag := false; ; {
// 设置消息长度为1024比特
buf := make([]byte, 1024)
if !flag {
// 客户端连接成功,提示可以操作的内容
if _, err := conn.Write([]byte(Usage())); err != nil {
log.Println("Error: ", err)
}
flag = true
continue
}
/* 读取客户端发送的数据,数据会保存到buf
这里有一个知识点:
conn.Read会返回接收到的值的长度,如果不指定长度,通过string转换的时候你会活得一个1024字节的字符串
但我们不需要后面的初始化的值,因此通过buf[:length]提取我们想要的值。
*/
if length, err := conn.Read(buf); err != nil {
// 读取失败
writeResponse(parseRequest(""), conn)
} else {
// 读取成功
req := string(buf[:length])
if req == "exit" {
break
}
writeResponse(parseRequest(req), conn)
}
}
}
func Usage() string {
return `
---------------------------------------------------------------
Hello, my name is randow_w, I'm glad to serve you.
I can provide you with the following services:
1.查工资
2.猜年龄
3.查天气
----------------------------------------------------------------`
}
// writeResponse 返回信息给客户端
func writeResponse(resp string, conn net.Conn) {
if _, err := conn.Write([]byte(resp)); err != nil {
log.Println("Error: ", err)
}
}
// parseRequest 解析客户端输入的信息
func parseRequest(req string) (resp string) {
switch req {
case "查工资":
resp = checkSalary()
case "猜年龄":
resp = guessAge()
case "查天气":
resp = chat()
default:
resp = "对不起,我爸爸还没有教我怎么回答你,能不能换一个问题(*^_^*)"
}
return
}
// 查工资
func checkSalary() string {
return "据权威机构推测,你未来有机会冲刺福布斯排行榜,加油哦(ง •_•)ง"
}
// 猜年龄
func guessAge() string {
return "永远18岁"
}
// 聊天
func chat() string {
return "你好,主人,今天是晴天,空气质量优,适合去爬山。"
}
TCP 客户端:
package main
import (
"bufio"
"fmt"
"net"
"os"
"os/signal"
"strings"
"syscall"
)
var sig = make(chan os.Signal)
func main() {
// 解析服务端地址
RemoteAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8000")
if err != nil {
panic(err)
}
// 解析本地连接地址
LocalAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8001")
if err != nil {
panic(err)
}
// 连接服务端
conn, err := net.DialTCP("tcp", LocalAddr, RemoteAddr)
if err != nil {
panic(err)
}
// 连接管理
HandleConnectionForClient(conn)
}
// handleConnection 读取数据, 在这里我们可以编写自己的交互程序
func HandleConnectionForClient(conn net.Conn) {
// 监控系统信号
go signalMonitor(conn)
// 初始化一个缓存区
Stdin := bufio.NewReader(os.Stdin)
for {
// 接收服务端返回的消息
getResponse(conn)
// 读取用户输入的信息,遇到换行符结束。
fmt.Print("[ random_w ]# ")
input, err := Stdin.ReadString('\n')
if err != nil {
fmt.Println(err)
}
// 删除字符串前后的空格,主要是删除换行符。
input = strings.TrimSpace(input)
// 空行不做处理
if len(input) == 0 {
continue
}
// 是否接收到退出指令
switch input {
case "quit", "exit":
sig <- syscall.SIGQUIT
default:
// 发送消息给服务端
sendMsgToServer(conn, input)
}
}
}
// sendMsgToServer 发送消息给服务端
func sendMsgToServer(conn net.Conn, msg string) {
for {
_, err := conn.Write([]byte(msg))
if err == nil {
break
}
}
}
// getResponse 接收服务端返回的消息
func getResponse(conn net.Conn) {
// 初始化一个1024字节的内存,用来接收服务端的消息
respByte := make([]byte, 1024)
// 接收服务端返回的消息
length, err := conn.Read(respByte)
if err != nil {
fmt.Println("[ server ]# 接收消息失败")
}
for line, str := range strings.Split(string(respByte[:length]), "\n") {
if len(str) != 0 {
if line == 1 {
fmt.Print(fmt.Sprintf("[ server ]# \n%s\n", str))
continue
}
fmt.Println(str)
}
}
}
// signalMonitor 监听系统信号,如果程序收到退出到的信号通过 Goroutine 通知 server 端,关闭连接后退出。
func signalMonitor(conn net.Conn) {
signal.Notify(sig, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGINT)
// 接收到结束信号退出此程序
select {
case <-sig:
// 通知服务端断开连接
_, _ = conn.Write([]byte("exit"))
fmt.Println("\nGood Bye !!!!!")
os.Exit(0)
}
}
go run server.go
go run client.go
[ server ]#
---------------------------------------------------------------
Hello, my name is randow_w, I'm glad to serve you.
I can provide you with the following services:
1.查工资
2.猜年龄
3.查天气
----------------------------------------------------------------
[ random_w ]# 查工资
据权威机构推测,你未来有机会冲刺福布斯排行榜,加油哦(ง •_•)ง
[ random_w ]# 猜年龄
永远18岁
[ random_w ]# 查天气
你好,主人,今天是晴天,空气质量优,适合去爬山。
[ random_w ]# 你好
对不起,我还在爸爸没有教我怎么回答你,能不能换一个问题(*^_^*)
[ random_w ]# quit
Good Bye !!!!!
UDP
package main
import (
"fmt"
"log"
"net"
)
func main() {
// 解析服务端监听地址
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8000")
if err != nil {
log.Panic(err)
}
// 创建监听器
listen, err := net.ListenUDP("udp", addr)
if err != nil {
log.Panic(err)
}
for {
// 设置消息长度为1024比特
buf := make([]byte, 1024)
// 读取消息,UDP不是面向连接的因此不需要等待连接
length, udpAddr, err := listen.ReadFromUDP(buf)
if err != nil {
log.Println("Error: ", err)
continue
}
fmt.Println("[ server ]# UdpAddr: ", udpAddr, "Data: ", string(buf[:length]))
}
}
package main
import (
"bufio"
"fmt"
"net"
"os"
"os/signal"
"strings"
"syscall"
)
var sig = make(chan os.Signal)
func main() {
// 解析服务端地址
RemoteAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8000")
if err != nil {
panic(err)
}
// 解析本地连接地址
LocalAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8001")
if err != nil {
panic(err)
}
// 连接服务端
conn, err := net.DialUDP("udp", LocalAddr, RemoteAddr)
if err != nil {
panic(err)
}
// 连接管理
HandleConnectionForClient(conn)
}
// handleConnection 读取数据, 在这里我们可以编写自己的交互程序
func HandleConnectionForClient(conn net.Conn) {
// 监控系统信号
go signalMonitor(conn)
// 初始化一个缓存区
Stdin := bufio.NewReader(os.Stdin)
for {
// 读取用户输入的信息,遇到换行符结束。
fmt.Print("[ random_w ]# ")
input, err := Stdin.ReadString('\n')
if err != nil {
fmt.Println(err)
}
// 删除字符串前后的空格,主要是删除换行符。
input = strings.TrimSpace(input)
// 空行不做处理
if len(input) == 0 {
continue
}
// 是否接收到退出指令
switch input {
case "quit", "exit":
sig <- syscall.SIGQUIT
default:
// 发送消息给服务端
sendMsgToServer(conn, input)
}
}
}
// sendMsgToServer 发送消息给服务端
func sendMsgToServer(conn net.Conn, msg string) {
for {
_, err := conn.Write([]byte(msg))
if err == nil {
break
}
}
}
// signalMonitor 监听系统信号,如果程序收到退出到的信号通过 Goroutine 通知 server 端,关闭连接后退出。
func signalMonitor(conn net.Conn) {
signal.Notify(sig, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGINT)
// 接收到结束信号退出此程序
select {
case <-sig:
// 通知服务端断开连接
_, _ = conn.Write([]byte("exit"))
fmt.Println("\nGood Bye !!!!!")
os.Exit(0)
}
}
net.Dial函数
func Dial(network, address string) (Conn, error) {
var d Dialer
return d.Dial(network, address)
}
第一个参数network:(string类型)决定着Go程序在底层会创建什么样的socket实例,并使用什么样的协议与其它程序通信。
network常用的9个可选值:
- tcp:代表TCP协议,其基于的IP协议的版本根据参数address的值自适应;
- tcp4:代表基于IP协议第四版的TCP协议;
- tcp6:代表基于IP协议第六版的TCP协议;
- udp:代表UDP协议,其基于的IP协议的版本根据参数address的值自适应;
- udp4:代表基于IP协议第四版的UDP协议;
- udp6:代表基于IP协议第六版的UDP协议;
- unix:代表Unix通信域下的一种内部socket协议,以SOCK_STREAM为socket类型。
- unixgram:代表Unix通信域下的一种内部socket协议,以SOCK_DGRAM 为socket类型;“unixgram” 表示 Unix 域数据报套接字,用于在进程之间传输不连续的数据包(类似于 UDP 协议)。
- unixpacket :代表Unix通信域下的一种内部socket协议,以SOCK_SEQPACKET为socket类型;“unixpacket” 表示 Unix 域数据包套接字,与 Unix 域流套接字类似,但是提供原子性的数据包传输,即保证在发送的数据包全部到达接收端之前,接收端不会看到任何数据包。
network不常用的3个可选项:
- ip:IP协议,可以用于任何网络类型。
- ip4:仅限IPv4的IP协议,可以用于任何网络类型。
- ip6:仅限IPv6的IP协议,可以用于任何网络类型。
需要注意的是,““unix”、unixgram” 和 “unixpacket” 这三个可选值只能在 Unix 系统上使用。如果在非 Unix 系统上使用这两个值,Dial 函数将返回错误。
net.DialTimeout函数
net.DialTimeout函数给定的超时时间,代表着函数为网络连接建立完成而等待的最长时间。
这是一个相对时间,由这个函数的参数timeout的值表示。
开始的时间几乎是我们调用net.DialTimeout函数的那一刻。在这之后,时间会花费在“解析参数network和address的值”,以及“创建socket实例并建立网络连接”这两件事情上。
不论执行到哪一步,只要在绝对的超时时间达到的那一刻,网络连接还没建立完成,该函数就会返回一个代表了I/O操作超时的错误值。
net.DialTimeout是利用net.Dialer 结构体实现超时功能的。
参考: