在运行服务器实例之前释放端口

huangapple go评论76阅读模式
英文:

Freeing a port before running a server instant

问题

我想在基于Linux的系统上运行一个go服务器。在某些情况下,我发现相同的端口被另一个应用程序占用,所以我想要终止该端口上正在运行的进程,并运行我的服务器。因此,我编写了以下代码:

func main() {

	host := "127.0.0.1"
	port := "8070"
	server := http.Server{
		Addr: "127.0.0.1:8070",
	}

	http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))

	ln, err := net.Listen("tcp", ":"+port)

	if err != nil {
		fmt.Fprintf(os.Stderr, "无法监听端口 %q:%s \n", port, err)
        // 终止该端口上正在运行的进程
		_, err := exec.Command("fuser", "-k", "8070/tcp").Output()

		if err != nil {
			fmt.Printf("无法终止端口 %q 上的进程\n", port)
		} else {
			fmt.Printf("TCP端口 %q 可用\n", port)
			server.ListenAndServe()
		}

	} else {
		ln.Close()
		server.ListenAndServe()
	}
}

我能够得到响应TCP端口8070可用,这意味着有另一个正在运行的进程,并且已经被终止,但是我的应用程序直接关闭,而没有在已经关闭的端口上运行我的服务器!

hajsf@AIS-DM-YOUSEF-L:~/myapp$ go run myapp
无法监听端口 "8070":listen tcp :8070: bind: address already in use 
TCP端口 "8070" 可用
hajsf@AIS-DM-YOUSEF-L:~/myapp$ 

在原始终端中(旧的应用程序实例),我得到了以下输出:

hajsf@AIS-DM-YOUSEF-L:~/myapp$ go run myapp
signal: killed
hajsf@AIS-DM-YOUSEF-L:~/myapp$ 

在运行服务器实例之前释放端口

英文:

I want to run a go server at linux based system, it happened in soe cases that i found the same port is busy with another app, so i want to kill the running process at that port, and run my server instead, so I wrote the below code:

func main() {

	host := "127.0.0.1"
	port := "8070"
	server := http.Server{
		Addr: "127.0.0.1:8070",
	}

	http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))

	ln, err := net.Listen("tcp", ":"+port)

	if err != nil {
		fmt.Fprintf(os.Stderr, "Can't listen on port %q: %s \n", port, err)
        // kill the running process at this port
		_, err := exec.Command("fuser", "-k", "8070/tcp").Output()

		if err != nil {
			fmt.Printf("Failed to kill process at Port %q\n", port)
		} else {
			fmt.Printf("TCP Port %q is available\n", port)
			server.ListenAndServe()
		}

	} else {
		ln.Close()
		server.ListenAndServe()
	}
}

I was able to get the response TCP Port 8070 is available whihc means there was another running process and it had been killed, but my app is closed directly without running my server at the same port which had been already closed!

hajsf@AIS-DM-YOUSEF-L:~/myapp$ go run myapp
Can't listen on port "8070": listen tcp :8070: bind: address already in use 
TCP Port "8070" is available
hajsf@AIS-DM-YOUSEF-L:~/myapp$ 

In the origional terminal (the old instance of the app0 I got;

hajsf@AIS-DM-YOUSEF-L:~/myapp$ go run myapp
signal: killed
hajsf@AIS-DM-YOUSEF-L:~/myapp$ 

在运行服务器实例之前释放端口

答案1

得分: 1

在CentOS 7上运行良好,但在Ubuntu服务器上出现相同的问题。@MoiioM的答案是正确的。但是,如果另一个应用程序不是golang应用程序本身,这里有另一种方法:在套接字上设置SO_REUSEADDR标志。

package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"os"
	"os/exec"
	"syscall"

	"golang.org/x/sys/unix"
)

func reusePort(network, address string, conn syscall.RawConn) error {
	return conn.Control(func(descriptor uintptr) {
		syscall.SetsockoptInt(int(descriptor), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
	})
}

func main() {

	port := "8070"
	server := http.Server{}

	http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))

	ln, err := net.Listen("tcp", ":"+port)

	if err != nil {
		fmt.Fprintf(os.Stderr, "无法监听端口 %q:%s \n", port, err)
		// 杀死在该端口上运行的进程
		_, err := exec.Command("fuser", "-k", "8070/tcp").Output()

		if err != nil {
			fmt.Printf("无法杀死端口 %q 上的进程\n", port)
		} else {
			fmt.Printf("TCP端口 %q 可用\n", port)
			config := &net.ListenConfig{Control: reusePort}
			listener, err := config.Listen(context.Background(), "tcp", ":"+port)
			if err != nil {
				panic(err)
			}
			if err := server.Serve(listener); err != nil {
				panic(err)
			}
		}

	} else {
		if err := server.Serve(ln); err != nil {
			panic(err)
		}
	}
}
英文:

worked well on CentOS 7, but same issue with a Ubuntu server. @MoiioM 's answer is fine. But if another app is not the golang app itself, here is another way: set a SO_REUSEADDR flag on the socket

package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"os"
	"os/exec"
	"syscall"

	"golang.org/x/sys/unix"
)

func reusePort(network, address string, conn syscall.RawConn) error {
	return conn.Control(func(descriptor uintptr) {
		syscall.SetsockoptInt(int(descriptor), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
	})
}

func main() {

	port := "8070"
	server := http.Server{}

	http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))

	ln, err := net.Listen("tcp", ":"+port)

	if err != nil {
		fmt.Fprintf(os.Stderr, "Can't listen on port %q: %s \n", port, err)
		// kill the running process at this port
		_, err := exec.Command("fuser", "-k", "8070/tcp").Output()

		if err != nil {
			fmt.Printf("Failed to kill process at Port %q\n", port)
		} else {
			fmt.Printf("TCP Port %q is available\n", port)
			config := &net.ListenConfig{Control: reusePort}
			listener, err := config.Listen(context.Background(), "tcp", ":"+port)
			if err != nil {
				panic(err)
			}
			if err := server.Serve(listener); err != nil {
				panic(err)
			}
		}

	} else {
		if err := server.Serve(ln); err != nil {
			panic(err)
		}
	}
}

答案2

得分: 1

如您在响应中所见 https://stackoverflow.com/questions/11583562/how-to-kill-a-process-running-on-particular-port-in-linux#comment52643643_11596144

您的命令 _, err := exec.Command("fuser", "-k", "8070/tcp").Output() 杀死了进程,但没有清理资源,即端口监听。在父进程被杀死后,端口会进入 TIME_WAIT 状态。

您需要等待一段时间,让您的操作系统/内核清理端口/套接字。

一个更好的选择是处理来自 fuser 的 kill sigint 信号,并进行优雅的关闭。

package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"time"
)

func runServer(host, port string) {
	server := http.Server{
		Addr: host + ":" + port,
	}
	http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))
	go func() {
		if err := server.ListenAndServe(); err != nil {
		}
	}()

	// 设置信号捕获
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt)

	// 等待 SIGINT (kill -2)
	<-stop

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := server.Shutdown(ctx); err != nil {
		// 处理错误
	}
}

func main() {

	host := "127.0.0.1"
	port := "8070"
	ln, err := net.Listen("tcp", host+":"+port)

	if err != nil {
		fmt.Fprintf(os.Stderr, "无法监听端口 %q:%s \n", port, err)
		// 杀死在该端口上运行的进程
		cmd := exec.Command("fuser", "-k", "-2", "8070/tcp")
		fmt.Println("等待")
		err = cmd.Run()

		if err != nil {
			fmt.Printf("无法杀死端口 %q 上的进程\n", port)
		} else {
			fmt.Printf("TCP 端口 %q 可用\n", port)
			runServer(host, port)
		}

	} else {
		ln.Close()
		runServer(host, port)
	}
}

英文:

As you can see in the response https://stackoverflow.com/questions/11583562/how-to-kill-a-process-running-on-particular-port-in-linux#comment52643643_11596144

Your command _, err := exec.Command(&quot;fuser&quot;, &quot;-k&quot;, &quot;8070/tcp&quot;).Output() kill the process but doesn't cleanup the resource ie: port listening.
The port is put into TIME_WAIT state after the parent process is killed.

And you need to wait some time your OS/Kernel cleanup the port/socket

A better alternative is to handle the kill sigint from fuser and do a graceful shutdown

package main

import (
	&quot;context&quot;
	&quot;fmt&quot;
	&quot;net&quot;
	&quot;net/http&quot;
	&quot;os&quot;
	&quot;os/exec&quot;
	&quot;os/signal&quot;
	&quot;time&quot;
)

func runServer(host, port string) {
	server := http.Server{
		Addr: host + &quot;:&quot; + port,
	}
	http.Handle(&quot;/www/&quot;, http.StripPrefix(&quot;/www/&quot;, http.FileServer(http.Dir(&quot;./www&quot;))))
	go func() {
		if err := server.ListenAndServe(); err != nil {
		}
	}()

	// Setting up signal capturing
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt)

	// Waiting for SIGINT (kill -2)
	&lt;-stop

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := server.Shutdown(ctx); err != nil {
		// handle err
	}
}

func main() {

	host := &quot;127.0.0.1&quot;
	port := &quot;8070&quot;
	ln, err := net.Listen(&quot;tcp&quot;, host+&quot;:&quot;+port)

	if err != nil {
		fmt.Fprintf(os.Stderr, &quot;Can&#39;t listen on port %q: %s \n&quot;, port, err)
		// kill the running process at this port
		cmd := exec.Command(&quot;fuser&quot;, &quot;-k&quot;, &quot;-2&quot;, &quot;8070/tcp&quot;)
		fmt.Println(&quot;wait&quot;)
		err = cmd.Run()

		if err != nil {
			fmt.Printf(&quot;Failed to kill process at Port %q\n&quot;, port)
		} else {
			fmt.Printf(&quot;TCP Port %q is available\n&quot;, port)
			runServer(host, port)
		}

	} else {
		ln.Close()
		runServer(host, port)
	}
}

答案3

得分: 0

我使用以下代码中的panic ... recover解决了这个问题:

package main

import (
	"fmt"
	"net"
	"net/http"
	"onsen/resources"
	"onsen/routes"
	"os"
	"os/exec"
)

func main() {
	http.Handle("/webUI/", http.StripPrefix("/webUI/", http.FileServer(http.FS(resources.WebUI))))
	http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))

	for key, value := range routes.Urls() {
		http.HandleFunc(key, value)
	}
	runServer()
}

func runServer() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered in f", r)
			runServer()
		}
	}()

	host := "127.0.0.1"
	port := "8070"

	server := http.Server{
		Addr: fmt.Sprintf("%v:%v", host, port),
	}

	ln, err := net.Listen("tcp", ":"+port)

	if err != nil {
		fmt.Fprintf(os.Stderr, "Can't listen on port %q: %s \n", port, err)
		// kill the running process at this port
		_, err := exec.Command("fuser", "-k", "8070/tcp").Output()

		if err != nil {
			panic(fmt.Sprintf("Failed to kill process at Port %q\n", port))
		} else {
			fmt.Printf("TCP Port %q is available\n", port)
			fmt.Println("server started...")
			if err := server.Serve(ln); err != nil {
				panic(err)
			}
		}
	} else {
		fmt.Println("server started...")
		if err := server.Serve(ln); err != nil {
			panic(err)
		}
	}
}

并且得到了以下输出:
在运行服务器实例之前释放端口

英文:

I was able to solve it with panic ... recover as below:

package main

import (
	&quot;fmt&quot;
	&quot;net&quot;
	&quot;net/http&quot;
	&quot;onsen/resources&quot;
	&quot;onsen/routes&quot;
	&quot;os&quot;
	&quot;os/exec&quot;
)

func main() {
	http.Handle(&quot;/webUI/&quot;, http.StripPrefix(&quot;/webUI/&quot;, http.FileServer(http.FS(resources.WebUI))))
	http.Handle(&quot;/www/&quot;, http.StripPrefix(&quot;/www/&quot;, http.FileServer(http.Dir(&quot;./www&quot;))))

	for key, value := range routes.Urls() {
		http.HandleFunc(key, value)
	}
	runServer()
}
func runServer() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println(&quot;Recovered in f&quot;, r)
			runServer()
		}
	}()

	host := &quot;127.0.0.1&quot;
	port := &quot;8070&quot;

	server := http.Server{
		Addr: fmt.Sprintf(&quot;%v:%v&quot;, host, port),
	}

	ln, err := net.Listen(&quot;tcp&quot;, &quot;:&quot;+port)

	if err != nil {
		fmt.Fprintf(os.Stderr, &quot;Can&#39;t listen on port %q: %s \n&quot;, port, err)
		// kill the running process at this port
		_, err := exec.Command(&quot;fuser&quot;, &quot;-k&quot;, &quot;8070/tcp&quot;).Output()

		if err != nil {
			panic(fmt.Sprintf(&quot;Failed to kill process at Port %q\n&quot;, port))
		} else {
			fmt.Printf(&quot;TCP Port %q is available\n&quot;, port)
			fmt.Println(&quot;server started...&quot;)
			if err := server.Serve(ln); err != nil {
				panic(err)
			}
		}
	} else {
		fmt.Println(&quot;server started...&quot;)
		if err := server.Serve(ln); err != nil {
			panic(err)
		}
	}
}

And got the output as below:
在运行服务器实例之前释放端口

huangapple
  • 本文由 发表于 2021年10月29日 20:07:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/69768611.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定