前言:学会这些 API,写一个查询网站 ip 的应用十分简单。

0X00 常见 API 总结
getaddrinfo()
这个 API 常用来做 DNS 查询,用法如下:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *node, // e.g. "www.example.com" or IP
const char *service, // e.g. "http" or port number
const struct addrinfo *hints,
struct addrinfo **res);
返回一个 status 值,如果 status == 0 则失败,这里有一个详细的例子,转自(http://beej.us/guide/bgnet/examples/showip.c):
/*
** showip.c -- show IP addresses for a host given on the command line
*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(int argc, char *argv[])
{
struct addrinfo hints, *res, *p;
int status;
char ipstr[INET6_ADDRSTRLEN];
if (argc != 2) {
fprintf(stderr,"usage: showip hostname\n");
return 1;
}
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
hints.ai_socktype = SOCK_STREAM;
if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
return 2;
}
printf("IP addresses for %s:\n\n", argv[1]);
for(p = res;p != NULL; p = p->ai_next) {
void *addr;
char *ipver;
// get the pointer to the address itself,
// different fields in IPv4 and IPv6:
if (p->ai_family == AF_INET) { // IPv4
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else { // IPv6
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
}
// convert the IP to a string and print it:
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
printf(" %s: %s\n", ipver, ipstr);
}
freeaddrinfo(res); // free the linked list
return 0;
}
最后用 freeaddrinfo() free 掉内存
下面我们正式讲述与 socket() 相关的 API
socket()
作用:用 socket() 拿到 socket 描述符
用法如下:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
每个参数的意义如下:
domain 的值可能是 PF_INET(ipv4) 或者 PF_INET6(ipv6),我们输出一下这个值:
int a = PF_INET;
int b = PF_INET6;
printf("PF_INET %d %d\n", a, b);
int a1 = AF_INET;
int b1 = AF_INET6;
printf("AF_INET %d %d\n", a1, b1);

其实 AF_INET 和 PF_INET 是一样的东西。
我们要做的其实是:在 struct sockaddr_in 中使用 AF_INET,在调用 socket() 中使用 PF_INET
结合 getaddrinfo() 我们通常这样使用:
int s;
struct addrinfo hints, *res;
// do the lookup
// [pretend we already filled out the "hints" struct]
getaddrinfo("www.example.com", "http", &hints, &res);
// [again, you should do error-checking on getaddrinfo(), and walk
// the "res" linked list looking for valid entries instead of just
// assuming the first one is good (like many of these examples do.)
// See the section on client/server for real examples.]
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
socket() 只返回一个 socket 描述符用于之后的系统调用,或者 -1 表示错误
bind()
作用:bind ip 和 port
bind() 函数如下:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
参数意义如下:
sockfd 文件描述符,my_addr ip 地址以及端口,addrlen 地址长度,区分 ipv4,ipv6
来让我们看一个例子
struct addrinfo hints, *res;
int sockfd;
// first, load up address structs with getaddrinfo():
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
// fill in my IP for me
getaddrinfo(NULL, "3490", &hints, &res);
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
// bind it to the port we passed in to getaddrinfo():
bind(sockfd, res->ai_addr, res->ai_addrlen);
这里使用了 AI_PASSIVE
作用是:告诉程序去绑一个正在运行的主机 ip,各位可以试着去输出一下,很奇怪的 ip。如果你想绑定一个特定的本地 ip。那么就不要使用 AI_PASSIVE
并给 getaddrinfo()
第一个参数提供 ip
如果 bind() 错了,也会返回 -1
在客户端的时候(不用关心绑定的端口号)不需要使用 bind(),直接调用 connect() 函数就好,它会检查 socket 有没有绑定,如果没有 bind 就会自己 bind 到一个没有用过的端口号上
connect() 你好呀
作用:建立连接
用法如下:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
从参数名就可以知道每个参数的含义
而且为了连接服务器,只需要 getaddrinfo() 提供的 res。比如:
struct addrinfo hints, *res;
int sockfd;
// first, load up address structs with getaddrinfo():
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
getaddrinfo("www.example.com", "3490", &hints, &res);
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
// connect!
connect(sockfd, res->ai_addr, res->ai_addrlen);
connect 也会返回一个 errno,如果为 -1 就说明没连接上。
listen() 有人会主动联系我吗?
作用:监听端口
用法如下:
int listen(int sockfd, int backlog);
这里出现了一个我们从未接触的参数 backlog
简单来说,backlog 参数指定队列将保留的挂起连接数。
大多数系统默默地将此数量限制在 20 左右;你可以把它设置为 5 或 10。同样它也会返回 -1 表示错误。而且,我们要在 bind() 后才能使用 listen(),只有这样才能控制 listen 的端口
大概这样使用
getaddrinfo();
socket();
bind();
listen();
/* accept() goes here */
accept()
作用:在 listen 中我们说到有一个等待连接的队列,队列中的每一个连接都等待着唤醒。accept() 用来唤醒队列中的连接。
用法如下:
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept() 会返回一个新的 socket 描述符,现在你有两个 socket 描述符,原来那个依旧在监听连接。新的这个描述符已经准备使用 send() 和 recv() 了。
我们注意一下第二个参数,通常是 sockaddr_storage
(与 sockaddr 兼容),在错误发生时 accept() 同样也会返回 -1
大概这样使用:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MYPORT "3490"
#define BACKLOG 10
// the port users will be connecting to
// how many pending connections queue will hold
int main(void)
{
struct sockaddr_storage their_addr;
socklen_t addr_size;
struct addrinfo hints, *res;
int sockfd, new_fd;
// !! don't forget your error checking for these calls !!
// first, load up address structs with getaddrinfo():
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
// fill in my IP for me
getaddrinfo(NULL, MYPORT, &hints, &res);
// make a socket, bind it, and listen on it:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
bind(sockfd, res->ai_addr, res->ai_addrlen);
listen(sockfd, BACKLOG);
// now accept an incoming connection:
addr_size = sizeof their_addr;
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);
// ready to communicate on socket descriptor new_fd!
...
}
send() and recv()
作用:接发数据
用法如下:
int send(int sockfd, const void *msg, int len, int flags);
int recv(int sockfd, void *buf, int len, int flags);
在 send() 函数中返回值是发送的字节长度,很可能比你提供的参数 len 要小,比如你要发送大数据的时候,它尽量去发最大数据,但还是可能没有那么长于是会丢掉多的数据。要记住!如果返回的长度小于你提供的参数 len,那么是你去决定是否要补全剩下的数据。
返回 -1,代表着错误。
recv() 函数差不多,如果发生错误,也会返回 -1。但是也可能返回 0。原因是:远程机器关闭了与你的连接。
sendto() and recvfrom()
作用:UDP 的 send() 和 recvfrom()
用法如下:
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, socklen_t tolen);
int recvfrom(int sockfd, void *buf, int len, unsigned int flags,
struct sockaddr *from, int *fromlen);
由于 UDP 不需要连接,所以需要手动的填上参数。
close() and shutdown()
作用:关闭连接
用法如下:
close(sockfd);
int shutdown(int sockfd, int how);
close() 就是最简单的关闭文件的方式,所以同样可以用在 socket 上。而且一旦某个 socket 被 close 了,试图在远端读或写这个 socket 都会收到 error
而 shutdown()
函数有那么一些不一样。它允许你给出关闭连接的方向 how
how 有三个值
0 Further receives are disallowed
1 Further sends are disallowed
2 Further sends and receives are disallowed (like close() )
值得注意的是:shutdown() 并不是真正关闭 socket 描述符,只是让它不能使用。如果要去除 socket 描述符的占用。你还是需要使用 close()
getpeername()
作用:得到对方的信息
用法如下:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
获得地址(addr)后,可以使用 inet_ntop(),getnameinfo() 或 gethostbyaddr() 来打印或获取更多信息。但是,你不能得到他们的登录名。但是如果对方程序正在跑一个 ident 守护进程,你还是有可能得到对方的登录名的
gethostname()
这个函数更简单了
作用:得到自己的信息
用法如下:
#include <unistd.h>
int gethostname(char *hostname, size_t size);
参数很简单:hostname 是一个指向字符数组的指针,它将包含函数返回的主机名,size 是 hostname 数组的字节长度。成功完成时函数返回 0,错误时返回 -1
0X01 总结
前面写了那么多,现在用一张图来总结一下吧:

网友评论