开发者学堂课程【Go 语言核心编程 - 面向对象、文件、单元测试、反射、TCP 编程:服务器接收客户端消息】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/626/detail/9780
服务器接收客户端消息
内容介绍
一、开发的难度
二、客户端功能
三、客户端代码
四、修改服务端代码
一、开发的难度
写代码时可能写了一段服务器代码然后写客户端代码测试,然后在写服务器代码客户端代码测试这样的开发模式,但是如果服务器端和客户端不是同一个类型那就没有办法,只能自己写模拟程序,不能完全不测试,因为服务器代码很多,必须依赖客户端的测试才能成功。所以开发难度比单机版难。
二、客户端功能
1.续写一个客户端程序,能链接到服务器端的8888接口
2.客户端可以发送单行数据,然后退出
3.能通过终端输入数据(输入一行发送一行),发送给服务器端口
4.在终端输入 exit,表示退出程序
三、客户端代码
1.代码
package main
import(
"fmt""
"net"
)
func main(){
conn, err := net.Dial("tcp", "192.168.20.253:8888")
if err !=nil{
fmt. Println("client dial err=",err)
return
}
fmt.Println( "conn 成功=",conn)
}
(1)Dial 函数
Dial 函数和服务端建立连接:
conn, err := net. Dial ("tcp","google.com:,80")
if err !=nil {
// handle error
}
fmt. Fprintf(conn, "GeT / HTTP/1.0\r\n\r\n")
status, err := bufio NewReader(conn).Readstring('\n')
//...
(2)代码解释
google.com:,80"为对方服务器的 ip➕端口,因为一个程序一定是 ip➕端口才能区分究竟是与哪一个 ip进行沟通,一个 ip 地址可以跑几万个程序,所以不能只写 IP 不写端口。最后会返回conn连接,使两个连接兑上,就像打电话接通一样。bufioNewReader(conn).Readstring('\n')是想创建一个 reader,reader 可以接收键盘的输入,可以一行一行输,一个字符一个字符输入过慢
(3)查看连接
查看是否连上,服务器不可以关闭,新开一个
D:\go project\src\go_code\chapter18\tcpdemo\client>go run client.go
conn成功=&<<0xc04209a000>>
D:\go project\src\go_code\chapter18\tcpdemo\client>
显示等待客户端连接,表示成功
2.如何在服务器将IP地址显示出来?
在 server 中通过 conn,conn 中有一个方法是 RemoteAddr()Addr,返回的是 addr
type Addr interface {
Network()string//网络名
String()string //字符串格式的地址
}
addr 是一个接口,调 string 就可以得到字符串的地址
Server 中代码更改为fmt. Printf("Accept()conn. suc con=%V 客户端 ip=%v\n", conn,conn.RemoteAddr() string()
重新连接看到显示客户端 ip=192.168.20.253:51741,客户端端口为51741,客户端端口是随机分配的。
四、修改服务端代码
接下来实现在服务端接收一句话并显示的功能
1.代码
func main() {
conn, err := net.Dial("tcp","192.168.20.253:8888")
if err != nil {
fmt.Println("client dial err=", err) return}
fmt.Println("conn 成功=",conn)
}
//功能一:客户端可以发送单行数据,然后就退出
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)
}
(1)os.stdin
Stdin、 Stdout 和 Stderr是指向标准输入、标准输出、标准错误输出的文件描述符。
var Args []string
准确讲 stdin 指向的就是标准输入,标准输入指的就是终端,在底层中在终端中写东西也会落入到文件中去,另外一个是文件读取的,本质网络编程也是这样
(2)代码解释:
conn.write
//Write 方法可能会在超过某个固定时间限制后超时返回错误,该错误的 Timeout()方法返回真
Write(b []byte) (n int,err error)
客户端逻辑,先将号连上,再创建 new reader 再从键盘里得到一个输入发给 client,此时服务器未做接收
2.改 server
接收工作不要在主线写,在主线写会造成阻塞(有一个客户在向服务器发送数据时别的客户全部堵在这里)
package main
import(
"fmt'
"net"//做网络 socket 开发时,net 包含有我们需要所有的方法和函数
"io"
func process(conn net.Conn) {
//这里我们循环的接收客户端发送的数据
defer conn. close()//关闭 conn
for{
//创建一个新的切片
buf := make([]byte, 1024)
//conn.Read(buf)
//1.等待客户端通过conn发送信息
//2.如果客户端没有wrtie[发送],那么协程就阻塞在这里
fmt.Printf("服务器在等待客户端%s发送信息\n", conn . RemoteAddr().string())
n, err := conn.Read(buf)//从conn读取
if err s= io.EoF {
fmt.Println("客户端退出")
return //!
//3.显示客户端发送的内容到服务器的终端
fmt.print(string(buf[:n]))
}
}
Func main(){
fmt.Println("服务器开始监听....")
//net.Listen("tcp","0.0.0.0:8888")
//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)
协程在处理时关键命令是协程一定要拿到连接,defer conn.Close 这里连接用完一定要关闭,如果这个连接不关闭连接会越来越多,服务器会因为连接没有释放而别的连接无法连上
如果服务器正在等待发送连接突然出现一个客户端直接关闭,导致连接无法收到,一旦检测到连接断掉也会报错,协程就会退出
连接的维护:每隔一段时间由 tcp 协议发送包:你还在吗?客户端通过 ycp 发送:我还在,隔一段时间发送一次。此过程我们无法看到
3.跑代码
D:\go project\src\go_code\chapter18\tcpdemo\client>go run client.go
hello,world,sgg回车
不停在循环,意味着一旦发生错误应该在
if err != nil {
fmt.Print1n("服务器的Read err=",err)
return
后加 return,不退出就会一直等
再次运行,没有出现问题
hello,world,ABC
服务器在等待客户端192.168.20.253:52156发送信息
服务器的 Read err=read tcp 192.168.20.253:8888->192.168.20.253:52156:wsarecv:An existing connection was forcibly closed by the remote host.
再去等待时发现对方已经关闭所以出错,这个错误信息可以不打出来,定义一个对方已退出
type Error interface {
error
Timeout() bool
//错误是否为超时?
Temporary () bool
// 错误是否是临时的?
}
Error代 表一个网络错误。
Erro 返回的还是一段字符串