1.开发语言:GO语言
2.IDE:Goland
3.开发用时:半天
4.源码已上传到我的GitHub,链接:https://github.com/2394799692/IM-2022-3-13- 或点此跳转
5.项目主要用于练习go语言实现并发编程和网络编程。
以下是本篇文章正文内容,欢迎朋友们进行指正,一起探讨,共同进步。——来自考研路上的lwj。QQ:2394799692
一、项目开发日志开发日志:
广播用户上线:
1.主go程中,创建监听套接字,记得defer
2.for循环监听客户端连接请求。Accept()函数
3.有一个客户端连接,创建新go程处理客户端数据 HandlerConnet(conn)defer
4.定义全局结构体类型 C,Name,Addr
5.创建全局map,channel
6.实现HandlerConnet,获取客户端IP+port——RemoteAddr().初始化新用户结构体信息。name==Addr
7.创建实现管理go程,在accept()之前。
8.实现Manager。初始化在线用户map。循环读取全局变量channel,如果无证据,阻塞。如果有证据,遍历在线用户map,将数据写到用户的C里
9.将新用户添加到 在线用户map中。key==IP+port value=新用户结构体
10.创建WriteMsgToClient go程,专门给当前用户写数据。——来源于用户自带的C中
11.创建WriteMsgToClient(clnt,conn)。遍历自带的C,读数据,conn.Write到客户端
12.HandlerConnet中,结束位置,组织用户上线信息写到全局channel——Manager的读就被激活(原来一直阻塞)
13.HandlerConnet中,结束加for{;}
广播用户消息:
1.封装函数MakeMsg()来处理广播,用户消息
2.HandlerConnet中,创建匿名go程,读取用户socket上发送来的聊天内容,写到全局channel
3.for循环conn.Read n==0 err!=nil
4.写给全局message——后续的事,原来广播用户上线模块 完成。
查询在线用户:
1.将读取到的用户消息msg结尾的“\n”去掉
2.判断是否是“who”命令
3.如果是,遍历在线用户列表,组织显示信息,写到socket中、
4.如果不是,写给全局messge
修改用户名:
1.将读取到的用户消息msg判断是否包含“rename”
2.提取”|“后面的字符串,存入到Client的Name成员中
3.更新在线用户列表。onlineMap。Key——IP+prot
4.提示用户更新完成。conn.Write
用户退出:
1.在用户成功登陆之后,创建监听用户退出的channel——isQuit
2.当conn.Read==0,isQuit<-true
3.在HandlerConnet结尾for中,添加select监听<-isQuit
4.条件满足,将用户从在线列表移除。组织用户下线消息,写入message
超时强T:
1.在select中监听定时器。(time.After())计时到达。将用户从在线列表移除。组织用户下线消息,写入message(广播)
2.创建监听用户活跃的channel——hasData
3.只要用户在执行聊天,改名,who、任意 *** 作,都在hasdata中写数据
4.在select中添加监听hasdata,条件满足,不做任何事情,目的是重置计时器
主go程(服务器):负责监听,接收用户(客户端)连接请求,建立通信关系。同时启动相应的go程处理任务
二、知识补充与整体框架图
1.TCP通信的基础知识
【GO语言】实现TCP—C/S设计模式的服务器server端与客户client端即时通信
【GO语言】实现TCP—C/S设计模式的服务器server端与客户client端【并发】通信
package main
import (
"fmt"
"net"
"strings"
"time"
)
//创建用户结构体类型!
type Client struct {
C chan string
Name string
Addr string
}
//创建全局map,存储在线用户
var onlineMap map[string]Client
//创建全局channel,用于传递用户消息
var message = make(chan string)
func WriteMsgToClient(clnt Client, conn net.Conn) {
//监听用户自带channel上是否有消息
for msg := range clnt.C {
conn.Write([]byte(msg + "\n"))
}
}
func MakeMsg(clnt Client, msg string) (buf string) {
buf = "[" + clnt.Addr + "]" + clnt.Name + ":" + msg
return
}
func HandlerConnect(conn net.Conn) {
defer conn.Close()
//创建channel判断,用户是否活跃
hasData := make(chan bool)
//获取用户网络地址 ip+port
netAddr := conn.RemoteAddr().String()
//创建用户的结构体信息
clnt := Client{make(chan string), netAddr, netAddr}
//将新链接用户,添加到在线用户map中
onlineMap[netAddr] = clnt
//发送用户上线消息到全局channel中
go WriteMsgToClient(clnt, conn)
//创建专门用来给当前用户发送消息的go程
message <- MakeMsg(clnt, "login")
//创建一个channel,用来判断退出状态
isQuit := make(chan bool)
//创建一个匿名go程,专门处理用户发送的消息。
go func() {
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 {
isQuit <- true
fmt.Printf("检测到客户端:%s退出", clnt.Name)
return
}
if err != nil {
fmt.Println("conn.Read err:", err)
return
}
//将读到的用户消息,写入到message中
msg := string(buf[:n-1]) //减去1的意思是去掉斜杆
//提取在线用户列表
if msg == "who" && len(msg) == 3 {
conn.Write([]byte("online user list:\n"))
//遍历当前map、获取在线用户
for _, user := range onlineMap {
userInfo := user.Addr + ":" + user.Name + "\n"
conn.Write([]byte(userInfo))
}
} else if len(msg) >= 8 && msg[:6] == "rename" {
newName := strings.Split(msg, "|")[1]
clnt.Name = newName //修改结构体成员name
onlineMap[netAddr] = clnt //更新onlineMap
conn.Write([]byte("rename successful\n"))
} else {
//将读到的用户消息,写入到message中
message <- MakeMsg(clnt, msg)
}
hasData <- true
}
}()
//保证不退出
for {
//监听channel上的数据流动
select {
case <-isQuit:
delete(onlineMap, clnt.Addr) //将用户从online移除
message <- MakeMsg(clnt, "logout") //写入用户退出消息到全局channel
return
case <-hasData:
//什么都不做,目的是重置下面case的计时器。
case <-time.After(time.Second * 10):
delete(onlineMap, clnt.Addr) //将用户从online移除
message <- MakeMsg(clnt, "logout") //写入用户退出消息到全局channel
return
}
}
}
func Manager() {
//初始化map
onlineMap = make(map[string]Client)
for { //循环从message中读取
//监听全局channel中是否有数据,有数据存储至msg,无数据阻塞
msg := <-message
//循环发送消息给所有在线用户
for _, clnt := range onlineMap {
clnt.C <- msg
}
}
}
func main() {
//创建监听套接字
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("Listen err", err)
return
}
defer listener.Close()
//循环监听客户端连接请求
//创建管理者go程,管理map和ch
go Manager()
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Accept err", err)
return
}
//启动go程处理客户端请求
go HandlerConnect(conn)
}
}
四、效果展示:
1)启动服务器后,开启3个客户端,同时发送数据,测试都能接收到:
2)使用rename|+新名字,实现改名功能
3)使用exit命令退出聊天室
4)超时自动下线
5)who展示所有在线人员
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)