使用TCP与C连接到端口

Ril*_*eyE 4 c sockets tcp

我对插座和任何类型的网络编程都是99%的新手,所以请耐心等待.

我的目标是连接到本地计算机(192.168.0.1)上的端口(本例中为2111).从那里开始,我打算发送和接收基本信息,但那是另一天.

我目前试过这个:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char **argv)
{
    int sd;
    int port;
    int start;
    int end;
    int rval;
    struct hostent *hostaddr;
    struct sockaddr_in servaddr;

    start = 2111;
    end   = 2112;
    for(port = start; port <= end; port++)
    {
        sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(sd == -1)
        {
            perror("Socket()\n");
            return (errno);
        }

        memset(&servaddr, 0, sizeof(servaddr));

        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(port);

        hostaddr = gethostbyname("192.168.0.1");

        memcpy(&servaddr.sin_addr, hostaddr->h_addr, hostaddr->h_length);

        rval = connect(sd, (struct sockaddr *)&servaddr, sizeof(servaddr));
        if(rval == -1)
        {
            printf("Port %d is closed\n", port);
            close(sd);
        }
        else printf("Port %d is open\n", port);

        close(sd);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

但是,我的connect()呼叫挂起约90秒,然后返回-1.

该设备直接连接到我的Mac Mini的以太网端口,制造商已确认该端口为2111或2112.

我究竟做错了什么?此外,它可以在ELI5(解释为我5)格式吗?我的例子好多了.

Ada*_*eld 6

当您呼叫connect()连接到主机时,您的计算机会发送一个SYN数据包以开始TCP连接的三次握手.从这里开始,有三种可能的情况:

  1. 如果对等方正在侦听该端口,它将使用SYN + ACK数据包进行响应,您的计算机将以最终ACK进行响应,并建立连接 - connect()成功返回.
  2. 如果对等方没有侦听该端口,它将使用ICMP数据包进行响应,该数据包的类型和代码指示端口已关闭,这会导致您的connect()呼叫几乎立即失败并出现错误ECONNREFUSED(连接被拒绝).在正常情况下,这需要1个网络往返时间(RTT),这通常是几十或几百毫秒.
  3. 如果您的计算机从未收到适当的SYN + ACK TCP数据包或连接拒绝的ICMP数据包,它会假定其原始SYN数据包被某个地方的网络丢弃,并将尝试多次重新发送SYN数据包,直到它获得其中一个数据包或者它会触发与操作系统相关的超时,此时connect()调用失败ETIMEDOUT.这通常为1-2分钟,具体取决于操作系统及其TCP设置.

你明显打击了#3案例.这可能是由几个不同的问题引起的:

  1. 您的原始SYN数据包在网络中丢失,可能是由于链路故障,路由器过载或防火墙
  2. 对等体的SYN + ACK或ICMP响应数据包在网络中丢失,可能是由于链路故障,路由器过载或防火墙
  3. 目标地址可能是不可路由/不可达的
  4. 对等体可能无法正确响应SYN + ACK或ICMP数据包

如果您通过以太网直接连接到设备,那么排除#1和#2.#4是可能的,但我认为#3是最可能的解释.

简要介绍一下数据包路由

您的计算机有多个网络接口 - 以太网(有时是多个以太网接口),Wi-Fi,环回设备,VPN隧道等.每当您创建一个套接字时,它必须绑定到一个或多个特定的网络接口,以便操作系统知道实际发送数据包的NIC.对于服务器的侦听套接字,通常绑定到所有网络接口(以侦听所有网络接口上的连接),但您也可以绑定到特定网络接口以仅侦听该网络接口.

对于客户端套接字,当您将它们连接到其他对等体时,通常不会将它们绑定到特定接口.默认情况下,您的计算机使用其内部路由表以及目标IP地址来确定要使用的网络接口.例如,如果您有一台带有两个NIC的网关机器,其中一个连接到IP 54.xyz的公共互联网,另一个连接到IP 192.168.1.1的内部专用网络,则该机器将很可能有路由表说"对于发往192.168.0.0/16的数据包,使用NIC 2,对于所有其他数据包,使用NIC 1".如果要绕过路由表,可以在调用bind()之前通过调用套接字将套接字绑定到所需的网络接口connect().

把它们放在一起

那么,这对你来说意味着什么呢?

首先,确保192.168.0.1实际上是您应该连接的正确目标地址.这个地址是如何确定的?您的计算机是否充当DHCP服务器以将该地址分配给其他主机?该主机是否使用静态IP配置?

接下来,确保路由表正确无误.如果另一台机器正在为自己分配一个静态IP,那么Mac很可能不知道如何路由到该目的地并且可能试图通过错误的接口进行路由.您可以使用该route(8)实用程序手动调整Mac OS X上的路由,但每次重启都会重置这些路由; 此博客文章显示了使用启动项自动在启动时添加新路由的示例.您将需要使用与连接到目标主机的以太网接口关联的IP地址.

或者,您可以bind()connect()绑定到要使用的接口的本地地址之前调用您的套接字,而不是使用路由表,但这不适用于其他程序,除非它们也提供该功能.例如,该curl(1)实用程序允许您传递--interface <name>命令行标志以指示它绑定到特定接口.