在go中實(shí)現(xiàn)一個(gè)tcp服務(wù)器還是很簡(jiǎn)單的,至少和C/C++相比還是很簡(jiǎn)單的了。
一個(gè)簡(jiǎn)單的例子
listen, err := net.Listen("tcp", "0.0.0.0:8088")
只需要這樣一行就可以監(jiān)聽(tīng)了,就能等待客戶端連接了。是不是還是很簡(jiǎn)單的
在C/C++中,需要依次 調(diào)用socket() bind() listen() accept()函數(shù),完成打開(kāi),綁定,監(jiān)聽(tīng),等待操作,才能完成等待客戶端來(lái)連接。
這還沒(méi)完,想要提高性能還需要自己通過(guò) epoll等手段完成多路復(fù)用。
其實(shí)在C/C++中是通過(guò)調(diào)用系統(tǒng)函數(shù)來(lái)完成的,只是go把這部分東西都給包裝了,只需要簡(jiǎn)單的一行就可以完成了。
其實(shí)在go中也可以通過(guò)系統(tǒng)函數(shù)自己來(lái)完成些事情。只是這些事情比較復(fù)雜,跨平臺(tái)還不好弄,像使用了epoll就只能在linux系統(tǒng)上編譯運(yùn)行了。
廢話不多說(shuō)了,直接上函數(shù)吧。在go中和C/C++中區(qū)別不大,同樣是通過(guò)系統(tǒng)調(diào)用這些函數(shù)來(lái)完成。
func Socket(domain, typ, proto int) (fd int, err error)
func Bind(fd int, sa Sockaddr) (err error)
func Listen(s int, n int) (err error)
func Accept(fd int) (nfd int, sa Sockaddr, err error)
在go開(kāi)啟tcp 服務(wù)需要用到的函數(shù).
func EpollCreate(size int) (fd int, err error)
func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error)
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
在go中使用epoll需要的函數(shù)
這些函數(shù)都是在syscall 包下,所以這些函數(shù)不是所有系統(tǒng)下都有的,
像epoll 相關(guān)的函數(shù),就只能在linux下才能編譯過(guò)。
所以,在這里就引申出一個(gè)東西就是 條件編譯
在C/C++中可以通過(guò)宏定義來(lái)實(shí)現(xiàn)條件編譯
#ifdef linux
cout<<"It is in Linux OS!"<<endl;
#endif
這段代碼就只能在linux系統(tǒng)上才會(huì)被編譯
go雖然沒(méi)有這么強(qiáng)大的宏命令來(lái)判斷,但是go中可以通過(guò)編譯標(biāo)簽 和 文件后綴來(lái)判斷。
比如在文件的第一行加上
// +build linux
.....
.....
這樣,這個(gè)文件就只能在linux上才會(huì)被編譯(注意,// +build linux下面一定要有一個(gè)空行)
詳細(xì)用法看這里 go條件編譯
下面開(kāi)始具體的代碼
//定義一個(gè)結(jié)構(gòu)體存儲(chǔ)相關(guān)的數(shù)據(jù)
type EpollM struct {
conn map[int]*ServerConn
socketFd int //監(jiān)聽(tīng)socket的fd
epollFd int //epoll的fd
}
//開(kāi)啟監(jiān)聽(tīng)
func (e *EpollM) Listen(ipAddr string, port int) error {
//使用系統(tǒng)調(diào)用,打開(kāi)一個(gè)socket
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
if err != nil {
return err
}
//ip地址轉(zhuǎn)換
var addr [4]byte
copy(addr[:], net.ParseIP(ipAddr).To4())
net.ParseIP(ipAddr).To4()
err = syscall.Bind(fd, &syscall.SockaddrInet4{
Port: port,
Addr: addr,
})
if err != nil {
return err
}
//開(kāi)啟監(jiān)聽(tīng)
err = syscall.Listen(fd, 10)
if err != nil {
return err
}
e.socketFd = fd
return nil
}
這樣就完成了監(jiān)聽(tīng)
下面是 epoll 處理部分
//處理epoll
func (e *EpollM) HandlerEpoll() error {
events := make([]syscall.EpollEvent, 100)
//在死循環(huán)中處理epoll
for {
//msec -1,會(huì)一直阻塞,直到有事件可以處理才會(huì)返回, n 事件個(gè)數(shù)
n, err := syscall.EpollWait(e.epollFd, events, -1)
if err != nil {
return err
}
for i := 0; i < n; i++ {
//先在map中是否有這個(gè)鏈接
conn := e.GetConn(int(events[i].Fd))
if conn == nil { //沒(méi)有這個(gè)鏈接,忽略
continue
}
if events[i].Events&syscall.EPOLLHUP == syscall.EPOLLHUP || events[i].Events&syscall.EPOLLERR == syscall.EPOLLERR {
//斷開(kāi)||出錯(cuò)
if err := e.CloseConn(int(events[i].Fd)); err != nil {
return err
}
} else if events[i].Events == syscall.EPOLLIN {
//可讀事件
conn.Read()
}
}
}
}
從連接中讀寫數(shù)據(jù)
//讀取數(shù)據(jù)
func (s *ServerConn) Read() {
data := make([]byte, 100)
//通過(guò)系統(tǒng)調(diào)用,讀取數(shù)據(jù),n是讀到的長(zhǎng)度
n, err := syscall.Read(s.fd, data)
if n == 0 {
return
}
if err != nil {
fmt.Printf("fd %d read error:%s\n", s.fd, err.Error())
} else {
fmt.Printf("%d say: %s \n", s.fd, data[:n])
s.Write([]byte(fmt.Sprintf("hello %d", s.fd)))
}
}
//向這個(gè)鏈接中寫數(shù)據(jù)
func (s *ServerConn) Write(data []byte) {
_, err := syscall.Write(s.fd, data)
if err != nil {
fmt.Printf("fd %d write error:%s\n", s.fd, err.Error())
}
}
最后在依次調(diào)用這些函數(shù)
package main
func main() {
epollM := NewEpollM()
//開(kāi)啟監(jiān)聽(tīng)
err := epollM.Listen("0.0.0.0", 8088)
if err != nil {
panic(err)
}
//創(chuàng)建epoll
err = epollM.CreateEpoll()
if err != nil {
panic(err)
}
//異步處理epoll
go func() {
err := epollM.HandlerEpoll()
epollM.Close()
panic(err)
}()
//等待client的連接
err = epollM.Accept()
epollM.Close()
panic(err)
}
到這整個(gè)服務(wù)就運(yùn)行起來(lái)了,代碼已經(jīng)上傳到github了,傳送門
整個(gè)過(guò)程看下來(lái),和C/C++實(shí)現(xiàn)過(guò)程還是非常相似的,都是通過(guò)系統(tǒng)調(diào)用完成的。也沒(méi)有什么難點(diǎn),就這樣吧