X-Git-Url: http://www.git.cypherpunks.ru/?p=goircd.git;a=blobdiff_plain;f=client.go;h=640a58a5cae90a356944af8a1e2bc64e33340c96;hp=07a209b61d0a524653e56714bfa7fe81b10e4ebf;hb=9f6cca6c56d12478afb026ca4bd4ec3a688d6c9b;hpb=def74ddc09249de9ab72001bb00550863d7f27e6 diff --git a/client.go b/client.go index 07a209b..640a58a 100644 --- a/client.go +++ b/client.go @@ -1,6 +1,6 @@ /* goircd -- minimalistic simple Internet Relay Chat (IRC) server -Copyright (C) 2014 Sergey Matveev +Copyright (C) 2014-2016 Sergey Matveev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,6 +15,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ + package main import ( @@ -22,103 +23,172 @@ import ( "log" "net" "strings" + "sync" "time" ) const ( - CRLF = "\x0d\x0a" - BUF_SIZE = 1380 + BufSize = 1500 + MaxOutBuf = 1 << 12 +) + +var ( + CRLF []byte = []byte{'\x0d', '\x0a'} ) type Client struct { - hostname string - conn net.Conn - registered bool - nickname string - username string - realname string + conn net.Conn + registered bool + nickname *string + username *string + realname *string + password *string + away *string + recvTimestamp time.Time + sendTimestamp time.Time + outBuf chan *string + alive bool + sync.Mutex +} + +func (c *Client) Host() string { + addr := c.conn.RemoteAddr().String() + if host, _, err := net.SplitHostPort(addr); err == nil { + addr = host + } + if domains, err := net.LookupAddr(addr); err == nil { + addr = strings.TrimSuffix(domains[0], ".") + } + return addr } -type ClientAlivenessState struct { - ping_sent bool - timestamp time.Time +func (c *Client) String() string { + return *c.nickname + "!" + *c.username + "@" + c.Host() +} + +func NewClient(conn net.Conn) *Client { + nickname := "*" + username := "" + c := Client{ + conn: conn, + nickname: &nickname, + username: &username, + recvTimestamp: time.Now(), + sendTimestamp: time.Now(), + alive: true, + outBuf: make(chan *string, MaxOutBuf), + } + go c.MsgSender() + return &c } -func (client Client) String() string { - return client.nickname + "!" + client.username + "@" + client.conn.RemoteAddr().String() +func (c *Client) SetDead() { + c.outBuf <- nil + c.alive = false } -func NewClient(hostname string, conn net.Conn) *Client { - return &Client{hostname: hostname, conn: conn, nickname: "*"} +func (c *Client) Close() { + c.Lock() + if c.alive { + c.SetDead() + } + c.Unlock() } // Client processor blockingly reads everything remote client sends, // splits messages by CRLF and send them to Daemon gorouting for processing // it futher. Also it can signalize that client is unavailable (disconnected). -func (client *Client) Processor(sink chan<- ClientEvent) { - var buf_net []byte - buf := make([]byte, 0) - log.Println(client, "New client") - sink <- ClientEvent{client, EVENT_NEW, ""} +func (c *Client) Processor(sink chan ClientEvent) { + sink <- ClientEvent{c, EventNew, ""} + log.Println(c, "New client") + buf := make([]byte, BufSize*2) + var n int + var prev int + var i int + var err error for { - buf_net = make([]byte, BUF_SIZE) - _, err := client.conn.Read(buf_net) + if prev == BufSize { + log.Println(c, "input buffer size exceeded, kicking him") + break + } + n, err = c.conn.Read(buf[prev:]) if err != nil { - log.Println(client, "connection lost", err) - sink <- ClientEvent{client, EVENT_DEL, ""} break } - buf_net = bytes.TrimRight(buf_net, "\x00") - buf = append(buf, buf_net...) - if !bytes.HasSuffix(buf, []byte(CRLF)) { + prev += n + CheckMore: + i = bytes.Index(buf[:prev], CRLF) + if i == -1 { continue } - for _, msg := range bytes.Split(buf[:len(buf)-2], []byte(CRLF)) { - if len(msg) > 0 { - sink <- ClientEvent{client, EVENT_MSG, string(msg)} - } + sink <- ClientEvent{c, EventMsg, string(buf[:i])} + copy(buf, buf[i+2:prev]) + prev -= (i + 2) + goto CheckMore + } + c.Close() + sink <- ClientEvent{c, EventDel, ""} +} + +func (c *Client) MsgSender() { + for msg := range c.outBuf { + if msg == nil { + c.conn.Close() + break } - buf = []byte{} + c.conn.Write(append([]byte(*msg), CRLF...)) } } // Send message as is with CRLF appended. -func (client *Client) Msg(text string) { - client.conn.Write([]byte(text + CRLF)) +func (c *Client) Msg(text string) { + c.Lock() + defer c.Unlock() + if !c.alive { + return + } + if len(c.outBuf) == MaxOutBuf { + log.Println(c, "output buffer size exceeded, kicking him") + if c.alive { + c.SetDead() + } + return + } + c.outBuf <- &text } // Send message from server. It has ": servername" prefix. -func (client *Client) Reply(text string) { - client.Msg(":" + client.hostname + " " + text) +func (c *Client) Reply(text string) { + c.Msg(":" + *hostname + " " + text) } // Send server message, concatenating all provided text parts and // prefix the last one with ":". -func (client *Client) ReplyParts(code string, text ...string) { +func (c *Client) ReplyParts(code string, text ...string) { parts := []string{code} for _, t := range text { parts = append(parts, t) } parts[len(parts)-1] = ":" + parts[len(parts)-1] - client.Reply(strings.Join(parts, " ")) + c.Reply(strings.Join(parts, " ")) } // Send nicknamed server message. After servername it always has target // client's nickname. The last part is prefixed with ":". -func (client *Client) ReplyNicknamed(code string, text ...string) { - client.ReplyParts(code, append([]string{client.nickname}, text...)...) +func (c *Client) ReplyNicknamed(code string, text ...string) { + c.ReplyParts(code, append([]string{*c.nickname}, text...)...) } // Reply "461 not enough parameters" error for given command. -func (client *Client) ReplyNotEnoughParameters(command string) { - client.ReplyNicknamed("461", command, "Not enough parameters") +func (c *Client) ReplyNotEnoughParameters(command string) { + c.ReplyNicknamed("461", command, "Not enough parameters") } // Reply "403 no such channel" error for specified channel. -func (client *Client) ReplyNoChannel(channel string) { - client.ReplyNicknamed("403", channel, "No such channel") +func (c *Client) ReplyNoChannel(channel string) { + c.ReplyNicknamed("403", channel, "No such channel") } -func (client *Client) ReplyNoNickChan(channel string) { - client.ReplyNicknamed("401", channel, "No such nick/channel") +func (c *Client) ReplyNoNickChan(channel string) { + c.ReplyNicknamed("401", channel, "No such nick/channel") }