美文网首页转载部分
网络编程基础——常见 API 总结

网络编程基础——常见 API 总结

作者: madao756 | 来源:发表于2019-07-22 01:50 被阅读0次

前言:学会这些 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 总结

前面写了那么多,现在用一张图来总结一下吧:

相关文章

  • 网络编程基础——常见 API 总结

    前言:学会这些 API,写一个查询网站 ip 的应用十分简单。 0X00 常见 API 总结 getaddrinf...

  • Linux高性能服务器入门

    第五章 Linux网络编程基础API 主机字节序和网络字节序 <5.1.1>Q:设定主机字节序和网络字节序是为了解...

  • TCP

    聊聊iOS中网络编程长连接的那些事 网络编程基础知识: 简述传输层协议TCP和UDP的区别总结:TCP协议在传送数...

  • MFC编程第一天——Windows编程与MFC编程基础

    Windows编程与MFC编程基础 一、Windows编程 1、Windows应用程序编程接口API Window...

  • Java学习路径

    *第一阶段:Java基础,包括java语法,面向对象特征,常见API,集合框架; *第二阶段:java界面编程,包...

  • Android应用开发:网络编程2

    网络编程 Java基础:网络编程 Uri、URL、UriMatcher、ContentUris详解 Android...

  • Http协议

    网络编程 Java基础:网络编程 Uri、URL、UriMatcher、ContentUris详解 Android...

  • iOS之网络基础(网络请求)

    一.网络基础 1 网络基础 001网络编程 (1)网络编程是一种实时更新应用数据的常用手段 ...

  • iOS-网络基础及应用-Http

    3.网络基础 3.1 网络基础 001 问题:为什么要学习网络编程? 回答:(1)网络编程是一种实时更...

  • Open GL名词解释

    常见的图形API OpenGL(Open Graphics Library)是⼀个跨编程语⾔、跨平台的编程图形程序...

网友评论

    本文标题:网络编程基础——常见 API 总结

    本文链接:https://www.haomeiwen.com/subject/yktvlctx.html