1.监听接口
2.接收客户端的tcp连接,建立客户端和服务端的链接
3.创建goroutine,处理该链接的请求(通常客户端会通过连接发送请求包)。使用goroutine能够处理很多的请求。
1.建立于服务端的链接
2.发送请求数据,接受服务器端返回的结果数据
3.关闭链接
每来一个客户端都能够开一个协程来进行处理
1.编写一个服务器端程序,在8888端口监听
2.可以和多个客户端创建链接
3.链接成功后,客户端可以发送数据,服务器端接受数据并显示在终端上
4.先使用telnet来测试,然后编写客户端程序来测试
ln, err := net.Listen("tcp", ":8080")
if err != nil {
// handle error
}
for {
conn, err := ln.Accept()
if err != nil {
// handle error
continue
}
go handleConnection(conn)
}
返回在一个本地网络地址laddr上监听的Listener。网络类型参数net必须是面向流的网络:
"tcp"、"tcp4"、"tcp6"、"unix"或"unixpacket"。参见Dial函数获取laddr的语法。
func Listen
func Listen(net, laddr string) (Listener, error)
使用案例:
// Listen on TCP port 2000 on all interfaces.
l, err := net.Listen("tcp", ":2000")
if err != nil {
log.Fatal(err)
}
defer l.Close()
for {
// Wait for a connection.
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
// Handle the connection in a new goroutine.
// The loop then returns to accepting, so that
// multiple connections may be served concurrently.
go func(c net.Conn) {
// Echo all incoming data.
io.Copy(c, c)
// Shut down the connection.
c.Close()
}(conn)
}
type Listener
type Listener interface {
// Addr返回该接口的网络地址
Addr() Addr
// Accept等待并返回下一个连接到该接口的连接
Accept() (c Conn, err error)
// Close关闭该接口,并使任何阻塞的Accept操作都会不再阻塞并返回错误。
Close() error
}
开始实战的代码:
Listen会返回一个listener和一个error错误信息,用listen和err来接受。
func main() {
fmt.Println("服务器开始监听。。。")
//1.第一个参数tcp表示网络协议是tcp
//2.第二个参数0.0.0.0:8888 表示在本地监听8888端口
listen, err := net.Listen("tcp", "0.0.0.0:8888")
如果,err不为空的话,就说明是有错误的,要打印出来监听所产生的错误,然后直接return。
package main
import (
"fmt"
"net"
)
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 {
//err不为空就表示失败了
fmt.Println("listen err=", err)
//一旦监听都不成功就别干了,就直接失败了,就直接return了
return
}
fmt.Printf("Listen suc=%v\n", Listen)
}
先打印出来,看看Listen是个什么东西,但是发现当调用完这个函数后,就直接停止了,就不会继续监听了,所以需要想个别的办法让server继续监听,不要让他直接就停止掉。
这时候注意到,官方给的Listener的字段方法,第二个方法Accept会等待连接,不让这个监听直接就关上。
type Listener interface {
// Addr返回该接口的网络地址
Addr() Addr
// Accept等待并返回下一个连接到该接口的连接
Accept() (c Conn, err error)
// Close关闭该接口,并使任何阻塞的Accept操作都会不再阻塞并返回错误。
Close() error
}
Accept返回的是一个Conn类型的c,查看Conn详细内容,发现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
}
新加入的代码如下,可以循环监听,在for循环中有Accept,这样可以循环的接受到新的连接,在for循环中,准备一个位置要起一个协程,这个协程是来为客服端进行服务的。
defer Listen.Close() //延时关闭listen
//循环的等待,等待的是客户端的连接
for {
fmt.Println("等待客户端来连接。。。")
conn, err := Listen.Accept()
if err != nil {
//这里出现错误,是Accept出错了,哪里出现的错误要体现出来
fmt.Println("Accept err=", err)
return
} else {
fmt.Printf("Accept() con=%v,ip=%v\n", conn, conn.RemoteAddr().String())
}
//这里准备起一个协程,来为客户端进行服务
//用telnet来进行测试连接
}
因为还没有客户端,所以来使用telnet来进行测试
在这里,for又蹦出来一个等待客户端的连接,说明这个telnet指令连接成功了。
下面开始写client,
conn, err := net.Dial("tcp", "0.0.0.0:8888")
如果成功,会建立一个连接,返回一个conn。
package main
import (
//"bufio"
"fmt"
"net"
//"os"
//"strings"
)
func main() {
//Dial,你用tcp我也用tcp,
//返回这个连接
conn, err := net.Dial("tcp", "0.0.0.0:8888")
if err != nil {
fmt.Println("client dial err=", err)
//连都连不上一定要return
return
}
fmt.Println("conn 成功=", conn)
}
fmt.Printf("Accept() con=%v,ip=%v\n", conn, conn.RemoteAddr().String())
查看Add,发现Add是一个结构类型的结构体,其中有个String()字段是string类型的,于是.String()就能调出来这个结构体的地址字段。
type Addr
type Addr interface {
Network() string // 网络名
String() string // 字符串格式的地址
}
给客户端加一个功能,可以发送单行数据,并退出。
1,首先调用bufio中的NewReader来读入键盘输入的东西,然后使用conn中的Write来写入,因为需要写入的是byte类型的,所以需要进行格式的强制转换。
//功能一:客户端可以发送单行数据,然后就退出
reader := bufio.NewReader(os.Stdin) //Stdin代表标准输入,即【终端】
//从终端读取到一行用户的输入,准备发送给服务器
line, err := reader.ReadString('\n')
if err != nil {
fmt.Println("readString err=", err)
}
//type Conn interface {
// Read从连接中读取数据
// Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
//Read(b []byte) (n int, err error)
// Write从连接中写入数据
// Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
//Write(b []byte) (n int, err error)
n, err := conn.Write([]byte(line + "\n"))
//从上面来看,conn.Write会返回一个n,代表的是字节的长度
if err != nil {
fmt.Println("conn.Writer err=", err)
}
fmt.Printf("客户端发送了%d字节的数据,并退出", n)
process接受的是conn,可以对conn的数据进行处理。
func process(conn net.Conn) {
//在这里,可以循环的接受客户端发送的数据
//在这里,需要关闭,如果不关闭的话,连接会越来越多
defer conn.Close() //关闭这个conn
for {
//每次都要创建一个新的切片
buf := make([]byte, 1024)
//conn.Read(buf)
//1.等待客户端通过conn发送信息
//2.如果客户端没有writer[发送],那么这个协程就阻塞在这里
//fmt.Printf("服务器在等待客户端发送信息%s\n", conn.RemoteAddr().String())
//Read(b []byte) (n int, err error),read需要一个切片
n, err := conn.Read(buf) //从conn读取
// Read reads data from the connection.
// Read can be made to time out and return an error after a fixed
// time limit; see SetDeadline and SetReadDeadline.
//Read(b []byte) (n int, err error)
if err != nil {
fmt.Println("服务器端的Read err=", err)
return
}
//3.显示客户端发送的内容到服务器的终端
fmt.Print(string(buf[:n])) //n 代表的是从管道中真正读到的内容是什么
//为什么要加一个n,因为buf是开了一个空间,如果不到n的话,可能会输出很多的空格
}
}
客户端输入过数据之后,客户端会退出,这时候err就有了,那么服务端就会报错。
代码实现在这里:
if err != nil {
fmt.Println("服务器端的Read err=", err)
return//在这里,client的退出也会报错
}
将之前的语句嵌套进一个for中就实现了循环的输入
for {
//从终端读取到一行用户的输入,准备发送给服务器
line, err := reader.ReadString('\n')
if err != nil {
fmt.Println("readString err=", err)
}
//因为上面用户输入的是加上了\n,所以这里需要去掉
line = strings.Trim(line, " \r\n")
//如果用户输入的是exit 就退出
if line == "exit" {
fmt.Println("客户端退出了")
break
}
//type Conn interface {
// Read从连接中读取数据
// Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
//Read(b []byte) (n int, err error)
// Write从连接中写入数据
// Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
//Write(b []byte) (n int, err error)
_, err = conn.Write([]byte(line + "\n"))
//从上面来看,conn.Write会返回一个n,代表的是字节的长度
if err != nil {
fmt.Println("conn.Writer err=", err)
}
}
效果如下图