server.go
net.Listen()
函数,network参数传入网络类型(tcp,udp),address参数传入监听的服务端地址,是IP:potr
形式(可以使用fmt.Sprintf()
进行格式化打印并返回字符串);返回Listener
监听套接字对象和error错误信息;defer listener.Close()
,关闭listen socketnet.Listener.Accept()
函数,返回一个Conn
连接(可以进行读写)和error错误信息,若accept成功,Conn
就是连接的读写套接字;package main
import (
"fmt"
"net"
)
type Server struct {
IP string
Port int
}
// 创建一个server的接口
func NewServer(ip string, port int) *Server {
server := &Server{
IP: ip,
Port: port,
}
return server
}
func (svr *Server) Handler(conn net.Conn) {
//当前链接的任务
fmt.Println("链接建立成功")
}
// 启动服务器的接口
func (svr *Server) Start() {
// socket listen
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", svr.IP, svr.Port))
if err != nil {
fmt.Println("listen error :", err)
return
}
//close listen socket
defer listener.Close()
//accept
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("accpet error :", err)
continue
}
// do handler
go svr.Handler(conn)
}
}
main.go
package main
func main() {
server := NewServer("127.0.0.1", 8888)
server.Start()
}
编译运行:
使用Linux原生nc指令模拟连接:
nc 127.0.0.1 8888
user.go
package main
import "net"
type User struct {
Name string
Addr string
C chan string //用户channel
conn net.Conn //与客户端的链接
}
func NewUser(conn net.Conn) *User {
//以地址名作为用户名
userAddr := conn.RemoteAddr().String()
user := &User{
Name: userAddr,
Addr: userAddr,
C: make(chan string),
conn: conn,
}
//启动监听当前User channel消息的goroutine
go user.ListenMessage()
return user
}
// 监听当前User channel的方法,一旦有消息,就直接发送给客户端
func (usr *User) ListenMessage() {
for {
msg := <-usr.C
usr.conn.Write([]byte(msg + "\n")) //使用二进制数组形式写入消息
}
}
server.go
package main
import (
"fmt"
"net"
"sync"
)
type Server struct {
IP string
Port int
//在线用户列表
OnlineMap map[string]*User
//读写锁
mapLock sync.RWMutex //go中同步的机制全在sync包中
//消息广播的channel
Message 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 (svr *Server) ListenMessage() {
for {
//从Message channel中读取数据
msg := <-svr.Message
//遍历onlinemap,将消息发送给所有的在线User
svr.mapLock.Lock()
for _, cli := range svr.OnlineMap {
cli.C <- msg
}
svr.mapLock.Unlock()
}
}
// 广播消息的方法
func (svr *Server) BroadCast(user *User, msg string) {
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
svr.Message <- sendMsg
}
func (svr *Server) Handler(conn net.Conn) {
//当前链接的任务
//fmt.Println("链接建立成功")
user := NewUser(conn)
//用户上线,加入到onlinemap中
svr.mapLock.Lock()
svr.OnlineMap[user.Name] = user
svr.mapLock.Unlock()
//广播当前用户上线消息
svr.BroadCast(user, "已上线")
//当前Handler阻塞,否则go程就结束了
select {}
}
// 启动服务器的接口
func (svr *Server) Start() {
// socket listen
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", svr.IP, svr.Port))
if err != nil {
fmt.Println("listen error :", err)
return
}
//close listen socket
defer listener.Close()
//循环监听Message的go程
go svr.ListenMessage()
//accept
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("accpet error :", err)
continue
}
//accept成功后,代表有个用户上线了
// do handler
go svr.Handler(conn)
}
}
编译运行:
用户上线广播功能已实现
Server.go
net.Conn.Read()
函数接收链接发送的数据,该函数输入参数是一个字节类型数组,输出数据的长度和错误信息,接收完数据后,再进行广播func (svr *Server) Handler(conn net.Conn) {
//当前链接的任务
//fmt.Println("链接建立成功")
user := NewUser(conn)
//用户上线,加入到onlinemap中
svr.mapLock.Lock()
svr.OnlineMap[user.Name] = user
svr.mapLock.Unlock()
//广播当前用户上线消息
svr.BroadCast(user, "已上线")
//接收用户发送的消息
go func() {
buf := make([]byte, 4096)
for {
n, err := user.conn.Read(buf)
//如果读取的消息长度为0,就代表当前用户已下线
if n == 0 {
svr.BroadCast(user, "已下线")
return
}
// err不是文件末尾
if err != nil && err != io.EOF {
fmt.Println("Conn read error:", err)
return
}
// 读取消息成功,提取用户消息,去除\n,然后广播
msg := string(buf[:n-1])
svr.BroadCast(user, msg)
}
}()
//当前Handler阻塞,否则go程就结束了
select {}
}
用户消息广播:
用户下线(ctrl + c):
调整Server和User的业务划分:
Server.go
package main
import (
"fmt"
"io"
"net"
"sync"
)
type Server struct {
IP string
Port int
//在线用户列表
OnlineMap map[string]*User
//读写锁
mapLock sync.RWMutex //go中同步的机制全在sync包中
//消息广播的channel
Message 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 (svr *Server) ListenMessage() {
for {
//从Message channel中读取数据
msg := <-svr.Message
//遍历onlinemap,将消息发送给所有的在线User
svr.mapLock.Lock()
for _, cli := range svr.OnlineMap {
cli.C <- msg
}
svr.mapLock.Unlock()
}
}
// 广播消息的方法
func (svr *Server) BroadCast(user *User, msg string) {
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
svr.Message <- sendMsg
}
func (svr *Server) Handler(conn net.Conn) {
//创建新用户
user := NewUser(conn, svr)
//用户上线
user.Online()
//接收用户发送的消息
go func() {
buf := make([]byte, 4096)
for {
n, err := user.conn.Read(buf)
//如果读取的消息长度为0,就代表当前用户已下线
if n == 0 {
user.Offline()
return
}
// err不是文件末尾
if err != nil && err != io.EOF {
fmt.Println("Conn read error:", err)
return
}
// 读取消息成功,提取用户消息,去除\n,然后广播
msg := string(buf[:n-1])
user.DoMessage(msg)
}
}()
//当前Handler阻塞,否则go程就结束了
select {}
}
// 启动服务器的接口
func (svr *Server) Start() {
// socket listen
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", svr.IP, svr.Port))
if err != nil {
fmt.Println("listen error :", err)
return
}
//close listen socket
defer listener.Close()
//循环监听Message的go程
go svr.ListenMessage()
//accept
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("accpet error :", err)
continue
}
//accept成功后,代表有个用户上线了
// do handler
go svr.Handler(conn)
}
}
User.go
package main
import "net"
type User struct {
Name string
Addr string
C chan string //用户channel
conn net.Conn //与客户端的链接
svr *Server //当前用户属于哪个Server
}
func NewUser(connect net.Conn, server *Server) *User {
//以地址名作为用户名
userAddr := connect.RemoteAddr().String()
user := &User{
Name: userAddr,
Addr: userAddr,
C: make(chan string),
conn: connect,
svr: server,
}
//启动监听当前User channel消息的goroutine
go user.ListenMessage()
return user
}
// 监听当前User channel的方法,一旦有消息,就直接发送给客户端
func (usr *User) ListenMessage() {
for {
msg := <-usr.C
usr.conn.Write([]byte(msg + "\n")) //使用二进制数组形式写入消息
}
}
// 用户上线
func (usr *User) Online() {
//用户上线,加入到onlinemap中
usr.svr.mapLock.Lock()
usr.svr.OnlineMap[usr.Name] = usr
usr.svr.mapLock.Unlock()
//广播当前用户上线消息
usr.svr.BroadCast(usr, "已上线")
}
// 用户下线
func (usr *User) Offline() {
//用户上线,加入到onlinemap中
usr.svr.mapLock.Lock()
delete(usr.svr.OnlineMap, usr.Name)
usr.svr.mapLock.Unlock()
//广播当前用户上线消息
usr.svr.BroadCast(usr, "已下线")
}
// 用户处理消息
func (usr *User) DoMessage(msg string) {
usr.svr.BroadCast(usr, msg)
}
User.go
// 给当前User对应的客户端发消息(单发)
func (usr *User) SendMsg(msg string) {
usr.conn.Write([]byte(msg))
}
// 用户处理消息
func (usr *User) DoMessage(msg string) {
if msg == "who" {
//用户想查询所有在线用户
usr.svr.mapLock.Lock()
for _, user := range usr.svr.OnlineMap {
onlineUser := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"
usr.SendMsg(onlineUser)
}
usr.svr.mapLock.Unlock()
} else {
usr.svr.BroadCast(usr, msg)
}
}
rename|...
格式作为用户名修改的消息格式strings.Split(msg, "|")
函数,该函数会按第二个参数来分割msg字符串,返回一个字符串数组User.go
// 用户处理消息
func (usr *User) DoMessage(msg string) {
if msg == "who" {
//用户想查询所有在线用户
usr.svr.mapLock.Lock()
for _, user := range usr.svr.OnlineMap {
onlineUser := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"
usr.SendMsg(onlineUser)
}
usr.svr.mapLock.Unlock()
} else if len(msg) > 7 && msg[:7] == "rename|" {
//修改指令是 rename|...
newName := strings.Split(msg, "|")[1]
//首先查询onlinemap中是否存在该用户名
_, ok := usr.svr.OnlineMap[newName]
if ok {
fmt.Println("当前用户名已经被使用!")
} else {
//删除原用户名对应的user,并加入新的用户名
usr.svr.mapLock.Lock()
delete(usr.svr.OnlineMap, usr.Name)
usr.svr.OnlineMap[newName] = usr
usr.svr.mapLock.Unlock()
//更新User的用户名
usr.Name = newName
usr.SendMsg("您已更改用户名为:" + newName + "\n")
}
} else {
usr.svr.BroadCast(usr, msg)
}
}
time.After(10 * time.Second)
,该定时器经过10s后会触发,定时器本质是一个channel,在出发后里面是有数据可读的;当再次执行该函数时,定时器就会被重置;isLive
中读数据的case写在定时器的case上面,每当isLive
的case触发,select语句就会触发,下面的case也会尝试执行,进而重置了定时器func (svr *Server) Handler(conn net.Conn) {
//创建新用户
user := NewUser(conn, svr)
//用户上线
user.Online()
//监听用户是否活跃的channel
isLive := make(chan bool)
//接收用户发送的消息
go func() {
buf := make([]byte, 4096)
for {
//从链接中读取消息
n, err := user.conn.Read(buf)
//如果读取的消息长度为0,就代表当前用户已下线
if n == 0 {
user.Offline()
return
}
// err不是文件末尾
if err != nil && err != io.EOF {
fmt.Println("Conn read error:", err)
return
}
// 读取消息成功,提取用户消息,去除\n,然后广播
msg := string(buf[:n-1])
user.DoMessage(msg)
//每当用户发一次消息,就代表活跃
isLive <- true
}
}()
//当前Handler阻塞,否则go程就结束了
for {
select {
case <-isLive:
//当从isLive中读到数据时,下面的case也会执行一次,就重置了定时器
//不作任何处理,只是为了更新定时器
case <-time.After(10 * time.Second):
//该case触发,表明超时
//将当前的User强制关闭
user.SendMsg("你被踢了")
//销毁用户资源
close(user.C)
//关闭链接
conn.Close()
//退出当前Handler
return //或者runtime.Goexit()
}
}
}
to|张三|张三你好...
User.go
func (usr *User) DoMessage(msg string) {
if msg == "who" {
//用户想查询所有在线用户
usr.svr.mapLock.Lock()
for _, user := range usr.svr.OnlineMap {
onlineUser := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"
usr.SendMsg(onlineUser)
}
usr.svr.mapLock.Unlock()
} else if len(msg) > 7 && msg[:7] == "rename|" {
//修改指令是 rename|...
newName := strings.Split(msg, "|")[1]
//首先查询onlinemap中是否存在该用户名
_, ok := usr.svr.OnlineMap[newName]
if ok {
fmt.Println("当前用户名已经被使用!")
} else {
//删除原用户名对应的user,并加入新的用户名
usr.svr.mapLock.Lock()
delete(usr.svr.OnlineMap, usr.Name)
usr.svr.OnlineMap[newName] = usr
usr.svr.mapLock.Unlock()
//更新User的用户名
usr.Name = newName
usr.SendMsg("您已更改用户名为:" + newName + "\n")
}
} else if len(msg) > 4 && msg[:3] == "to|" {
//消息格式:to|张三|张三你好...
//获取对方用户名
remoteName := strings.Split(msg, "|")[1]
if remoteName == "" {
usr.SendMsg("私聊消息格式不正确,请使用\"to|张三|张三你好...\"格式\n")
return
}
//根据用户名找到User对象
remoteUser, ok := usr.svr.OnlineMap[remoteName]
if !ok {
usr.SendMsg(remoteName + "用户不存在\n")
return
}
//获取消息内容,通过User对象发送给对方
privateMsg := strings.Split(msg, "|")[2]
remoteUser.SendMsg(usr.Name + " : " + privateMsg)
} else {
usr.svr.BroadCast(usr, msg)
}
}
现在基本的聊天功能已经实现,以下是完整代码:
server.go
package main
import (
"fmt"
"io"
"net"
"sync"
"time"
)
type Server struct {
IP string
Port int
//在线用户列表
OnlineMap map[string]*User
//读写锁
mapLock sync.RWMutex //go中同步的机制全在sync包中
//消息广播的channel
Message 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 (svr *Server) ListenMessage() {
for {
//从Message channel中读取数据
msg := <-svr.Message
//遍历onlinemap,将消息发送给所有的在线User
svr.mapLock.Lock()
for _, cli := range svr.OnlineMap {
cli.C <- msg
}
svr.mapLock.Unlock()
}
}
// 广播消息的方法
func (svr *Server) BroadCast(user *User, msg string) {
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
svr.Message <- sendMsg
}
func (svr *Server) Handler(conn net.Conn) {
//创建新用户
user := NewUser(conn, svr)
//用户上线
user.Online()
//监听用户是否活跃的channel
isLive := make(chan bool)
//接收用户发送的消息
go func() {
buf := make([]byte, 4096)
for {
//从链接中读取消息
n, err := user.conn.Read(buf)
//如果读取的消息长度为0,就代表当前用户已下线
if n == 0 {
user.Offline()
return
}
// err不是文件末尾
if err != nil && err != io.EOF {
fmt.Println("Conn read error:", err)
return
}
// 读取消息成功,提取用户消息,去除\n,然后广播
msg := string(buf[:n-1])
user.DoMessage(msg)
//每当用户发一次消息,就代表活跃
isLive <- true
}
}()
//当前Handler阻塞,否则go程就结束了
for {
select {
case <-isLive:
//当从isLive中读到数据时,下面的case也会执行一次,就重置了定时器
//不作任何处理,只是为了更新定时器
case <-time.After(60 * time.Second):
//该case触发,表明超时
//将当前的User强制关闭
user.SendMsg("你被踢了")
//销毁用户资源
close(user.C)
//关闭链接
conn.Close()
//退出当前Handler
return //或者runtime.Goexit()
}
}
}
// 启动服务器的接口
func (svr *Server) Start() {
// socket listen
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", svr.IP, svr.Port))
if err != nil {
fmt.Println("listen error :", err)
return
}
//close listen socket
defer listener.Close()
//循环监听Message的go程
go svr.ListenMessage()
//accept
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("accpet error :", err)
continue
}
//accept成功后,代表有个用户上线了
// do handler
go svr.Handler(conn)
}
}
user.go
package main
import (
"fmt"
"net"
"strings"
)
type User struct {
Name string
Addr string
C chan string //用户channel
conn net.Conn //与客户端的链接
svr *Server //当前用户属于哪个Server
}
func NewUser(connect net.Conn, server *Server) *User {
//以地址名作为用户名
userAddr := connect.RemoteAddr().String()
user := &User{
Name: userAddr,
Addr: userAddr,
C: make(chan string),
conn: connect,
svr: server,
}
//启动监听当前User channel消息的goroutine
go user.ListenMessage()
return user
}
// 监听当前User channel的方法,一旦有消息,就直接发送给客户端
func (usr *User) ListenMessage() {
for {
msg := <-usr.C
usr.conn.Write([]byte(msg + "\n")) //使用二进制数组形式写入消息
}
}
// 用户上线
func (usr *User) Online() {
//用户上线,加入到onlinemap中
usr.svr.mapLock.Lock()
usr.svr.OnlineMap[usr.Name] = usr
usr.svr.mapLock.Unlock()
//广播当前用户上线消息
usr.svr.BroadCast(usr, "已上线")
}
// 用户下线
func (usr *User) Offline() {
//用户上线,加入到onlinemap中
usr.svr.mapLock.Lock()
delete(usr.svr.OnlineMap, usr.Name)
usr.svr.mapLock.Unlock()
//广播当前用户上线消息
usr.svr.BroadCast(usr, "已下线")
}
// 给当前User对应的客户端发消息(单发)
func (usr *User) SendMsg(msg string) {
usr.conn.Write([]byte(msg))
}
// 用户处理消息
func (usr *User) DoMessage(msg string) {
if msg == "who" {
//用户想查询所有在线用户
usr.svr.mapLock.Lock()
for _, user := range usr.svr.OnlineMap {
onlineUser := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"
usr.SendMsg(onlineUser)
}
usr.svr.mapLock.Unlock()
} else if len(msg) > 7 && msg[:7] == "rename|" {
//修改指令是 rename|...
newName := strings.Split(msg, "|")[1]
//首先查询onlinemap中是否存在该用户名
_, ok := usr.svr.OnlineMap[newName]
if ok {
fmt.Println("当前用户名已经被使用!")
} else {
//删除原用户名对应的user,并加入新的用户名
usr.svr.mapLock.Lock()
delete(usr.svr.OnlineMap, usr.Name)
usr.svr.OnlineMap[newName] = usr
usr.svr.mapLock.Unlock()
//更新User的用户名
usr.Name = newName
usr.SendMsg("您已更改用户名为:" + newName + "\n")
}
} else if len(msg) > 4 && msg[:3] == "to|" {
//消息格式:to|张三|张三你好...
//获取对方用户名
remoteName := strings.Split(msg, "|")[1]
if remoteName == "" {
usr.SendMsg("私聊消息格式不正确,请使用\"to|张三|张三你好...\"格式\n")
return
}
//根据用户名找到User对象
remoteUser, ok := usr.svr.OnlineMap[remoteName]
if !ok {
usr.SendMsg(remoteName + "用户不存在\n")
return
}
//获取消息内容,通过User对象发送给对方
privateMsg := strings.Split(msg, "|")[2]
remoteUser.SendMsg(usr.Name + " : " + privateMsg)
} else {
usr.svr.BroadCast(usr, msg)
}
}
main.go
package main
func main() {
server := NewServer("127.0.0.1", 8888)
server.Start()
}
net.Dial()
函数连接服务器,输入参数是协议类型和服务器地址(ip:port),输出链接套接字和错误信息client.go
package main
import (
"fmt"
"net"
)
type Client struct {
ServerIP string
ServerPort int
ClientName string
conn net.Conn
}
func NewClient(serverIP string, serverPotr int) *Client {
//创建客户端对象
client := &Client{
ServerIP: serverIP,
ServerPort: serverPotr,
}
//链接server
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIP, serverPotr))
if err != nil {
fmt.Println("net.Dial error:", err)
return nil
}
client.conn = conn
//返回对象
return client
}
func main() {
client := NewClient("127.0.0.1", 8888)
if client == nil {
fmt.Println(">>>>>>>>链接服务器失败>>>>>>>>")
return
}
fmt.Println(">>>>>>>>链接服务器成功>>>>>>>>>")
//启动客户端业务
select {}
}
flag.StringVar()
函数用来将命令行参数绑定string变量,输入参数p为要写入的string类型的变量,name为当前命令行参数的提示符,value为当前参数的默认值,usage是对当前参数的说明;同理int类型参数写入需要使用flag.IntVar()
init()
函数中实现,在main()
函数中调用flag.Parse()
函数进行命令行解析,检测执行该程序时,命令行是否有参数init()
函数中实现,也可以在main()
函数的第一行实现,之后调用flag.Parse()
进行命令行解析package main
import (
"flag"
"fmt"
"net"
)
type Client struct {
ServerIP string
ServerPort int
ClientName string
conn net.Conn
}
func NewClient(serverIP string, serverPotr int) *Client {
//创建客户端对象
client := &Client{
ServerIP: serverIP,
ServerPort: serverPort,
}
//链接server
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIP, serverPotr))
if err != nil {
fmt.Println("net.Dial error:", err)
return nil
}
client.conn = conn
//返回对象
return client
}
var serverIP string
var serverPort int
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("127.0.0.1", 8888)
if client == nil {
fmt.Println(">>>>>>>>链接服务器失败>>>>>>>>")
return
}
fmt.Println(">>>>>>>>链接服务器成功>>>>>>>>>")
//启动客户端业务
select {}
}
menu()
,用于菜单显示Run()
,用于客户端的启动package main
import (
"flag"
"fmt"
"net"
)
type Client struct {
ServerIP string
ServerPort int
ClientName string
conn net.Conn
flag int // 当前客户端的模型
}
func NewClient(serverIP string, serverPotr int) *Client {
//创建客户端对象
client := &Client{
ServerIP: serverIP,
ServerPort: serverPort,
flag: 999,
}
//链接server
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIP, serverPotr))
if err != nil {
fmt.Println("net.Dial error:", err)
return nil
}
client.conn = conn
//返回对象
return client
}
func (clt *Client) menu() bool {
var flag int
fmt.Println("*****************************")
fmt.Println("**********1.公聊模式**********")
fmt.Println("**********2.私聊模式**********")
fmt.Println("**********3.更新用户名********")
fmt.Println("**********0.退出**************")
fmt.Println("******************************")
fmt.Scanln(&flag) // 输入flag
if flag >= 0 && flag <= 3 {
clt.flag = flag
return true
} else {
fmt.Println(">>>>>>请输入合法的flag!<<<<<<")
return false
}
}
func (clt *Client) Run() {
for clt.flag != 0 {
//模式不对就一直循环菜单
for clt.menu() == false {
}
//根据不同的模式处理不同的业务
switch clt.flag {
case 1:
//公聊模式
fmt.Println("公聊模式")
case 2:
//私聊模式
fmt.Println("私聊模式")
case 3:
//更新用户名
fmt.Println("更新用户名")
}
}
}
var serverIP string
var serverPort int
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("127.0.0.1", 8888)
if client == nil {
fmt.Println(">>>>>>>>链接服务器失败>>>>>>>>")
return
}
fmt.Println(">>>>>>>>链接服务器成功>>>>>>>>>")
//启动客户端业务
client.Run()
}
UpdateName
,用于处理用户名更新业务,将新的用户名封装成更改用户名的指令,并通过conn.Write()
函数直接发送给客户端进行处理;DealResponse
,用于不断阻塞读取服务端的回执消息,直接使用io.Copy()
函数将套接字conn的数据拷贝到标准输出;该函数在main中以新的go程执行package main
import (
"flag"
"fmt"
"io"
"net"
"os"
)
type Client struct {
ServerIP string
ServerPort int
ClientName string
conn net.Conn
flag int // 当前客户端的模型
}
func NewClient(serverIP string, serverPotr int) *Client {
//创建客户端对象
client := &Client{
ServerIP: serverIP,
ServerPort: serverPort,
flag: 999,
}
//链接server
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIP, serverPotr))
if err != nil {
fmt.Println("net.Dial error:", err)
return nil
}
client.conn = conn
//返回对象
return client
}
func (clt *Client) menu() bool {
var flag int
fmt.Println("*****************************")
fmt.Println("**********1.公聊模式**********")
fmt.Println("**********2.私聊模式**********")
fmt.Println("**********3.更新用户名********")
fmt.Println("**********0.退出**************")
fmt.Println("******************************")
fmt.Scanln(&flag) // 输入flag
if flag >= 0 && flag <= 3 {
clt.flag = flag
return true
} else {
fmt.Println(">>>>>>请输入合法的flag!<<<<<<")
return false
}
}
func (clt *Client) Run() {
for clt.flag != 0 {
//模式不对就一直循环菜单
for clt.menu() == false {
}
//根据不同的模式处理不同的业务
switch clt.flag {
case 1:
//公聊模式
fmt.Println("公聊模式")
case 2:
//私聊模式
fmt.Println("私聊模式")
case 3:
//更新用户名
clt.UpdateName()
}
}
}
func (clt *Client) UpdateName() bool {
fmt.Println(">>>>请输入用户名:")
fmt.Scanln(&clt.ClientName)
sendMsg := "rename|" + clt.ClientName + "\n"
_, err := clt.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println("conn.Write error:", err)
return false
}
return true
}
func (clt *Client) DealResponse() {
//不断阻塞等待套接字conn的数据,并copy到stdout中
//一旦clt.conn有数据,就直接拷贝到stdout中,是永久阻塞的
io.Copy(os.Stdout, clt.conn)
//上面的一行代码等价于下面的代码
// for {
// buf := make([]byte, 4096)
// clt.conn.Read(buf)
// fmt.Println(buf)
// }
}
var serverIP string
var serverPort int
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("127.0.0.1", 8888)
if client == nil {
fmt.Println(">>>>>>>>链接服务器失败>>>>>>>>")
return
}
fmt.Println(">>>>>>>>链接服务器成功>>>>>>>>>")
//单独开启一个goroutine,去处理server的回执消息
go client.DealResponse()
//启动客户端业务
client.Run()
}
PublicChat
,用于处理公聊业务,用户的信息打包发送给服务端func (clt *Client) Run() {
for clt.flag != 0 {
//模式不对就一直循环菜单
for clt.menu() == false {
}
//根据不同的模式处理不同的业务
switch clt.flag {
case 1:
//公聊模式
fmt.Println(">>>>>>公聊模式,输入exit退出")
clt.PublicChat()
case 2:
//私聊模式
fmt.Println("私聊模式")
case 3:
//更新用户名
clt.UpdateName()
}
}
}
func (clt *Client) PublicChat() {
var chatMsg string
fmt.Println(">>>>>请输入聊天内容:")
fmt.Scanln(&chatMsg)
for chatMsg != "exit" {
//消息不为空则发送给服务器
if len(chatMsg) != 0 {
sendMsg := chatMsg + "\n"
_, err := clt.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println("conn.Write error:", err)
break
}
}
chatMsg = ""
fmt.Println(">>>>>请输入聊天内容:")
fmt.Scanln(&chatMsg)
}
}
PrivateChat
,用于处理私聊业务SelectUsers()
,用于返回在线用户func (clt *Client) Run() {
for clt.flag != 0 {
//模式不对就一直循环菜单
for clt.menu() == false {
}
//根据不同的模式处理不同的业务
switch clt.flag {
case 1:
//公聊模式
fmt.Println(">>>>>>公聊模式,输入exit退出")
clt.PublicChat()
case 2:
//私聊模式
fmt.Println(">>>>>>私聊模式,输入exit退出")
clt.PrivateChat()
case 3:
//更新用户名
clt.UpdateName()
}
}
}
// 查询在线用户
func (clt *Client) SelectUsers() {
sendMsg := "who\n"
_, err := clt.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println("conn.Write error:", err)
return
}
}
func (clt *Client) PrivateChat() {
clt.SelectUsers()
var remoteName string
var chatMsg string
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 := clt.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println("conn.Write error:", err)
break
}
}
chatMsg = ""
fmt.Println(">>>>>请输入消息内容,输入exit退出:")
fmt.Scanln(&chatMsg)
}
clt.SelectUsers()
fmt.Println(">>>>>>请输入聊天对象(用户名),输入exit退出:")
fmt.Scanln(&remoteName)
}
}
client.go
package main
import (
"flag"
"fmt"
"io"
"net"
"os"
)
type Client struct {
ServerIP string
ServerPort int
ClientName string
conn net.Conn
flag int // 当前客户端的模型
}
func NewClient(serverIP string, serverPotr int) *Client {
//创建客户端对象
client := &Client{
ServerIP: serverIP,
ServerPort: serverPort,
flag: 999,
}
//链接server
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIP, serverPotr))
if err != nil {
fmt.Println("net.Dial error:", err)
return nil
}
client.conn = conn
//返回对象
return client
}
func (clt *Client) menu() bool {
var flag int
fmt.Println("*****************************")
fmt.Println("**********1.公聊模式**********")
fmt.Println("**********2.私聊模式**********")
fmt.Println("**********3.更新用户名********")
fmt.Println("**********0.退出**************")
fmt.Println("******************************")
fmt.Scanln(&flag) // 输入flag
if flag >= 0 && flag <= 3 {
clt.flag = flag
return true
} else {
fmt.Println(">>>>>>请输入合法的flag!<<<<<<")
return false
}
}
func (clt *Client) Run() {
for clt.flag != 0 {
//模式不对就一直循环菜单
for clt.menu() == false {
}
//根据不同的模式处理不同的业务
switch clt.flag {
case 1:
//公聊模式
fmt.Println(">>>>>>公聊模式,输入exit退出")
clt.PublicChat()
case 2:
//私聊模式
fmt.Println(">>>>>>私聊模式,输入exit退出")
clt.PrivateChat()
case 3:
//更新用户名
clt.UpdateName()
}
}
}
func (clt *Client) UpdateName() bool {
fmt.Println(">>>>请输入用户名:")
fmt.Scanln(&clt.ClientName)
sendMsg := "rename|" + clt.ClientName + "\n"
_, err := clt.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println("conn.Write error:", err)
return false
}
return true
}
func (clt *Client) DealResponse() {
//不断阻塞等待套接字conn的数据,并copy到stdout中
//一旦clt.conn有数据,就直接拷贝到stdout中,是永久阻塞的
io.Copy(os.Stdout, clt.conn)
//上面的一行代码等价于下面的代码
// for {
// buf := make([]byte, 4096)
// clt.conn.Read(buf)
// fmt.Println(buf)
// }
}
func (clt *Client) PublicChat() {
var chatMsg string
fmt.Println(">>>>>请输入聊天内容:")
fmt.Scanln(&chatMsg)
for chatMsg != "exit" {
//消息不为空则发送给服务器
if len(chatMsg) != 0 {
sendMsg := chatMsg + "\n"
_, err := clt.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println("conn.Write error:", err)
break
}
}
chatMsg = ""
fmt.Println(">>>>>请输入聊天内容:")
fmt.Scanln(&chatMsg)
}
}
// 查询在线用户
func (clt *Client) SelectUsers() {
sendMsg := "who\n"
_, err := clt.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println("conn.Write error:", err)
return
}
}
func (clt *Client) PrivateChat() {
clt.SelectUsers()
var remoteName string
var chatMsg string
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 := clt.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println("conn.Write error:", err)
break
}
}
chatMsg = ""
fmt.Println(">>>>>请输入消息内容,输入exit退出:")
fmt.Scanln(&chatMsg)
}
clt.SelectUsers()
fmt.Println(">>>>>>请输入聊天对象(用户名),输入exit退出:")
fmt.Scanln(&remoteName)
}
}
var serverIP string
var serverPort int
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("127.0.0.1", 8888)
if client == nil {
fmt.Println(">>>>>>>>链接服务器失败>>>>>>>>")
return
}
fmt.Println(">>>>>>>>链接服务器成功>>>>>>>>>")
//单独开启一个goroutine,去处理server的回执消息
go client.DealResponse()
//启动客户端业务
client.Run()
}