一般的开发中, 需要使用bind
的情况是在服务端。今天遇到一个特殊的使用场景,客户端因为安全性要求,连接时不能使用内核分配的端口,需要使用指定的端口和服务器连接。自然就用到了bind
接口。从bind
又引入了reuse属性的使用。这里使用gethostbyname_r来做域名到IP地址的转换(curl也是如此),另外像云风skynet则使用getaddrinfo接口,后者用法更简单一些。当然他们都阻塞的。
以下是验证代码:
//anker<lichengman2006@gmail.com> @2021-06-17T20:18:16
//gcc client.c -o client
//client www.google.com 80 9090 1
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (argc != 5) {
fprintf(stderr,"usage: \n"
"client $serverIp $serverPort $clientPort $reuse\n"
"example: ./client www.google.com 80 9090 1\n");
exit(1);
}
int h_errnop;
int HOSTENT_SIZE = 9000;
struct hostent *buf = NULL;
struct hostent *he = NULL;
char *hostname = argv[1];
int port = atoi(argv[2]);
buf = calloc(1, HOSTENT_SIZE);
if(!buf)
exit(1);
// int gethostbyname_r(const char *name,
// struct hostent *ret, char *buf, size_t buflen,
// struct hostent **result, int *h_errnop);
// name:Here name is either a hostname, or an IPv4 address in standard dot notation (as for inet_addr(3)), or an IPv6 address in colon (and possibly dot) notation
// ret:which will be filled in on success, and a temporary work buffer buf of size buflen
// result: will point to the result on success. In case of an error or if no entry is found result will be NULL. The functions return 0 on success and a nonzero error number on failure
gethostbyname_r(hostname,
(struct hostent *)buf,
(char *)buf + sizeof(struct hostent),
HOSTENT_SIZE - sizeof(struct hostent),
&he, /* DIFFERENCE */
&h_errnop);
if(!he){ /* failure */
he = NULL; /* set return code to NULL */
free(buf);
exit(1);
}
char *curr;
struct sockaddr_in serveraddr;
for(int i = 0; (curr = he->h_addr_list[i]) != NULL; i++) {
//if(he->h_addrtype == AF_INET6){
if(he->h_addrtype == AF_INET){ // only support ipv4
serveraddr.sin_family = he->h_addrtype;
memcpy(&serveraddr.sin_addr, curr, sizeof(struct in_addr));
serveraddr.sin_port = htons((unsigned short)port);
char s[64] = {'\0'};
inet_ntop(AF_INET, &(serveraddr.sin_addr), s, 64);
printf("get ip:%s\n", s);
}else{
// just print
//struct sockaddr_in6 _addr;
//memcpy(&_addr.sin6_addr, curr, sizeof(struct in6_addr));
struct sockaddr_in6 *paddr = (struct sockaddr_in6*)curr;
char s[64] = {'\0'};
inet_ntop(AF_INET6, &(paddr->sin6_addr), s, 64);
printf("get ipv6:%s\n", s);
}
}
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
int enable_reuse = atoi(argv[4]);
if(enable_reuse > 0){
enable_reuse = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable_reuse, sizeof(int)) < 0){
perror("set reuse failed\n");
exit(1);
}else{
printf("set reuse succ!\n");
}
}
int bindport = atoi(argv[3]);
struct sockaddr_in clientaddr;
clientaddr.sin_family = AF_INET;
clientaddr.sin_addr.s_addr = htonl(INADDR_ANY);
clientaddr.sin_port = htons(bindport);
if ((bind(sockfd, (struct sockaddr*)&clientaddr, sizeof(clientaddr))) != 0) {
printf("socket bind failed...\n");
exit(0);
}
else
printf("Socket successfully binded.. port:%d\n", bindport);
if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)) == -1) {
perror("connect");
exit(1);
}else{
printf("connect succ!\n");
sleep(100);
close(sockfd);
}
}
通过代码可以验证客户端可以通过bind
来使用指定端口连接服务器,以下均验证bind时,reuse
属性导致情况:
- 如不使用
reuse
模式,在第一个程序结束时,本地端口还处于TIME_WAIT
状态。此时第2个客户端无论是否reuse都是无法再次使用的,提示绑定失败。 - 在reuse模式下导致的
TIME_WAIT
状态,第2个客户端可以通过reuse模式使用,但是非reuse模式会绑定失败。 - 在reuse模式下,可以同时启动多个客户端。但前提是连接不同的服务器地址。因为如果连接两个服务器地址相同>,此时网络五元组相同,协议栈根本无法区分,会报
connect: Cannot assign requested address
错误。 - 理论上讲(因为没有亲自证实),
reuse
模式对listen
是无效的。
评论 (0)