这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天。
这篇文章主要记录 GO TCP 编程的实践,总共分成两个部分:
- 实现简单的服务端和客户端
- 建造一个简单的聊天室
实现简单的服务端和客户端
主要通过 GO 中的 net 包进行 TCP 链接
服务端
服务端通过 net.Listen() 来监听某个端口,第一个参数是协议,第二个参数是 IP 和 端口
net.Listener.Accept() 可以等待并返回下一个客户端的连接
package main
import (
"fmt"
"log"
"net"
"strings"
)
func solve(c net.Conn) {
defer c.Close()
buf := make([]byte, 1024)
for {
n, err := c.Read(buf)
if err != nil {
log.Fatal(err)
}
if strings.EqualFold(string(buf[:n]), "exit") {
return
}
// 注意长度的限定
fmt.Println(c.RemoteAddr().String(), ": ", string(buf[:n]))
}
}
func main() {
servers, err := net.Listen("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatal(nil)
}
fmt.Println("服务器启动")
defer servers.Close()
for {
work, err := servers.Accept()
if err != nil {
log.Fatal(nil)
}
fmt.Println(work.RemoteAddr().String(), "成功连接至服务器")
go solve(work)
}
}
客户端
客户端通过 bufio 包,读入终端输入
通过 net.Dial() 对某个 IP 端口建立连接,参数与 net.Listen() 一致
package main
import (
"bufio"
"fmt"
"log"
"net"
"os"
"strings"
)
func main() {
rd := bufio.NewReader(os.Stdin)
// fmt.Println(string(buf))
conn, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatal(err)
}
fmt.Println("成功连接服务器", conn.RemoteAddr().String())
for {
fmt.Println("请输入:")
buf, err := rd.ReadString('\n')
if err != nil {
log.Fatal(err)
}
buf = strings.Trim(buf, "\n\r ")
conn.Write([]byte(buf))
}
}
结果展示
建造一个简单的聊天室
服务端
整体上用一个 map[string]net.Conn 来储存已经和服务端进行 TCP 连接的客户端,key 是 IP 和 端口
每当有用户发送聊天信息的时候,服务端通过 sentMesGroup() 将信息发送给其他在聊天室的用户
在客户端选择断开后,继续进行读入操作时,会返回一个 EOF 错误。当出现 EOF 错误时,将当前的客户端从 map[string]net.Conn 中删除
package main
import (
"fmt"
"io"
"log"
"net"
)
var UserTotal map[string]net.Conn = make(map[string]net.Conn, 0)
type Message struct {
MesConn net.Conn
data string
}
func process(conn net.Conn) {
defer conn.Close()
defer delete(UserTotal, conn.RemoteAddr().String())
for {
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err == io.EOF {
break
}
if err != nil {
fmt.Println("用户 ", conn.RemoteAddr(), " 传输异常,断开连接")
fmt.Println("错误:", err)
break
}
now := Message{data: string(buf[:n]), MesConn: conn}
now.sentMesGroup()
}
}
func (this Message) sentMesGroup() {
for key, User := range UserTotal {
if key == this.MesConn.RemoteAddr().String() {
continue
}
_, err := User.Write([]byte(fmt.Sprintf("用户 %s 发送消息:%s", this.MesConn.RemoteAddr().String(), this.data)))
if err != nil {
log.Fatal(err)
}
}
}
func enterInfo(conn net.Conn) {
for _, User := range UserTotal {
_, err := User.Write([]byte(fmt.Sprintf("用户 %s 进入聊天室", conn.RemoteAddr().String())))
if err != nil {
log.Fatal(err)
}
}
}
func main() {
ser, err := net.Listen("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatal(err)
}
fmt.Println("聊天室开启")
defer ser.Close()
for {
conn, err := ser.Accept()
if err != nil {
log.Fatal(err, 111111)
}
UserTotal[conn.RemoteAddr().String()] = conn
enterInfo(conn)
go process(conn)
}
}
客户端
客户端除了发送信息以外,还创建了一个 goroutine 来接收信息
当输入的信息为 exit 时,会直接退出聊天室
package main
import (
"bufio"
"fmt"
"log"
"net"
"os"
"strings"
)
func input(conn net.Conn) {
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(buf[:n]))
}
}
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
fmt.Println("成功进入聊天室")
rd := bufio.NewReader(os.Stdin)
go input(conn)
for {
buf, err := rd.ReadString('\n')
if err != nil {
log.Fatal(err)
}
buf = strings.Trim(buf, "\n\r ")
if strings.EqualFold(buf, "exit") {
break
}
_, err = conn.Write([]byte(buf))
if err != nil {
log.Fatal(err)
}
fmt.Println("成功输入内容:", buf)
}
}