Add a README

This commit is contained in:
2018-12-23 15:12:56 +08:00
parent 1f71eb11f2
commit d9445b707e
53 changed files with 6861 additions and 7082 deletions
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

+52
View File
@@ -0,0 +1,52 @@
package main
//宏
const (
//最大包头长度
max_head_length uint32 = 4096 //4K
//最大消息长度
max_message_length uint32 = 0x6400000 //64M
//最大头像大小
max_photo_size uint32 = 0x400000 //4M
//服务器地址
server_addr string = "localhost:3727"
)
//消息头的通用字段名称
const (
keyname_operation string = "Operation" //操作名
keyname_token string = "Token"
)
//控制消息
const (
operation_panic string = "Panic"
operation_trans_message string = "TransMessage"
operation_send_message string = "SendMessage"
operation_ping string = "Ping"
operation_login string = "Login"
operation_logout string = "Logout"
operation_offline string = "Offline"
operation_get_user_list string = "GetUserList"
operation_update_userinfo string = "UpdateUserInfo"
)
//帮助
const help_message string = `
欢迎使用StormChat!
输入“help”查看命令列表
输入“.”退出命令模式,退出后可输入“.”重新进入命令模式
聊天时可在行末输入‘\’以换行输入消息
`
//指令列表
const command_list string = `
指令列表:
. 退出命令模式 login 登录
to 设定聊天对象 logout 注销
friends 查看用户列表 help 显示命令列表
scroll 消息面板翻页 clear 清空消息记录
exit 退出程序
CTRL + C 强制退出
`
+256
View File
@@ -0,0 +1,256 @@
package main
import (
"bufio"
"fmt"
"os"
"strings"
"time"
)
/*
#include <conio.h>
#include <stdlib.h>
int getkey() {
return getch();
}
*/
import "C"
var commandMode bool //指示当前是否处于命令模式
func main() {
defer func() {
fmt.Print("按任意键退出程序")
C.getkey()
DestroyConsoleWindow()
}()
InitConsole()
client := NewSession()
if client == nil {
return
}
SetUserNameText("未登录")
SetCurrentFriendText("")
PrintInfo(help_message)
if len(os.Args) >= 3 {
client.Login(os.Args[1], os.Args[2])
commandMode = false
}
commandMode = true
PrintInfo(command_list)
if InputCommand(client) {
return
}
var message string
var input string
for {
if message == "" {
ClearInput()
}
reader := bufio.NewReader(os.Stdin)
input, _ = reader.ReadString('\n')
input = strings.TrimSuffix(input, "\r\n")
input = strings.TrimSuffix(input, "\n")
switch input {
case ".":
message = ""
if InputCommand(client) {
return
}
case ".quit":
return
case ".exit":
return
default:
message += input
if strings.HasSuffix(input, "\\") {
message = strings.TrimSuffix(message, "\\")
message += "\r\n"
} else {
client.SendMessage([]byte(message))
message = ""
} //发送成功,清空消息缓存
}
}
}
//输入指令
func InputCommand(client *Session) bool {
defer SetOperationText("聊天")
SetOperationText("命令模式")
for {
ClearInput()
var input string
reader := bufio.NewReader(os.Stdin)
input, _ = reader.ReadString('\n')
input = strings.TrimSuffix(input, "\r\n")
input = strings.TrimSuffix(input, "\n")
switch strings.TrimRight(input, " ") {
case "scroll":
ScrollMessage()
case "login":
success := LoginWithInput(client)
if !success {
client.destroy()
client = NewSession()
PrintError("登录失败,请重试。")
} //如果连接已中断则重新创建session
case "logout":
client.Logout()
case "to":
SwitchReceiver(client)
case "friends":
ViewFriends(client)
case "clear":
ClearOutput()
case "help":
PrintInfo(command_list)
case ".":
return false
case "exit":
return true
default:
PrintError("无效命令")
}
}
}
//发起登录。如果返回值为false则session已失效(根据发送数据失败判断)
func LoginWithInput(session *Session) bool {
SetOperationText("登录")
defer func() {
if commandMode {
SetOperationText("命令模式")
} else {
SetOperationText("聊天")
}
}()
if session.status == session_status_running {
PrintError("已经登录,请先注销。")
return true //虽然登录失败但连接仍有效,因此返回true
}
reader := bufio.NewReader(os.Stdin)
PrintInputTip("user: ")
user, _ := reader.ReadString('\n')
user = strings.TrimSuffix(user, "\r\n")
user = strings.TrimSuffix(user, "\n")
PrintInputTip("password: ")
pwd, _ := reader.ReadString('\n')
pwd = strings.TrimSuffix(pwd, "\r\n")
pwd = strings.TrimSuffix(pwd, "\n")
if !session.Login(user, pwd) {
return false
}
return true
}
//更换聊天对象
func SwitchReceiver(session *Session) {
if session.status != session_status_running {
PrintError("请先登录")
return
}
SetOperationText("切换好友")
PrintInputTip("好友ID或昵称(输入*启用广播模式): ")
var found bool = false //好友是否存在
var friend string
reader := bufio.NewReader(os.Stdin)
friend, _ = reader.ReadString('\n')
friend = strings.TrimSuffix(friend, "\r\n")
friend = strings.TrimSuffix(friend, "\n")
if friend == "*" {
SetCurrentFriendText("广播模式")
session.receiver = friend
} //广播消息
//查询好友列表
for _, item := range session.friends {
if strings.ToLower(item.NickName) == strings.ToLower(friend) || item.User == friend {
SetCurrentFriendText(item.NickName)
session.receiver = item.User
found = true
break
}
}
if commandMode {
SetOperationText("命令模式")
} else {
SetOperationText("聊天")
}
if !found {
PrintError("未找到名为【" + friend + "】的好友")
}
}
//查看好友列表
func ViewFriends(session *Session) {
list := session.friends
if session.status != session_status_running {
PrintError("请先登录")
return
}
if len(list) == 0 {
PrintInfo("好友列表为空")
return
}
for _, item := range list {
PrintInfo(item.User + " " + item.NickName)
}
}
//滚动消息面板
func ScrollMessage() {
if commandMode {
defer SetOperationText("命令模式")
} else {
defer SetOperationText("聊天")
}
defer ClearInput()
SetOperationText("翻页模式")
PrintInputTip("上翻:P\n下翻:N\n退出翻页:ESC\n")
for {
key := C.getkey()
switch key {
case 'p':
ScrollOutputArea(-1)
case 'n':
ScrollOutputArea(1)
case 27:
return
}
}
}
//输出错误
func PrintError(err string) {
PrintOutputLine("[ERROR] "+err, FOREGROUND_RED)
}
//输出信息
func PrintInfo(text string) {
PrintOutputLine(text, FOREGROUND_RED|FOREGROUND_GREEN)
}
func PrintLog(str string) {
PrintOutputLine("[INFO] "+str, FOREGROUND_INTENSITY)
}
//输出消息头
func PrintMessageHead(head string, intensity bool) {
if intensity {
PrintOutputLine(head, FOREGROUND_INTENSITY)
} else {
PrintOutputLine(head, FOREGROUND_GREEN)
}
}
//输出消息体
func PrintMessage(msg string) {
PrintOutputLine(msg, FORGROUND_WHITE)
}
//构造消息头
func MakeHead(operation string) map[string]string {
headInfo := make(map[string]string)
headInfo[keyname_token] = string(time.Now().UnixNano())
headInfo[keyname_operation] = operation
return headInfo
}
+324
View File
@@ -0,0 +1,324 @@
package main
import (
"encoding/binary"
"encoding/json"
"net"
"sync/atomic"
"time"
)
//当前session的状态
const (
session_status_created = iota //已创建,但未登录
session_status_running // 通道已建立,session正常运行
session_status_stoped // 标识连接已断开
session_status_destroyed
)
type Session struct {
sender *UserInfo //消息发送者
receiver string //消息接收者
friends []UserInfo //好友列表
conn *net.TCPConn //TCP连接
status int32 //session运行状态
stopChan chan bool //Session终止标识,传递数据即终止。仅由读消息循环用于终止写循环
writeCh chan []byte //写消息通道
writeResultCh chan bool //是否写成功
}
//初始化
func NewSession() *Session {
tcpAddr, _ := net.ResolveTCPAddr("tcp", server_addr)
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
PrintError("连接服务器失败:" + err.Error())
return nil
}
var session = new(Session)
session.sender = nil
session.receiver = ""
session.status = session_status_created
session.stopChan = make(chan bool)
session.writeCh = make(chan []byte)
session.writeResultCh = make(chan bool)
session.conn = conn
go session.SendLoop()
go session.ReceiveLoop()
return session
}
//销毁聊天连接
func (session *Session) destroy() {
if session.status == session_status_destroyed {
return
}
session.status = session_status_destroyed
session.conn.Close()
close(session.writeCh)
close(session.writeResultCh)
}
//消息写循环
func (session *Session) SendLoop() {
defer session.destroy()
for {
select {
case data, ok := <-session.writeCh:
if !ok {
atomic.SwapInt32(&session.status, session_status_stoped)
continue
}
writeLen, err := session.conn.Write(data)
if err != nil || (writeLen != len(data)) {
PrintError(err.Error())
atomic.SwapInt32(&session.status, session_status_stoped)
session.writeResultCh <- false
} //写失败
session.writeResultCh <- true
case <-session.stopChan:
return
}
}
}
//消息读循环
func (session *Session) ReceiveLoop() {
defer func() {
session.stopChan <- true
}() //终止写循环
var err error //错误
for {
if atomic.LoadInt32(&session.status) >= session_status_stoped {
return
} //检查session是否已终止
//读取数据
head := session.readDataWithLength()
data := session.readDataWithLength()
if head == nil || data == nil {
PrintError("获取数据失败")
atomic.SwapInt32(&session.status, session_status_stoped)
continue
}
//解析包头
headInfo := make(map[string]string)
err = json.Unmarshal(head, &headInfo)
if err != nil {
PrintError("解析包头失败")
continue
}
switch headInfo[keyname_operation] {
case operation_send_message:
session.ResultHandler(headInfo)
break
case operation_update_userinfo:
session.ResultHandler(headInfo)
break
case operation_offline:
case operation_panic:
session.PanicHandler(headInfo)
break
case operation_login:
session.LoginDoneHandler(headInfo, data)
break
case operation_trans_message:
session.MessageHandler(headInfo, data)
break
case operation_get_user_list:
session.GetUserListHandler(headInfo, data)
break
default:
continue
}
}
}
//处理请求结果反馈消息
func (session *Session) ResultHandler(headInfo map[string]string) {
if headInfo["Error"] == "" {
PrintLog(headInfo[keyname_operation] + " successfully.")
} else {
PrintError("Failed to " + headInfo[keyname_operation] + ": " + headInfo["Error"])
}
}
//服务器报错
func (session *Session) PanicHandler(headInfo map[string]string) {
PrintError("Server Error: " + headInfo["Error"])
atomic.SwapInt32(&session.status, session_status_stoped)
}
//登录反馈
func (session *Session) LoginDoneHandler(headInfo map[string]string, data []byte) {
session.ResultHandler(headInfo)
if headInfo["Error"] == "" {
user := new(UserInfo)
err := json.Unmarshal(data, user)
if err != nil {
PrintError("解析用户信息失败")
return
} else {
session.sender = user
SetUserNameText(user.NickName)
}
atomic.SwapInt32(&session.status, session_status_running)
session.GetUserList()
}
}
//获取用户列表
func (session *Session) GetUserListHandler(headInfo map[string]string, data []byte) {
session.ResultHandler(headInfo)
if headInfo["Error"] != "" {
return
}
err := json.Unmarshal(data, &session.friends)
if err != nil {
PrintError("解析用户信息失败: " + err.Error())
}
}
//输出消息
func (session *Session) MessageHandler(headInfo map[string]string, message []byte) {
if headInfo["Error"] != "" {
PrintError("发送消息失败: " + headInfo["Error"])
return
}
var user *UserInfo = nil
for _, item := range session.friends {
if item.User == headInfo["From"] {
user = &item
break
}
}
when, _ := time.Parse(time.RFC3339, headInfo["When"])
if user == nil {
PrintMessageHead(headInfo["From"]+" ["+when.Format("2006-01-02 15:04:05")+"]", false)
} else {
PrintMessageHead(user.NickName+" ["+when.Format("2006-01-02 15:04:05")+"]", false)
}
PrintMessage(string(message))
if session.receiver == "" && user != nil {
session.receiver = user.User
SetCurrentFriendText(user.NickName)
} //如果无当前聊天对象则将聊天对象设置为消息发送者
}
//发送数据
func (session *Session) send(head []byte, data []byte) bool {
if atomic.LoadInt32(&session.status) >= session_status_stoped {
return false
}
var buf = make([]byte, 4) //长度标识
var packet = make([]byte, 0, len(head)+len(data)+4+4) //headLen + head + msgLen + data
//写包头
//取包头长
binary.BigEndian.PutUint32(buf[0:4], uint32(len(head)))
packet = append(packet, buf...) //写包头长度
if len(head) > 0 {
packet = append(packet, head...) //写包头
}
//写数据
//取数据长
binary.BigEndian.PutUint32(buf[0:4], uint32(len(data)))
packet = append(packet, buf...) //写数据长度
if len(data) > 0 {
packet = append(packet, data...) //写数据
}
if len(packet) != cap(packet) {
return false
} //数据异常
if atomic.LoadInt32(&session.status) >= session_status_stoped {
return false
}
session.writeCh <- packet
return <-session.writeResultCh
}
//读取数据
func (session *Session) readDataWithLength() []byte {
var dataLen uint32 //数据长度
var lenBuffer [4]byte //数据长度缓存区
var data []byte //数据缓存区
//获取数据长度
readedLen, err := session.conn.Read(lenBuffer[0:4])
if err != nil || readedLen != 4 {
PrintError("获取消息失败。 Error: " + err.Error())
return nil
}
dataLen = binary.BigEndian.Uint32(lenBuffer[0:4])
data = make([]byte, dataLen)
if dataLen == 0 {
return data
}
readedLen, err = session.conn.Read(data)
if err != nil || readedLen != int(dataLen) {
PrintError("获取消息失败。Error" + err.Error())
return nil
}
return data
}
//发送数据
func (session *Session) SendData(headInfo map[string]string, data []byte) bool {
head, err := json.Marshal(headInfo)
if err != nil {
PrintError("Marshal Failed.")
return false
}
return session.send(head, data)
}
//发送消息
func (session *Session) SendMessage(msg []byte) bool {
if session.sender == nil {
PrintError("请先登录")
return false
}
if session.receiver == "" {
PrintError("请先设定当前聊天好友")
return false
}
if len(msg) == 0 {
PrintError("无法发送空消息")
return false
}
PrintMessageHead(session.sender.NickName+" ["+time.Now().Format("2006-01-02 15:04:05")+"]", true)
PrintMessage(string(msg))
headInfo := MakeHead(operation_send_message)
headInfo["To"] = session.receiver
return session.SendData(headInfo, msg)
}
//登录
func (session *Session) Login(user string, pwd string) bool {
logInfo := MakeHead(operation_login)
logInfo["User"] = user
logInfo["Pwd"] = pwd
return session.SendData(logInfo, nil)
}
//请求登出
func (session *Session) Logout() {
if session.sender == nil {
PrintError("当前未登录")
return
} //未登录
headInfo := MakeHead(operation_logout)
session.SendData(headInfo, nil)
atomic.SwapInt32(&session.status, session_status_created)
session.sender = nil
session.friends = nil
session.receiver = ""
SetUserNameText("未登录")
SetCurrentFriendText("")
PrintLog("已登出.")
}
//获取好友列表
func (session *Session) GetUserList() {
headInfo := MakeHead(operation_get_user_list)
session.SendData(headInfo, nil)
}
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
package main
//用户信息
type UserInfo struct {
User string //用户名
NickName string //昵称
Motto string //签名
UGroup string //用户组。ugroup_*常量值
}
//用户组
const (
ugroup_user string = "User" //普通用户
ugroup_vip string = "Vip" //会员
ugroup_admin string = "Admin" //管理员
ugroup_group string = "Group" //群聊
)
+158
View File
@@ -0,0 +1,158 @@
package main
import (
"math"
"strings"
"syscall"
"unsafe"
)
const (
FOREGROUND_BLUE int = 1
FOREGROUND_GREEN int = 2
FOREGROUND_RED int = 4
FOREGROUND_INTENSITY int = 8
BACKGROUND_BLUE int = 16
BACKGROUND_GREEN int = 32
BACKGROUND_RED int = 64
BACKGROUND_INTENSITY int = 128
FORGROUND_WHITE int = 7
)
var conctrl *syscall.LazyDLL //Windows控制台窗口辅助输出库
var consoleWindow uintptr //控制台窗口控制器指针
var messagePannel uintptr //消息输出窗格
var operationPannel uintptr //操作提示窗格
var usrenamePannel uintptr //用户名显示窗格
var currentFriendPannel uintptr //当前聊天对象显示窗格
var inputPannel uintptr //输入窗格
var spliter uintptr //分割线
//初始化
func InitConsole() {
conctrl = syscall.NewLazyDLL("conctrl_x64.dll")
proc := conctrl.NewProc("CreateConsoleWindow")
consoleWindow, _, _ = proc.Call(100, 999)
if consoleWindow == 0 {
panic("Failed to init console. conctrl.dll not found.")
}
proc = conctrl.NewProc("CreatePannel")
messagePannel, _, _ = proc.Call(consoleWindow, 0, 0, 100, 20)
operationPannel, _, _ = proc.Call(consoleWindow, 0, 21, 20, 1)
usrenamePannel, _, _ = proc.Call(consoleWindow, 35, 21, 30, 1)
currentFriendPannel, _, _ = proc.Call(consoleWindow, 65, 21, 35, 1)
inputPannel, _, _ = proc.Call(consoleWindow, 0, 23, 100, 20)
proc = conctrl.NewProc("CreateSpliter")
spliter, _, _ = proc.Call(consoleWindow, 0, 22, 100, 0, uintptr(FORGROUND_WHITE))
proc = conctrl.NewProc("FocusOnPannel")
proc.Call(inputPannel, 0, 0)
SetTitle("StormChat")
}
//滚动输出区域
func ScrollOutputArea(lineCount int) {
var proc *syscall.LazyProc
if lineCount > 0 {
proc = conctrl.NewProc("ScrollPannelForward")
} else {
proc = conctrl.NewProc("ScrollPannelBackward")
}
proc.Call(messagePannel, uintptr(math.Abs(float64(lineCount))))
}
//设置控制台标题
func SetTitle(title string) {
proc := conctrl.NewProc("SetConsoleWindowTitle")
titleB := append([]byte(title), 0)
pTtile := *(*uintptr)(unsafe.Pointer(&titleB))
proc.Call(uintptr(pTtile), 1)
}
//设置当前操作提示文本
func SetOperationText(op string) {
proc := conctrl.NewProc("ClearPannel")
proc.Call(operationPannel)
AddPannelLine(operationPannel, op, false, FORGROUND_WHITE)
}
//设置用户昵称显示区文本
func SetUserNameText(name string) {
blank := (30 - len(name)) / 2
if blank > 0 {
name = strings.Repeat(" ", blank) + name
}
proc := conctrl.NewProc("ClearPannel")
proc.Call(usrenamePannel)
AddPannelLine(usrenamePannel, name, false, FORGROUND_WHITE)
}
//设置当前聊天好友提示文本
func SetCurrentFriendText(friend string) {
if friend != "" {
friend = "To: " + friend
}
blank := 34 - len(friend)
if blank > 0 {
friend = strings.Repeat(" ", blank) + friend
}
proc := conctrl.NewProc("ClearPannel")
proc.Call(currentFriendPannel)
AddPannelLine(currentFriendPannel, friend, false, FORGROUND_WHITE)
}
//向输出区写行
func PrintOutputLine(text string, attribute int) {
AddPannelLine(messagePannel, text, false, attribute)
}
//向输入区写文本
func PrintInputTip(text string) {
ClearInput()
AddPannelText(inputPannel, text, true, FORGROUND_WHITE)
}
//清空输入区
func ClearInput() {
proc := conctrl.NewProc("ClearPannel")
proc.Call(inputPannel)
proc = conctrl.NewProc("FocusOnPannel")
proc.Call(inputPannel, 0, 0)
}
func ClearOutput() {
proc := conctrl.NewProc("ClearPannel")
proc.Call(messagePannel)
}
//销毁控制器,恢复控制台
func DestroyConsoleWindow() {
proc := conctrl.NewProc("FocusOnPannel")
proc.Call(messagePannel, 0, 0)
proc = conctrl.NewProc("DestroyConsoleWindow")
proc.Call(consoleWindow)
}
//向窗格加入文本行
func AddPannelLine(pannel uintptr, text string, focus bool, attribute int) {
proc := conctrl.NewProc("AddPannelLine")
addPannel(proc, pannel, text, focus, attribute)
}
//向窗格加入文本
func AddPannelText(pannel uintptr, text string, focus bool, attribute int) {
proc := conctrl.NewProc("AddPannelText")
addPannel(proc, pannel, text, focus, attribute)
}
//向窗格加入文本(行)
func addPannel(proc *syscall.LazyProc, pannel uintptr, text string, focus bool, attribute int) {
line := append([]byte(text), 0)
pLine := *(*uintptr)(unsafe.Pointer(&line))
var focusInt int
if focus {
focusInt = 1
} else {
focusInt = 0
}
proc.Call(pannel, uintptr(pLine), uintptr(focusInt), 1, uintptr(attribute))
}