IMv9.0版本总结[服务端+客户端],最终版本

news/2025/2/11 3:14:26/

 一、经历的版本

经历了多个版本,基础内容在前面,可以使用之前的基础环境:

v1:
https://blog.csdn.net/wtt234/article/details/132139454

v2:
https://blog.csdn.net/wtt234/article/details/132144907

v3:
https://blog.csdn.net/wtt234/article/details/132148572

v4:
https://blog.csdn.net/wtt234/article/details/132169338

v5:
https://blog.csdn.net/wtt234/article/details/132169651

v6:
https://blog.csdn.net/wtt234/article/details/132170959

v7:
https://blog.csdn.net/wtt234/article/details/132171479


v8:
https://blog.csdn.net/wtt234/article/details/132171722
————————————————
 

 二、简要说明

前面都是服务端的8个版本+msys2[netcat,nc]的方式进行的测试,下面就简单的客户端+最终的服务端进行。

三、客户端

package mainimport ("flag""fmt""io""net""os"
)type Client struct {ServerIp   stringServerPort intName       stringconn       net.Connflag       int //当前client的模式
}func NewClient(serverIp string, serverPort int) *Client {//创建客户端对象client := &Client{ServerIp:   serverIp,ServerPort: serverPort,flag:       999,}//链接serverconn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))if err != nil {fmt.Println("net.Dial error:", err)return nil}client.conn = conn//返回对象return client
}// 处理server回应的消息, 直接显示到标准输出即可
func (client *Client) DealResponse() {//一旦client.conn有数据,就直接copy到stdout标准输出上, 永久阻塞监听io.Copy(os.Stdout, client.conn)
}func (client *Client) menu() bool {var flag intfmt.Println("1.公聊模式")fmt.Println("2.私聊模式")fmt.Println("3.更新用户名")fmt.Println("0.退出")fmt.Scanln(&flag)if flag >= 0 && flag <= 3 {client.flag = flagreturn true} else {fmt.Println(">>>>请输入合法范围内的数字<<<<")return false}}// 查询在线用户
func (client *Client) SelectUsers() {sendMsg := "who\n"_, err := client.conn.Write([]byte(sendMsg))if err != nil {fmt.Println("conn Write err:", err)return}
}// 私聊模式
func (client *Client) PrivateChat() {var remoteName stringvar chatMsg stringclient.SelectUsers()fmt.Println(">>>>请输入聊天对象[用户名], exit退出:")fmt.Scanln(&remoteName)for remoteName != "exit" {fmt.Println(">>>>请输入消息内容, exit退出:")fmt.Scanln(&chatMsg)for chatMsg != "exit" {//消息不为空则发送if len(chatMsg) != 0 {sendMsg := "to|" + remoteName + "|" + chatMsg + "\n\n"_, err := client.conn.Write([]byte(sendMsg))if err != nil {fmt.Println("conn Write err:", err)break}}chatMsg = ""fmt.Println(">>>>请输入消息内容, exit退出:")fmt.Scanln(&chatMsg)}client.SelectUsers()fmt.Println(">>>>请输入聊天对象[用户名], exit退出:")fmt.Scanln(&remoteName)}
}func (client *Client) PublicChat() {//提示用户输入消息var chatMsg stringfmt.Println(">>>>请输入聊天内容,exit退出.")fmt.Scanln(&chatMsg)for chatMsg != "exit" {//发给服务器//消息不为空则发送if len(chatMsg) != 0 {sendMsg := chatMsg + "\n"_, err := client.conn.Write([]byte(sendMsg))if err != nil {fmt.Println("conn Write err:", err)break}}chatMsg = ""fmt.Println(">>>>请输入聊天内容,exit退出.")fmt.Scanln(&chatMsg)}}func (client *Client) UpdateName() bool {fmt.Println(">>>>请输入用户名:")fmt.Scanln(&client.Name)sendMsg := "rename|" + client.Name + "\n"_, err := client.conn.Write([]byte(sendMsg))if err != nil {fmt.Println("conn.Write err:", err)return false}return true
}func (client *Client) Run() {for client.flag != 0 {for client.menu() != true {}//根据不同的模式处理不同的业务switch client.flag {case 1://公聊模式client.PublicChat()breakcase 2://私聊模式client.PrivateChat()breakcase 3://更新用户名client.UpdateName()break}}
}var serverIp string
var serverPort int// ./client -ip 127.0.0.1 -port 8888
func init() {flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP地址(默认是127.0.0.1)")flag.IntVar(&serverPort, "port", 8888, "设置服务器端口(默认是8888)")
}func main() {//命令行解析flag.Parse()client := NewClient(serverIp, serverPort)if client == nil {fmt.Println(">>>>> 链接服务器失败...")return}//单独开启一个goroutine去处理server的回执消息go client.DealResponse()fmt.Println(">>>>>链接服务器成功...")//启动客户端的业务client.Run()
}

四、服务端

4.1user.go

package mainimport ("net""strings"
)type User struct {Name stringAddr stringC    chan stringconn net.Connserver *Server
}//创建一个用户的API
func NewUser(conn net.Conn, server *Server) *User {userAddr := conn.RemoteAddr().String()user := &User{Name: userAddr,Addr: userAddr,C:    make(chan string),conn: conn,server: server,}//启动监听当前user channel消息的goroutinego user.ListenMessage()return user
}//用户的上线业务
func (this *User) Online() {//用户上线,将用户加入到onlineMap中this.server.mapLock.Lock()this.server.OnlineMap[this.Name] = thisthis.server.mapLock.Unlock()//广播当前用户上线消息this.server.BroadCast(this, "已上线")
}//用户的下线业务
func (this *User) Offline() {//用户下线,将用户从onlineMap中删除this.server.mapLock.Lock()delete(this.server.OnlineMap, this.Name)this.server.mapLock.Unlock()//广播当前用户上线消息this.server.BroadCast(this, "下线")}//给当前User对应的客户端发送消息
func (this *User) SendMsg(msg string) {this.conn.Write([]byte(msg))
}//用户处理消息的业务
func (this *User) DoMessage(msg string) {if msg == "who" {//查询当前在线用户都有哪些this.server.mapLock.Lock()for _, user := range this.server.OnlineMap {onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"this.SendMsg(onlineMsg)}this.server.mapLock.Unlock()} else if len(msg) > 7 && msg[:7] == "rename|" {//消息格式: rename|张三newName := strings.Split(msg, "|")[1]//判断name是否存在_, ok := this.server.OnlineMap[newName]if ok {this.SendMsg("当前用户名被使用\n")} else {this.server.mapLock.Lock()delete(this.server.OnlineMap, this.Name)this.server.OnlineMap[newName] = thisthis.server.mapLock.Unlock()this.Name = newNamethis.SendMsg("您已经更新用户名:" + this.Name + "\n")}} else if len(msg) > 4 && msg[:3] == "to|" {//消息格式:  to|张三|消息内容//1 获取对方的用户名remoteName := strings.Split(msg, "|")[1]if remoteName == "" {this.SendMsg("消息格式不正确,请使用 \"to|张三|你好啊\"格式。\n")return}//2 根据用户名 得到对方User对象remoteUser, ok := this.server.OnlineMap[remoteName]if !ok {this.SendMsg("该用户名不不存在\n")return}//3 获取消息内容,通过对方的User对象将消息内容发送过去content := strings.Split(msg, "|")[2]if content == "" {this.SendMsg("无消息内容,请重发\n")return}remoteUser.SendMsg(this.Name + "对您说:" + content)} else {this.server.BroadCast(this, msg)}
}//监听当前User channel的 方法,一旦有消息,就直接发送给对端客户端
func (this *User) ListenMessage() {for {msg := <-this.Cthis.conn.Write([]byte(msg + "\n"))}
}

4.2server.go

package mainimport ("fmt""io""net""sync""time"
)type Server struct {Ip   stringPort int//在线用户的列表OnlineMap map[string]*UsermapLock   sync.RWMutex//消息广播的channelMessage chan string
}//创建一个server的接口
func NewServer(ip string, port int) *Server {server := &Server{Ip:        ip,Port:      port,OnlineMap: make(map[string]*User),Message:   make(chan string),}return server
}//监听Message广播消息channel的goroutine,一旦有消息就发送给全部的在线User
func (this *Server) ListenMessager() {for {msg := <-this.Message//将msg发送给全部的在线Userthis.mapLock.Lock()for _, cli := range this.OnlineMap {cli.C <- msg}this.mapLock.Unlock()}
}//广播消息的方法
func (this *Server) BroadCast(user *User, msg string) {sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msgthis.Message <- sendMsg
}func (this *Server) Handler(conn net.Conn) {//...当前链接的业务//fmt.Println("链接建立成功")user := NewUser(conn, this)user.Online()//监听用户是否活跃的channelisLive := make(chan bool)//接受客户端发送的消息go func() {buf := make([]byte, 4096)for {n, err := conn.Read(buf)if n == 0 {user.Offline()return}if err != nil && err != io.EOF {fmt.Println("Conn Read err:", err)return}//提取用户的消息(去除'\n')msg := string(buf[:n-1])//用户针对msg进行消息处理user.DoMessage(msg)//用户的任意消息,代表当前用户是一个活跃的isLive <- true}}()//当前handler阻塞for {select {case <-isLive://当前用户是活跃的,应该重置定时器//不做任何事情,为了激活select,更新下面的定时器case <-time.After(time.Second * 300)://已经超时//将当前的User强制的关闭user.SendMsg("你被踢了")//销毁用的资源close(user.C)//关闭连接conn.Close()//退出当前Handlerreturn //runtime.Goexit()}}
}//启动服务器的接口
func (this *Server) Start() {//socket listenlistener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))if err != nil {fmt.Println("net.Listen err:", err)return}//close listen socketdefer listener.Close()//启动监听Message的goroutinego this.ListenMessager()for {//acceptconn, err := listener.Accept()if err != nil {fmt.Println("listener accept err:", err)continue}//do handlergo this.Handler(conn)}
}

4.3main.go

package mainfunc main() {server := NewServer("127.0.0.1", 8888)server.Start()
}


http://www.ppmy.cn/news/1016963.html

相关文章

企业举办活动邀请媒体的意义和重要性

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 企业举办活动并邀请媒体的意义和重要性是多方面的&#xff0c;主要有以下一些&#xff1a; 1. 品牌曝光与宣传&#xff1a;邀请媒体参与企业活动可以提高企业的品牌曝光度。媒体报道能够…

使用Openoffice或LibreOffice实现World、Excel、PPTX在线预览

使用Openoffice或LibreOffice实现World、Excel、PPTX在线预览 预览方案使用第三方服务使用前端库转换格式 jodconverterjodconverter概述主要特性OpenOfficeLibreOffice jodconverter的基本使用添加依赖配置创建DocumentConverter实例上传与转换预览启动上传与预览World 与Spri…

【Kubernetes】Kubernetes之YAML文件详解

YAML 一、YAML 的概述1. Kubernetes 支持资源管理格式2. YAML 语法格式 二、YAML 文件1. 如何获取 api 资源相关信息2. 编写资源配置文件2.1 手动编写 yaml 文件详解K8S中的port 2.2 使用镜像生成 yaml 文件2.3 根据现有资源导出 yaml 文件 总结1. 如何获取资源清单文件&#x…

[excel]vlookup函数对相同的ip进行关联

一、需求&#xff08;由于ip不可泄漏所以简化如下&#xff09; 有两个sheet: 找到sheet1在sheet2中存在的ip&#xff0c;也就是找到有漏洞的ip 二、实现 vlookup函数有4个参数 第一个:当前表要匹配的列&#xff0c;选择第一个sheet当前行需要处理的ip即可 第二个:第二个shee…

ldap登录 AD域登录工具类

1.依赖包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-ldap</artifactId></dependency>2.yml配置 ldap:base: dcspring,dciourls: ldap://192.168.40.40:389referral: followcheck: t…

sqlalchemy------一对多和多对多

1.一对多 表模型 class Hobby(Base):__tablename__ hobbyid Column(Integer, primary_keyTrue)caption Column(String(50), default篮球)def __str__(self):return self.captiondef __repr__(self):return self.captionclass Person(Base):__tablename__ personid Colum…

无人驾驶实战-第十一课(控制理论)

在七月算法上报了《无人驾驶实战》课程&#xff0c;老师讲的真好。好记性不如烂笔头&#xff0c;记录一下学习内容。 课程入口&#xff0c;感兴趣的也可以跟着学一下。 ————————————————————————————————————————— 无人驾驶中控制系…

pydantic接口定义检查(一)

之前在学习 FastAPI的时候&#xff0c;FastAPI 构建 API 高性能的 web 框架&#xff08;一&#xff09; 看到pydantic 使用场景非常多&#xff0c;也单独来看看这个模块。 pydantic的官方文档&#xff1a;https://docs.pydantic.dev/latest/ Usage界面&#xff1a;https://docs…