内容参考net包,net包提供了可移植的网络I/O接口,包括TCP/IP、UDP、域名解析和Unix域socket。
虽然本包提供了对网络原语的访问,大部分使用者只需要Dial、Listen和Accept函数提供的基本接口;以及相关的Conn和Listener接口。crypto/tls包提供了相同的接口和类似的Dial和Listen函数。
服务端
在服务器端我们需要绑定服务到指定的非激活端口, 并监听此端口;当有客户端请求到达的时候可以接收到来自客户端连接的请求。
package main
import (
"fmt"
"net"
"os"
"strings"
"time"
)
func main() {
// 监听的服务器端口
service := "127.0.0.1:1200"
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
// 在服务器端我们需要绑定服务到指定的非激活端口, 并监听此端口,
// 当有客户端请求到达的时候可以接收到来自客户端连接的请求
//
// func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error)
// ListenTCP在本地TCP地址laddr上声明并返回一个*TCPListener, net参数必须是"tcp", "tcp4", "tcp6",
// 如果laddr的端口字段为0, 函数将选择一个当前可用的端口, 可以用Listener的Addr方法获得该端口
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
for {
// 接受每一种请求
// func (l *TCPListener) Accept() (Conn, error)
// Accept用于实现Listener接口的Accept方法
// 他会等待下一个呼叫, 并返回一个该呼叫的Conn接口
conn, err := listener.Accept()
// 当有错误发生的情况下最好是由服务端记录错误, 然后当前连接的客户端直接报错而退出, 从而不会
// 影响到当前服务端运行的整个服务
if err != nil {
continue
}
// 处理客户端的连接
go handleClient(conn)
}
}
func handleClient(conn net.Conn) {
// 超时时间, 当一定时间内客户端无请求发送, conn便会自动关闭, 下面的for循环即会因为连接已关闭而跳出
conn.SetReadDeadline(time.Now().Add(1 * time.Minute))
// request在创建时需要指定一个最大长度以防止flood attack; 每次读取到请求处理完毕后,
// 需要清理request, 因为conn.Read()会将新读取到的内容append到原内容之后
// request 为提供的读取的最长缓冲区, 缓冲区满后会自动回写到客户端接收, 因此客户端需要注意接收到数据的完整性后在处理
// 但是也并非都是写满后才返回给客户端, 有可能提前返回数据, 需要一个协议
// 如果该缓冲区给的不够, 可能造成客户端传递过来的信息被截断, 无法得到预期的结果
request := make([]byte, 9)
defer conn.Close()
for {
// 不断读取客户端发来的请求, 由于我们需要保持与客户端的长连接, 所以不能在读取完一次请求后就关闭连接
// 将读取到的数据追加保存到request中
readLen, err := conn.Read(request)
if err != nil {
fmt.Println(err)
break
}
// 如果没有读取到客户端任何信息, 则默认客户端已经关闭
if readLen == 0 {
break
} else if strings.TrimSpace(string(request[:readLen])) == "timestamp" {
date := time.Now().Format("2006-01-02 15:04:05")
conn.Write([]byte(date))
} else {
conn.Write([]byte(request[:readLen]))
}
// 每次发送写完毕后都清除缓存空间, 防止追加
request = make([]byte, 8)
}
}
客户端
首先程序将用户的输入作为参数service传入net.ResolveTCPAddr获取一个tcpAddr,然后把tcpAddr传入DialTCP后创建了一个TCP连接conn,通过conn来发送请求信息,最后通过ioutil.ReadAll从conn中读取全部的文本,也就是服务端响应反馈的信息。
package main
import (
"fmt"
"net"
"os"
"io/ioutil"
)
func main() {
// 获取提供服务的服务器ip地址
service := os.Args[1]
// func ResolveTCPAddr(net, addr string) (*TCPAddr, error)
// ResolveTCPAddr获取一个TCPAddr, 一个TCPAddr类型, 他表示一个TCP的地址信息
// ResolveTCPAddr将addr作为TCP地址解析并返回
// 参数addr格式为"host:port"或"[ipv6-host%zone]:port", 解析得到网络名和端口名
// net必须是"tcp", "tcp4"或"tcp6"
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
// 通过net包中的DialTCP函数来建立一个TCP连接, 并返回一个TCPConn类型的对象
// 当连接建立时服务器端也创建一个同类型的对象, 此时客户端和服务器端通过各自拥
// 有的TCPConn对象来进行数据交换
//
// 一般而言, 客户端通过TCPConn对象将请求信息, 发送到服务器端, 读取服务器端响应的信息
// 服务器端读取并解析来自客户端的请求并返回应答信息, 这个连接只有当任一端关闭了连接之后才失效,
// 不然这连接可以一直在使用
//
// func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error)
// DialTCP在网络协议net上连接本地地址laddr和远端地址raddr
// net必须是"tcp", "tcp4", "tcp6"
// 如果laddr不是nil, 将使用它作为本地地址, 否则自动选择一个本地地址
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
// 向连接的服务器端写入信息
// net包中有一个类型TCPConn, 这个类型可以用来作为客户端和服务器端交互的通道, 他有两个主要的函数:
// func (c *TCPConn) Write(b []byte) (n int, err os.Error)
// func (c *TCPConn) Read(b []byte) (n int, err os.Error)
// TCPConn可以用在客户端和服务器端来读写数据
//_, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
_, err = conn.Write([]byte("timestamp"))
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
_, err = conn.Write([]byte("a~~~"))
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
_, err = conn.Write([]byte("b~~~~~"))
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
_, err = conn.Write([]byte("c~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"))
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
// 当客户端停止写入时, 需要告诉服务端, 信息发送终止, 服务端就返回全部的数据
if err = conn.CloseWrite(); err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
// 读取服务器端响应的全部内容
// ReadAll从r读取数据直到EOF或遇到error, 返回读取的数据和遇到的错误
// 成功的调用返回的err为nil而非EOF, 因为本函数定义为读取r直到EOF, 它不会将读取返回的EOF视为应报告的错误
result, err := ioutil.ReadAll(conn)
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
// 输出获取到的内容
fmt.Println(string(result))
os.Exit(0)
}
服务端启动
服务端每次请求结束后都会以文件结束符标志输出。
zhgxun-pro:go zhgxun$ go run server.go
EOF
EOF
EOF
EOF
EOF
EOF
客户端输出
当客户端停止写入时,服务端将全部数据返回。
zhgxun-pro:go zhgxun$
zhgxun-pro:go zhgxun$ go run client.go 127.0.0.1:1200
2017-09-17 21:07:44a~~~b~~~~~c~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
zhgxun-pro:go zhgxun$ go run client.go 127.0.0.1:1200
2017-09-17 21:07:46a~~~b~~~~~c~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
zhgxun-pro:go zhgxun$ go run client.go 127.0.0.1:1200
2017-09-17 21:07:47a~~~b~~~~~c~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
zhgxun-pro:go zhgxun$ go run client.go 127.0.0.1:1200
2017-09-17 21:07:48a~~~b~~~~~c~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
zhgxun-pro:go zhgxun$ go run client.go 127.0.0.1:1200
2017-09-17 21:07:49a~~~b~~~~~c~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
zhgxun-pro:go zhgxun$ go run client.go 127.0.0.1:1200
2017-09-17 21:07:50a~~~b~~~~~c~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
zhgxun-pro:go zhgxun$