桌面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
相关产品推荐
相关产品推荐

