网络编程基本
golang的主要设计目标之一就是面向大规模后端服务程序,网络通信是服务端程序不可少的一部分
网络编程两种
- TCP socket编程,是网络编程的主流。TCP socket编程是因为底层是基于tcp/ip协议的
- b/s结构的http编程,使用浏览器去访问服务器时,使用的就是http协议,而http底层依旧是用tcp socket实现的
协议(tcp/ip)
TCP/IP(Transmission Control Protocol/Internet Protocol)的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是Internet最基本的协议、Internet国际互联网络的基础,简单地说,就是由网络层的IP协议和传输层的TCP协议组成的。
OSI与TCP/IP参考模型
ip地址
每个internet上的主机和路由器都有一个ip地址,它包括网络号和主机号,ip地址有ipv4(32位)或者ipv6(128位)
端口(port)
此处端口特指TCP/IP协议中的端口,是逻辑端口
如果把ip地址比作一间房子,端口就是出入这个房子的门。一个ip地址的端口可以有 65536(256X256)个,端口通过端口号来标记。端口号范围 0 ~ 65535
端口(port)-分类
- 0号是保留端口
- 1-1024是固定端口(程序员不要使用)
又叫有名端口,即被某些程序固定使用 - 1025-65535是动态端口(程序员可以使用)
端口(port)- 使用注意
- 在计算机(尤其是做服务器)要尽可能的少开端口
- 一个端口只能被一个程序监听
- netstat -an 查看本机在监听哪些端口
- netstat -anb 查看监听端口的pid
tcp socket 编程的客户端和服务端
tcp socket编程-服务端处理流程
- 监听端口 8888
- 接收客户端的tcp连接,建立客户端和服务端的连接
- 创建goroutine, 处理该连接的请求(通常客户端会通过连接发送请求包)
tcp socket编程-客户端处理流程
- 建立与服务端的连接
- 发送请求数据[终端],接收服务器端返回的结果数据
- 关闭连接
程序示意图
服务器端功能
- func Listen
func Listen(net, laddr string) (Listener, error)
返回在一个本地网络地址laddr上监听的Listener。网络类型参数net必须是面向流的网络:
“tcp”、“tcp4”、“tcp6”、“unix"或"unixpacket”。参见Dial函数获取laddr的语法
- type Listener
type Listener interface {
// Addr返回该接口的网络地址
Addr() Addr
// Accept等待并返回下一个连接到该接口的连接
Accept() (c Conn, err error)
// Close关闭该接口,并使任何阻塞的Accept操作都会不再阻塞并返回错误。
Close() error
}
Listener是一个用于面向流的网络协议的公用的网络监听器接口。多个线程可能会同时调用一个Listener的方法
- type Conn
type Conn interface {
// Read从连接中读取数据
// Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
Read(b []byte) (n int, err error)
// Write从连接中写入数据
// Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
Write(b []byte) (n int, err error)
// Close方法关闭该连接
// 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误
Close() error
// 返回本地网络地址
LocalAddr() Addr
// 返回远端网络地址
RemoteAddr() Addr
// 设定该连接的读写deadline,等价于同时调用SetReadDeadline和SetWriteDeadline
// deadline是一个绝对时间,超过该时间后I/O操作就会直接因超时失败返回而不会阻塞
// deadline对之后的所有I/O操作都起效,而不仅仅是下一次的读或写操作
// 参数t为零值表示不设置期限
SetDeadline(t time.Time) error
// 设定该连接的读操作deadline,参数t为零值表示不设置期限
SetReadDeadline(t time.Time) error
// 设定该连接的写操作deadline,参数t为零值表示不设置期限
// 即使写入超时,返回值n也可能>0,说明成功写入了部分数据
SetWriteDeadline(t time.Time) error
}
Conn接口代表通用的面向流的网络连接。多个线程可能会同时调用同一个Conn的方法
服务器端程序,在8888端口监听
可以和多个客户端创建连接
连接成功后,客户端可以发送数据,服务端接收数据,并显示在终端上
先使用telnet测试,然后编写客户端程序测试
- 服务端代码
package main
import (
"fmt"
"net"
)
func process(conn net.Conn) {
defer conn.Close() //关闭conn
for {
buf := make([]byte, 1024)
//1. 等待客户端通过conn发送信息
//2. 如果客户端没有write[发送],那么协程就阻塞在这里
fmt.Printf("服务器在等待客户端%s 发送信息\n", conn.RemoteAddr().String())
n, err := conn.Read(buf) //从conn读取
if err != nil {
fmt.Printf("客户端退出 err=%v", err)
return
}
//3. 显示客户端发送的内容到服务器的终端
fmt.Print(string(buf[:n]))
}
}
func main() {
fmt.Println("服务器开启监听...")
//1. tcp表示使用网络协议是tcp
//2. 0.0.0.0:8888表示在本地监听 8888端口
listen, err := net.Listen("tcp", "0.0.0.0:8888")
if err != nil {
fmt.Println("listen err=", err)
return
}
defer listen.Close() //延时关闭listen
//循环等待客户端来连接我
for {
//等待客户端连接
fmt.Println("等待客户端来连接...")
conn, err := listen.Accept()
if err != nil {
fmt.Println("Accept() err=", err)
} else {
fmt.Printf("Accept() suc con=%v 客户端ip=%v\n", conn, conn.RemoteAddr().String())
}
//启一个协程,为客户端服务
go process(conn)
}
fmt.Printf("listen suc=%v\n", listen)
}
客户端功能
- func Dial
func Dial(network, address string) (Conn, error)
在网络network上连接地址address,并返回一个Conn接口。可用的网络类型有:
“tcp”、“tcp4”、“tcp6”、“udp”、“udp4”、“udp6”、“ip”、“ip4”、“ip6”、“unix”、“unixgram”、“unixpacket”
对TCP和UDP网络,地址格式是host:port或[host]:port,参见函数JoinHostPort和SplitHostPort
Dial("tcp", "12.34.56.78:80")
Dial("tcp", "google.com:http")
Dial("tcp", "[2001:db8::1]:http")
Dial("tcp", "[fe80::1%lo0]:80")
对IP网络,network必须是"ip"、“ip4”、"ip6"后跟冒号和协议号或者协议名,地址必须是IP地址字面值
Dial("ip4:1", "127.0.0.1")
Dial("ip6:ospf", "::1")
对Unix网络,地址必须是文件系统路径
- 客户端程序,能连接到服务端的8888端口
- 客户端可以发送单行数据,然后就退出
- 能通过终端输入数据(输入一行发送一行),并发送给服务器端[]
- 在终端输入exit,表示退出程序
package main
import (
"bufio"
"fmt"
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8888")
if err != nil {
fmt.Println("client dial err=", err)
return
}
// 客户端发送单行数据,然后退出
reader := bufio.NewReader(os.Stdin) //os.Stdin 代表标准输入[终端]
//从终端读取一行用户输入,并准备发送给服务器
line, err := reader.ReadString('\n')
if err != nil {
fmt.Println("readString err=", err)
}
//再将line发给服务器
n, err := conn.Write([]byte(line))
if err != nil {
fmt.Println("conn.Write err=", err)
}
fmt.Printf("客户端发送了 %d字节的数据,并退出", n)
}
改进
客户端
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8888")
if err != nil {
fmt.Println("client dial err=", err)
return
}
//从终端(标准输入)读取
reader := bufio.NewReader(os.Stdin)
for {
line, err := reader.ReadString('\n')
if err != nil {
fmt.Println("ReadString err=", err)
}
//如果用户输入的是exit退出
line = strings.Trim(line, " \r\n")
if line == "exit" {
fmt.Println("客户端退出...")
break
}
//将line发送给服务器
_, err = conn.Write([]byte(line + "\n"))
if err != nil {
fmt.Println("conn.Write err=", err)
}
}
}
服务端
package main
import (
"fmt"
"net"
)
func process(conn net.Conn) {
//循环接收客户端发送的数据
defer conn.Close()
for {
//创建一个新的切片
buf := make([]byte, 1024)
//等待客户端通过conn发送信息
//如果客户端没有write[发送], 那么协程就阻塞在这里
//fmt.Printf("服务器在等待客户端%s 发送信息\n", conn.RemoteAddr().String())
n, err := conn.Read(buf)//从conn读取
if err != nil {
fmt.Printf("客户端退出 err=%v\n", err)
return
}
//3. 显示客户端发送的内容到服务器终端
fmt.Print(string(buf[:n]))
}
}
func main() {
fmt.Println("服务器开始监听...")
listen ,err := net.Listen("tcp", "0.0.0.0:8888")
if err != nil {
fmt.Println("listen err=", err)
return
}
defer listen.Close()
//循环等待客户端来连接
for {
fmt.Println("等待客户端来连接")
conn, err := listen.Accept()
if err != nil {
fmt.Println("Accept() err=", err)
} else {
fmt.Printf("Accept() suc con=%v 客户端ip=%v\n", conn, conn.RemoteAddr().String())
}
//准备一个协程,处理客户端
go process(conn)
}
//fmt.Printf("listen=%v\n", listen)
}