Golang GO와 마이크로서비스 ChatServer 2
13634 단어 golang
인연
최근 읽기<>(유금량, 2021.1) 본 시리즈 노트는 golang으로 연습할 예정
케이스 요구사항(채팅 서버)
목표
설계
단원 테스트
ChatServer_test.go package chat_server
import (
"fmt"
cs "learning/gooop/chat_server"
"strings"
"testing"
"time"
)
func Test_ChatServer(t *testing.T) {
fnAssertTrue := func(b bool, msg string) {
if !b {
t.Fatal(msg)
}
}
port := 3333
server := cs.NewChatServer()
err := server.Open(port)
if err != nil {
t.Fatal(err)
}
clientCount := 3
address := fmt.Sprintf("localhost:%v", port)
for i := 0;i < clientCount;i++ {
err, client := cs.DialChatClient(address)
if err != nil {
t.Fatal(err)
}
id := fmt.Sprintf("c%02d", i)
client.RecvHandler(func(client cs.IChatClient, msg cs.IMsg) {
t.Logf("%v recv: %v
", id, msg)
})
go func() {
client.SetName(id)
client.Send(&cs.NameMsg{id })
n := 0
for range time.Tick(time.Duration(1) * time.Second) {
client.Send(&cs.ChatMsg{id, fmt.Sprintf("msg %02d from %v", n, id) })
n++
if n >= 3 {
break
}
}
client.Close()
}()
}
passedSeconds := 0
for range time.Tick(time.Second) {
passedSeconds++
t.Logf("%v seconds passed", passedSeconds)
if passedSeconds >= 5 {
break
}
}
server.Close()
logs := server.GetLogs()
fnHasLog := func(log string) bool {
for _,it := range logs {
if strings.Contains(it, log) {
return true
}
}
return false
}
for i := 0;i < clientCount;i++ {
msg := fmt.Sprintf("tChatServer.handleIncomingConn, clientCount=%v", i + 1)
fnAssertTrue(fnHasLog(msg), "expecting log: " + msg)
msg = fmt.Sprintf("tChatServer.handleClientClosed, c%02d", i)
fnAssertTrue(fnHasLog(msg), "expecting log: " + msg)
}
}
테스트 출력 $ go test -v ChatServer_test.go
=== RUN Test_ChatServer
tChatServer.handleIncomingConn, clientCount=1
tChatServer.handleIncomingConn, clientCount=2
tChatServer.handleIncomingConn, clientCount=3
ChatServer_test.go:59: 1 seconds passed
ChatServer_test.go:35: c00 recv: &{c00 msg 00 from c00}
ChatServer_test.go:35: c02 recv: &{c00 msg 00 from c00}
ChatServer_test.go:35: c02 recv: &{c01 msg 00 from c01}
ChatServer_test.go:35: c01 recv: &{c00 msg 00 from c00}
ChatServer_test.go:35: c01 recv: &{c01 msg 00 from c01}
ChatServer_test.go:35: c01 recv: &{c02 msg 00 from c02}
ChatServer_test.go:35: c00 recv: &{c01 msg 00 from c01}
ChatServer_test.go:35: c00 recv: &{c02 msg 00 from c02}
ChatServer_test.go:35: c02 recv: &{c02 msg 00 from c02}
ChatServer_test.go:35: c00 recv: &{c01 msg 01 from c01}
ChatServer_test.go:35: c01 recv: &{c00 msg 01 from c00}
ChatServer_test.go:35: c01 recv: &{c02 msg 01 from c02}
ChatServer_test.go:35: c02 recv: &{c01 msg 01 from c01}
ChatServer_test.go:35: c02 recv: &{c00 msg 01 from c00}
ChatServer_test.go:59: 2 seconds passed
ChatServer_test.go:35: c00 recv: &{c00 msg 01 from c00}
ChatServer_test.go:35: c02 recv: &{c02 msg 01 from c02}
ChatServer_test.go:35: c00 recv: &{c02 msg 01 from c02}
tChatClient.postConnClosed, c00, serverFlag=false
tChatClient.postConnClosed, c02, serverFlag=false
tChatClient.postConnClosed, c01, serverFlag=false
tChatClient.postConnClosed, c02, serverFlag=true
tChatServer.handleClientClosed, c02
tChatServer.handleClientClosed, c02, clientCount=2
tChatClient.postConnClosed, c01, serverFlag=true
tChatServer.handleClientClosed, c01
tChatServer.handleClientClosed, c01, clientCount=1
ChatServer_test.go:59: 3 seconds passed
tChatClient.postConnClosed, c00, serverFlag=true
tChatServer.handleClientClosed, c00
tChatServer.handleClientClosed, c00, clientCount=0
ChatServer_test.go:59: 4 seconds passed
ChatServer_test.go:59: 5 seconds passed
--- PASS: Test_ChatServer (5.00s)
PASS
ok command-line-arguments 5.003s
IMsg.go
메시지 인터페이스를 정의하고 관련 메시지의 실현을 정의합니다.임의의 메시지 내용의 디코딩을 편리하게 하기 위해, 메시지 전송 시,base64 디코딩을 사용한다package chat_server
import (
"encoding/base64"
"fmt"
)
type IMsg interface {
Encode() string
}
type NameMsg struct {
Name string
}
func (me *NameMsg) Encode() string {
return fmt.Sprintf("NAME %s
", base64.StdEncoding.EncodeToString([]byte(me.Name)))
}
type ChatMsg struct {
Name string
Words string
}
func (me *ChatMsg) Encode() string {
return fmt.Sprintf("CHAT %s %s
",
base64.StdEncoding.EncodeToString([]byte(me.Name)),
base64.StdEncoding.EncodeToString([]byte(me.Words)),
)
}
IMsgDecoder.go
메시지 디코더 및 그 실현 정의package chat_server
import (
"encoding/base64"
"strings"
)
type IMsgDecoder interface {
Decode(line string) (bool, IMsg)
}
type tMsgDecoder struct {
}
func (me *tMsgDecoder) Decode(line string) (bool, IMsg) {
items := strings.Split(line, " ")
size := len(items)
if items[0] == "NAME" && size == 2 {
name, err := base64.StdEncoding.DecodeString(items[1])
if err != nil {
return false, nil
}
return true, &NameMsg{
Name: string(name),
}
}
if items[0] == "CHAT" && size == 3 {
name, err := base64.StdEncoding.DecodeString(items[1])
if err != nil {
return false, nil
}
words, err := base64.StdEncoding.DecodeString(items[2])
if err != nil {
return false, nil
}
return true, &ChatMsg{
Name: string(name),
Words: string(words),
}
}
return false, nil
}
var MsgDecoder = &tMsgDecoder{}
IChatClient.go
채팅 클라이언트 인터페이스를 정의합니다.이번에는 서버에 맞게 닫기 알림 방법을 추가합니다.package chat_server
type IChatClient interface {
GetName() string
SetName(name string)
Send(msg IMsg)
RecvHandler(handler ClientRecvFunc)
CloseHandler(handler ClientCloseFunc)
Close()
}
type ClientRecvFunc func(client IChatClient, msg IMsg)
type ClientCloseFunc func(client IChatClient)
tChatClient.go
채팅 클라이언트, IChatClient 인터페이스 구현.이번 추가 닫기 알림, 쓰기 버퍼, 읽기 시간 초과 제어, 쓰기 순환 세부 문제 해결.package chat_server
import (
"bufio"
"fmt"
"io"
"net"
"sync/atomic"
"time"
)
type tChatClient struct {
conn net.Conn
name string
openFlag int32
closeFlag int32
serverFlag bool
closeChan chan bool
sendChan chan IMsg
sendLogs []IMsg
dropLogs []IMsg
recvLogs []IMsg
pendingSend int32
recvHandler ClientRecvFunc
closeHandler ClientCloseFunc
}
var gMaxPendingSend int32 = 100
func DialChatClient(address string) (error, IChatClient) {
conn, err := net.Dial("tcp", address)
if err != nil {
return err, nil
}
return nil, openChatClient(conn, false)
}
func openChatClient(conn net.Conn, serverFlag bool) IChatClient {
it := &tChatClient{
conn: conn,
openFlag: 0,
closeFlag: 0,
serverFlag: serverFlag,
closeChan: make(chan bool),
sendChan: make(chan IMsg, gMaxPendingSend),
name: "anonymous",
sendLogs: []IMsg{},
dropLogs: []IMsg{},
recvLogs: []IMsg{},
}
it.open()
return it
}
func (me *tChatClient) GetName() string {
return me.name
}
func (me *tChatClient) SetName(name string) {
me.name = name
}
func (me *tChatClient) open(){
if !atomic.CompareAndSwapInt32(&me.openFlag, 0, 1) {
return
}
go me.beginWrite()
go me.beginRead()
}
func (me *tChatClient) isClosed() bool {
return me.closeFlag != 0
}
func (me *tChatClient) isNotClosed() bool {
return !me.isClosed()
}
func (me *tChatClient) Send(msg IMsg) {
if me.isClosed() {
return
}
if me.pendingSend < gMaxPendingSend {
atomic.AddInt32(&me.pendingSend, 1)
me.sendChan
IChatServer.go
채팅 서버 인터페이스를 정의하여 테스트를 편리하게 하고 로그 수집 방법을 제공합니다package chat_server
type IChatServer interface {
Open(port int) error
Broadcast(msg IMsg)
Close()
GetLogs() []string
}
tChatServer.go
채팅 서버 IChatServer 구현package chat_server
import (
"errors"
"fmt"
"net"
"sync"
"sync/atomic"
)
type tChatServer struct {
openFlag int32
closeFlag int32
clients []IChatClient
clientCount int
clientLock *sync.RWMutex
listener net.Listener
recvLogs []IMsg
logs []string
}
func NewChatServer() IChatServer {
it := &tChatServer{
openFlag: 0,
closeFlag: 0,
clients: []IChatClient{},
clientCount: 0,
clientLock: new(sync.RWMutex),
listener: nil,
recvLogs: []IMsg{},
}
return it
}
func (me *tChatServer) Open(port int) error {
if !atomic.CompareAndSwapInt32(&me.openFlag, 0, 1) {
return errors.New("server already opened")
}
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
if err != nil {
return err
}
me.listener = listener
go me.beginListening()
return nil
}
func (me *tChatServer) logf(f string, args... interface{}) {
msg := fmt.Sprintf(f, args...)
me.logs = append(me.logs, msg)
fmt.Println(msg)
}
func (me *tChatServer) GetLogs() []string {
return me.logs
}
func (me *tChatServer) isClosed() bool {
return me.closeFlag != 0
}
func (me *tChatServer) isNotClosed() bool {
return !me.isClosed()
}
func (me *tChatServer) beginListening() {
for !me.isClosed() {
conn, err := me.listener.Accept()
if err != nil {
me.Close()
break
}
me.handleIncomingConn(conn)
}
}
func (me *tChatServer) Close() {
if !atomic.CompareAndSwapInt32(&me.closeFlag, 0, 1) {
return
}
_ = me.listener.Close()
me.closeAllClients()
}
func (me *tChatServer) closeAllClients() {
me.clientLock.Lock()
defer me.clientLock.Unlock()
for i,it := range me.clients {
if it != nil {
it.Close()
me.clients[i] = nil
}
}
me.clientCount = 0
}
func (me *tChatServer) handleIncomingConn(conn net.Conn) {
// init client
client := openChatClient(conn, true)
client.RecvHandler(me.handleClientMsg)
client.CloseHandler(me.handleClientClosed)
// lock me.clients
me.clientLock.Lock()
defer me.clientLock.Unlock()
// append to me.clients
if len(me.clients) > me.clientCount {
me.clients[me.clientCount] = client
} else {
me.clients = append(me.clients, client)
}
me.clientCount++
me.logf("tChatServer.handleIncomingConn, clientCount=%v", me.clientCount)
}
func (me *tChatServer) handleClientMsg(client IChatClient, msg IMsg) {
me.recvLogs = append(me.recvLogs, msg)
if nameMsg,ok := msg.(*NameMsg);ok {
client.SetName(nameMsg.Name)
} else if _, ok := msg.(*ChatMsg);ok {
me.Broadcast(msg)
}
}
func (me *tChatServer) handleClientClosed(client IChatClient) {
me.logf("tChatServer.handleClientClosed, %s", client.GetName())
me.clientLock.Lock()
defer me.clientLock.Unlock()
if me.clientCount <= 0 {
return
}
lastI := me.clientCount - 1
for i,it := range me.clients {
if it == client {
if i == lastI {
me.clients[i] = nil
} else {
me.clients[i], me.clients[lastI] = me.clients[lastI], nil
}
me.clientCount--
break
}
}
me.logf("tChatServer.handleClientClosed, %s, clientCount=%v", client.GetName(), me.clientCount)
}
func (me *tChatServer) Broadcast(msg IMsg) {
me.clientLock.RLock()
defer me.clientLock.RUnlock()
for _,it := range me.clients {
if it != nil {
it.Send(msg)
}
}
}
(미완성 미계속)
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
set container
There is no built-in set container in Go
How to implement Set
struct{} => type
struct{}{} => 0bytes
How to create
set :=...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.
package chat_server
import (
"fmt"
cs "learning/gooop/chat_server"
"strings"
"testing"
"time"
)
func Test_ChatServer(t *testing.T) {
fnAssertTrue := func(b bool, msg string) {
if !b {
t.Fatal(msg)
}
}
port := 3333
server := cs.NewChatServer()
err := server.Open(port)
if err != nil {
t.Fatal(err)
}
clientCount := 3
address := fmt.Sprintf("localhost:%v", port)
for i := 0;i < clientCount;i++ {
err, client := cs.DialChatClient(address)
if err != nil {
t.Fatal(err)
}
id := fmt.Sprintf("c%02d", i)
client.RecvHandler(func(client cs.IChatClient, msg cs.IMsg) {
t.Logf("%v recv: %v
", id, msg)
})
go func() {
client.SetName(id)
client.Send(&cs.NameMsg{id })
n := 0
for range time.Tick(time.Duration(1) * time.Second) {
client.Send(&cs.ChatMsg{id, fmt.Sprintf("msg %02d from %v", n, id) })
n++
if n >= 3 {
break
}
}
client.Close()
}()
}
passedSeconds := 0
for range time.Tick(time.Second) {
passedSeconds++
t.Logf("%v seconds passed", passedSeconds)
if passedSeconds >= 5 {
break
}
}
server.Close()
logs := server.GetLogs()
fnHasLog := func(log string) bool {
for _,it := range logs {
if strings.Contains(it, log) {
return true
}
}
return false
}
for i := 0;i < clientCount;i++ {
msg := fmt.Sprintf("tChatServer.handleIncomingConn, clientCount=%v", i + 1)
fnAssertTrue(fnHasLog(msg), "expecting log: " + msg)
msg = fmt.Sprintf("tChatServer.handleClientClosed, c%02d", i)
fnAssertTrue(fnHasLog(msg), "expecting log: " + msg)
}
}
$ go test -v ChatServer_test.go
=== RUN Test_ChatServer
tChatServer.handleIncomingConn, clientCount=1
tChatServer.handleIncomingConn, clientCount=2
tChatServer.handleIncomingConn, clientCount=3
ChatServer_test.go:59: 1 seconds passed
ChatServer_test.go:35: c00 recv: &{c00 msg 00 from c00}
ChatServer_test.go:35: c02 recv: &{c00 msg 00 from c00}
ChatServer_test.go:35: c02 recv: &{c01 msg 00 from c01}
ChatServer_test.go:35: c01 recv: &{c00 msg 00 from c00}
ChatServer_test.go:35: c01 recv: &{c01 msg 00 from c01}
ChatServer_test.go:35: c01 recv: &{c02 msg 00 from c02}
ChatServer_test.go:35: c00 recv: &{c01 msg 00 from c01}
ChatServer_test.go:35: c00 recv: &{c02 msg 00 from c02}
ChatServer_test.go:35: c02 recv: &{c02 msg 00 from c02}
ChatServer_test.go:35: c00 recv: &{c01 msg 01 from c01}
ChatServer_test.go:35: c01 recv: &{c00 msg 01 from c00}
ChatServer_test.go:35: c01 recv: &{c02 msg 01 from c02}
ChatServer_test.go:35: c02 recv: &{c01 msg 01 from c01}
ChatServer_test.go:35: c02 recv: &{c00 msg 01 from c00}
ChatServer_test.go:59: 2 seconds passed
ChatServer_test.go:35: c00 recv: &{c00 msg 01 from c00}
ChatServer_test.go:35: c02 recv: &{c02 msg 01 from c02}
ChatServer_test.go:35: c00 recv: &{c02 msg 01 from c02}
tChatClient.postConnClosed, c00, serverFlag=false
tChatClient.postConnClosed, c02, serverFlag=false
tChatClient.postConnClosed, c01, serverFlag=false
tChatClient.postConnClosed, c02, serverFlag=true
tChatServer.handleClientClosed, c02
tChatServer.handleClientClosed, c02, clientCount=2
tChatClient.postConnClosed, c01, serverFlag=true
tChatServer.handleClientClosed, c01
tChatServer.handleClientClosed, c01, clientCount=1
ChatServer_test.go:59: 3 seconds passed
tChatClient.postConnClosed, c00, serverFlag=true
tChatServer.handleClientClosed, c00
tChatServer.handleClientClosed, c00, clientCount=0
ChatServer_test.go:59: 4 seconds passed
ChatServer_test.go:59: 5 seconds passed
--- PASS: Test_ChatServer (5.00s)
PASS
ok command-line-arguments 5.003s
IMsg.go
메시지 인터페이스를 정의하고 관련 메시지의 실현을 정의합니다.임의의 메시지 내용의 디코딩을 편리하게 하기 위해, 메시지 전송 시,base64 디코딩을 사용한다package chat_server
import (
"encoding/base64"
"fmt"
)
type IMsg interface {
Encode() string
}
type NameMsg struct {
Name string
}
func (me *NameMsg) Encode() string {
return fmt.Sprintf("NAME %s
", base64.StdEncoding.EncodeToString([]byte(me.Name)))
}
type ChatMsg struct {
Name string
Words string
}
func (me *ChatMsg) Encode() string {
return fmt.Sprintf("CHAT %s %s
",
base64.StdEncoding.EncodeToString([]byte(me.Name)),
base64.StdEncoding.EncodeToString([]byte(me.Words)),
)
}
IMsgDecoder.go
메시지 디코더 및 그 실현 정의package chat_server
import (
"encoding/base64"
"strings"
)
type IMsgDecoder interface {
Decode(line string) (bool, IMsg)
}
type tMsgDecoder struct {
}
func (me *tMsgDecoder) Decode(line string) (bool, IMsg) {
items := strings.Split(line, " ")
size := len(items)
if items[0] == "NAME" && size == 2 {
name, err := base64.StdEncoding.DecodeString(items[1])
if err != nil {
return false, nil
}
return true, &NameMsg{
Name: string(name),
}
}
if items[0] == "CHAT" && size == 3 {
name, err := base64.StdEncoding.DecodeString(items[1])
if err != nil {
return false, nil
}
words, err := base64.StdEncoding.DecodeString(items[2])
if err != nil {
return false, nil
}
return true, &ChatMsg{
Name: string(name),
Words: string(words),
}
}
return false, nil
}
var MsgDecoder = &tMsgDecoder{}
IChatClient.go
채팅 클라이언트 인터페이스를 정의합니다.이번에는 서버에 맞게 닫기 알림 방법을 추가합니다.package chat_server
type IChatClient interface {
GetName() string
SetName(name string)
Send(msg IMsg)
RecvHandler(handler ClientRecvFunc)
CloseHandler(handler ClientCloseFunc)
Close()
}
type ClientRecvFunc func(client IChatClient, msg IMsg)
type ClientCloseFunc func(client IChatClient)
tChatClient.go
채팅 클라이언트, IChatClient 인터페이스 구현.이번 추가 닫기 알림, 쓰기 버퍼, 읽기 시간 초과 제어, 쓰기 순환 세부 문제 해결.package chat_server
import (
"bufio"
"fmt"
"io"
"net"
"sync/atomic"
"time"
)
type tChatClient struct {
conn net.Conn
name string
openFlag int32
closeFlag int32
serverFlag bool
closeChan chan bool
sendChan chan IMsg
sendLogs []IMsg
dropLogs []IMsg
recvLogs []IMsg
pendingSend int32
recvHandler ClientRecvFunc
closeHandler ClientCloseFunc
}
var gMaxPendingSend int32 = 100
func DialChatClient(address string) (error, IChatClient) {
conn, err := net.Dial("tcp", address)
if err != nil {
return err, nil
}
return nil, openChatClient(conn, false)
}
func openChatClient(conn net.Conn, serverFlag bool) IChatClient {
it := &tChatClient{
conn: conn,
openFlag: 0,
closeFlag: 0,
serverFlag: serverFlag,
closeChan: make(chan bool),
sendChan: make(chan IMsg, gMaxPendingSend),
name: "anonymous",
sendLogs: []IMsg{},
dropLogs: []IMsg{},
recvLogs: []IMsg{},
}
it.open()
return it
}
func (me *tChatClient) GetName() string {
return me.name
}
func (me *tChatClient) SetName(name string) {
me.name = name
}
func (me *tChatClient) open(){
if !atomic.CompareAndSwapInt32(&me.openFlag, 0, 1) {
return
}
go me.beginWrite()
go me.beginRead()
}
func (me *tChatClient) isClosed() bool {
return me.closeFlag != 0
}
func (me *tChatClient) isNotClosed() bool {
return !me.isClosed()
}
func (me *tChatClient) Send(msg IMsg) {
if me.isClosed() {
return
}
if me.pendingSend < gMaxPendingSend {
atomic.AddInt32(&me.pendingSend, 1)
me.sendChan
IChatServer.go
채팅 서버 인터페이스를 정의하여 테스트를 편리하게 하고 로그 수집 방법을 제공합니다package chat_server
type IChatServer interface {
Open(port int) error
Broadcast(msg IMsg)
Close()
GetLogs() []string
}
tChatServer.go
채팅 서버 IChatServer 구현package chat_server
import (
"errors"
"fmt"
"net"
"sync"
"sync/atomic"
)
type tChatServer struct {
openFlag int32
closeFlag int32
clients []IChatClient
clientCount int
clientLock *sync.RWMutex
listener net.Listener
recvLogs []IMsg
logs []string
}
func NewChatServer() IChatServer {
it := &tChatServer{
openFlag: 0,
closeFlag: 0,
clients: []IChatClient{},
clientCount: 0,
clientLock: new(sync.RWMutex),
listener: nil,
recvLogs: []IMsg{},
}
return it
}
func (me *tChatServer) Open(port int) error {
if !atomic.CompareAndSwapInt32(&me.openFlag, 0, 1) {
return errors.New("server already opened")
}
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
if err != nil {
return err
}
me.listener = listener
go me.beginListening()
return nil
}
func (me *tChatServer) logf(f string, args... interface{}) {
msg := fmt.Sprintf(f, args...)
me.logs = append(me.logs, msg)
fmt.Println(msg)
}
func (me *tChatServer) GetLogs() []string {
return me.logs
}
func (me *tChatServer) isClosed() bool {
return me.closeFlag != 0
}
func (me *tChatServer) isNotClosed() bool {
return !me.isClosed()
}
func (me *tChatServer) beginListening() {
for !me.isClosed() {
conn, err := me.listener.Accept()
if err != nil {
me.Close()
break
}
me.handleIncomingConn(conn)
}
}
func (me *tChatServer) Close() {
if !atomic.CompareAndSwapInt32(&me.closeFlag, 0, 1) {
return
}
_ = me.listener.Close()
me.closeAllClients()
}
func (me *tChatServer) closeAllClients() {
me.clientLock.Lock()
defer me.clientLock.Unlock()
for i,it := range me.clients {
if it != nil {
it.Close()
me.clients[i] = nil
}
}
me.clientCount = 0
}
func (me *tChatServer) handleIncomingConn(conn net.Conn) {
// init client
client := openChatClient(conn, true)
client.RecvHandler(me.handleClientMsg)
client.CloseHandler(me.handleClientClosed)
// lock me.clients
me.clientLock.Lock()
defer me.clientLock.Unlock()
// append to me.clients
if len(me.clients) > me.clientCount {
me.clients[me.clientCount] = client
} else {
me.clients = append(me.clients, client)
}
me.clientCount++
me.logf("tChatServer.handleIncomingConn, clientCount=%v", me.clientCount)
}
func (me *tChatServer) handleClientMsg(client IChatClient, msg IMsg) {
me.recvLogs = append(me.recvLogs, msg)
if nameMsg,ok := msg.(*NameMsg);ok {
client.SetName(nameMsg.Name)
} else if _, ok := msg.(*ChatMsg);ok {
me.Broadcast(msg)
}
}
func (me *tChatServer) handleClientClosed(client IChatClient) {
me.logf("tChatServer.handleClientClosed, %s", client.GetName())
me.clientLock.Lock()
defer me.clientLock.Unlock()
if me.clientCount <= 0 {
return
}
lastI := me.clientCount - 1
for i,it := range me.clients {
if it == client {
if i == lastI {
me.clients[i] = nil
} else {
me.clients[i], me.clients[lastI] = me.clients[lastI], nil
}
me.clientCount--
break
}
}
me.logf("tChatServer.handleClientClosed, %s, clientCount=%v", client.GetName(), me.clientCount)
}
func (me *tChatServer) Broadcast(msg IMsg) {
me.clientLock.RLock()
defer me.clientLock.RUnlock()
for _,it := range me.clients {
if it != nil {
it.Send(msg)
}
}
}
(미완성 미계속)
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
set container
There is no built-in set container in Go
How to implement Set
struct{} => type
struct{}{} => 0bytes
How to create
set :=...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.
package chat_server
import (
"encoding/base64"
"fmt"
)
type IMsg interface {
Encode() string
}
type NameMsg struct {
Name string
}
func (me *NameMsg) Encode() string {
return fmt.Sprintf("NAME %s
", base64.StdEncoding.EncodeToString([]byte(me.Name)))
}
type ChatMsg struct {
Name string
Words string
}
func (me *ChatMsg) Encode() string {
return fmt.Sprintf("CHAT %s %s
",
base64.StdEncoding.EncodeToString([]byte(me.Name)),
base64.StdEncoding.EncodeToString([]byte(me.Words)),
)
}
메시지 디코더 및 그 실현 정의
package chat_server
import (
"encoding/base64"
"strings"
)
type IMsgDecoder interface {
Decode(line string) (bool, IMsg)
}
type tMsgDecoder struct {
}
func (me *tMsgDecoder) Decode(line string) (bool, IMsg) {
items := strings.Split(line, " ")
size := len(items)
if items[0] == "NAME" && size == 2 {
name, err := base64.StdEncoding.DecodeString(items[1])
if err != nil {
return false, nil
}
return true, &NameMsg{
Name: string(name),
}
}
if items[0] == "CHAT" && size == 3 {
name, err := base64.StdEncoding.DecodeString(items[1])
if err != nil {
return false, nil
}
words, err := base64.StdEncoding.DecodeString(items[2])
if err != nil {
return false, nil
}
return true, &ChatMsg{
Name: string(name),
Words: string(words),
}
}
return false, nil
}
var MsgDecoder = &tMsgDecoder{}
IChatClient.go
채팅 클라이언트 인터페이스를 정의합니다.이번에는 서버에 맞게 닫기 알림 방법을 추가합니다.package chat_server
type IChatClient interface {
GetName() string
SetName(name string)
Send(msg IMsg)
RecvHandler(handler ClientRecvFunc)
CloseHandler(handler ClientCloseFunc)
Close()
}
type ClientRecvFunc func(client IChatClient, msg IMsg)
type ClientCloseFunc func(client IChatClient)
tChatClient.go
채팅 클라이언트, IChatClient 인터페이스 구현.이번 추가 닫기 알림, 쓰기 버퍼, 읽기 시간 초과 제어, 쓰기 순환 세부 문제 해결.package chat_server
import (
"bufio"
"fmt"
"io"
"net"
"sync/atomic"
"time"
)
type tChatClient struct {
conn net.Conn
name string
openFlag int32
closeFlag int32
serverFlag bool
closeChan chan bool
sendChan chan IMsg
sendLogs []IMsg
dropLogs []IMsg
recvLogs []IMsg
pendingSend int32
recvHandler ClientRecvFunc
closeHandler ClientCloseFunc
}
var gMaxPendingSend int32 = 100
func DialChatClient(address string) (error, IChatClient) {
conn, err := net.Dial("tcp", address)
if err != nil {
return err, nil
}
return nil, openChatClient(conn, false)
}
func openChatClient(conn net.Conn, serverFlag bool) IChatClient {
it := &tChatClient{
conn: conn,
openFlag: 0,
closeFlag: 0,
serverFlag: serverFlag,
closeChan: make(chan bool),
sendChan: make(chan IMsg, gMaxPendingSend),
name: "anonymous",
sendLogs: []IMsg{},
dropLogs: []IMsg{},
recvLogs: []IMsg{},
}
it.open()
return it
}
func (me *tChatClient) GetName() string {
return me.name
}
func (me *tChatClient) SetName(name string) {
me.name = name
}
func (me *tChatClient) open(){
if !atomic.CompareAndSwapInt32(&me.openFlag, 0, 1) {
return
}
go me.beginWrite()
go me.beginRead()
}
func (me *tChatClient) isClosed() bool {
return me.closeFlag != 0
}
func (me *tChatClient) isNotClosed() bool {
return !me.isClosed()
}
func (me *tChatClient) Send(msg IMsg) {
if me.isClosed() {
return
}
if me.pendingSend < gMaxPendingSend {
atomic.AddInt32(&me.pendingSend, 1)
me.sendChan
IChatServer.go
채팅 서버 인터페이스를 정의하여 테스트를 편리하게 하고 로그 수집 방법을 제공합니다package chat_server
type IChatServer interface {
Open(port int) error
Broadcast(msg IMsg)
Close()
GetLogs() []string
}
tChatServer.go
채팅 서버 IChatServer 구현package chat_server
import (
"errors"
"fmt"
"net"
"sync"
"sync/atomic"
)
type tChatServer struct {
openFlag int32
closeFlag int32
clients []IChatClient
clientCount int
clientLock *sync.RWMutex
listener net.Listener
recvLogs []IMsg
logs []string
}
func NewChatServer() IChatServer {
it := &tChatServer{
openFlag: 0,
closeFlag: 0,
clients: []IChatClient{},
clientCount: 0,
clientLock: new(sync.RWMutex),
listener: nil,
recvLogs: []IMsg{},
}
return it
}
func (me *tChatServer) Open(port int) error {
if !atomic.CompareAndSwapInt32(&me.openFlag, 0, 1) {
return errors.New("server already opened")
}
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
if err != nil {
return err
}
me.listener = listener
go me.beginListening()
return nil
}
func (me *tChatServer) logf(f string, args... interface{}) {
msg := fmt.Sprintf(f, args...)
me.logs = append(me.logs, msg)
fmt.Println(msg)
}
func (me *tChatServer) GetLogs() []string {
return me.logs
}
func (me *tChatServer) isClosed() bool {
return me.closeFlag != 0
}
func (me *tChatServer) isNotClosed() bool {
return !me.isClosed()
}
func (me *tChatServer) beginListening() {
for !me.isClosed() {
conn, err := me.listener.Accept()
if err != nil {
me.Close()
break
}
me.handleIncomingConn(conn)
}
}
func (me *tChatServer) Close() {
if !atomic.CompareAndSwapInt32(&me.closeFlag, 0, 1) {
return
}
_ = me.listener.Close()
me.closeAllClients()
}
func (me *tChatServer) closeAllClients() {
me.clientLock.Lock()
defer me.clientLock.Unlock()
for i,it := range me.clients {
if it != nil {
it.Close()
me.clients[i] = nil
}
}
me.clientCount = 0
}
func (me *tChatServer) handleIncomingConn(conn net.Conn) {
// init client
client := openChatClient(conn, true)
client.RecvHandler(me.handleClientMsg)
client.CloseHandler(me.handleClientClosed)
// lock me.clients
me.clientLock.Lock()
defer me.clientLock.Unlock()
// append to me.clients
if len(me.clients) > me.clientCount {
me.clients[me.clientCount] = client
} else {
me.clients = append(me.clients, client)
}
me.clientCount++
me.logf("tChatServer.handleIncomingConn, clientCount=%v", me.clientCount)
}
func (me *tChatServer) handleClientMsg(client IChatClient, msg IMsg) {
me.recvLogs = append(me.recvLogs, msg)
if nameMsg,ok := msg.(*NameMsg);ok {
client.SetName(nameMsg.Name)
} else if _, ok := msg.(*ChatMsg);ok {
me.Broadcast(msg)
}
}
func (me *tChatServer) handleClientClosed(client IChatClient) {
me.logf("tChatServer.handleClientClosed, %s", client.GetName())
me.clientLock.Lock()
defer me.clientLock.Unlock()
if me.clientCount <= 0 {
return
}
lastI := me.clientCount - 1
for i,it := range me.clients {
if it == client {
if i == lastI {
me.clients[i] = nil
} else {
me.clients[i], me.clients[lastI] = me.clients[lastI], nil
}
me.clientCount--
break
}
}
me.logf("tChatServer.handleClientClosed, %s, clientCount=%v", client.GetName(), me.clientCount)
}
func (me *tChatServer) Broadcast(msg IMsg) {
me.clientLock.RLock()
defer me.clientLock.RUnlock()
for _,it := range me.clients {
if it != nil {
it.Send(msg)
}
}
}
(미완성 미계속)
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
set container
There is no built-in set container in Go
How to implement Set
struct{} => type
struct{}{} => 0bytes
How to create
set :=...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.
package chat_server
type IChatClient interface {
GetName() string
SetName(name string)
Send(msg IMsg)
RecvHandler(handler ClientRecvFunc)
CloseHandler(handler ClientCloseFunc)
Close()
}
type ClientRecvFunc func(client IChatClient, msg IMsg)
type ClientCloseFunc func(client IChatClient)
채팅 클라이언트, IChatClient 인터페이스 구현.이번 추가 닫기 알림, 쓰기 버퍼, 읽기 시간 초과 제어, 쓰기 순환 세부 문제 해결.
package chat_server
import (
"bufio"
"fmt"
"io"
"net"
"sync/atomic"
"time"
)
type tChatClient struct {
conn net.Conn
name string
openFlag int32
closeFlag int32
serverFlag bool
closeChan chan bool
sendChan chan IMsg
sendLogs []IMsg
dropLogs []IMsg
recvLogs []IMsg
pendingSend int32
recvHandler ClientRecvFunc
closeHandler ClientCloseFunc
}
var gMaxPendingSend int32 = 100
func DialChatClient(address string) (error, IChatClient) {
conn, err := net.Dial("tcp", address)
if err != nil {
return err, nil
}
return nil, openChatClient(conn, false)
}
func openChatClient(conn net.Conn, serverFlag bool) IChatClient {
it := &tChatClient{
conn: conn,
openFlag: 0,
closeFlag: 0,
serverFlag: serverFlag,
closeChan: make(chan bool),
sendChan: make(chan IMsg, gMaxPendingSend),
name: "anonymous",
sendLogs: []IMsg{},
dropLogs: []IMsg{},
recvLogs: []IMsg{},
}
it.open()
return it
}
func (me *tChatClient) GetName() string {
return me.name
}
func (me *tChatClient) SetName(name string) {
me.name = name
}
func (me *tChatClient) open(){
if !atomic.CompareAndSwapInt32(&me.openFlag, 0, 1) {
return
}
go me.beginWrite()
go me.beginRead()
}
func (me *tChatClient) isClosed() bool {
return me.closeFlag != 0
}
func (me *tChatClient) isNotClosed() bool {
return !me.isClosed()
}
func (me *tChatClient) Send(msg IMsg) {
if me.isClosed() {
return
}
if me.pendingSend < gMaxPendingSend {
atomic.AddInt32(&me.pendingSend, 1)
me.sendChan
IChatServer.go
채팅 서버 인터페이스를 정의하여 테스트를 편리하게 하고 로그 수집 방법을 제공합니다package chat_server
type IChatServer interface {
Open(port int) error
Broadcast(msg IMsg)
Close()
GetLogs() []string
}
tChatServer.go
채팅 서버 IChatServer 구현package chat_server
import (
"errors"
"fmt"
"net"
"sync"
"sync/atomic"
)
type tChatServer struct {
openFlag int32
closeFlag int32
clients []IChatClient
clientCount int
clientLock *sync.RWMutex
listener net.Listener
recvLogs []IMsg
logs []string
}
func NewChatServer() IChatServer {
it := &tChatServer{
openFlag: 0,
closeFlag: 0,
clients: []IChatClient{},
clientCount: 0,
clientLock: new(sync.RWMutex),
listener: nil,
recvLogs: []IMsg{},
}
return it
}
func (me *tChatServer) Open(port int) error {
if !atomic.CompareAndSwapInt32(&me.openFlag, 0, 1) {
return errors.New("server already opened")
}
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
if err != nil {
return err
}
me.listener = listener
go me.beginListening()
return nil
}
func (me *tChatServer) logf(f string, args... interface{}) {
msg := fmt.Sprintf(f, args...)
me.logs = append(me.logs, msg)
fmt.Println(msg)
}
func (me *tChatServer) GetLogs() []string {
return me.logs
}
func (me *tChatServer) isClosed() bool {
return me.closeFlag != 0
}
func (me *tChatServer) isNotClosed() bool {
return !me.isClosed()
}
func (me *tChatServer) beginListening() {
for !me.isClosed() {
conn, err := me.listener.Accept()
if err != nil {
me.Close()
break
}
me.handleIncomingConn(conn)
}
}
func (me *tChatServer) Close() {
if !atomic.CompareAndSwapInt32(&me.closeFlag, 0, 1) {
return
}
_ = me.listener.Close()
me.closeAllClients()
}
func (me *tChatServer) closeAllClients() {
me.clientLock.Lock()
defer me.clientLock.Unlock()
for i,it := range me.clients {
if it != nil {
it.Close()
me.clients[i] = nil
}
}
me.clientCount = 0
}
func (me *tChatServer) handleIncomingConn(conn net.Conn) {
// init client
client := openChatClient(conn, true)
client.RecvHandler(me.handleClientMsg)
client.CloseHandler(me.handleClientClosed)
// lock me.clients
me.clientLock.Lock()
defer me.clientLock.Unlock()
// append to me.clients
if len(me.clients) > me.clientCount {
me.clients[me.clientCount] = client
} else {
me.clients = append(me.clients, client)
}
me.clientCount++
me.logf("tChatServer.handleIncomingConn, clientCount=%v", me.clientCount)
}
func (me *tChatServer) handleClientMsg(client IChatClient, msg IMsg) {
me.recvLogs = append(me.recvLogs, msg)
if nameMsg,ok := msg.(*NameMsg);ok {
client.SetName(nameMsg.Name)
} else if _, ok := msg.(*ChatMsg);ok {
me.Broadcast(msg)
}
}
func (me *tChatServer) handleClientClosed(client IChatClient) {
me.logf("tChatServer.handleClientClosed, %s", client.GetName())
me.clientLock.Lock()
defer me.clientLock.Unlock()
if me.clientCount <= 0 {
return
}
lastI := me.clientCount - 1
for i,it := range me.clients {
if it == client {
if i == lastI {
me.clients[i] = nil
} else {
me.clients[i], me.clients[lastI] = me.clients[lastI], nil
}
me.clientCount--
break
}
}
me.logf("tChatServer.handleClientClosed, %s, clientCount=%v", client.GetName(), me.clientCount)
}
func (me *tChatServer) Broadcast(msg IMsg) {
me.clientLock.RLock()
defer me.clientLock.RUnlock()
for _,it := range me.clients {
if it != nil {
it.Send(msg)
}
}
}
(미완성 미계속)
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
set container
There is no built-in set container in Go
How to implement Set
struct{} => type
struct{}{} => 0bytes
How to create
set :=...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.
package chat_server
type IChatServer interface {
Open(port int) error
Broadcast(msg IMsg)
Close()
GetLogs() []string
}
채팅 서버 IChatServer 구현
package chat_server
import (
"errors"
"fmt"
"net"
"sync"
"sync/atomic"
)
type tChatServer struct {
openFlag int32
closeFlag int32
clients []IChatClient
clientCount int
clientLock *sync.RWMutex
listener net.Listener
recvLogs []IMsg
logs []string
}
func NewChatServer() IChatServer {
it := &tChatServer{
openFlag: 0,
closeFlag: 0,
clients: []IChatClient{},
clientCount: 0,
clientLock: new(sync.RWMutex),
listener: nil,
recvLogs: []IMsg{},
}
return it
}
func (me *tChatServer) Open(port int) error {
if !atomic.CompareAndSwapInt32(&me.openFlag, 0, 1) {
return errors.New("server already opened")
}
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
if err != nil {
return err
}
me.listener = listener
go me.beginListening()
return nil
}
func (me *tChatServer) logf(f string, args... interface{}) {
msg := fmt.Sprintf(f, args...)
me.logs = append(me.logs, msg)
fmt.Println(msg)
}
func (me *tChatServer) GetLogs() []string {
return me.logs
}
func (me *tChatServer) isClosed() bool {
return me.closeFlag != 0
}
func (me *tChatServer) isNotClosed() bool {
return !me.isClosed()
}
func (me *tChatServer) beginListening() {
for !me.isClosed() {
conn, err := me.listener.Accept()
if err != nil {
me.Close()
break
}
me.handleIncomingConn(conn)
}
}
func (me *tChatServer) Close() {
if !atomic.CompareAndSwapInt32(&me.closeFlag, 0, 1) {
return
}
_ = me.listener.Close()
me.closeAllClients()
}
func (me *tChatServer) closeAllClients() {
me.clientLock.Lock()
defer me.clientLock.Unlock()
for i,it := range me.clients {
if it != nil {
it.Close()
me.clients[i] = nil
}
}
me.clientCount = 0
}
func (me *tChatServer) handleIncomingConn(conn net.Conn) {
// init client
client := openChatClient(conn, true)
client.RecvHandler(me.handleClientMsg)
client.CloseHandler(me.handleClientClosed)
// lock me.clients
me.clientLock.Lock()
defer me.clientLock.Unlock()
// append to me.clients
if len(me.clients) > me.clientCount {
me.clients[me.clientCount] = client
} else {
me.clients = append(me.clients, client)
}
me.clientCount++
me.logf("tChatServer.handleIncomingConn, clientCount=%v", me.clientCount)
}
func (me *tChatServer) handleClientMsg(client IChatClient, msg IMsg) {
me.recvLogs = append(me.recvLogs, msg)
if nameMsg,ok := msg.(*NameMsg);ok {
client.SetName(nameMsg.Name)
} else if _, ok := msg.(*ChatMsg);ok {
me.Broadcast(msg)
}
}
func (me *tChatServer) handleClientClosed(client IChatClient) {
me.logf("tChatServer.handleClientClosed, %s", client.GetName())
me.clientLock.Lock()
defer me.clientLock.Unlock()
if me.clientCount <= 0 {
return
}
lastI := me.clientCount - 1
for i,it := range me.clients {
if it == client {
if i == lastI {
me.clients[i] = nil
} else {
me.clients[i], me.clients[lastI] = me.clients[lastI], nil
}
me.clientCount--
break
}
}
me.logf("tChatServer.handleClientClosed, %s, clientCount=%v", client.GetName(), me.clientCount)
}
func (me *tChatServer) Broadcast(msg IMsg) {
me.clientLock.RLock()
defer me.clientLock.RUnlock()
for _,it := range me.clients {
if it != nil {
it.Send(msg)
}
}
}
(미완성 미계속)
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
set containerThere is no built-in set container in Go How to implement Set struct{} => type struct{}{} => 0bytes How to create set :=...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.