首页 > 其他分享 >YouCompleteMe如何获得未使用的端口

YouCompleteMe如何获得未使用的端口

时间:2024-05-16 18:19:33浏览次数:25  
标签:YouCompleteMe bind self 端口 获得 ._ port inet ycmd

intro

由于每次vim都启动一个ycmd服务进程,并且端口地址是由vim客户端指定的(因为ycmd启动之后vim客户端需要连接过去),所以vim在指定端口的时候就需要给出一个当前没有使用中的端口。

那么如何获得一个未使用的端口呢?

tsecer@harry: ps aux | fgrep ycmd
tsecer+ 3022084  0.9  2.5 1372356 840644 ?      Ssl  *   0:45 /usr/bin/python3.11 /home/tsecer/.vim/bundle/YouCompleteMe/python/ycm/../../third_party/ycmd/ycmd --port=52721 --options_file=/tmp/tmp32kne20x --log=info --idle_suicide_seconds=1800 --stdout=/tmp/ycmd_52721_stdout_g2udc2_0.log --stderr=/tmp/ycmd_52721_stderr_u7831z99.log
tsecer+ 3101254  0.0  0.1 2359252 33960 pts/8   Sl+ * 0:00 vim ../third_party/ycmd/ycmd/utils.py
tsecer+ 3101255  0.0  0.2 626420 73136 ?        Ssl  *   0:00 /usr/bin/python3.11 /home/tsecer/.vim/bundle/YouCompleteMe/python/ycm/../../third_party/ycmd/ycmd --port=56359 --options_file=/tmp/tmpxbiz25f7 --log=info --idle_suicide_seconds=1800 --stdout=/tmp/ycmd_56359_stdout_epz8p7zc.log --stderr=/tmp/ycmd_56359_stderr_3_4qdtui.log
tsecer+ 3164762  0.0  0.0  13212  1088 pts/2    S+   *   0:00 grep -F --color=auto ycmd
tsecer@harry: 

Linux系统中有一些系统调用可以让操作系统自动分配一个可用的端口。例如sendto

Call sendto without calling bind first, the socket will be bound automatically (to a free port).

或者通过bind一个0端口。

Another option is to specify port 0 to bind(). That will allow you to bind to a specific IP address (in case you have multiple installed) while still binding to a random port. If you need to know which port was picked, you can use getsockname() after the binding has been performed.

这两种方法都有一个进程安全的问题:执行系统调用之后需要保持socket一直存在,否则理论上这个socket关闭之后可能会分配给下一个进程(尽管和pid一样,port应该不会立马回收,而是尽量先单调递增)。

ycm

可以看到,ycm是通过YouCompleteMe类的_SetUpServer函数来设置server信息,并且端口是通过utils.GetUnusedLocalhostPort函数获得。

# ~/.vim/bundle/YouCompleteMe/python/ycm/youcompleteme.py
class YouCompleteMe:
  def __init__( self, default_options = {} ):
    self._logger = logging.getLogger( 'ycm' )
    self._client_logfile = None
    self._server_stdout = None
    self._server_stderr = None
    self._server_popen = None
    self._default_options = default_options
    self._ycmd_keepalive = YcmdKeepalive()
    self._SetUpLogging()
    self._SetUpServer()
    self._ycmd_keepalive.Start()


  def _SetUpServer( self ):
    self._available_completers = {}
    self._user_notified_about_crash = False
    self._filetypes_with_keywords_loaded = set()
    self._server_is_ready_with_cache = False
    self._message_poll_requests = {}
    ###...
        # The temp options file is deleted by ycmd during startup.
    with NamedTemporaryFile( delete = False, mode = 'w+' ) as options_file:
      json.dump( options_dict, options_file )

    server_port = utils.GetUnusedLocalhostPort()

    BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port )
    BaseRequest.hmac_secret = hmac_secret

函数实现的确是通过bind获得之后马上关闭socket。

### ~/.vim/bundle/YouCompleteMe/third_party/ycmd/ycmd/utils.py
def GetUnusedLocalhostPort():
  sock = socket.socket()
  # This tells the OS to give us any free port in the range [1024 - 65535]
  sock.bind( ( '', 0 ) ) 
  port = sock.getsockname()[ 1 ] 
  sock.close()
  return port

kernel

看起来是随机未使用端口,而不是连续的。

///@file:linux\net\ipv4\inet_connection_sock.c
/*
 * Find an open port number for the socket.  Returns with the
 * inet_bind_hashbucket locks held if successful.
 */
static struct inet_bind_hashbucket *
inet_csk_find_open_port(const struct sock *sk, struct inet_bind_bucket **tb_ret,
			struct inet_bind2_bucket **tb2_ret,
			struct inet_bind_hashbucket **head2_ret, int *port_ret)
{
	struct inet_hashinfo *hinfo = tcp_or_dccp_get_hashinfo(sk);
	int i, low, high, attempt_half, port, l3mdev;
	struct inet_bind_hashbucket *head, *head2;
	struct net *net = sock_net(sk);
	struct inet_bind2_bucket *tb2;
	struct inet_bind_bucket *tb;
	u32 remaining, offset;
	bool relax = false;

	l3mdev = inet_sk_bound_l3mdev(sk);
ports_exhausted:
	attempt_half = (sk->sk_reuse == SK_CAN_REUSE) ? 1 : 0;
other_half_scan:
	inet_sk_get_local_port_range(sk, &low, &high);
	high++; /* [32768, 60999] -> [32768, 61000[ */
	if (high - low < 4)
		attempt_half = 0;
	if (attempt_half) {
		int half = low + (((high - low) >> 2) << 1);

		if (attempt_half == 1)
			high = half;
		else
			low = half;
	}
	remaining = high - low;
	if (likely(remaining > 1))
		remaining &= ~1U;

	offset = get_random_u32_below(remaining);
	/* __inet_hash_connect() favors ports having @low parity
	 * We do the opposite to not pollute connect() users.
	 */
	offset |= 1U;

other_parity_scan:
	port = low + offset;
	for (i = 0; i < remaining; i += 2, port += 2) {
		if (unlikely(port >= high))
			port -= remaining;
		if (inet_is_local_reserved_port(net, port))
			continue;
		head = &hinfo->bhash[inet_bhashfn(net, port,
						  hinfo->bhash_size)];
		spin_lock_bh(&head->lock);
		if (inet_use_bhash2_on_bind(sk)) {
			if (inet_bhash2_addr_any_conflict(sk, port, l3mdev, relax, false))
				goto next_port;
		}

		head2 = inet_bhashfn_portaddr(hinfo, sk, net, port);
		spin_lock(&head2->lock);
		tb2 = inet_bind2_bucket_find(head2, net, port, l3mdev, sk);
		inet_bind_bucket_for_each(tb, &head->chain)
			if (inet_bind_bucket_match(tb, net, port, l3mdev)) {
				if (!inet_csk_bind_conflict(sk, tb, tb2,
							    relax, false))
					goto success;
				spin_unlock(&head2->lock);
				goto next_port;
			}
		tb = NULL;
		goto success;
next_port:
		spin_unlock_bh(&head->lock);
		cond_resched();
	}

	offset--;
	if (!(offset & 1))
		goto other_parity_scan;

	if (attempt_half == 1) {
		/* OK we now try the upper half of the range */
		attempt_half = 2;
		goto other_half_scan;
	}

	if (READ_ONCE(net->ipv4.sysctl_ip_autobind_reuse) && !relax) {
		/* We still have a chance to connect to different destinations */
		relax = true;
		goto ports_exhausted;
	}
	return NULL;
success:
	*port_ret = port;
	*tb_ret = tb;
	*tb2_ret = tb2;
	*head2_ret = head2;
	return head;
}

outro

ycm使用bind系统调用,传入参数为0的端口地址(INADDR_ANY ),由操作系统分配端口之后再销毁socket。尽管这种操作理论上不是进程安全的,但是考虑到内核端口的分配策略和vim的使用场景(启动频率极低),所以在实践中应该不会出现并发导致的问题。

标签:YouCompleteMe,bind,self,端口,获得,._,port,inet,ycmd
From: https://www.cnblogs.com/tsecer/p/18196453

相关文章

  • 【端口监听】端口占用导致服务启动失败
    现象:启动一个服务,提示端口占用2200,服务启动失败排查顺序:1、查看2200的占用情况,看一下是如个程序监听了这个端口netstat-lntp|grep2200发现并没有服务监听2、没有服务监听,那可能是有服务用到了这个端口,比如作为客户端连接出去的,换个语句netstat-an|grep2200发现......
  • Windows 10开启免密ssh登录&远程端口转发
    安装OpenSSH服务端设置-系统-可选功能-添加功能-在这里搜索OpenSSH服务端,然后开始安装即可开启sshd服务端可以使用图形界面使用命令行执行services.msc找到服务启动即可,并将其设置为自动。可以使用powershell执行命令启动服务启动服务:Start-Servicesshd查看状态:Get-Service......
  • 检查某个端口是否被udp网络程序占用
    代码分为2部分;1.随机生成一个未被udp占用的端口号2.启动一个udp程序,使用我们刚才找到的端口号 #include<iostream>#include<sys/socket.h>#include<netinet/in.h>#include<cstring>#include<cstdlib>#include<ctime>#include<unistd.h>......
  • 端口映射
    一个没有公网IP的内网设备要连接外网,通过网关路由器进行映射网关将数据包包装上网关IP(即公网IP)和新的未使用的端口,对应内网设备的内网IP和端口,即内网IP+内网端口----------------公网IP+公网端口多个内网IP请求则是映射成不同的公网端口,如内网IP1+内网端口1-------------......
  • 解决端口被占用问题
    step1:当我们运行项目的时候,控制台打印出现error:此时会显示端口地址已经被占用,端口占用的解决办法:step2:打开cmd,命令提示符,输入netstat-ano 会显示所有已经在运行的端口,step3:输入你想要查的正在占用的端口号,netstat-ano|findstr8080step4:此时会显示端口8080对应的tcp号......
  • TCP KEEPALIVE以获得更好的POSTGRESQL体验
    一、数据库连接断开的原因连接断开的可能原因有多种:1、数据库服务器崩溃如果服务器由于某种原因崩溃,要调查服务器是否存在问题,您应该首先查看PostgreSQL日志,看看是否可以找到匹配的崩溃报告。2、客户端放弃的连接如果客户端在没有正确关闭数据库连接的情况下退出,服务器在网......
  • ssh修改端口后,gitee(git)连接不上
    将本服务器的ssh端口22改为1068后,gitpull和gitpush时,出现:ssh:connecttohostgitee.comport[端口]:Connectiontimedout错误目测是因为,gitee的链接,使用的是SSH协议,但是服务器的端口由22变为了1068,所以请求gitee服务器时,也由22变更为了1068,所以只要SSH协议的链接加上22......
  • 一三云服务器配置教程:要开放哪些端口?如何设置宝塔端口更安全?
    布署宝塔面板云服务器需要开放哪些端口?1、以一三云服务器为例,如需完整使用宝塔的所有功能,需要放行如下防火墙规则:20/21————–(FTP主动模式端口)39000-40000——(FTP被动模式-Linux 系统)3000-4000———(FTP被动模式– Windows系统)22——————(SSH远程登录)80—————(网站)4......
  • 一三云服务器宝塔面板FTP上传需要开放哪些端口
    在服务器的配置和维护中,FTP(文件传输协议)是一个常见的协议,用于在客户端和服务器之间传输文件。当涉及到FTP上传时,确保正确的端口已经开放是至关重要的。本文将详细讨论在FTP上传过程中需要开放的端口,并解释这些端口的作用和配置方法。首先,FTP主要使用两个类型的端口:控制连接端口和......
  • Springboot单机多副本运行,解决端口冲突
    一、代码方式(修改配置类)@BeanpublicWebServerFactoryCustomizer<ConfigurableWebServerFactory>MyCustomizer(){returnnewWebServerFactoryCustomizer<ConfigurableWebServerFactory>(){@Overridepublicvoidcustomize(ConfigurableWebSer......