You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

桌面Ubuntu虚拟机TCP服务器与笔记本Ubuntu虚拟机客户端连接失败求助

跨虚拟机TCP通信问题排查与解决方案

刚接触Socket编程,遇到跨虚拟机TCP通信问题:物理桌面的Ubuntu虚拟机上运行TCP服务器,同一虚拟机内客户端连接127.0.0.1可正常通信,但物理笔记本的Ubuntu虚拟机客户端无法与该服务器通信。

TCP_CLIENT.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <memory.h>

typedef struct _test_struct{
    
    unsigned int a;
    unsigned int b;
} test_struct_t;


typedef struct result_struct_{ 
    unsigned int c;

} result_struct_t;

#define DEST_PORT            2000
#define SERVER_IP_ADDRESS   "127.0.0.1"

test_struct_t client_data;
result_struct_t result;

void
setup_tcp_communication(){

    /*Step 1 : Initialization*/
    /*Socket handle*/
    int sockfd = 0, 
        sent_recv_bytes = 0;

    int addr_len = 0;

    addr_len = sizeof(struct sockaddr);

    /*to store socket addesses : ip address and port*/
    struct sockaddr_in dest;

    /*Step 2: specify server information*/
    /*Ipv4 sockets, Other values are IPv6*/
    dest.sin_family = AF_INET;

    /*Client wants to send data to server process which is running on server machine, and listening on 
     * port on DEST_PORT, server IP address SERVER_IP_ADDRESS.
     * Inform client about which server to send data to : All we need is port number, and server ip address. Pls note that
     * there can be many processes running on the server listening on different no of ports, 
     * our client is interested in sending data to server process which is lisetning on PORT = DEST_PORT*/ 
    dest.sin_port = DEST_PORT;
    struct hostent *host = (struct hostent *)gethostbyname(SERVER_IP_ADDRESS);
    dest.sin_addr = *((struct in_addr *)host->h_addr);

    /*Step 3 : Create a TCP socket*/
    /*Create a socket finally. socket() is a system call, which asks for three paramemeters*/
    sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);


    connect(sockfd, (struct sockaddr *)&dest,sizeof(struct sockaddr));

    /*Step 4 : get the data to be sent to server*/
    /*Our client is now ready to send data to server. sendto() sends data to Server*/

PROMPT_USER:

    /*prompt the user to enter data*/
    printf("Enter a : ?\n");
    scanf("%u", &client_data.a);
    printf("Enter b : ?\n");
    scanf("%u", &client_data.b);

    
    /*step 5 : send the data to server*/
    sent_recv_bytes = sendto(sockfd, 
           &client_data,
           sizeof(test_struct_t), 
           0, 
           (struct sockaddr *)&dest, 
           sizeof(struct sockaddr));
    
    printf("No of bytes sent = %d\n", sent_recv_bytes);
   
    /*Step 6 : Client also want to reply from server after sending data*/
    
    /*recvfrom is a blocking system call, meaning the client program will not run past this point
     * untill the data arrives on the socket from server*/
    sent_recv_bytes =  recvfrom(sockfd, (char *)&result, sizeof(result_struct_t), 0,
                    (struct sockaddr *)&dest, &addr_len);

    printf("No of bytes recvd = %d\n", sent_recv_bytes);
    
    printf("Result recvd = %u\n", result.c);
    /*Step 7: Client would want to send the data again to the server, go into infinite loop*/
    goto PROMPT_USER;
}
    

int
main(int argc, char **argv){

    setup_tcp_communication();
    printf("application quits\n");
    return 0;
}

TCP_SERVER.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <memory.h>
#include <errno.h>
/*Server process is running on this port no. Client has to send data to this port no*/
#define SERVER_PORT     2000 

typedef struct _test_struct{
        
        unsigned int a;
        unsigned int b;
    } test_struct_t;
    
    
    typedef struct result_struct_{ 
        unsigned int c;
    
    } result_struct_t;

test_struct_t test_struct;
result_struct_t res_struct;
char data_buffer[1024];

void
setup_tcp_server_communication(){

    /*Step 1 : Initialization*/
    /*Socket handle and other variables*/
    /*Master socket file descriptor, used to accept new client connection only, no data exchange*/
    int master_sock_tcp_fd = 0, 
        sent_recv_bytes = 0, 
        addr_len = 0, 
        opt = 1;

    /*client specific communication socket file descriptor, 
     * used for only data exchange/communication between client and server*/
    int comm_socket_fd = 0;     
    /* Set of file descriptor on which select() polls. Select() unblocks whever data arrives on 
     * any fd present in this set*/
    fd_set readfds;             
    /*variables to hold server information*/
    struct sockaddr_in server_addr, /*structure to store the server and client info*/
                       client_addr;

    /*step 2: tcp master socket creation*/
    if ((master_sock_tcp_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP )) == -1)
    {
        printf("socket creation failed\n");
        exit(1);
    }

    /*Step 3: specify server Information*/
    server_addr.sin_family = AF_INET;/*This socket will process only ipv4 network packets*/
    server_addr.sin_port = SERVER_PORT;/*Server will process any data arriving on port no 2000*/
    
    /*3232249957; //( = 192.168.56.101); Server's IP address, 
    //means, Linux will send all data whose destination address = address of any local interface 
    //of this machine, in this case it is 192.168.56.101*/
    server_addr.sin_addr.s_addr = INADDR_ANY; 

    addr_len = sizeof(struct sockaddr);

    /* Bind the server. Binding means, we are telling kernel(OS) that any data 
     * you recieve with dest ip address = 192.168.56.101, and tcp port no = 2000, pls send that data to this process
     * bind() is a mechnism to tell OS what kind of data server process is interested in to recieve. Remember, server machine
     * can run multiple server processes to process different data and service different clients. Note that, bind() is 
     * used on server side, not on client side*/

    if (bind(master_sock_tcp_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
    {
        printf("socket bind failed\n");
        return;
    }

    /*Step 4 : Tell the Linux OS to maintain the queue of max length to Queue incoming
     * client connections.*/
    if (listen(master_sock_tcp_fd, 5)<0)  
    {
        printf("listen failed\n");
        return;
    }

    /* Server infinite loop for servicing the client*/

    while(1){

        /*Step 5 : initialze and dill readfds*/
        FD_ZERO(&readfds);                     /* Initialize the file descriptor set*/
        FD_SET(master_sock_tcp_fd, &readfds);  /*Add the socket to this set on which our server is running*/

        printf("blocked on select System call...\n");


        /*Step 6 : Wait for client connection*/
        /*state Machine state 1 */
        
        /*Call the select system call, server process blocks here. Linux OS keeps this process blocked untill the data arrives on any of the file Drscriptors in the 'readfds' set*/
        select(master_sock_tcp_fd + 1, &readfds, NULL, NULL, NULL); 

        /*Some data on some fd present in set has arrived, Now check on which File descriptor the data arrives, and process accordingly*/
        if (FD_ISSET(master_sock_tcp_fd, &readfds))
        { 
            /*Data arrives on Master socket only when new client connects with the server (that is, 'connect' call is invoked on client side)*/
            printf("New connection recieved recvd, accept the connection. Client and Server completes TCP-3 way handshake at this point\n");

            /* step 7 : accept() returns a new temporary file desriptor(fd). Server uses this 'comm_socket_fd' fd for the rest of the
             * life of connection with this client to send and recieve msg. Master socket is used only for accepting
             * new client's connection and not for data exchange with the client*/
            /* state Machine state 2*/
            comm_socket_fd = accept(master_sock_tcp_fd, (struct sockaddr *)&client_addr, &addr_len);
            if(comm_socket_fd < 0){

                /* if accept failed to return a socket descriptor, display error and exit */
                printf("accept error : errno = %d\n", errno);
                exit(0);
            }

            printf("Connection accepted from client : %s:%u\n", 
                inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

            while(1){
                printf("Server ready to service client msgs.\n");
                /*Drain to store client info (ip and port) when data arrives from client, sometimes, server would want to find the identity of the client sending msgs*/
                memset(data_buffer, 0, sizeof(data_buffer));

                /*Step 8: Server recieving the data from client. Client IP and PORT no will be stored in client_addr
                 * by the kernel. Server will use this client_addr info to reply back to client*/

                /*Like in client case, this is also a blocking system call, meaning, server process halts here untill
                 * data arrives on this comm_socket_fd from client whose connection request has been accepted via accept()*/
                /* state Machine state 3*/
                sent_recv_bytes = recvfrom(comm_socket_fd, (char *)data_buffer, sizeof(data_buffer), 0,
                        (struct sockaddr *)&client_addr, &addr_len);

                /* state Machine state 4*/
                printf("Server recvd %d bytes from client %s:%u\n", sent_recv_bytes, 
                        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

                if(sent_recv_bytes == 0){
                /*If server recvs empty msg from client, server may close the connection and wait
                 * for fresh new connection from client - same or different*/
                close(comm_socket_fd);
                break; /*goto step 5*/

                }

                test_struct_t *client_data = (test_struct_t *)data_buffer;
                
                /* If the client sends a special msg to server, then server close the client connection
                 * for forever*/
                /*Step 9 */
                if(client_data->a == 0 && client_data->b ==0){

                    close(comm_socket_fd);
                    printf("Server closes connection with client : %s:%u\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                    /*Goto state machine State 1*/
                    break;/*Get out of inner while loop, server is done with this client, time to check for new connection request by executing selct()*/
                }

                result_struct_t result;
                result.c = client_data->a + client_data->b;

                /* Server replying back to client now*/
                sent_recv_bytes = sendto(comm_socket_fd, (char *)&result, sizeof(result_struct_t), 0,
                        (struct sockaddr *)&client_addr, sizeof(struct sockaddr));

                printf("Server sent %d bytes in reply to client\n", sent_recv_bytes);
                /*Goto state machine State 3*/
            }
        }
    }/*step 10 : wait for new client request again*/    
}

int
main(int argc, char **argv){

    setup_tcp_server_communication();
    return 0;
}

已尝试的解决方法

  • 将tcp_client.c中的IP地址替换为VMware VMnet1和VMware VMnet8的对应IP地址:客户端可运行并输入数据,但服务器未识别到连接,客户端随后退出
  • 在Ubuntu终端执行ip addr获取服务器虚拟机的IP地址并替换:结果与上述情况一致
  • 使用物理桌面的实际IP地址:客户端无法输入数据,处于闲置状态

排查与解决方案

不需要搭建Apache服务器,问题核心在网络连通性和程序细节上,按以下步骤处理:

1. 确认虚拟机网络模式与IP可达性

  • 两台虚拟机需设置为桥接模式(Bridge),确保它们处于同一网段,获得和物理机同段的IP地址
  • 在客户端虚拟机执行ping <服务器虚拟机IP>,在服务器虚拟机执行ping <客户端虚拟机IP>,验证双向网络连通。若不通,检查VMware网络配置或虚拟机防火墙

2. 检查服务器端口监听状态

在服务器虚拟机执行命令,确认端口2000处于监听状态:

netstat -tulpn | grep 2000

需确保输出中端口绑定的是0.0.0.0(对应代码中的INADDR_ANY),而非仅127.0.0.1

3. 关闭防火墙或开放端口

  • 临时关闭服务器虚拟机防火墙测试:sudo ufw disable
  • 若测试通过,永久开放2000端口:sudo ufw allow 2000/tcp
  • 同时检查物理机防火墙,确保未拦截虚拟机间的通信

4. 修正客户端程序的关键错误

客户端代码存在两处致命问题:

  • 端口未转换为网络字节序:将dest.sin_port = DEST_PORT;改为:
    dest.sin_port = htons(DEST_PORT);
    
  • 未检查connect调用结果:添加错误判断,避免后续无意义执行:
    if (connect(sockfd, (struct sockaddr *)&dest, sizeof(struct sockaddr)) == -1) {
        perror("connect failed");
        exit(1);
    }
    
  • 补充:TCP连接建立后,使用send/recv替代sendto/recvfrom更规范(后者主要用于UDP),但不影响功能

5. 正确设置客户端的服务器IP

客户端需使用服务器虚拟机的实际网卡IP(通过服务器虚拟机ip addr获取的非127.0.0.1地址,例如192.168.x.x),而非物理机IP或VMnet虚拟网卡IP

内容的提问来源于stack exchange,提问作者chree

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 11:02:00