我尝试用Golang编写了一个通过Unix域套接字进行通信的回显服务器
我想要一个可以在ssh-agent中保存密钥的程序,为了练习,我首先写了一个echo服务器。我选择使用golang的原因如下。
-
- bashからではunix domain socketを直接扱えないようなので諦めた
-
- OSX, Linuxで動いて欲しい
- クロスコンパイルが簡単(らしい)
因为昨天是我第一次写golang,所以非常欢迎大家提供意见,相反,我更希望能够得到各种指正。
创建UNIX域套接字
因为类似于ssh-agent的情况,我们会在启动时自动配置套接字文件,而不是从外部指定。在寻找类似于mktemp -d的功能时,我发现了ioutil.TempDir函数并进行了使用。另外,为了防止被非执行者访问,我们还修改了访问权限。
main() {
tempDir, err := ioutil.TempDir("", "golang-sample-echo-server.")
pid := strconv.Itoa(os.Getpid())
socket := tempDir + "/server." + pid
listener, err := net.Listen("unix", socket)
if err != nil {
log.Printf("error: %v\n", err)
return
}
if err := os.Chmod(socket, 0700); err != nil {
log.Printf("error: %v\n", err)
return
}
}
处理内容
-
- 使用ioutil.TempDir()创建临时目录
-
- 获取pid并在创建的临时目录中创建socket文件
- 将socket文件的权限限制为仅所有者可访问
等待接收
我在主函数中定义了一个函数来接收在上面创建的监听器,并进行调用。
我的代码是参考了《在Go中使用Unix套接字》(堆栈溢出)的方法。
func server(listener net.Listener) {
for {
fd, err := listener.Accept()
if err != nil {
return
}
go process(fd)
}
}
func process(fd net.Conn) {
for {
buf := make([]byte, 512)
nr, err := fd.Read(buf)
if err != nil {
break
}
data := buf[0:nr]
fmt.Printf("Recieved: %v", string(data));
_, err = fd.Write(data)
if err != nil {
log.Printf("error: %v\n", err)
break
}
}
fd.Close()
}
处理内容
-
- listener.Accept()用于等待输入。
-
- 当有输入时,调用process()函数,同时读取到固定大小的缓冲区并进行回显。
-
- 当没有可读取的数据时,关闭fd并结束。
-
- 由于在goroutine中调用process()函数,因此不需要等待处理返回结果就可以再次通过listener.Accept()等待输入。
- 如果listener被关闭,listener.Accept()将返回错误,从而退出for循环。
結束時的清理
因为在这个过程中会留下一些垃圾,所以我们会适时进行清理。
我参考了使用Golang编写的Web应用程序通过UNIX域套接字进行公开的代码。
func shutdown(listener net.Listener, tempDir string, close chan int) {
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
interrupt := 0
for {
s := <-c
switch s {
case os.Interrupt:
if (interrupt == 0) {
fmt.Println("Interrupt...")
interrupt++
continue
}
}
break
}
if err := listener.Close(); err != nil {
log.Printf("error: %v\n", err)
}
if err := os.Remove(tempDir); err != nil {
log.Printf("error: %v\n", err)
}
close <- 1
}()
}
进行的操作
-
- 创建一个用于等待信号的频道
-
- 在goroutine中调用处理函数以响应信号的发生,并将频道设置为等待通知状态
-
- 在信号发生时,goroutine会重新启动注册的处理过程
-
- 如果是Ctrl+C,则设置为第一次忽略
-
- 调用listener.Close()和删除目录
- 在调用者给定的频道上通知退出
最后的主函数
可构建的代码已经被推送到github上。
func main() {
log.SetFlags(log.Lshortfile)
tempDir, err := ioutil.TempDir("", "golang-sample-echo-server.")
pid := strconv.Itoa(os.Getpid())
socket := tempDir + "/server." + pid
listener, err := net.Listen("unix", socket)
if err != nil {
log.Printf("error: %v\n", err)
return
}
if err := os.Chmod(socket, 0700); err != nil {
log.Printf("error: %v\n", err)
return
}
close := make(chan int)
shutdown(listener, tempDir, close)
fmt.Printf("GOLANG_SAMPLE_SOCK=%v;export GOLANG_SAMPLE_SOCK;\n", socket)
fmt.Printf("GOLANG_SAMPLE_PID=%v;export GOLANG_SAMPLE_PID;\n", pid)
server(listener)
_ = <-close
}
处理内容
从创建Unix域套接字开始的继续。
-
- 创建一个用于传递给shutdown()函数的channel,调用shutdown函数
-
- 输出用于客户端连接的socket和pid
-
- 将创建的listener传递给server()函数并启动
-
- 等待shutdown()函数的结束
执行两次Ctrl+C将调用shutdown()函数中的listener.Close()
listener.Close()导致server()函数中的listener.Accept()返回错误
接收listener.Accept()的错误导致server()函数的循环终止
等待shutdown的channel结束
确认动作
$ golang/bin/golang-sample-echo-server
GOLANG_SAMPLE_SOCK=/var/folders/ld/92yggdj91l72jq205zr0l1540000gn/T/golang-sample-echo-server.660221887/server.61435;export GOLANG_SAMPLE_SOCK;
GOLANG_SAMPLE_PID=61435;export GOLANG_SAMPLE_PID;
Recieved: sample echo server test
$ echo 'sample echo server test' | nc -U /var/folders/ld/92yggdj91l72jq205zr0l1540000gn/T/golang-sample-echo-server.660221887/server.61435
sample echo server test
剩余任务
-
- コマンドラインオプションの対応を追加
-
- 自らdaemon化するように処理を追加
- 自らを停止するオプション・処理の追加
给您的附赠品
将日志简化易懂
在出现错误时,很难确定日志来自哪一行,让我感到非常困扰。
通过添加以下行到main函数的第一行,把格式改为[文件名]:[行号]: [消息],真是太感激了。
log.SetFlags(log.Lshortfile)
追加于2016年10月12日
因为有一些同事的指出,我做出了相应的处理。
fd.Close() 的操作已被更改为 defer。
只是将末尾的fd.Close()移到了开头,并加上了defer。
func process(fd net.Conn) {
defer fd.Close()
for {
buf := make([]byte, 512)
nr, err := fd.Read(buf)
if err != nil {
break
}
data := buf[0:nr]
fmt.Printf("Recieved: %v", string(data));
_, err = fd.Write(data)
if err != nil {
log.Printf("error: %v\n", err)
break
}
}
}
将server转换成结构体。
- サーバーがListenerの管理をする為に、main関数内でやっていたことの一部をサーバー側に移動しています。
package main
import (
"net"
"log"
"os"
"fmt"
)
type Server struct {
listener net.Listener
}
func NewServer() *Server{
s := new(Server)
return s;
}
func (s *Server) Open(socket string) {
listener, err := net.Listen("unix", socket)
if err != nil {
log.Printf("error: %v\n", err)
return
}
s.listener = listener;
if err := os.Chmod(socket, 0700); err != nil {
log.Printf("error: %v\n", err)
s.Close()
return
}
}
func (s *Server) Close() {
if err := s.listener.Close(); err != nil {
log.Printf("error: %v\n", err)
}
}
func (s *Server) Start() {
for {
fd, err := s.listener.Accept()
if err != nil {
return
}
go s.Process(fd)
}
}
func (s *Server) Process(fd net.Conn) {
defer fd.Close()
for {
buf := make([]byte, 512)
nr, err := fd.Read(buf)
if err != nil {
break
}
data := buf[0:nr]
fmt.Printf("Recieved: %v", string(data));
_, err = fd.Write(data)
if err != nil {
log.Printf("error: %v\n", err)
break
}
}
}
package main
import (
"os"
"os/signal"
"io/ioutil"
"fmt"
"syscall"
"strconv"
"log"
)
func main() {
log.SetFlags(log.Lshortfile)
tempDir, err := ioutil.TempDir("", "golang-sample-echo-server.")
if err != nil {
log.Printf("error: %v\n", err)
return
}
pid := strconv.Itoa(os.Getpid())
socket := tempDir + "/server." + pid
if err := os.Chmod(tempDir, 0700); err != nil {
log.Printf("error: %v\n", err)
return
}
defer func() {
if err := os.Remove(tempDir); err != nil {
log.Printf("error: %v\n", err)
}
}()
server := NewServer()
server.Open(socket)
registerShutdown(server)
fmt.Printf("GOLANG_SAMPLE_SOCK=%v;export GOLANG_SAMPLE_SOCK;\n", socket)
fmt.Printf("GOLANG_SAMPLE_PID=%v;export GOLANG_SAMPLE_PID;\n", pid)
server.Start()
}
func registerShutdown(server *Server) {
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
interrupt := 0
for {
s := <-c
switch s {
case os.Interrupt:
if (interrupt == 0) {
fmt.Println("Interrupt...")
interrupt++
continue
}
}
break
}
server.Close()
}()
}
追加说明于2016年10月13日
由于没有运动,所以在2016/10/14进行了微调。
因为还提到了错误处理,所以已经进行了相应的处理。
因为只有差异更容易理解,所以只提供差异。
服务器在出现错误时返回错误,实际的错误处理在主函数内进行。
diff --git a/main.go b/main.go
index 6478944..92dbdbf 100644
--- a/main.go
+++ b/main.go
@@ -29,11 +29,14 @@ func main() {
}
}()
server := NewServer()
- server.Open(socket)
+ if err := server.Open(socket); err != nil {
+ log.Printf("error: %v\n", err)
+ return;
+ }
registerShutdown(server)
fmt.Printf("GOLANG_SAMPLE_SOCK=%v;export GOLANG_SAMPLE_SOCK;\n", socket)
fmt.Printf("GOLANG_SAMPLE_PID=%v;export GOLANG_SAMPLE_PID;\n", pid)
- server.Start()
+ server.Start();
}
func registerShutdown(server *Server) {
@@ -53,6 +56,8 @@ func registerShutdown(server *Server) {
}
break
}
- server.Close()
+ if err := server.Close(); err != nil {
+ log.Printf("error: %v\n", err)
+ }
}()
}
diff --git a/server.go b/server.go
index 64a03af..3f40115 100644
--- a/server.go
+++ b/server.go
@@ -2,7 +2,6 @@ package main
import (
"net"
- "log"
"os"
"fmt"
)
@@ -16,37 +15,37 @@ func NewServer() *Server{
return s;
}
-func (s *Server) Open(socket string) {
+func (s *Server) Open(socket string) error {
listener, err := net.Listen("unix", socket)
if err != nil {
- log.Printf("error: %v\n", err)
- return
+ return err
}
s.listener = listener;
if err := os.Chmod(socket, 0600); err != nil {
- log.Printf("error: %v\n", err)
s.Close()
- return
+ return err
}
+ return nil
}
-func (s *Server) Close() {
+func (s *Server) Close() error{
if err := s.listener.Close(); err != nil {
- log.Printf("error: %v\n", err)
+ return err;
}
+ return nil
}
func (s *Server) Start() {
for {
fd, err := s.listener.Accept()
if err != nil {
- return
+ break;
}
go s.Process(fd)
}
}
-func (s *Server) Process(fd net.Conn) {
+func (s *Server) Process(fd net.Conn) error{
defer fd.Close()
for {
buf := make([]byte, 512)
@@ -58,8 +57,8 @@ func (s *Server) Process(fd net.Conn) {
fmt.Printf("Recieved: %v", string(data));
_, err = fd.Write(data)
if err != nil {
- log.Printf("error: %v\n", err)
- break
+ return err
}
}
+ return nil
我使用了参考的网站
Go语言编写的Web应用程序可以通过UNIX域套接字进行公开。
在Go中使用UNIX套接字(来自Stack Overflow)。
使用原生的汉语对以下进行改写,只需要一个选项:
The Go编程语言
– net 包
使用Go编写的Web应用程序可以通过UNIX域套接字进行公开。
在Go中使用UNIX套接字(来自Stack Overflow)。