Skip to content

Latest commit

 

History

History
68 lines (60 loc) · 4.6 KB

静态编译的socket传输.md

File metadata and controls

68 lines (60 loc) · 4.6 KB

最近工作中遇到一个小需求,就是向一个接口发送一个携带自定义headerGET请求,用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的问题了。 这个函数属于glibcNSS层,也就是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超时。 然而入参就真的没问题吗?

getaddrinfo

针对dns的解析有两种,一种是传输进来的hostip,另一种是域名,针对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_flagsai_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

这里面还有一个tipssockaddr_insockaddrsockaddr更早,sockaddr_in是补全形式,将ipport分开了,两个结构体长度是一样的,因此指针可以直接相互引用。

参考