1.监听接口
2.接收客户端的tcp连接,建立客户端和服务端的链接
3.创建goroutine,处理该链接的请求(通常客户端会通过连接发送请求包)。使用goroutine能够处理很多的请求。
客户端的处理流程1.建立于服务端的链接
2.发送请求数据,接受服务器端返回的结果数据
3.关闭链接
服务器端和n个客户端服务的示意图每来一个客户端都能够开一个协程来进行处理
服务器端的功能1.编写一个服务器端程序,在8888端口监听
2.可以和多个客户端创建链接
3.链接成功后,客户端可以发送数据,服务器端接受数据并显示在终端上
4.先使用telnet来测试,然后编写客户端程序来测试
Listen函数创建的服务端: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)
}
}
效果如下图
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)