一、背景
系统:CentOS7 64位 物理机
IP:192.168.2.199/24
使用端口:9999
二、问题描述
在tty1上运行服务器程序,在tty2上运行客户端程序
若连接成功,则服务器程序会向客户端程序发送“Hello World!”字符串。
服务端正常监听,但是客户端在连接时却连接不上,connect()函数返回-1。
服务端部分代码
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
struct sockaddr_in serv_addr, clnt_addr;
socklen_t clnt_addr_size;
char message[] = "Hello World!";
if(argc != 2)
{
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
serv_sock = socket(AF_INET, SOCK_STREAM, 0);
if(serv_sock == -1)
handling_error("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htonl(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
handling_error("bind() error");
if(listen(serv_sock, 5) == -1)
handling_error("listen() error");
clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr*) &clnt_addr, &clnt_addr_size);
if(clnt_sock == -1)
handling_error("accept() error");
write(clnt_sock, message, sizeof(message));
close(clnt_sock);
close(serv_sock);
return 0;
}
客户端部分代码
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len;
if(argc != 3)
{
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
serv_addr.sin_port = htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
error_handling("connect() error!");
str_len = read(sock, message, sizeof(message) - 1);
if(str_len == -1)
error_handling("read() error!");
printf("Message from server: %s \n", message);
close(sock);
return 0;
}
三、问题解决
在上面两部分代码中使用了error_handling自定义函数,为了找出错误原因,调用了perror(const char *message)函数,该函数会打印message字符串,并在其后打印errno变量,Linux中系统调用的错误都存储于 errno中,errno由操作系统维护,存储就近发生的错误,即下一次的错误码会覆盖掉上一次的错误。
error_handling(char *message)函数
void error_handling(char *message)
{
perror(message);
exit(1);
}
运行后,客户端报错:connect() error!:Connection refused,即链接请求被拒绝,在看了一遍程序没有发现明显错误后,我想看一下8888端口是否在使用。
lsof -i:8888
结果出来为空,8888端口并没有被使用。然后我再查看服务器进程在使用哪个端口。
netstat -tnlp
然后发现服务端程序正常监听,但是端口不是8888,而是39388,这意味着是操作系统给随机分配了一个端口,而没有使用我们指定的端口。
回到服务端程序,仔细查看初始化套接字的那段代码,找出了错误。
端口为16位,在转换主机字节序和网络字节序的时候使用了htonl(),htonl()函数是进行32位的转换,CentOS7系统采用的字节序为小端字节序。
假如采用htonl()函数,则操作系统会选择[0x20, 0x21)段的值作为端口号,因此,htonl(8888)的实际结果为0而非8888的网络字节序。由于端口的实际值为0,这会导致当服务端调用函数listen时,内核会为服务端选择一个临时端口,这个临时端口通常与我们所指定的端口并不相同。
因此将htonl()函数换为htons()函数就能解决这个问题了。
四、总结
引起Connection refused的可能原因:服务端在客户端所请求的端口上没有服务在等待连接。
也许是服务端根本没有启动;也许是服务端启动成功了,但客户端所请求的端口与服务端正在监听的端口不一致(客户端所请求的端口并不一定与我们所指定的一致,服务端正在监听的端口也并不一定与我们所指定的一致,这正是本文所讨论的内容)。当然,不排除还存在其他情况会导致此错误,只是笔者还未遇到或听说而已。
五、参考文章:
Connection refused问题的解决:https://blog.csdn.net/wohenfanjian/article/details/51118895
errno:https://www.cnblogs.com/fjutacm/p/5969c7593fdb6516c11a55b0e6813938.html http://www.cnblogs.com/Jimmy1988/p/7485133.html
lsof安装:https://blog.csdn.net/qq_38158631/article/details/78684723
netstat命令安装:https://blog.csdn.net/zpwangshisuifeng/article/details/78526873?locationNum=7&fps=1