最近工作中遇到一个小需求,就是向一个接口发送一个携带自定义
header
的GET
请求,用linux c写。
如果是动态编译下实际很简单,想法就是写一个socket,然后手动构建一个HTTP GET
请求头,然后send
过去即可,然而再实际编写时却出了问题,就是静态编译时对于域名的处理。
对于GET
请求我找了一个轮子:httpclient.c,这个轮子比较好的达到了我的基础需求,但是在静态编译时候,报了警告:
/usr/bin/ld: /tmp/cc8wgsyL.o: in function `getHostInfo':
httpclient.c:(.text+0x108d): 警告:Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
因为是个告警,我并不是很在意,然后就直接运行试试:
$ ./producer www.baidu.com 80 sdsad
[1] 23534 segmentation fault (core dumped) ./producer www.baidu.com 80 sdsad
出现了段错误
,猜测应该是空指针
导致的问题。之前实际在busybox
上遇到这个问题,那基本确定又是getaddrinfo
的问题了。
这个函数属于glibc
的NSS
层,也就是libnss
的支持,这个库在设计时候就是动态的,因为需要依靠系统配置
来决定dns server
,所以在静态编译
的情况下会出现无法解析域名的情况,这样后续的addrinfo
就会有空指针
导致后续函数出错,从而导致段错误。
那解决方式就只有重写getaddrinfo
这个函数了,这个函数的作用简单来说就是把域名
解析成IP
,而这个过程需要dns
参与其中,而解析的过程实际就是需要向DNS Server
发送一个query
然后提取信息即可,但是我又找到一个轮子:getaddrinfo.h
这个轮子在大方向上实现了目的,但是并不是直接替换函数就行了,因为还要为两个轮子做做适配才行。
首先是clinet
代码,核心连接函数是establishConnection
,它的入参是通过getaddrinfo.h
中提供的重写函数生成的。那先放在这往下看,假设这儿的入参没有问题,那么在下面connect
时候可能会出问题:
if (connect(clientfd, info->ai_addr, info->ai_addrlen) < 0)
这儿connect
走的是系统超时
也就是net.ipv4.tcp_syn_retries
的配置,我的系统默认配置是6
,也就是127
秒:
$ sysctl net.ipv4.tcp_syn_retries
net.ipv4.tcp_syn_retries = 6
这儿先改一下,加一个超时设置:
struct timeval timeo = {3, 0};
socklen_t time_len = sizeof(timeo);
timeo.tv_sec = atoi("3");
setsockopt(clientfd, SOL_SOCKET, SO_SNDTIMEO, &timeo, time_len);
这样clientfd
这个socket fd
就被会设置成3s
超时。
然而入参
就真的没问题吗?
针对dns
的解析有两种,一种是传输进来的host
是ip
,另一种是域名
,针对IP
的情况函数中调用了一个__dns_is_ipv4
识别,然后再用__dns_ipv4
去处理,然后把addrinfo
填充起来。
然而坑的是,这儿端口是被设置成0
的,这其实对于实现dns
解析这个目的是没问题的,因为我们不需要端口,但是解析后的addrinfo
如果给establishConnection
使用就有问题了,因为端口是0
,所以这儿加一个入参,把端口设置一下:
static int __dns_ipv4(const char *ip, const char *port,struct addrinfo **res) {
......
sa->sin_port = htons((atoi(port)));
......
}
同样的对于域名的处理用的是__dns_parse
函数填充,这儿就不止是端口了,还有ai_flags
,ai_socktype
,一样设置一下:
static int __dns_parse(const char *buf, const char *port,int query_len, struct addrinfo **res) {
......
info->ai_flags = AI_PASSIVE;
info->ai_socktype = SOCK_STREAM;
sa->sin_port = htons((atoi(port)));
......
}
最后这样才能生成一个可供使用的addrinfo
。
这里面还有一个
tips
是sockaddr_in
和sockaddr
,sockaddr
更早,sockaddr_in
是补全形式,将ip
和port
分开了,两个结构体长度是一样的,因此指针可以直接相互引用。