一、背景
系统:CentOS7 64位(运行在虚拟机VMWare上)
虚拟网卡IP:192.168.137.129
使用的端口:9999
Sockets API处于堵塞模式(默认的)
二、问题描述
在终端1上,启动服务器后,可以成功监听,即此时服务器正等待客户端的连接。但在终端2上,启动客户端后,返回一个错误——Connection refused(该信息是通过strerror(errno)输出的,strerror()在<string.h>中,errno在<errno.h>中)。
服务端部分代码。如下:
. . . int sockfd; if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { cout << strerror(errno) << endl; return -1; } struct sockaddr_in localaddr; bzero(&localaddr, sizeof(localaddr)); localaddr.sin_family = AF_INET; localaddr.sin_port = htonl(9999); if (inet_pton(AF_INET, "192.168.137.129", &localaddr.sin_addr) <= 0) { close(sockfd); cout << strerror(errno) << endl; return -1; } if (bind(sockfd, (struct sockaddr *)&localaddr, sizeof(localaddr)) < 0) { close(sockfd); cout << strerror(errno) << endl; return -1; } if (listen(sockfd, 100) < 0) { close(sockfd); cout << strerror(errno) << endl; return -1; } . . .
客户端部分代码,如下:
. . . int sockfd; if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { cout << strerror(errno) << endl; return -1; } struct sockaddr_in srvaddr; bzero(&srvaddr, sizeof(srvaddr)); srvaddr.sin_family = AF_INET; srvaddr.sin_port = htonl(9999); if (inet_pton(AF_INET, "192.168.137.129", &srvaddr.sin_addr) <= 0) { close(sockfd); cout << strerror(errno) << endl; return -1; } if (connect(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr)) < 0) { close(sockfd); cout << strerror(errno) << endl; return -1; } . . .
将htonl(9999)替换成hons(9999),分别实施于服务器与客户端。
四、问题产生的原因
对于服务端:服务端原来的源代码中,端口值设定为htonl(9999)。事实上,sockaddr_in.sin_port为短整型,占16位;且CentOS7系统采用的字节序为小端字节序。因此,htonl(9999)的实际结果为0而非9999的网络字节序。由于端口的实际值为0,这会导致当服务端调用函数listen时,内核会为服务端选择一个临时端口,这个临时端口通常与我们所指定的端口并不相同。
对于客户端:事实上,客户端所请求的端口号为0(保留端口)。当客户端在调用函数connect时,服务器端在客户端所请求的端口上没有服务在等待连接,所以connect最终出错返回,出错的原因正如我们所看到的:Connection refused。
五、总结
引起Connection refused的可能原因:服务端在客户端所请求的端口上没有服务在等待连接。
也许是服务端根本没有启动;也许是服务端启动成功了,但客户端所请求的端口与服务端正在监听的端口不一致(客户端所请求的端口并不一定与我们所指定的一致,服务端正在监听的端口也并不一定与我们所指定的一致,这正是本文所讨论的内容)。当然,不排除还存在其他情况会导致此错误,只是笔者还未遇到或听说而已。