golang 实现简易聊天室

理论

声明:此案例只用于学习,掌握相关知识,请勿用于实际开发。

TCP连接过程

tcp建立连接会有三次握手

  1. 客户端发送的TCP报文中标志位SYN置1,初始序号seq=x(随机选择)。Client进入SYN_SENT状态,等待Server确认。
  2. 服务器收到数据包后,根据标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=x+1,随机产生一个初始序号seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
  3. Client收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给Server。Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

tcp建立连接会有三次握手

  1. Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
  2. Server收到FIN后,发送一个ACK给Client,确认序号为u + 1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
  3. Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
  4. Client收到FIN后,Client进入TIME_WAIT状态(主动关闭方才会进入该状态),接着发送一个ACK给Server,确认序号为w + 1,Server进入CLOSED状态,完成四次挥手。

以上就是书本上面的理论知识的,还是来做实际操作吧!

聊天室的组成

1、服务端和客户端
这里的服务端,只用户协调数据

服务端代码

package main

import (
	"fmt"
	"log"
	"net"
)

// Client 创建用户结构体类型
type Client struct {
	Msg  chan string // 消息内容
	Addr string      // IP地址
}

var (
	// 创建全局map,将用户存储到这里
	onlineMap = make(map[string]Client)

	// 创建全局channel传递用户消息
	message = make(chan string)
)

func main() {
	// 创建监听套接字
	listener, err := net.Listen("tcp", "127.0.0.1:9001")
	if err != nil {
		fmt.Println("Listen err", err)
		return
	}
	defer listener.Close()
	// 创建管理者go程
	go Manager()
	// 循环监听客户端请求

	for {
		// 等待客户端的连接, 无连接就等待
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("accept err", err)
			return
		}
		// 获取用户网络地址IP + 端口号(port)
		netAddr := conn.RemoteAddr().String()

		// 创建新连接用户的结构体
		c := Client{Msg: make(chan string), Addr: netAddr}

		// 将新连接用户添加到在线用户map中,key:IP+port value:client
		onlineMap[netAddr] = c

		// 创建专门用来给当前用户发送消息的goroutine
		go WriteMsgToClient(c, conn)

		// 发送用户上线消息到全局通道中
		message <- MakeMsg(c, "login")

		// 创建一个sendMsg,专门处理用户发送的消息
		go SendMsg(conn, c)
	}
}

func WriteMsgToClient(c Client, conn net.Conn) {
	// 监听传过来的用户的客户端是否有消息,如果有消息就写消息,没有进行等待状态
	for msg := range c.Msg {
		_, err := conn.Write([]byte(msg + "\n"))
		if err != nil {
			// 打印错误
			log.Printf("[%v], err: %v", c.Addr, err)
		}
	}
}

func SendMsg(conn net.Conn, c Client) {
	buf := make([]byte, 4096)
	for {
		n, err := conn.Read(buf)
		if n == 0 {
			fmt.Printf("检测到客户端%s退出 \n", c.Addr)
			delete(onlineMap, conn.RemoteAddr().String()) // 删除此客户端
			conn.Close()
			return
		}
		if err != nil {
			fmt.Println("conn Read err", err)
			return
		}
		// 将读到的用户消息保存到msg中,string类型
		msg := string(buf[:n])

		// 将读到的用户消息广播给所用在线用户(写入到message中)
		message <- MakeMsg(c, msg)
	}
}

func Manager() {
	// 初始化map,onlineMap
	for { // 循环从message中读取是否有数据

		// 监听channel中是否有数据,有数据存储至message,无数据就阻塞
		msg := <-message

		// 循环发送消息给所有在线用户,给所有用户赋值
		for _, c := range onlineMap {
			c.Msg <- msg
		}
	}
}

// MakeMsg 组装要发送的消息结构
func MakeMsg(c Client, msg string) (buf string) {
	return "[" + c.Addr + "]" + msg
}

客服端代码

package main

import (
	"fmt"
	"net"
)

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:9001")
	if err != nil {
		panic(err)
	}
	conn.Write([]byte("123" + "\n"))
	buf := make([]byte, 1024)
	for {
		n, err := conn.Read(buf)
		if err != nil {
			panic(err)
		}
		if n != 0 {
			fmt.Println(string(buf[:n]))
		}
	}
}

转载自:查看原文章请点击

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值