I’ve done a lot of googling but not had much luck with my issues. I am new to network programming and trying to learn, I’ve attempted to set up a simple server & client that communicate (following an online tutorial located here -> http://tech.pro/tutorial/704/csharp-tutorial-simple-threaded-tcp-server)
The issue I’m having is that I keep getting the exception «Only one usage of each socket address (protocol/network address/port) is normally permitted» when trying to start the TcpListener on the server.
I’ve tried disabling my firewall, changing the port to be used, moving variables around but to no avail (the client works fine, but it obviously can’t find the server because I cannot launch it).
I’ve seen solutions describing the use of Socket.Poll() but since I’m only using the TcpListener object, I have no idea how to make use of the Poll function.
My code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Text;
namespace ServerTutorial {
class Server {
private readonly Thread m_listenThread;
public Server() {
m_listenThread = new Thread(new ThreadStart(ListenForClients));
m_listenThread.Start();
}
public void ListenForClients() {
var listener = new TcpListener(IPAddress.Any, 3000);
listener.Start();
while (true) {
//Blocks until a client has connected to the server
TcpClient client = listener.AcceptTcpClient();
//Send a message to the client
var encoder = new ASCIIEncoding();
NetworkStream clientStream = client.GetStream();
byte[] buffer = encoder.GetBytes("Hello Client!");
clientStream.Write(buffer, 0, buffer.Length);
clientStream.Flush();
//Create a thread to handle communication with the connected client
var clientThread = new Thread(new ParameterizedThreadStart(HandleClient));
clientThread.Start(client);
}
}
private void HandleClient(object clientObj) { //Param thread start can only accept object types, hence the cast
var client = (TcpClient) clientObj;
NetworkStream clientStream = client.GetStream();
var message = new byte[4096];
while (true) {
int bytesRead = 0;
try {
//Block until a client sends a message
bytesRead = clientStream.Read(message, 0, 4096);
} catch {
//A socket error has occurred
System.Diagnostics.Debug.WriteLine("A socket error has occured");
break;
}
if (bytesRead == 0) {
//The client has disconnected from the server
System.Diagnostics.Debug.WriteLine("A client has disconnected from the server");
client.Close();
break;
}
//Message has been received
var encoder = new ASCIIEncoding();
System.Diagnostics.Debug.WriteLine(encoder.GetString(message, 0, bytesRead));
}
}
}
}
In my main method:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ServerTutorial {
class Program {
static void Main(string[] args) {
var server = new Server();
server.ListenForClients();
}
}
}
Any help is hugely appreciated!
You are overloading the TCP/IP stack. Windows (and I think all socket stacks actually) have a limitation on the number of sockets that can be opened in rapid sequence due to how sockets get closed under normal operation. Whenever a socket is closed, it enters the TIME_WAIT state for a certain time (240 seconds IIRC). Each time you poll, a socket is consumed out of the default dynamic range (I think its about 5000 dynamic ports just above 1024), and each time that poll ends, that particular socket goes into TIME_WAIT. If you poll frequently enough, you will eventually consume all of the available ports, which will result in TCP error 10048.
Generally, WCF tries to avoid this problem by pooling connections and things like that. This is usually the case with internal services that are not going over the internet. I am not sure if any of the wsHttp bindings support connection pooling, but the netTcp binding should. I would assume named pipes does not run into this problem. I couldn’t say for the MSMQ binding.
There are two solutions you can use to get around this problem. You can either increase the dynamic port range, or reduce the period of TIME_WAIT. The former is probably the safer route, but if you are consuming an extremely high volume of sockets (which doesn’t sound like the case for your scenario), reducing TIME_WAIT is a better option (or both together.)
Changing the Dynamic Port Range
- Open regedit.
- Open key HKLMSystemCurrentControlSetServicesTcpipParameters
- Edit (or create as DWORD) the MaxUserPort value.
- Set it to a higher number. (i.e. 65534)
Changing the TIME_WAIT delay
- Open regedit.
- Open key HKLMSystemCurrentControlSetServicesTcpipParameters
- Edit (or create as DWORD) the TcpTimedWaitDelay.
- Set it to a lower number. Value is in seconds. (i.e. 60 for 1 minute delay)
One of the above solutions should fix your problem. If it persists after changing the port range, I would see try increasing the period of your polling so it happens less frequently…that will give you more leeway to work around the time wait delay. I would change the time wait delay as a last resort.
При «ручном» прогоне плагина выходит ошибка —
>>> RESTART: C:UsersandrewDocumentsMinecraft PythonpypluginsJuicyRaspberryPiemcpipycmdsvr.py
registering command: blk
registering command: hi
Traceback (most recent call last):
File "C:UsersandrewDocumentsMinecraft PythonpypluginsJuicyRaspberryPiemcpipycmdsvr.py", line 84, in <module>
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
File "C:UsersandrewAppDataLocalProgramsPythonPython36-32libsocketserver.py", line 453, in __init__
self.server_bind()
File "C:UsersandrewAppDataLocalProgramsPythonPython36-32libsocketserver.py", line 467, in server_bind
self.socket.bind(self.server_address)
OSError: [WinError 10048] Обычно разрешается только одно использование адреса сокета (протокол/сетевой адрес/порт)
Spigot выдаёт при этом ошибку —
[08:44:55 WARN]: java.net.ConnectException: Connection refused: connect
[08:44:55 WARN]: at java.net.DualStackPlainSocketImpl.connect0(Native Method)
[08:44:55 WARN]: at java.net.DualStackPlainSocketImpl.socketConnect(Unknown Source)
[08:44:55 WARN]: at java.net.AbstractPlainSocketImpl.doConnect(Unknown Source)
[08:44:55 WARN]: at java.net.AbstractPlainSocketImpl.connectToAddress(Unknown Source)
[08:44:55 WARN]: at java.net.AbstractPlainSocketImpl.connect(Unknown Source)
[08:44:55 WARN]: at java.net.PlainSocketImpl.connect(Unknown Source)
[08:44:55 WARN]: at java.net.SocksSocketImpl.connect(Unknown Source)
[08:44:55 WARN]: at java.net.Socket.connect(Unknown Source)
[08:44:55 WARN]: at java.net.Socket.connect(Unknown Source)
[08:44:55 WARN]: at java.net.Socket.<init>(Unknown Source)
[08:44:55 WARN]: at java.net.Socket.<init>(Unknown Source)
[08:44:55 WARN]: at org.wensheng.plugins.JuicyRaspberryPie.onCommand(JuicyRaspberryPie.java:153)
[08:44:55 WARN]: at org.bukkit.command.PluginCommand.execute(PluginCommand.java:44)
[08:44:55 WARN]: at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:141)
[08:44:55 WARN]: at org.bukkit.craftbukkit.v1_12_R1.CraftServer.dispatchCommand(CraftServer.java:649)
[08:44:55 WARN]: at net.minecraft.server.v1_12_R1.PlayerConnection.handleCommand(PlayerConnection.java:1397)
[08:44:55 WARN]: at net.minecraft.server.v1_12_R1.PlayerConnection.a(PlayerConnection.java:1232)
[08:44:55 WARN]: at net.minecraft.server.v1_12_R1.PacketPlayInChat.a(PacketPlayInChat.java:45)
[08:44:55 WARN]: at net.minecraft.server.v1_12_R1.PacketPlayInChat.a(PacketPlayInChat.java:1)
[08:44:55 WARN]: at net.minecraft.server.v1_12_R1.PlayerConnectionUtils$1.run(SourceFile:13)
[08:44:55 WARN]: at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
[08:44:55 WARN]: at java.util.concurrent.FutureTask.run(Unknown Source)
[08:44:55 WARN]: at net.minecraft.server.v1_12_R1.SystemUtils.a(SourceFile:46)
[08:44:55 WARN]: at net.minecraft.server.v1_12_R1.MinecraftServer.D(MinecraftServer.java:748)
[08:44:55 WARN]: at net.minecraft.server.v1_12_R1.DedicatedServer.D(DedicatedServer.java:406)
[08:44:55 WARN]: at net.minecraft.server.v1_12_R1.MinecraftServer.C(MinecraftServer.java:679)
[08:44:55 WARN]: at net.minecraft.server.v1_12_R1.MinecraftServer.run(MinecraftServer.java:577)
[08:44:55 WARN]: at java.lang.Thread.run(Unknown Source)
Файл на который ругается —
# wenshengwang at gmail dot com
# BSD License
"""This is a TCP server. It is started by the JuicyRaspberryPie plugin and
listen on localhost port 32123.
When it start, it scan the "pplugins" directory for any python files and try
to load them as modules, in these modules, it search for any functions whose
docstring starts with "_mcp" and register them as commands.
When the server receive a command, if it matches one in the registry, it will
be executed. If not, it will execute a dummy command.
"""
import os
import sys
import glob
import socketserver
import threading
import types
import importlib
plugin_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))
sys.path.insert(0, plugin_dir)
from mcpi.minecraft import Minecraft
HOST = 'localhost'
# TODO: read port from config.yml
PORT = 4444
KEEP_RUNNING = True
def keep_running():
return KEEP_RUNNING
# command registry
mc_functions = {}
pp_files = glob.glob(os.path.join(plugin_dir, "pplugins", "*.py"))
# import all files and put minecraft function into the mc_functions dict
for pp_file in pp_files:
basename = os.path.basename(pp_file)
if basename != "__init__.py":
try:
module = importlib.import_module("pplugins." + basename[:-3])
for item in dir(module):
if isinstance(module.__dict__[item], types.FunctionType):
docs = module.__dict__[item].__doc__
if docs and docs.startswith("_mcp"):
print("registering command:", module.__dict__[item].__name__)
mc_functions[item] = module.__dict__[item]
except (NameError, ImportError) as e:
print(e)
def chat(msg="Whaaat?!"):
mc = Minecraft.create()
mc.postToChat(msg)
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
global KEEP_RUNNING
self.data = self.request.recv(1024)
# firt 2 bytes are length info, from Java's writeUTF
args = self.data[2:].decode('utf-8').split()
cmd = args[0]
if cmd == "list":
s = "Available commands: %s" % ( " ".join(list(mc_functions.keys())))
self.request.sendall(s.encode('utf-8'))
threading.Thread(target=chat, args=(s,), kwargs={}).start()
return
if cmd == "help":
s = 'JuicyRaspberryPie: put your Python files in pplugins, then "/p cmd" to call your function, "/p list" to see list of commands'
self.request.sendall(s.encode('utf-8'))
threading.Thread(target=chat, args=(s,), kwargs={}).start()
return
if cmd == "BYE":
print("got shutdown request, signing off")
KEEP_RUNNING = False
return
threading.Thread(target=mc_functions.get(cmd, chat), args=tuple(args[1:]), kwargs={}).start()
self.request.sendall("ok".encode('utf-8'))
if __name__ == "__main__":
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
def server_serve():
while keep_running():
server.handle_request()
threading.Thread(target=server_serve).start()
P_Mis
13.09.19 — 14:39
Сервер 1С находится на одном сервере, пользователи подключаются с другого сервера по тонкому клиенту.
Столкнулись с проблемой: после какого-то кол-ва пользователей зашедших в базу, новых перестает пускать — просто падает платформа. Началось внезапно, ничего не меняли, ни конфигурацию, ни платформу, ни сервер. На текущей платформе проработали больше месяца нормально. Через технологический журнал на клиенте выцепил ошибку, с которой падает платформа «Обычно разрешается одно использование адреса сокета (протокол/сетевой адрес/порт)». Ошибку это возвращает платформе винда, везде пишут, что проблема в том, что не хватает динамических портов, которые можно расширить через реестр, но это не помогло.
Через netstat -ano на сервере удаленных рабочих столов, откуда подключаются пользователи видно, что заняты все порты с 1560 по 1591 тонким клиентом. Но многие клиенты сидят на рандомных портах типа 45434. Заметили, что новые пользователи не могут зайти в базу, когда все порты 1560-1591 заняты. Если убить любого тонкого клиента, который занимает порт в этом диапазоне и порт освобождается, то новый пользователь может зайти в базу.
Так же, когда все эти порты заняты, не получается запустить отладку из конфигуратора, ругается на «Для выполнения отладки необходимо включить поддержку сетевого протокола TCP/IP» — что тоже описано в инете как проблема занятых портов.
Не понятно, зачем тонкому клиенту на сервере удаленных рабочих столов занимать эти порты, ведь используются они сервером 1С для рабочих процессов. И почему когда свободных портов нет на РДП сервере, клиент не может подключиться к серверу 1С. Но часть клиентов спокойно висят на рандомных портах типа 45434. Такое ощущение, что при коннекте, клиент все таки занимает какой-то из портов в этом диапазоне, а после этого его перекидывает на любой свободный до 65535, но эти порты из диапазона 1560-1591 не успевают освободиться.
Объясните, по какому принципу клиент занимает порты и можно ли это как-то где-то настроить? То, что происходит в описанной ситуации это какой-то сбой либо некорректная настройка и достаточно в настройках службы 1С указать бОльший пул портов? Но никогда не слышал, чтобы в базах, где работает большое кол-во пользователей, увеличивали диапазон портов для рабочих процессов на сервере 1С.
P_Mis
1 — 13.09.19 — 14:57
В первый раз появилось на платформе 8.3.14.1854, после этого откатились на 8.3.12.1685, на которой были несколько месяцев, ситуация не изменилась.
eklmn
2 — 13.09.19 — 15:53
вам хватит на 1 локальный порт 1561 всех подключить
сбрасывайте настройки в дефолт, переустанавливайте платформу.
P_Mis
3 — 13.09.19 — 16:03
(2) Так какие настройки? На РДП сервере стоит только тонкий клиент, какие и где там настройки можно сбросить?
fbear
4 — 13.09.19 — 16:05
https://its.1c.ru/db/metod8dev#content:5908:hdoc
6. Необходимо настроить сетевой стек для обеспечения возможности обработки большого числа подключений
Настройки, которые необходимо выполнить (в дополнение к настройке 5.2. Настроить рабочий сервер в соответствии с инструкцией):
Запустить regedit и в ветке HKLMSystemCurrentControlSetServicesTcpipParameters указать
MaxFreeTcbs= 100000
TcpTimedWaitDelay= 30
MaxUserPort= 65535
Запустить regedit и в ветке HKLMSystemCurrentControlSetServicesAFDParameters указать
EnableDynamicBacklog= 1
MinimumDynamicBacklog= 20
MaximumDynamicBacklog= 20000
DynamicBacklogGrowthDelta= 10
Устанавливаем диапазон исходящих портов (1025; 65535)
Выполнить: netsh int ipv4 set dynamic port tcp start=1025 num=64510
Выполнить: netsh int ipv4 set dynamic port udp start=1025 num=64510
P_Mis
5 — 13.09.19 — 16:16
(4) Все эти настройки сделаны, это все как раз гуглится по ошибке «Обычно разрешается одно использование адреса сокета (протокол/сетевой адрес/порт)». Но легче не стало вообще. Да и 80 пользователей не так много.
P_Mis
6 — 13.09.19 — 16:17
Проблема в том, что тонкий клиент почему-то занимает 1560-1591 порты на рдп сервере (не на 1С сервере) и из-за этого не могут подключаться новые клиенты. Так как если через netstat выбрать клиента, который занял 1560 порт, завершить его, то другой пользователь сможет зайти.
Fragster
7 — 13.09.19 — 16:19
кто-то подкрутил параметр количество соединений на процесс?
Fragster
8 — 13.09.19 — 16:19
сколько rphost на сервере?
P_Mis
9 — 13.09.19 — 16:27
(7) Вроде никто ничего не крутил. Сейчас стоит 128.
(8) 1.
Fragster
10 — 13.09.19 — 17:59
режим отладки при запуске клиента?
P_Mis
11 — 13.09.19 — 18:43
(10) О, интересная мысль, надо будет проверить. Вообще запускается все без отладки, но вдруг каким-то образом она включается…
P_Mis
12 — 13.09.19 — 18:46
(10) (11) Во блин, точно! На одном из клиентов, который занял 1562 порт запущена отладка. Каким образом она могла включиться сама то?
P_Mis
13 — 16.09.19 — 11:25
Нашел в чем была причина, очень все нетривиально оказалось ) У нас в одном документе добавлено очень много доп.реквизитов, а чтобы они не подряд располагались на форме, была изменена форма в пользовательском режиме, красиво и по группам расположили доп.реквизиты.
Чтобы эта настройка применилась у всех, копировали все настройки эталонного пользователя другим пользователям. Видимо, в момент копирования настроек эталонного пользователя, у него была включена отладка, которая так и прилетела всем. Ну и в какой-то момент пользователей просто стало слишком много и проблема проявилась.
P_Mis
14 — 16.09.19 — 11:28
(13) Соответственно решение это в личных настройках пользователей очистить настройку из закладки Прочие настройки — Прочие настройки. Ну и переписать копирование настроек, если это делается автоматом.
Tweekaz 24 / 23 / 3 Регистрация: 21.04.2012 Сообщений: 253 |
||||
1 |
||||
Обычно разрешается только одно использование адреса сокета (протокол/сетевой адрес/порт)10.11.2012, 18:51. Показов 57157. Ответов 6 Метки нет (Все метки)
При запуске вызывается ошибка на строчке с Bind. Код Обычно разрешается только одно использование адреса сокета (протокол/сетевой адрес/порт)
Вот такое у нас..
__________________ 0 |
16931 / 12508 / 3286 Регистрация: 17.09.2011 Сообщений: 20,745 |
|
10.11.2012, 19:01 |
2 |
Какое-то другое приложение уже принимает соединения на пор 7777. 1 |
24 / 23 / 3 Регистрация: 21.04.2012 Сообщений: 253 |
|
10.11.2012, 19:08 [ТС] |
3 |
Какое-то другое приложение уже принимает соединения на пор 7777. так на любом порте 0 |
16931 / 12508 / 3286 Регистрация: 17.09.2011 Сообщений: 20,745 |
|
10.11.2012, 19:29 |
4 |
так на любом порте Значит ваше же приложение, но с разных потоков. 1 |
Tweekaz 24 / 23 / 3 Регистрация: 21.04.2012 Сообщений: 253 |
||||
10.11.2012, 19:42 [ТС] |
5 |
|||
Значит ваше же приложение, но с разных потоков.
Вот весь код, то есть никак не могу сам забивать порт. 0 |
kolorotur 16931 / 12508 / 3286 Регистрация: 17.09.2011 Сообщений: 20,745 |
||||
10.11.2012, 20:55 |
6 |
|||
никак не могу сам забивать порт. Ну как же не можете, если забиваете?
2 |
24 / 23 / 3 Регистрация: 21.04.2012 Сообщений: 253 |
|
10.11.2012, 23:10 [ТС] |
7 |
kolorotur, спасибо, не додумался) 0 |
Понимаю, что тем с таким заголовком уже уйма, но ответа я так и не увидел. А точнее — что делать в моем случае.
Итак, делаю сервер, который слушает локалку по указанному порту. За основу взял эту библиотеку — https://github.com/nterry/AwesomeSockets.
Вот так выглядит метод запуска прослушивания:
public async void StartListening(int port)
{
if (CancellationTokenSource == null)
CancellationTokenSource = new CancellationTokenSource();
_cancellationToken = CancellationTokenSource.Token;
Clients = new ObservableCollection<Client>();
BindingOperations.EnableCollectionSynchronization(Clients, _lockObject);
try
{
while (_tcpListen == null)
{
ShowCallbackMessageAction?.Invoke("Try to start server");
_tcpListen = AweSock.TcpListen(port);
if (_tcpListen != null)
{
ShowCallbackMessageAction?.Invoke("Server started");
var waitForConnectionTask = Task.Run(() => WaitForConnectionLoop(), _cancellationToken);
var checkClientsConnectionTask = Task.Run(() => CheckClientsConnectionLoop(), _cancellationToken);
await Task.WhenAll(waitForConnectionTask, checkClientsConnectionTask);
}
// https://stackoverflow.com/a/32768637/4944499
await Task.Delay(1000, _cancellationToken).ContinueWith(task => { });
}
}
catch (Exception exception)
{
ShowCallbackMessageAction?.Invoke($"Error on start server: {exception.Message}");
}
}
Вот так метод остановки прослушивания:
public void StopListening()
{
ShowCallbackMessageAction?.Invoke("Begin stop server...");
ShowCallbackMessageAction?.Invoke("Disconnect all clients");
DisconnectAllClients();
CancellationTokenSource?.Cancel();
_tcpListen?.Close();
_tcpListen = null;
ShowCallbackMessageAction?.Invoke("Server stopped");
}
Метод из библиотеки, вызываемый в моем методе StartListening:
public static ISocket TcpListen(int port, int backlog = 10)
{
var listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//var ip = new IPAddress(new byte[] { 0, 0, 0, 0 });
//var localEndPoint = new IPEndPoint(ip, port);
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);
listenSocket.Bind(localEndPoint);
listenSocket.Listen(backlog);
return AwesomeSocket.New(listenSocket);
}
Так вот — это все работает до того момента, пока я приложение свое не закрою без отписки от прослушивания. Т.е., например, случился крэш. После того, как я повторно запускаю сервер, то вижу сообщение:
Везде пишут, что это нормально и порт сам освободится через некоторое время (вроде 240 секунд), но в моем случае этого не происходит. Помогает только перезагрузка компа.
Как быть?
P_Mis
13.09.19 — 14:39
Сервер 1С находится на одном сервере, пользователи подключаются с другого сервера по тонкому клиенту.
Столкнулись с проблемой: после какого-то кол-ва пользователей зашедших в базу, новых перестает пускать — просто падает платформа. Началось внезапно, ничего не меняли, ни конфигурацию, ни платформу, ни сервер. На текущей платформе проработали больше месяца нормально. Через технологический журнал на клиенте выцепил ошибку, с которой падает платформа «Обычно разрешается одно использование адреса сокета (протокол/сетевой адрес/порт)». Ошибку это возвращает платформе винда, везде пишут, что проблема в том, что не хватает динамических портов, которые можно расширить через реестр, но это не помогло.
Через netstat -ano на сервере удаленных рабочих столов, откуда подключаются пользователи видно, что заняты все порты с 1560 по 1591 тонким клиентом. Но многие клиенты сидят на рандомных портах типа 45434. Заметили, что новые пользователи не могут зайти в базу, когда все порты 1560-1591 заняты. Если убить любого тонкого клиента, который занимает порт в этом диапазоне и порт освобождается, то новый пользователь может зайти в базу.
Так же, когда все эти порты заняты, не получается запустить отладку из конфигуратора, ругается на «Для выполнения отладки необходимо включить поддержку сетевого протокола TCP/IP» — что тоже описано в инете как проблема занятых портов.
Не понятно, зачем тонкому клиенту на сервере удаленных рабочих столов занимать эти порты, ведь используются они сервером 1С для рабочих процессов. И почему когда свободных портов нет на РДП сервере, клиент не может подключиться к серверу 1С. Но часть клиентов спокойно висят на рандомных портах типа 45434. Такое ощущение, что при коннекте, клиент все таки занимает какой-то из портов в этом диапазоне, а после этого его перекидывает на любой свободный до 65535, но эти порты из диапазона 1560-1591 не успевают освободиться.
Объясните, по какому принципу клиент занимает порты и можно ли это как-то где-то настроить? То, что происходит в описанной ситуации это какой-то сбой либо некорректная настройка и достаточно в настройках службы 1С указать бОльший пул портов? Но никогда не слышал, чтобы в базах, где работает большое кол-во пользователей, увеличивали диапазон портов для рабочих процессов на сервере 1С.
P_Mis
1 — 13.09.19 — 14:57
В первый раз появилось на платформе 8.3.14.1854, после этого откатились на 8.3.12.1685, на которой были несколько месяцев, ситуация не изменилась.
eklmn
2 — 13.09.19 — 15:53
вам хватит на 1 локальный порт 1561 всех подключить
сбрасывайте настройки в дефолт, переустанавливайте платформу.
P_Mis
3 — 13.09.19 — 16:03
(2) Так какие настройки? На РДП сервере стоит только тонкий клиент, какие и где там настройки можно сбросить?
fbear
4 — 13.09.19 — 16:05
https://its.1c.ru/db/metod8dev#content:5908:hdoc
6. Необходимо настроить сетевой стек для обеспечения возможности обработки большого числа подключений
Настройки, которые необходимо выполнить (в дополнение к настройке 5.2. Настроить рабочий сервер в соответствии с инструкцией):
Запустить regedit и в ветке HKLMSystemCurrentControlSetServicesTcpipParameters указать
MaxFreeTcbs= 100000
TcpTimedWaitDelay= 30
MaxUserPort= 65535
Запустить regedit и в ветке HKLMSystemCurrentControlSetServicesAFDParameters указать
EnableDynamicBacklog= 1
MinimumDynamicBacklog= 20
MaximumDynamicBacklog= 20000
DynamicBacklogGrowthDelta= 10
Устанавливаем диапазон исходящих портов (1025; 65535)
Выполнить: netsh int ipv4 set dynamic port tcp start=1025 num=64510
Выполнить: netsh int ipv4 set dynamic port udp start=1025 num=64510
P_Mis
5 — 13.09.19 — 16:16
(4) Все эти настройки сделаны, это все как раз гуглится по ошибке «Обычно разрешается одно использование адреса сокета (протокол/сетевой адрес/порт)». Но легче не стало вообще. Да и 80 пользователей не так много.
P_Mis
6 — 13.09.19 — 16:17
Проблема в том, что тонкий клиент почему-то занимает 1560-1591 порты на рдп сервере (не на 1С сервере) и из-за этого не могут подключаться новые клиенты. Так как если через netstat выбрать клиента, который занял 1560 порт, завершить его, то другой пользователь сможет зайти.
Fragster
7 — 13.09.19 — 16:19
кто-то подкрутил параметр количество соединений на процесс?
Fragster
8 — 13.09.19 — 16:19
сколько rphost на сервере?
P_Mis
9 — 13.09.19 — 16:27
(7) Вроде никто ничего не крутил. Сейчас стоит 128.
(8) 1.
Fragster
10 — 13.09.19 — 17:59
режим отладки при запуске клиента?
P_Mis
11 — 13.09.19 — 18:43
(10) О, интересная мысль, надо будет проверить. Вообще запускается все без отладки, но вдруг каким-то образом она включается…
P_Mis
12 — 13.09.19 — 18:46
(10) (11) Во блин, точно! На одном из клиентов, который занял 1562 порт запущена отладка. Каким образом она могла включиться сама то?
P_Mis
13 — 16.09.19 — 11:25
Нашел в чем была причина, очень все нетривиально оказалось ) У нас в одном документе добавлено очень много доп.реквизитов, а чтобы они не подряд располагались на форме, была изменена форма в пользовательском режиме, красиво и по группам расположили доп.реквизиты.
Чтобы эта настройка применилась у всех, копировали все настройки эталонного пользователя другим пользователям. Видимо, в момент копирования настроек эталонного пользователя, у него была включена отладка, которая так и прилетела всем. Ну и в какой-то момент пользователей просто стало слишком много и проблема проявилась.
P_Mis
14 — 16.09.19 — 11:28
(13) Соответственно решение это в личных настройках пользователей очистить настройку из закладки Прочие настройки — Прочие настройки. Ну и переписать копирование настроек, если это делается автоматом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using UnityEngine; class StateObject { public Socket workSocket = null; //сокет клиента public const int bufferSize = 1024; //максимальные размер буфера public byte[] buffer = new byte[bufferSize]; //буфер public StringBuilder sb = new StringBuilder(); //полученные данные в виде строки } //Все, что поступает на сервер, обрабатывается и в зависимости от принятого, делаются те или иные действия. //Для удобства написания кода, вынесем всю логику работы с клиентом в отдельный класс. public class ServerHandler { private Socket client_socket; // сокет клиента private Thread thread_for_check; // поток для проверки состояния подключения // конструктор public ServerHandler(Socket client_socket) { this.client_socket = client_socket; // запуск потока проверки состояния подключения this.thread_for_check = new System.Threading.Thread(CheckConnection); this.thread_for_check.Start(); } //будет инициализировать новый экземпляр класса StateObject и вызывать асинхронную операцию приема данных BeginReceive() private void Receive() { try { StateObject state = new StateObject(); state.workSocket = this.client_socket; //BeginReceive() принимает следующие аргументы: буфер (он же массив байт), смещение, размер буфера, флаг сокета(используется для различных режимов приема данных, мы не будем использовать какой-либо из них, так как нет в этом необходимости), метод для завершения операции и объект для хранения данных. state.workSocket.BeginReceive(state.buffer, 0, StateObject.bufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallBack), state); } //что должен делать сервер при отключении от него клиента. Тут два варианта развития событий: первый, клиент вышел корректно, через Disconnect; второе, при отправке или приеме сообщения на стороне сервера произошла ошибка. catch (SocketException) { Disconnect(); } } // метод завершения операции приема. В нем будет происходить извлечение класса StateObject, проверка на количество принятых байт, конвертирование байт в строку, вывод принятых данных и повторный вызов метода Receive(). private void ReceiveCallBack(IAsyncResult ar) { try { string cmd = String.Empty; //создается пустая строка, для хранения принятых данных. StateObject state = ar.AsyncState as StateObject; // инициализируется новый экземпляр класса StateObject из переменной ar. Socket handler = state.workSocket; // Теперь нам надо создать сокет, который в дальнейшем завершит операцию. if (handler.Connected) { //количество принятых байт int bytes = handler.EndReceive(ar); // Когда мы вызываем метод EndReceive(ar) в качестве возвращаемого аргумента, мы получаем количество принятых байт, поэтому мы сохраняем это количество в переменную bytes. if (bytes > 0) // Далее надо проверить, сколько байт мы получили, если их количество равно нулю, то вызываем повторно метод BeginReceive(), передаем те же переменные, что в методе Receive(). { //сохраняем принятые данных в классе state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytes)); cmd = Encoding.UTF8.GetString(state.buffer, 0, bytes); if (!String.IsNullOrEmpty(cmd)) // Если все нормально, то есть количество принятых байт больше нуля, то сохраняем эти байты, перекодировав байты в строку. Перекодировкой занимается метод GetString(). Но прежде чем к нему перейти, надо выбирать вид кодировки. Я использовал UTF-8, так как нам пригодятся русские буквы. Чтобы получить строку методу нужно передать буфер, смещение и длину буфера. Все эти данные достаются из класса StateObject, за исключение последнего. Количество принятых данных мы получаем после завершения операции, поэтому передаем то количество, которое сохранили в переменную bytes. { Debug.Log("Receive: " + cmd); Send(cmd); // пере-отправка сообщения } else //если приняли пустую строку, то принимаем дальше { handler.BeginReceive(state.buffer, 0, StateObject.bufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallBack), state); } } } } catch (Exception) { Debug.Log("Error"); } } ///////////////////////////////////////////////////////////////////////// //Этот метод будет вызывать метод Receive. public void StartReceiver() { Receive(); } // отправитель сообщений private void Send(string msg) { try { Socket socket = this.client_socket; byte[] buffer = Encoding.UTF8.GetBytes(msg); socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(SendCallBack), socket); } //что должен делать сервер при отключении от него клиента. Тут два варианта развития событий: первый, клиент вышел корректно, через Disconnect; второе, при отправке или приеме сообщения на стороне сервера произошла ошибка. catch (SocketException) { Disconnect(); } } // метод завершения операции отправки сообщений private void SendCallBack(IAsyncResult ar) { Socket handler = ar.AsyncState as Socket; handler.EndSend(ar); //после завершения операции отправки, добавим вызов метода Receive, чтобы сервер продолжал принимать данные. //продолжить прием данных Receive(); } ///////////////////////////////////////////////////////////////////////// // При возникновении какой-либо ошибки, следует отключать клиента, так как нам не нужен висящий в списке «ошибочный клиент». С таким клиентом сервер не будет работать, поэтому лучше, чтобы клиент пере зашёл. // Чтобы отключить клиента, надо вызвать метод Shutdown, указав в аргументе отключения для отправки и приема сообщений. Далее в блоке finally разрешить существующее соединение между клиентом и сервером, вызвав метод Close. public void Disconnect() { Debug.Log("Client disconnected"); try { this.thread_for_check.Abort(); // Abort немедленно завершает поток. Это нужно из-за того, что метод CheckConnection никогда не закончится, так как в нем используется бесконечный цикл. this.client_socket.Shutdown(SocketShutdown.Both); } catch (Exception ex) { Debug.Log(ex.ToString()); } finally { this.client_socket.Close(); } } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// // Осталась ситуация, когда клиент сам вызывает отключение. Это очень важный момент, так как ошибок мы при этом не получаем, а свойство сокета IsConnected не покажет вам реальное состояние // Мы же будем проверять состояние сокета, и смотреть на количество передаваемых байтов в сети. Начнем с написания метода, который возвращает нам состояние подключения. private bool IsConnected(Socket socket) { try { // Poll. Он принимает два параметра: первый – интервал в миллисекундах, второй – состояние сокета, либо чтение, либо запись. Так как наш сервер является пассивным, что значит, что он всегда слушает сокет методом Receive, более логично проверять, что состояние сокета доступно на чтение данных. // Available – оно показывает, сколько байт доступно для чтения. Как только наш сокет не может прочитать данные, либо данные вообще отсутствуют – мы делаем вывод, что соединение разорвано. return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0); } catch (SocketException) { return false; } } // Чтобы сильно не нагружать систему, добавим вызов бесконечного цикла, с интервалом в 5 секунд, запущенный в новом потоке. Нам метод будет просто вызывать метод IsConnected, и в случае false, будет отключать клиента. private void CheckConnection() { while (true) { System.Threading.Thread.Sleep(5000); if (!IsConnected(this.client_socket)) { Disconnect(); return; } } } } |
Полагаю, некоторые коллеги уже оказывались в ситуации, когда отладка внезапно пропадала, и различные «шаманские» методики (переустановка платформы, чистка локального кэша и прочее) результата не давали. Опишу свой опыт по выявлению и устранению причины.
Преамбула
Данная статья содержит основные моменты из моей публикации. Рассчитываю, что эта информация поможет оказавшимся в подобной ситуации решить проблему. Или хотя бы послужит примером диагностики в конкретной ситуации.
Начальное состояние
Периодические отваливается отладка. Основные подозреваемые — брандмауэр и антивирус выключены.
Анализ
Текущий порт отлачика tcp://srv1c:1562.
netstat -naot 1 | find «1562» при запуске сеанса отладки показывает наличие состояний SYN_SENT.
Настроен полный технологический журнал, поскольку заранее неизвестны события, содержащие необходимую информацию. Для сбора данных по клиентским сеансам моего пользователя файл настроек расположен в %UserProfile%AppDataLocal1C1cv8conf
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="C:v8clientlogs" history="1">
<event>
<ne property="name" value=""/>
</event>
<property name="all">
</property>
</log>
</config>
Значимые события технологического журнала клиента, собранного с момента запуска сеанса отладки до момента прекращения появления состояний SYN_SENT:
30:25.862000-0,EXCP,1,process=1cv8,ClientID=0,Exception=NetDataExchangeException,Descr='server_addr=any:1560 descr=10048(0x00002740): Обычно разрешается только одно использование адреса сокета (протокол/сетевой адрес/порт). line=261 file=srcDataExchangeServerImpl.cpp'
30:25.862003-0,EXCP,1,process=1cv8,ClientID=0,Exception=NetDataExchangeException,Descr='server_addr=any:1561 descr=10048(0x00002740): Обычно разрешается только одно использование адреса сокета (протокол/сетевой адрес/порт). line=261 file=srcDataExchangeServerImpl.cpp'
30:25.862006-0,EXCP,1,process=1cv8,ClientID=0,Exception=NetDataExchangeException,Descr='server_addr=any:1562 descr=10048(0x00002740): Обычно разрешается только одно использование адреса сокета (протокол/сетевой адрес/порт). line=261 file=srcDataExchangeServerImpl.cpp'
30:30.199000-0,EXCP,3,process=1cv8,Usr=Админ,ClientID=3,Exception=NetDataExchangeException,Descr='server_addr=tcp://127.0.0.1:1562 descr=127.0.0.1:1562:10060(0x0000274C): Попытка установить соединение была безуспешной, т.к. от другого компьютера за требуемое время не получен нужный отклик, или было разорвано уже установленное соединение из-за неверного отклика уже подключенного компьютера. ;
line=1043 file=srcDataExchangeTcpClientImpl.cpp'
30:34.208000-0,EXCP,3,process=1cv8,Usr=Админ,ClientID=4,Exception=NetDataExchangeException,Descr='server_addr=tcp://127.0.0.1:1562 descr=127.0.0.1:1562:10060(0x0000274C): Попытка установить соединение была безуспешной, т.к. от другого компьютера за требуемое время не получен нужный отклик, или было разорвано уже установленное соединение из-за неверного отклика уже подключенного компьютера. ;
line=1043 file=srcDataExchangeTcpClientImpl.cpp'
Ошибка 10048(0x00002740) является причиной, 10060(0x0000274C) — следствие.
Настройка TCP протокола
MaxUserPort
При соединение по TCP/IP открывается сокет и выбирается динамический порт. По умолчанию, диапазон динамических портов от 1024 по 5000. Увеличиваем до максимума.
Key: HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesTcpipParameters
Value: MaxUserPort
Data Type: REG_DWORD
Range: 5000 to 65534
Default value: 5000
Recommended value: 65534
TcpTimedWaitDelay
Когда соединение TCP закрывается, то сокет сразу не освобождается, а переходит в статус TIME_WAIT и ресурсы освободятся только через определённое время. По умолчанию, только через 4 минуты. Снизим это время до минимума — 30 секунд.
Key: HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesTcpipParameters
Value: TcpTimedWaitDelay
Data Type: REG_DWORD
Range: 30 to 300
Default value: 240
Recommended value: 30
Отключение автотюнинга tcp протокола:
netsh int tcp set global autotuninglevel=disabled
Выполняем перезагрузку.
Результат
Мониторинг сервера в течении нескольких дней показал, что после проведенных изменений даже при большом количестве сессий 1С отладка перестала отваливаться.
Понимаю, что тем с таким заголовком уже уйма, но ответа я так и не увидел. А точнее — что делать в моем случае.
Итак, делаю сервер, который слушает локалку по указанному порту. За основу взял эту библиотеку — https://github.com/nterry/AwesomeSockets.
Вот так выглядит метод запуска прослушивания:
public async void StartListening(int port)
{
if (CancellationTokenSource == null)
CancellationTokenSource = new CancellationTokenSource();
_cancellationToken = CancellationTokenSource.Token;
Clients = new ObservableCollection<Client>();
BindingOperations.EnableCollectionSynchronization(Clients, _lockObject);
try
{
while (_tcpListen == null)
{
ShowCallbackMessageAction?.Invoke("Try to start server");
_tcpListen = AweSock.TcpListen(port);
if (_tcpListen != null)
{
ShowCallbackMessageAction?.Invoke("Server started");
var waitForConnectionTask = Task.Run(() => WaitForConnectionLoop(), _cancellationToken);
var checkClientsConnectionTask = Task.Run(() => CheckClientsConnectionLoop(), _cancellationToken);
await Task.WhenAll(waitForConnectionTask, checkClientsConnectionTask);
}
// https://stackoverflow.com/a/32768637/4944499
await Task.Delay(1000, _cancellationToken).ContinueWith(task => { });
}
}
catch (Exception exception)
{
ShowCallbackMessageAction?.Invoke($"Error on start server: {exception.Message}");
}
}
Вот так метод остановки прослушивания:
public void StopListening()
{
ShowCallbackMessageAction?.Invoke("Begin stop server...");
ShowCallbackMessageAction?.Invoke("Disconnect all clients");
DisconnectAllClients();
CancellationTokenSource?.Cancel();
_tcpListen?.Close();
_tcpListen = null;
ShowCallbackMessageAction?.Invoke("Server stopped");
}
Метод из библиотеки, вызываемый в моем методе StartListening:
public static ISocket TcpListen(int port, int backlog = 10)
{
var listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//var ip = new IPAddress(new byte[] { 0, 0, 0, 0 });
//var localEndPoint = new IPEndPoint(ip, port);
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);
listenSocket.Bind(localEndPoint);
listenSocket.Listen(backlog);
return AwesomeSocket.New(listenSocket);
}
Так вот — это все работает до того момента, пока я приложение свое не закрою без отписки от прослушивания. Т.е., например, случился крэш. После того, как я повторно запускаю сервер, то вижу сообщение:
Везде пишут, что это нормально и порт сам освободится через некоторое время (вроде 240 секунд), но в моем случае этого не происходит. Помогает только перезагрузка компа.
Как быть?