内容参考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$