net.DialTCP выдает ошибку «отказано в соединении» на Linux, но не на Windows - PullRequest
1 голос
/ 27 апреля 2020

Код

Для воспроизведения требуется два запущенных приложения и соединяющихся друг с другом через TCP. Итак, я сделал крошечное репо, которое также включает скрипт сборки powershell. ссылка на полное репо

Однако, чтобы избежать лишнего клика, вот код для clientA.go.

package main

import (
    "fmt"
    "net"
    "time"
)

func main() {
    clientA, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf(":%v", "2222"))
    if err != nil {
        fmt.Println(err)
        return
    }

    clientB, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf(":%v", "3333"))
    if err != nil {
        fmt.Println(err)
        return
    }

    for {
        clientAtoB, err := net.DialTCP("tcp4", clientA, clientB)
        if err != nil {
            fmt.Println(err)
        } else {
            defer clientAtoB.Close()
            clientAtoB.SetLinger(0)
            clientAtoB.SetNoDelay(true)
            clientAtoB.SetKeepAlive(false)
            fmt.Println("connected as Client A!")
            buffer := make([]byte, 64)
            _, err = clientAtoB.Read(buffer)
            if err != nil {
                continue
            }
        }
        time.Sleep(time.Second)
    }
}

Код для clientB.go идентичен за исключением того, что локальные и удаленные конечные точки меняются местами:

clientBtoA, err := net.DialTCP("tcp4", clientB, clientA)

Проблема

Я строю один и тот же код go для Windows и Linux, но во время выполнения приложения дают разные результаты. В частности, как набираются TCP-соединения на каждой платформе.

На Windows, когда я запускаю два исполняемых файла clientA.exe и clientB.exe (созданный из сценария build.ps1), я получаю желаемый результат. Как видно на снимке экрана: windows results

Однако, когда я загружаю и запускаю Linux двоичные файлы, результат будет другим:

ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$ sudo chmod +x clientA clientB
ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$ ls -la
total 10984
drwxrwxr-x 3 ubuntu ubuntu    4096 Apr 27 03:09 .
drwxrwxr-x 4 ubuntu ubuntu    4096 Apr 27 03:08 ..
drwxrwxr-x 8 ubuntu ubuntu    4096 Apr 27 03:08 .git
-rw-rw-r-- 1 ubuntu ubuntu   11255 Apr 27 03:12 A.txt
-rw-rw-r-- 1 ubuntu ubuntu   11255 Apr 27 03:12 B.txt
-rw-rw-r-- 1 ubuntu ubuntu     247 Apr 27 03:08 build.ps1
-rwxrwxr-x 1 ubuntu ubuntu 2950662 Apr 27 03:08 clientA
-rw-rw-r-- 1 ubuntu ubuntu 2642944 Apr 27 03:08 clientA.exe
-rw-rw-r-- 1 ubuntu ubuntu     718 Apr 27 03:08 clientA.go
-rwxrwxr-x 1 ubuntu ubuntu 2950662 Apr 27 03:08 clientB
-rw-rw-r-- 1 ubuntu ubuntu 2642944 Apr 27 03:08 clientB.exe
-rw-rw-r-- 1 ubuntu ubuntu     718 Apr 27 03:08 clientB.go
ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$ ./clientA > A.txt & ./clientB > B.txt &
[1] 24914
[2] 24915
ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$ cat A.txt
dial tcp4 :2222->:3333: connect: connection refused
ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$ cat B.txt
dial tcp4 :3333->:2222: connect: connection refused
ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$                                               

Я не Не следует ожидать ошибку connection refused, поскольку эти два приложения работают в одной среде, поэтому брандмауэры не действуют, а разрешения одинаковы.

Как получить один и тот же результат независимо от платформы? Или почему они отличаются в первую очередь?

Edit

Успешное соединение на Windows - это не просто удача хорошего времени. На Windows я могу запустить A в течение 5 минут, затем при запуске B оба успешно подключатся.

Обновление (2020-04-27)

После получения отзыва от Go разработчики, мне сказали, что это, скорее всего, проблема конфигурации Linux, а не спецификация c до Go. Кроме разрешений, я не могу ничего сказать, что помешало бы двум приложениям в одной среде установить TCP-соединение, подобное этому? (Эти вещи низкого уровня Linux на самом деле не моя сильная сторона.)

1 Ответ

3 голосов
/ 02 мая 2020

Почему это не работает на Linux, совершенно очевидно. И A, и B являются клиентами, которые подключаются к коллеге, которому необходимо прослушать. На Linux (или UNIX), если вы попытаетесь запустить ClientA, он попытается дозвониться до адреса и порта ClientB. Если нет никакого процесса, уже прослушивающего этот адрес и порт, чтобы принять соединение , в этот момент ClientA немедленно завершится ошибкой connection refused (это не совсем так, но в большинстве случаев, см. Мой РЕДАКТИРОВАТЬ в конце ответа).

Вкл. Windows, под капотом Golang использует (для протоколов tcp, tcp4 и tcp6) ConnectEx API для ориентированного на соединение Розетки. Этот API ведет себя не так, как API Linux connect. Если ConnectEx не может подключиться немедленно, возвращается код ошибки ERROR_IO_PENDING, и ОС за кадром ждет / повторяет попытку до тех пор, пока соединение не будет принято и установлено (или оно сдается и окончательно прервется), а затем отправит уведомление - это называется перекрывающимся I / О.

Соответствующая часть документации MSDN ConnectEx:

Сокеты, ориентированные на соединение, часто не могут немедленно завершить соединение, и поэтому операция инициируется, и функция немедленно возвращается с ошибкой ERROR_IO_PENDING или Ошибка WSA_IO_PENDING. Когда операция подключения завершается и достигается успех или сбой, о состоянии сообщается с использованием механизма уведомления о завершении, указанного в lpOverlapped.

Теперь, что происходит в вашем случае на Windows, это то, что вы пытаетесь ConnectEx с обеих сторон, и ОС соединяет эти сокеты для вас. Это будет работать, только если другая сторона подключится в течение определенного периода. Если вы попытаетесь разумно увеличить интервал time.Sleep в обоих клиентах (например, 17 и 28), вы даже увидите, что даже на Windows им будет трудно подключиться.

Ответ на ваш вопрос в том, что ваш код в том виде, в котором он написан сейчас, зависит от специфики ОС c поведения TCP-набора в Golang на Windows и не является переносимым. Чтобы ваше ПО было переносимым на любой платформе, поддерживаемой Golang вы, вероятно, хотите изменить лог c, чтобы и ClientA, и ClientB прослушивали на входящее соединение, а также периодически пытались подключить к противоположной стороне.

РЕДАКТИРОВАТЬ : Я не говорю, что ваш код вообще не может работать на Linux. На самом деле он использует редкий режим соединения, называемый TCP одновременное соединение , где вы можете соединить два процесса, не имея ни одного из них listen. Обе стороны набора отправляют свои SYN одновременно, поэтому каждая сторона отвечает SYN / ACK и затем ACK для завершения трехстороннего рукопожатия и соединения ESTABLI SH. Это требует очень точной синхронизации и синхронизации вызова connect в обоих клиентах. Обе стороны будут подключаться, если в ядре Linux разрешено одновременное подключение TCP и что достигается синхронизация c между connect с (вряд ли это достигается простым запуском обоих клиентов вручную или из одного и того же сценария; даже имитация внутри одного процесса и потока это не так просто).

...