UDP仅发送首个数据包后提示连接拒绝问题求助
问题描述
我制作了一个可复现的示例(保留了原大型源码的所有头文件),目前仅能成功发送第一个字符a,后续调用send()时会报错send() failed: Connection refused。
我发现仅在程序初始化时创建一次socket、结束时关闭的场景下会出现该问题;如果每次调用send()前创建socket、发送后立即关闭则无报错。
可复现代码
#include <pcap.h> #include <cstdio> #include <getopt.h> #include <cstring> #include <cstdlib> #include <string> #include <iostream> #include <map> #include <vector> #define BUFSIZE 256 #define __FAVOR_BSD #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include<netdb.h> #include<err.h> #include <arpa/inet.h> #include <netinet/ether.h> #include <netinet/ip.h> #include <netinet/ip6.h> #include <netinet/tcp.h> #include <netinet/udp.h> #include <netinet/ip_icmp.h> #include <netinet/icmp6.h> using namespace std; #define BUFFER 1024 // buffer length int sock; // socket descriptor void start_connection(){ struct sockaddr_in server, from; // address structures of the server and the client struct hostent *servent; // network host entry required by gethostbyname() memset(&server,0,sizeof(server)); // erase the server structure server.sin_family = AF_INET; // make DNS resolution of the first parameter using gethostbyname() if ((servent = gethostbyname("127.0.0.1")) == NULL) // check the first parameter errx(1,"gethostbyname() failed\n"); // copy the first parameter to the server.sin_addr structure memcpy(&server.sin_addr,servent->h_addr,servent->h_length); server.sin_port = htons(2055); if ((sock = socket(AF_INET , SOCK_DGRAM , 0)) == -1) //create a client socket err(1,"socket() failed\n"); // create a connected UDP socket if (connect(sock, (struct sockaddr *)&server, sizeof(server)) == -1) err(1, "connect() failed"); } void send_data(char * string){ char buffer[BUFFER]; int msg_size,i; //send data to the server memcpy(buffer,string,2); msg_size=2; i = send(sock,buffer,msg_size,0); // send data to the server if (i == -1) // check if data was sent correctly err(1,"send() failed"); else if (i != msg_size) err(1,"send(): buffer written partially"); printf("%s",string); } int main(int argc, char *argv[]) { //Start UDP connection start_connection(); send_data("a"); send_data("b"); send_data("c"); //Close the UDP connection close(sock); return 0; }
问题原因
你使用的是已连接的UDP套接字(通过connect()绑定了固定服务端地址),UDP本身是无连接协议,但connect()会让系统记住目标地址,同时监听对应ICMP错误。如果服务端未监听2055端口,第一次发送后系统会收到ICMP端口不可达报文,此时系统会把这个套接字的错误状态标记下来,后续再调用send()就会直接返回Connection refused,不会再尝试发送数据。
而每次新建套接字的方式不会触发这个问题,因为每次都是全新的套接字,系统还没收到对应的ICMP错误,所以第一次发送能正常执行。
解决方案
方案1:改用未连接UDP套接字(推荐)
去掉connect()调用,每次发送用sendto()指定服务端地址,这样即使收到ICMP错误,也不会影响后续发送(没有绑定固定连接,系统不会缓存错误状态)。修改后的代码如下:
#include <pcap.h> #include <cstdio> #include <getopt.h> #include <cstring> #include <cstdlib> #include <string> #include <iostream> #include <map> #include <vector> #define BUFSIZE 256 #define __FAVOR_BSD #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include<netdb.h> #include<err.h> #include <arpa/inet.h> #include <netinet/ether.h> #include <netinet/ip.h> #include <netinet/ip6.h> #include <netinet/tcp.h> #include <netinet/udp.h> #include <netinet/ip_icmp.h> #include <netinet/icmp6.h> using namespace std; #define BUFFER 1024 // buffer length int sock; // socket descriptor struct sockaddr_in server; // 全局保存服务端地址 void start_connection(){ struct hostent *servent; // network host entry required by gethostbyname() memset(&server,0,sizeof(server)); // 清空服务端地址结构 server.sin_family = AF_INET; // 解析服务端地址 if ((servent = gethostbyname("127.0.0.1")) == NULL) errx(1,"gethostbyname() failed\n"); memcpy(&server.sin_addr,servent->h_addr,servent->h_length); server.sin_port = htons(2055); // 创建UDP套接字 if ((sock = socket(AF_INET , SOCK_DGRAM , 0)) == -1) err(1,"socket() failed\n"); // 移除connect调用,改用sendto指定目标 } void send_data(char * string){ char buffer[BUFFER]; int msg_size,i; memcpy(buffer,string,2); msg_size=2; // 使用sendto代替send,明确指定服务端地址 i = sendto(sock,buffer,msg_size,0, (struct sockaddr *)&server, sizeof(server)); if (i == -1) err(1,"sendto() failed"); else if (i != msg_size) err(1,"sendto(): buffer written partially"); printf("%s",string); } int main(int argc, char *argv[]) { start_connection(); send_data("a"); send_data("b"); send_data("c"); close(sock); return 0; }
方案2:清除套接字错误状态(兼容已连接场景)
如果必须使用已连接的UDP套接字,可以在每次send()前清除套接字的错误队列,这样即使之前收到ICMP错误,后续发送也会重新尝试。在send_data函数开头添加以下代码:
int error; socklen_t len = sizeof(error); // 获取并清除套接字错误状态 getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len);
不过这种方式不如方案1可靠,因为ICMP错误可能会重复触发,而且不同系统的处理逻辑可能有差异。
内容的提问来源于stack exchange,提问作者ampersander
相关产品推荐
相关产品推荐

