Chat Chat Chat

承认吧,你肯定也会有深夜寂寞时特别想找人聊天但翻遍朋友圈,却还是没有发现合适的人的时候,今天我们就来写一个随机配对聊天程序,解决广大单身狗寂寞时想要找人聊天的需求吧。

为了让聊天不那么 gay ,我们的聊天程序会让异性匹配聊天,用户通过客户端连接到聊天程序服务器 localhost:8080 ,例如使用 netcat 通过 nc localhost 8080 命令连接聊天程序服务器,服务器会根据你的性别匹配异性聊天。

在实现方面,为了达到异性相互聊天的目标,我们创建两个通道,分别是 boy 通道和 girl 通道,并通过 net.Listen 方法监听 8080 端口,对于每个连接我们起一个 goroutine 进行匹配操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var boy = make(chan io.ReadWriteCloser)
var girl = make(chan io.ReadWriteCloser)

func main() {
l, err := net.Listen("tcp", "localhost:8080")
if err != nil {
panic(err)
}
for {
c, err := l.Accept()
if err != nil {
log.Println(err)
}
go match(c)
}
}

match 方法会首先询问你的性别,如果是男生则匹配女生聊天,反之亦然。如果当前只有一个男生连进来,程序会执行第一个 select 中的 case boy <- c: 条件,由于 boy 通道是 unbuffered channel ,直到有女生进来之前该操作都会阻塞。如果此时进来一个女生,程序会执行第二个select 操作,由于 girl 通道也是 unbuffered channel ,所以case girl <- c: 语句也会阻塞,但此时 case p := <-boy: 会被执行,这时候就可以通过 chat 方法让两个 net.Conn 相互交流了。match 方法实现的逻辑就达到了异性交流的目的了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func match(c io.ReadWriteCloser) {
fmt.Fprint(c, "Are you a boy or a girl? ")
var sex string
fmt.Fscanln(c, &sex)
if strings.HasPrefix("boy", sex) {
select {
case boy <- c:
case p := <-girl:
chat(p, c)
}
} else if strings.HasPrefix("girl", sex) {
select {
case girl <- c:
case p := <-boy:
chat(p, c)
}
}
}

上面的 chat 方法依赖 cp 方法 ,该方法封装了 io.Copy 方法,主要就是为了当任意一方结束聊天时发送一个 err 到我们的 errc ,以关闭双方的连接。

1
2
3
4
func cp(w io.Writer, r io.Reader, errc chan<- error) {
_, err := io.Copy(w, r)
errc <- err
}

chat 方法通过两个 goroutinego cp(a, b, errc)go cp(b, a, errc) 使a和b这两个 net.Conn 相互发送消息,如果任意一方结束聊天, errc 通道就会返回一个 error 值,a和b就会被关闭,此时聊天结束。

1
2
3
4
5
6
7
8
9
10
11
12
func chat(a, b io.ReadWriteCloser) {
fmt.Fprintln(a, "Found one! say hi")
fmt.Fprintln(b, "Found one! say hi")
errc := make(chan error, 1)
go cp(a, b, errc)
go cp(b, a, errc)
if err := <- errc; err != nil {
log.Println(err)
}
a.Close()
b.Close()
}
Pieces of Valuable Programming Knowledges