Loading...
墨滴

阿酷尔工作室

2021/10/16  阅读:36  主题:默认主题

Beej网络编程指南

9手册

在Unix世界里,有很多手册。它们有小部分描述了你可以使用的单个函数。

当然,手动的东西太难打了。我的意思是,在Unix世界里,没有人,包括我自己,喜欢打那么多。事实上,我可以长篇大论地说我有多喜欢简洁,但我会简洁,不会用冗长的抨击来烦扰你,说我几乎在所有情况下都非常喜欢简洁。

[掌声]

谢谢。我要说的是,这些页面在Unix世界中被称为“手册页”,为了让你阅读愉快,我在这里包含了我自己的截断变体。问题是,这些函数中的许多比我所说的更通用,但我将只介绍与互联网套接字编程相关的部分。

但是等等!这不是我男人页面的全部问题:

  • 它们不完整,只显示了指南中的基础知识。

  • 现实世界中有比这更多的人文页面。

  • 它们与您系统上的不同。

  • 对于系统上的某些功能,头文件可能不同。

  • 对于系统上的某些函数,函数参数可能不同。

如果你想了解真正的信息,可以在本地的Unix手册页面上输入man any,其中的“不管”是你非常感兴趣的东西,比如“接受”。(我肯定微软Visual Studio在他们的帮助部分也有类似的东西。但是“人”更好,因为它比“帮助”简洁一个字节。Unix又赢了!)

那么,如果这些有如此大的缺陷,为什么还要在指南中包含它们呢?嗯,有几个原因,但最好的是(a)这些版本是专门面向网络编程的,比真正的版本更容易消化,(b)这些版本包含示例!

哦!说到这些例子,我不倾向于插入所有的错误检查,因为它确实增加了代码的长度。但是你绝对应该在你进行任何系统调用的时候进行错误检查,除非你完全100%确定它不会失败,你甚至应该这样做!

9.1接受()

在侦听套接字上接受传入连接

9.1.0.1简介

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

9.1.0.2说明

一旦您经历了获取SOCK_STREAM套接字并用listk()为传入连接设置它的麻烦,那么您将调用接受()来实际获取一个新的套接字描述符,用于与新连接的客户端的后续通信。

您用于监听的旧套接字仍然存在,并且将在它们进来时用于进一步接受()调用。

ParameterDescriptionsThe listen()ing socket descriptor.addrThis is filled in with the address of the site that’s connecting to you.addrlenThis is filled in with the sizeof() the structure returned in the addr parameter. You can safely ignore it if you assume you’re getting a struct sockaddr_in back, which you know you are, because that’s the type you passed in for addr.

接受()通常会阻塞,您可以使用选择()提前查看侦听套接字描述符是否“准备好读取”。如果是,那么有一个新的连接等待接受()编辑!耶。或者,您可以使用fcntl()在侦听套接字上设置O_NONBLOCK标志,然后它永远不会阻塞,而是选择返回-1,并将errno设置为EWOULDBLOCK

接受()返回的套接字描述符是一个真正的套接字描述符,打开并连接到远程主机。完成后必须关闭()

9.1.0.3返回值

接受()返回新连接的套接字描述符,或-1开错误,并适当设置errno

9.1.0.4实例

struct sockaddr_storage their_addr;
socklen_t addr_size;
struct addrinfo hints, *res;
int sockfd, new_fd;

// first, load up address structs with getaddrinfo():

memset(&hints, 0sizeof 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!

9.1.0.5也看

套接字``(),getaddrinfo(),``听()``,``结构sockaddr_in

9.2绑定()

将套接字与IP地址和端口号关联起来

9.2.0.1简介

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

9.2.0.2描述

当远程计算机想要连接到您的服务器程序时,它需要两条信息:IP地址和端口号。bind)调用允许您这样做。

首先,我们调用getaddrinfo()来加载一个带有目标地址和端口信息的结构Sockaddr。然后我们调用套接字()来获取一个套接字描述符,然后将套接字和地址传递给bind(,然后IP地址和端口神奇地(使用实际的魔法)绑定到套接字上!

如果您不知道您的IP地址,或者您知道您在机器上只有一个IP地址,或者您不关心使用了哪个机器的IP地址,您可以简单地将hint参数中的AI_PASSIVE标志传递给getaddrinfo()。这样做的目的是用一个特殊的值填入结构体sokaddr的IP地址部分,该值告诉bind)它应该自动填入主机的IP地址。

什么是什么?是什么特殊值被加载到struct sokaddr的IP地址,导致它自动填充地址与当前主机?我会告诉你,但请记住,这是只有当你填写的结构sokaddr手工;如果不是,使用getaddrinfo()的结果,如上所述。在IPv4中,sin_addr。s_addr结构sockaddr_in结构的字段被设置为INADDR_ANY。在IPv6中,结构sockaddr_in6结构的sin6_addr字段被分配到从全局变量in6addr_any。或者,如果要in6_addr声明一个新结构,可以将其初始化为IN6ADDR_ANY_INIT

最后,addrlen参数应该设置为sizeofmy_addr

9.2.0.3返回值

成功时返回零,错误时返回-1(errno将相应地设置)。

9.2.0.4实例

// modern way of doing things with getaddrinfo()

struct addrinfo hints, *res;
int sockfd;

// first, load up address structs with getaddrinfo():

memset(&hints, 0sizeof 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:
// (you should actually walk the "res" linked list and error-check!)

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);
// example of packing a struct by hand, IPv4

struct sockaddr_in myaddr;
int s;

myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(3490);

// you can specify an IP address:
inet_pton(AF_INET, "63.161.169.137", &(myaddr.sin_addr));

// or you can let it automatically select one:
myaddr.sin_addr.s_addr = INADDR_ANY;

s = socket(PF_INET, SOCK_STREAM, 0);
bind(s, (struct sockaddr*)&myaddr, sizeof myaddr);

9.2.0.5也看

getaddrinfo(),套接字``()结构sockaddr_in结构in_addr

9.3连接()

将套接字连接到服务器

9.3.0.1简介

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int connect(int sockfd, const struct sockaddr *serv_addr,
                socklen_t addrlen)
;

9.3.0.2说明

一旦您已经建立了一个套接字描述符与套接字()调用,您可以连接()该套接字到远程服务器使用良好命名的连接()系统调用。所有您需要做的是传递套接字描述符和服务器的地址,你有兴趣更好地了解。(哦,还有地址的长度,这通常是传递给这样的函数。)

通常,这些信息是调用getaddrinfo()的结果,但是如果你愿意,你可以填写你自己的结构。

如果您还没有在套接字描述符上调用bind(),它会自动绑定到您的IP地址和一个随机的本地端口。如果您不是服务器,这通常很好,因为您真的不在乎您的本地端口是什么;你只关心远程端口是什么,所以你可以把它放在serv_addr参数中。如果您真的希望您的客户端套接字位于特定的IP地址和端口上,您可以调用bind(),但这种情况非常罕见。

一旦套接字被连接(),你就可以自由地发送()和recv)数据。

特别注意:如果您连接()一个SOCK_DGRAMUDP套接字到一个远程主机,您可以使用mail()和recv()以及sendto()和recvfrom()。如果您愿意的话。

9.3.0.3返回值

成功时返回零,错误时返回-1(errno将相应地设置)。

9.3.0.4例子

// connect to www.example.com port 80 (http)

struct addrinfo hints, *res;
int sockfd;

// first, load up address structs with getaddrinfo():

memset(&hints, 0sizeof hints);
hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;

// we could put "80" instead on "http" on the next line:
getaddrinfo("www.example.com""http", &hints, &res);

// make a socket:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// connect it to the address and port we passed in to getaddrinfo():

connect(sockfd, res->ai_addr, res->ai_addrlen);

9.3.0.5另见

套接字`(),`绑定()

9.4关闭()

关闭套接字描述符

9.4.0.1简介

    #include <unistd.h>
    
    int close(int s);

9.4.0.2描述

在您完成了使用套接字的任何疯狂的计划,你已经编造,你不想发送()或recv()或,事实上,做任何其他与套接字,你可以关闭()它,它将被释放,永远不会再使用。

如果远程端调用recv(),它将返回0;如果远程端调用了mail(),它将接收到一个信号SIGPIPE,而mail()将返回-1errno将被设置为EPIPE

Windows用户:你需要使用的函数叫做关闭(),而不是关闭()。如果你试图在套接字描述符上使用关闭(),Windows可能会生气...你不喜欢它生气的时候。

9.4.0.3返回值

成功时返回零,错误时返回-1(errno将相应地设置)。

9.4.0.4例子

s = socket(PF_INET, SOCK_DGRAM, 0);
.
.
.
// a whole lotta stuff...*BRRRONNNN!*
.
.
.
close(s);  // not much to it, really.

9.4.0.5查看

套接字`(),`关机()

9.5 getaddrinfo(),freaddrinfo(),gai_strerror()

获取有关主机名和/或服务的信息,并加载一个带有结果的结构sokaddr

9.5.0.1简介

    #include <sys/types.h>    #include <sys/socket.h>    #include <netdb.h>        int getaddrinfo(const char *nodename, const char *servname,                    const struct addrinfo *hints, struct addrinfo **res);        void freeaddrinfo(struct addrinfo *ai);        const char *gai_strerror(int ecode);        struct addrinfo {      int     ai_flags;          // AI_PASSIVE, AI_CANONNAME, ...      int     ai_family;         // AF_xxx      int     ai_socktype;       // SOCK_xxx      int     ai_protocol;       // 0 (auto) or IPPROTO_TCP, IPPROTO_UDP           socklen_t  ai_addrlen;     // length of ai_addr      char   *ai_canonname;      // canonical name for nodename      struct sockaddr  *ai_addr; // binary address      struct addrinfo  *ai_next; // next structure in linked list    };

9.5.0.2描述

getaddrinfo()是一个非常好的函数,它可以返回特定主机名的信息(比如它的IP地址),并为您加载一个构建的Sockaddr,处理一些细节(比如它是IPv4还是IPv6)。它取代了旧的函数gethstatbyname()和getservbyname()。下面的描述包含了很多可能有点令人生畏的信息,但是实际使用非常简单。首先看看这些例子可能是值得的。

您感兴趣的主机名位于nodename参数中。地址可以是主机名,如“www.example.com”,也可以是IPv4或IPv6地址(作为字符串传递)。如果您使用AI_PASSIVE标志,该参数也可以为NULL(见下文)。

servname参数基本上是端口号。它可以是端口号(作为字符串传递,如“80”),也可以是服务名称,如“超文本传输协议”或“tftp”或“smtp”或“pop”等。众所周知的服务名称可以在IANA端口列表48或您的 /etc/services文件中找到。

最后,对于输入参数,我们有一些提示。这是您真正定义getaddrinfo()函数要做什么的地方。在使用memset()之前,将整个结构归零。让我们看看在使用前需要设置的字段。

ai_flags可以设置为多种内容,但这里有几个重要的内容。(可以通过与|运算符一起按位ORing来指定多个标志)。查看手册页面以获取完整的标志列表。

AI_CANONNAME将结果的ai_canonname填充为主机的规范(真实)名称。AI_PASSIVE将结果的IP地址填充为INADDR_ANY(IPv4)或in6addr_any(IPv6);这将导致后续调用bind(),以用当前主机的地址自动填充struct solkaddr的IP地址。当您不想对地址进行硬编码时,这非常适合设置服务器。

如果您使用AI_PASSIVE,标志,那么您可以在节点名称传递NULL(因为bind()稍后会为您填写)。

继续输入参数,您可能需要将ai_family设置为AF_UNSPEC,这告诉getaddrinfo()查找IPv4和IPv6地址。您也可以通过AF_INET或AF_INET6将自己限制在其中一个。

接下来,ocktype字段应该设置为SOCK_STREAMSOCK_DGRAM,这取决于您想要哪种类型的套接字。

最后,只需将ai_protocol保持在0即可自动选择协议类型。

现在,在你把所有的东西都放进去之后,你终于可以打电话给getaddrinfo()了!

当然,这就是乐趣的开始。res现在将指向一个结构地址的链接列表,您可以通过这个列表获得所有与您传递的提示相匹配的地址。

现在,有可能得到一些地址因为这样或那样的原因而不起作用,所以Linux手册页所做的就是循环遍历列表,调用套接字()和连接()(或者绑定(),如果您正在设置带有AI_PASSIVE标志的服务器),直到它成功。

最后,当你完成了链接列表,你需要调用freaddrinfo()来释放内存(否则它会泄露,有些人会不高兴)。

9.5.0.3返回值

成功时返回零,错误时返回非零。如果返回非零,您可以使用函数gai_strerror()在返回值中获取错误代码的可打印版本。

9.5.0.4实例

// code for a client connecting to a server
// namely a stream socket to www.example.com on port 80 (http)
// either IPv4 or IPv6

int sockfd;  
struct addrinfo hints, *servinfo, *p;
int rv;

memset(&hints, 0sizeof hints);
hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6
hints.ai_socktype = SOCK_STREAM;

if ((rv = getaddrinfo("www.example.com""http", &hints, &servinfo)) != 0) {
    fprintf(stderr"getaddrinfo: %s\n", gai_strerror(rv));
    exit(1);
}

// loop through all the results and connect to the first we can
for(p = servinfo; p != NULL; p = p->ai_next) {
    if ((sockfd = socket(p->ai_family, p->ai_socktype,
            p->ai_protocol)) == -1) {
        perror("socket");
        continue;
    }

    if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
        perror("connect");
        close(sockfd);
        continue;
    }

    break// if we get here, we must have connected successfully
}

if (p == NULL) {
    // looped off the end of the list with no connection
    fprintf(stderr"failed to connect\n");
    exit(2);
}

freeaddrinfo(servinfo); // all done with this structure
// code for a server waiting for connections
// namely a stream socket on port 3490, on this host's IP
// either IPv4 or IPv6.

int sockfd;  
struct addrinfo hints, *servinfo, *p;
int rv;

memset(&hints, 0sizeof hints);
hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP address

if ((rv = getaddrinfo(NULL"3490", &hints, &servinfo)) != 0) {
    fprintf(stderr"getaddrinfo: %s\n", gai_strerror(rv));
    exit(1);
}

// loop through all the results and bind to the first we can
for(p = servinfo; p != NULL; p = p->ai_next) {
    if ((sockfd = socket(p->ai_family, p->ai_socktype,
            p->ai_protocol)) == -1) {
        perror("socket");
        continue;
    }

    if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
        close(sockfd);
        perror("bind");
        continue;
    }

    break// if we get here, we must have connected successfully
}

if (p == NULL) {
    // looped off the end of the list with no successful bind
    fprintf(stderr"failed to bind socket\n");
    exit(2);
}

freeaddrinfo(servinfo); // all done with this structure

9.5.0.5也看

#####################################################################################``#########

9.6地名()

返回系统的名称

9.6.0.1简介

    #include <sys/unistd.h>        int gethostname(char *name, size_t len);

9.6.0.2说明

你的系统有一个名字。他们都有。这是一个比我们谈论的其他网络东西稍微更Unixy的东西,但是它仍然有它的用途。

例如,您可以获取您的主机名,然后调用gethostbyname)来查找您的IP地址。

参数name应该指向一个将保存主机名的缓冲区,len是该缓冲区的大小(以字节为单位)。gethostname()不会覆盖缓冲区的结尾(它可能会返回错误,或者它可能会停止写入),如果缓冲区中有空间,它将NUL终止字符串。

9.6.0.3返回值

成功时返回零,错误时返回-1(errno将相应地设置)。

9.6.0.4例子

char hostname[128];gethostname(hostname, sizeof hostname);printf("My hostname: %s\n", hostname);

9.6.0.5另见

地名()

9.7 gethokbyname(), gethokbyaddr(

获取主机名的IP地址,反之亦然

9.7.0.1简介

    #include <sys/socket.h>    #include <netdb.h>        struct hostent *gethostbyname(const char *name); // DEPRECATED!    struct hostent *gethostbyaddr(const char *addr, int len, int type);

9.7.0.2描述

请注意:这两个函数被*getaddrinfo()和getnameinfo()所取代!特别是,gethostby*name)不能很好地与IPv6一起工作。

这些函数在主机名和IP地址之间来回映射。例如,如果您有www.example.com,您可以使用gethostbyname)获取其IP地址并将其存储在结构in_addr中。

相反,如果你有一个结构in_addr结构in6_addr,你可以使用gethstatbyaddr()来获取主机名。gethstatbyaddr()与IPv6兼容,但``*是*``你应该使用更新的getnameinfo()来代替。

(如果你有一个包含点和数字格式的IP地址的字符串,你想查找的主机名,你最好使用带有AI_CANONNAME标志的getaddrinfo()。)

gethstatbyname()接受一个类似www.yahoo.com的字符串,并返回一个包含大量信息的结构宿主,包括IP地址。(其他信息是官方主机名、别名列表、地址类型、地址长度和地址列表——这是一个通用结构,一旦你看到了如何使用,就很容易用于我们的特定目的。)

gethstatbyaddr()接受一个结构in_addr结构in6_addr并带给你一个相应的主机名(如果有的话),所以它有点像gethstatbyname()的相反。至于参数,即使addr是一个char*,你实际上想要传递一个指向结构in_addr的指针。len应该是sizeof(结构in_addr),类型应该是AF_INET

那么返回的这个结构宿主是什么呢?它有许多字段包含有关所讨论宿主的信息。

FieldDescriptionchar h_nameThe real canonical host name.char **h_aliasesA list of aliases that can be accessed with arrays—the last element is NULLint h_addrtypeThe result’s address type, which really should be AF_INET for our purposes.int lengthThe length of the addresses in bytes, which is 4 for IP (version 4) addresses.char h_addr_listA list of IP addresses for this host. Although this is a char, it’s really an array of struct in_addrs in disguise. The last array element is NULL.h_addrA commonly defined alias for h_addr_list[0]. If you just want any old IP address for this host (yeah, they can have more than one) just use this field.

9.7.0.3返回值

成功时返回指向结果结构宿主的指针,错误时返回NULL

这些函数不是通常的perror()和所有通常用于错误报告的东西,而是在变量h_errno中具有并行结果,可以使用函数herror()或hstrirror()打印。这些函数的工作原理就像您习惯的经典errno、perror()和strarror()函数一样。

9.7.0.4例子

// THIS IS A DEPRECATED METHOD OF GETTING HOST NAMES
// use getaddrinfo() instead!

#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    int i;
    struct hostent *he;
    struct in_addr **addr_list;

    if (argc != 2) {
        fprintf(stderr,"usage: ghbn hostname\n");
        return 1;
    }

    if ((he = gethostbyname(argv[1])) == NULL) {  // get the host info
        herror("gethostbyname");
        return 2;
    }

    // print information about this host:
    printf("Official name is: %s\n", he->h_name);
    printf("    IP addresses: ");
    addr_list = (struct in_addr **)he->h_addr_list;
    for(i = 0; addr_list[i] != NULL; i++) {
        printf("%s ", inet_ntoa(*addr_list[i]));
    }
    printf("\n");

    return 0;
}
// THIS HAS BEEN SUPERCEDED
// use getnameinfo() instead!

struct hostent *he;
struct in_addr ipv4addr;
struct in6_addr ipv6addr;

inet_pton(AF_INET, "192.0.2.34", &ipv4addr);
he = gethostbyaddr(&ipv4addr, sizeof ipv4addr, AF_INET);
printf("Host name: %s\n", he->h_name);

inet_pton(AF_INET6, "2001:db8:63b3:1::beef", &ipv6addr);
he = gethostbyaddr(&ipv6addr, sizeof ipv6addr, AF_INET6);
printf("Host name: %s\n", he->h_name);

9.7.0.5另见

getaddrinfo``(),getnameinfo``(),gethstatname``(),``errno,perror(),``strrror()``,``结构``in_addr

9.8 getnameinfo()

查找给定结构体ockaddr的主机名和服务名信息。

9.8.0.1简介

    #include <sys/socket.h>
    #include <netdb.h>
    
    int getnameinfo(const struct sockaddr *sa, socklen_t salen,
                    char *host, size_t hostlen,
                    char *serv, size_t servlen, int flags)
;

9.8.0.2描述

这个函数与getaddrinfo()相反,也就是说,这个函数接受一个已经加载的结构sokaddr,并对其进行名称和服务名称查找。它取代了旧的gethostbyaddr()和getservbyport()函数

您必须在sa参数中传递一个指向struct solkaddr的指针(实际上可能是一个结构sockaddr_in或您已经转换的结构sockaddr_in6,以及salen中该结构的长度。

生成的主机名和服务名将被写入主机服务器参数指向的区域。当然,您必须在host len和servlen中指定这些缓冲区的最大长度。

最后,有几个标志可以传递,但这里有几个好的标志。NI_NOFQDN将导致主机只包含主机名,而不是整个域名。如果在DNS查找中找不到名称,NI_NAMEREQD将导致函数失败(如果不指定此标志并且找不到名称,getnameinfo()将在主机中放置IP地址的字符串版本)。

像往常一样,查看您当地的手册页面以获取完整的独家新闻。

9.8.0.3返回值

成功时返回零,错误时返回非零。如果返回值为非零,则可以将其传递给gai_strerror()以获得人类可读的字符串。有关更多信息,请参阅getaddrinfo

9.8.0.4例子

struct sockaddr_in6 sa; // could be IPv4 if you want
char host[1024];
char service[20];

// pretend sa is full of good information about the host and port...

getnameinfo(&sa, sizeof sa, host, sizeof host, service, sizeof service, 0);

printf("   host: %s\n", host);    // e.g. "www.example.com"
printf("service: %s\n", service); // e.g. "http"

9.8.0.5另见

Getaddrinfo(),gethokbyaddr(``)

9.9 getpeername()

返回连接远程端的地址信息

9.9.0.1简介

    #include <sys/socket.h>
    
    int getpeername(int s, struct sockaddr *addr, socklen_t *len);

9.9.0.2说明

一旦你接受了一个远程连接,或者连接了一个服务器,你现在就有了一个所谓的对等点。你的对等点就是你连接的计算机,由一个IP地址和一个端口来识别。所以...

getpeername()简单地返回一个结构sockaddr_in填充有关连接到的机器的信息。

为什么它被称为“名称”?嗯,有很多不同类型的套接字,不仅仅是像我们在本指南中使用的互联网套接字,所以“名称”是一个很好的通用术语,涵盖了所有情况。然而,在我们的例子中,对等方的“名称”是它的IP地址和端口。

虽然该函数返回len中结果地址的大小,但您必须用addr的大小预加载len

9.9.0.3返回值

成功时返回零,错误时返回-1(errno将相应地设置)。

9.9.0.4实例

// assume s is a connected socketsocklen_t len;struct sockaddr_storage addr;char ipstr[INET6_ADDRSTRLEN];int port;len = sizeof addr;getpeername(s, (struct sockaddr*)&addr, &len);// deal with both IPv4 and IPv6:if (addr.ss_family == AF_INET) {    struct sockaddr_in *s = (struct sockaddr_in *)&addr;    port = ntohs(s->sin_port);    inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);} else { // AF_INET6    struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;    port = ntohs(s->sin6_port);    inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);}printf("Peer IP address: %s\n", ipstr);printf("Peer port      : %d\n", port);

9.9.0.5也看

gethokbyname``()``, gethokbyname(), gethokbyaddr(

9.10errno

保存上次系统调用的错误代码

9.10.0.1简介

    #include <errno.h>        int errno;

9.10.0.2描述

这个变量保存了许多系统调用的错误信息。如果你记得的话,像套接字()和监听()这样的东西在错误时返回-1,它们设置errno的确切值来让你知道具体发生了哪个错误。

头文件errno. h列出了一堆错误的常量符号名称,如EADDRINUSEEPIPE、ECONNREFUSED等。您的本地手册页面将告诉您哪些代码可以作为错误返回,您可以在运行时使用这些代码以不同的方式处理不同的错误。

或者,更常见的是,您可以调用perror()或strirror()来获取该错误的可读版本。

对于多线程爱好者来说,有一点需要注意的是,在大多数系统中errno是以线程安全的方式定义的。(也就是说,它实际上不是一个全局变量,但它的行为就像单线程环境中的全局变量一样。)

9.10.0.3返回值

变量的值是最近发生的错误,如果最后一个操作成功,这可能是“成功”的代码。

9.10.0.4例子

s = socket(PF_INET, SOCK_STREAM, 0);
if (s == -1) {
    perror("socket"); // or use strerror()
}

tryagain:
if (select(n, &readfds, NULLNULL) == -1) {
    // an error has occurred!!

    // if we were only interrupted, just restart the select() call:
    if (errno == EINTR) goto tryagain;  // AAAA! goto!!!

    // otherwise it's a more serious error:
    perror("select");
    exit(1);
}

9.10.0.5另见

错误的``选择

9.11 fcntl()

控制套接字描述符

9.11.0.1简介

    #include <sys/unistd.h>
    #include <sys/fcntl.h>
    
    int fcntl(int s, int cmd, long arg);

9.11.0.2说明

此函数通常用于执行文件锁定和其他面向文件的操作,但它也有一些与套接字相关的函数,您可能会不时看到或使用这些函数。

参数s是您希望操作的套接字描述符,cmd应该设置为F_SETFLarg可以是以下命令之一。(就像我说的,fcntl()比我在这里说的要多,但我试图保持面向套接字。)

cmdDescriptionO_NONBLOCKSet the socket to be non-blocking. See the section on blocking for more details.O_ASYNCSet the socket to do asynchronous I/O. When data is ready to be recv()’d on the socket, the signal SIGIO will be raised. This is rare to see, and beyond the scope of the guide. And I think it’s only available on certain systems.

9.11.0.3返回值

成功时返回零,错误时返回-1(errno将相应地设置)。

fcntl()系统调用的不同用法实际上有不同的返回值,但我在这里没有讨论它们,因为它们与套接字无关。有关更多信息,请参阅本地fcntl()手册页。

9.11.0.4实例

int s = socket(PF_INET, SOCK_STREAM, 0);fcntl(s, F_SETFL, O_NONBLOCK);  // set to non-blockingfcntl(s, F_SETFL, O_ASYNC);     // set to asynchronous I/O

9.11.0.5也看

阻塞发送()

9.12 hton(), htonl(), ntohs(), ntohl()

将多字节整数类型从主机字节顺序转换为网络字节顺序

9.12.0.1简介

    #include <netinet/in.h>        uint32_t htonl(uint32_t hostlong);    uint16_t htons(uint16_t hostshort);    uint32_t ntohl(uint32_t netlong);    uint16_t ntohs(uint16_t netshort);

9.12.0.2描述

只是让你不高兴的是,不同的计算机在内部对它们的多字节整数(即任何大于char的整数)使用不同的字节顺序。结果是,如果你从英特尔盒子向苹果电脑发送()一个两字节的短int(在它们变成英特尔盒子之前,我的意思是),一台电脑认为是数字1,另一台电脑认为是数字256,反之亦然。

解决这个问题的方法是,每个人都把分歧放在一边,同意摩托罗拉和IBM是对的,而英特尔是以奇怪的方式做到的,所以我们都在发送字节顺序之前将其转换为“大端”。由于英特尔是一台“小端”机器,将我们首选的字节顺序称为“网络字节顺序”在政治上要正确得多。所以这些函数从你的本机字节顺序转换为网络字节顺序,然后再转换回来。

(这意味着在英特尔上,这些函数交换周围的所有字节,而在PowerPC上,它们什么也不做,因为字节已经处于网络字节顺序。但是无论如何,你应该在你的代码中始终使用它们,因为有人可能想在英特尔机器上构建它,并且仍然让事情正常工作。)

请注意,所涉及的类型是32位(4字节,可能是int)和16位(2字节,很可能是短)数字。64位机器可能有一个用于64位int的htonll),但我没有见过它。你只需要写你自己的。

不管怎样,这些函数的工作方式是,您首先决定是从主机(您的机器)字节顺序还是从网络字节顺序转换。如果是“主机”,您要调用的函数的第一个字母是“h”。否则是“网络”的“n”。函数名称的中间总是“to”,因为您正在一个“转换”“另一个”,倒数第二个字母显示您正在转换的内容。最后一个字母是数据的大小,短的“s”,长的“l”。因此:

FunctionDescriptionhtons()host to network shorthtonl()host to network longntohs()network to host shortntohl()network to host long

9.12.0.3返回值

每个函数返回转换后的值。

9.12.0.4实例

uint32_t some_long = 10;uint16_t some_short = 20;uint32_t network_byte_order;// convert and sendnetwork_byte_order = htonl(some_long);send(s, &network_byte_order, sizeof(uint32_t), 0);some_short == ntohs(htons(some_short)); // this expression is true

9.13inet_ntoa()、inet_aton()、inet_addr

将IP地址从点和数字字符串转换为结构in_addr

9.13.0.1简介

    #include <sys/socket.h>    #include <netinet/in.h>    #include <arpa/inet.h>        // ALL THESE ARE DEPRECATED! Use inet_pton()  or inet_ntop() instead!!        char *inet_ntoa(struct in_addr in);    int inet_aton(const char *cp, struct in_addr *inp);    in_addr_t inet_addr(const char *cp);

9.13.0.2描述

这些函数不建议使用,因为它们不处理IPv6!使用*inet_ntop()**inet_pton()*代替!它们包含在这里是因为它们仍然可以在野外找到。

所有这些函数都从结构in_addr(最有可能是结构sockaddr_in的一部分)转换成点和数字格式的字符串(例如“192.168.5.10”),反之亦然。如果你有一个在命令行上传递的IP地址,这是获得一个结构in_addr连接()或其他什么的最简单方法。如果你需要更多的权力,尝试一些DNS函数,比如gethostbyname)或者在你的本地国家尝试一次政变

函数inet_ntoa()将结构in_addr中的网络地址转换为点和数字格式的字符串。“ntoa”中的“n”代表网络,“a”代表ASCII,这是因为历史原因(所以它是“网络到ASCII”——“toa”后缀在C库中有一个类似的朋友,叫做atoi(),它将ASCII字符串转换为整数)。

函数inet_aton()是相反的,从点和数字字符串转换成in_addr_t(这是s_addr结构in_addr的字段类型)。

最后,函数inet_addr()是一个较旧的函数,它的功能基本上与inet_aton()相同。理论上它是不建议使用的,但是你会经常看到它,如果你使用它,警察不会来找你。

9.13.0.3返回值

inet_aton()如果地址是有效的,则返回非零;如果地址无效,则返回零。

inet_ntoa()返回静态缓冲区中的点和数字字符串,该字符串在每次调用函数时被覆盖。

inet_addr()将地址作为in_addr_t返回,如果有错误,则返回-1。(这与试图将字符串255.255.255.255转换为有效的IP地址的结果相同。这就是inet_aton()更好的原因。)

9.13.0.4例子

struct sockaddr_in antelope;char *some_addr;inet_aton("10.0.0.1", &antelope.sin_addr); // store IP in antelopesome_addr = inet_ntoa(antelope.sin_addr); // return the IPprintf("%s\n", some_addr); // prints "10.0.0.1"// and this call is the same as the inet_aton() call, above:antelope.sin_addr.s_addr = inet_addr("10.0.0.1");

9.13.0.5另见

inet_ntop()`,`inet_pton``()`,`gethokbyname(),gethokbyaddr()

9.14inet_ntop()inet_pton()

将IP地址转换为人类可读的形式并返回。

9.14.0.1简介

    #include <arpa/inet.h>        const char *inet_ntop(int af, const void *src,                          char *dst, socklen_t size);        int inet_pton(int af, const char *src, void *dst);

9.14.0.2描述

这些函数用于处理人类可读的IP地址,并将其转换为二进制表示,用于各种函数和系统调用。“n”代表“网络”,“p”代表“呈现”。或者“文本呈现”。但是你可以把它想象成“可打印的”。“ntop”是“网络到可打印的”。看到了吗?

有时,在查看IP地址时,您不想查看一堆二进制数字。您希望它以可打印的形式显示,如192.0.2.1802001:db8:8714:3a90::12。在这种情况下,inet_ntop()适合您。

inet_ntop()接受af参数中地址系列(AF_INET或AF_INET6)。src参数应该是指向结构in_addr或结构in6_addr的指针,其中包含您希望转换为字符串的地址。最后,dst和size是指向目标字符串的指针以及该字符串的最大长度。

dst字符串的最大长度应该是多少?IPv4和IPv6地址的最大长度是多少?幸运的是,有几个宏可以帮助你。最大长度是:INET_ADDRSTRLENINET6_ADDRSTRLEN

其他时候,您可能有一个包含可读形式的IP地址的字符串,并且您希望将其打包到结构sockaddr_in结构sockaddr_in6中。在这种情况下,相反的funcioninet_pton()是您要寻找的。

inet_pton()在af参数中也接受一个地址系列(AF_INET或AF_INET6)src参数是指向包含可打印形式IP地址的字符串的指针。最后,dst参数指向结果应该存储的地方,这可能是一个结构in_addr结构in6_addr

这些函数不做DNS查找-为此您需要getaddrinfo()

9.14.0.3返回值

inet_ntop()在成功时返回dst参数,在失败时返回NULL(并设置errno)。

inet_pton()在成功时返回1。如果有错误(errno被设置),它返回-1;如果输入不是有效的IP地址,它返回0

9.14.0.4例子

// IPv4 demo of inet_ntop() and inet_pton()struct sockaddr_in sa;char str[INET_ADDRSTRLEN];// store this IP address in sa:inet_pton(AF_INET, "192.0.2.33", &(sa.sin_addr));// now get it back and print itinet_ntop(AF_INET, &(sa.sin_addr), str, INET_ADDRSTRLEN);printf("%s\n", str); // prints "192.0.2.33"// IPv6 demo of inet_ntop() and inet_pton()// (basically the same except with a bunch of 6s thrown around)struct sockaddr_in6 sa;char str[INET6_ADDRSTRLEN];// store this IP address in sa:inet_pton(AF_INET6, "2001:db8:8714:3a90::12", &(sa.sin6_addr));// now get it back and print itinet_ntop(AF_INET6, &(sa.sin6_addr), str, INET6_ADDRSTRLEN);printf("%s\n", str); // prints "2001:db8:8714:3a90::12"// Helper function you can use://Convert a struct sockaddr address to a string, IPv4 and IPv6:char *get_ip_str(const struct sockaddr *sa, char *s, size_t maxlen){    switch(sa->sa_family) {        case AF_INET:            inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr),                    s, maxlen);            break;        case AF_INET6:            inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr),                    s, maxlen);            break;        default:            strncpy(s, "Unknown AF", maxlen);            return NULL;    }    return s;}

9.14.0.5查看

#####################################################################################

9.15听()

告诉套接字监听传入连接

9.15.0.1简介

    #include <sys/socket.h>        int listen(int s, int backlog);

9.15.0.2描述

您可以使用套接字描述符(由套接字()系统调用生成)并告诉它侦听传入的连接。这就是服务器和客户端的区别,伙计们。

积压参数可能意味着不同的东西,这取决于你所在的系统,但大致上是指在内核开始拒绝新连接之前,你可以有多少个挂起的连接。所以当新连接进来时,你应该很快接受它们,这样积压就不会被填满。试着把它设置为10左右,如果你的客户端在重载下开始收到“连接拒绝”,把它设置得更高。

在调用listk()之前,服务器应该调用bind()将自己附加到一个特定的端口号。该端口号(在服务器的IP地址上)将是客户端连接到的端口号。

9.15.0.3返回值

成功时返回零,错误时返回-1(errno将相应地设置)。

9.15.0.4例子

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, whicheverhints.ai_socktype = SOCK_STREAM;hints.ai_flags = AI_PASSIVE;     // fill in my IP for megetaddrinfo(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);listen(sockfd, 10); // set s up to be a server (listening) socket// then have an accept() loop down here somewhere

9.15.0.5另见

接受()绑定()套接字()

9.16 perror()

将错误打印为可读字符串

9.16.0.1简介

    #include <stdio.h>    #include <string.h>   // for strerror()        void perror(const char *s);    char *strerror(int errnum);

9.16.0.2说明

由于许多函数在错误时返回-1,并将变量errno的值设置为某个数字,如果您可以轻松地以对您有意义的形式打印该值,那将是非常好的。

幸运的是,perror()做到了这一点。如果您希望在错误之前打印更多的描述,您可以将参数s指向它(或者您可以将s保留为NULL,并且不会打印任何其他内容)。

简而言之,这个函数接受errno值,ECONNRESET,并很好地打印它们,如“对等连接重置”

函数strirror()与perror()非常相似,只是它返回一个指向给定值的错误消息字符串的指针(通常会传入变量errno

9.16.0.3返回值

Strerror()返回指向错误消息字符串的指针。

9.16.0.4实例

int s;s = socket(PF_INET, SOCK_STREAM, 0);if (s == -1) { // some error has occurred    // prints "socket error: " + the error message:    perror("socket error");}// similarly:if (listen(s, 10) == -1) {    // this prints "an error: " + the error message from errno:    printf("an error: %s\n", strerror(errno));}

9.16.0.5也看

伊尔诺

9.17投票()

同时测试多个套接字上的事件

9.17.0.1简介

    #include <sys/poll.h>        int poll(struct pollfd *ufds, unsigned int nfds, int timeout);

9.17.0.2描述

这个函数与Select()非常相似,因为它们都监视事件的文件描述符集,例如传入数据准备好recv()、套接字准备好发送()数据、带外数据准备好recv()、错误等。

其基本思想是,在ufds中传递一个nfds结构的数组,以及一个以毫秒为单位的超时(每秒1000毫秒)。如果您想永远等待,超时可以是负的。如果超时时没有任何事件发生在任何套接字描述符上,将返回投票()

每一个元素都代表一个套接字描述符,并包含以下字段:
    struct pollfd {        int fd;         // the socket descriptor        short events;   // bitmap of events we're interested in        short revents;  // when poll() returns, bitmap of events that occurred    };在调用轮询()之前,用套接字描述符加载fd(如果您将fd设置为负数,则忽略此结构图,并将其Revents字段设置为零),然后通过按位ORing以下宏来构造事件字段:

MacroDescriptionPOLLINAlert me when data is ready to recv() on this socket.POLLOUTAlert me when I can send() data to this socket without blocking.POLLPRIAlert me when out-of-band data is ready to recv() on this socket.

一旦调用返回,Revents字段将被构造为上述字段的按位或,告诉您哪些描述符实际发生了该事件。此外,这些其他字段可能存在:

MacroDescriptionPOLLERRAn error has occurred on this socket.POLLHUPThe remote side of the connection hung up.POLLNVALSomething was wrong with the socket descriptor fd—maybe it’s uninitialized?

9.17.0.3返回值

返回ufds数组中发生事件的元素数;如果超时发生,这可以是零。在错误时也返回-1(errno将相应地设置)。

9.17.0.4例子

int s1, s2;int rv;char buf1[256], buf2[256];struct pollfd ufds[2];s1 = socket(PF_INET, SOCK_STREAM, 0);s2 = socket(PF_INET, SOCK_STREAM, 0);// pretend we've connected both to a server at this point//connect(s1, ...)...//connect(s2, ...)...// set up the array of file descriptors.//// in this example, we want to know when there's normal or out-of-band// data ready to be recv()'d...ufds[0].fd = s1;ufds[0].events = POLLIN | POLLPRI; // check for normal or out-of-bandufds[1].fd = s2;ufds[1].events = POLLIN; // check for just normal data// wait for events on the sockets, 3.5 second timeoutrv = poll(ufds, 2, 3500);if (rv == -1) {    perror("poll"); // error occurred in poll()} else if (rv == 0) {    printf("Timeout occurred! No data after 3.5 seconds.\n");} else {    // check for events on s1:    if (ufds[0].revents & POLLIN) {        recv(s1, buf1, sizeof buf1, 0); // receive normal data    }    if (ufds[0].revents & POLLPRI) {        recv(s1, buf1, sizeof buf1, MSG_OOB); // out-of-band data    }    // check for events on s2:    if (ufds[1].revents & POLLIN) {        recv(s1, buf2, sizeof buf2, 0);    }}

9.17.0.5另见

选择()

9.18 recv(), recvfrom()

在套接字上接收数据

9.18.0.1简介

    #include <sys/types.h>    #include <sys/socket.h>        ssize_t recv(int s, void *buf, size_t len, int flags);    ssize_t recvfrom(int s, void *buf, size_t len, int flags,                     struct sockaddr *from, socklen_t *fromlen);

9.18.0.2说明

一旦建立并连接了套接字,就可以使用recv()(用于TCPSOCK_STREAM套接字)和recvfrom()(用于UDPSOCK_DGRAM套接字)从远程端读取传入数据。

这两个函数都使用套接字描述符s、缓冲区buf的指针、缓冲区len的大小(以字节为单位)以及一组控制函数工作方式的标志。

另外,recvfrom()需要一个struct solkaddr*,它将告诉您数据来自哪里,并将在fromlen中填入struct solkaddr的大小。(您还必须将fromlen初始化为from或structsokaddr的大小。)

那么,您可以将哪些奇妙的标志传递给这个函数呢?这里有一些,但是您应该检查您的本地手册页面以获得更多信息以及您的系统实际上支持什么。您可以按位或将这些放在一起,或者如果您希望它是常规的香草recv),只需将标志设置0

MacroDescriptionMSG_OOBReceive Out of Band data. This is how to get data that has been sent to you with the MSG_OOB flag in send(). As the receiving side, you will have had signal SIGURG raised telling you there is urgent data. In your handler for that signal, you could call recv() with this MSG_OOB flag.MSG_PEEKIf you want to call recv() “just for pretend”, you can call it with this flag. This will tell you what’s waiting in the buffer for when you call recv() “for real” (i.e. without the MSG_PEEK flag. It’s like a sneak preview into the next recv() call.MSG_WAITALLTell recv() to not return until all the data you specified in the len parameter. It will ignore your wishes in extreme circumstances, however, like if a signal interrupts the call or if some error occurs or if the remote side closes the connection, etc. Don’t be mad with it.

当您调用recv()时,它会阻塞,直到有一些数据要读取。如果您不想阻塞,请将套接字设置为非阻塞,或者在调用recv()或recvfrom()之前检查是否有传入数据。

9.18.0.3返回值

返回实际接收的字节数(可能小于len参数中请求的字节数),或-1(并相应地设置errno)。

如果远程端关闭了连接,recv)将返回0。这是确定远程端是否关闭连接的正常方法。正常是好的,叛逆!

9.18.0.4实例

// stream sockets and recv()struct addrinfo hints, *res;int sockfd;char buf[512];int byte_count;// get host info, make socket, and connect itmemset(&hints, 0, sizeof hints);hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whicheverhints.ai_socktype = SOCK_STREAM;getaddrinfo("www.example.com", "3490", &hints, &res);sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);connect(sockfd, res->ai_addr, res->ai_addrlen);// all right! now that we're connected, we can receive some data!byte_count = recv(sockfd, buf, sizeof buf, 0);printf("recv()'d %d bytes of data in buf\n", byte_count);// datagram sockets and recvfrom()struct addrinfo hints, *res;int sockfd;int byte_count;socklen_t fromlen;struct sockaddr_storage addr;char buf[512];char ipstr[INET6_ADDRSTRLEN];// get host info, make socket, bind it to port 4950memset(&hints, 0, sizeof hints);hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whicheverhints.ai_socktype = SOCK_DGRAM;hints.ai_flags = AI_PASSIVE;getaddrinfo(NULL, "4950", &hints, &res);sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);bind(sockfd, res->ai_addr, res->ai_addrlen);// no need to accept(), just recvfrom():fromlen = sizeof addr;byte_count = recvfrom(sockfd, buf, sizeof buf, 0, &addr, &fromlen);printf("recv()'d %d bytes of data in buf\n", byte_count);printf("from IP address %s\n",    inet_ntop(addr.ss_family,        addr.ss_family == AF_INET?            ((struct sockadd_in *)&addr)->sin_addr:            ((struct sockadd_in6 *)&addr)->sin6_addr,        ipstr, sizeof ipstr);

9.18.0.5也看

发送``()``,发送(),``选择()``,轮询(),阻塞

9.19选择()

检查套接字描述符是否已准备好读/写

9.19.0.1简介

    #include <sys/select.h>        int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,               struct timeval *timeout);        FD_SET(int fd, fd_set *set);    FD_CLR(int fd, fd_set *set);    FD_ISSET(int fd, fd_set *set);    FD_ZERO(fd_set *set);

9.19.0.2描述

Select()函数为您提供了一种同时检查多个套接字的方法,看看它们是否有数据等待recv()d,或者您是否可以在不阻塞的情况下向它们发送()数据,或者是否发生了一些异常。

您可以使用上面的FD_SET()宏填充套接字描述符集。一旦您有了套接字,您可以将它作为以下参数之一传递给函数:如果您想知道套接字中的任何一个何时准备好recv()数据,请读取;如果任何套接字准备好发送()数据,请写入;如果您需要知道任何套接字何时发生异常(错误),请执行exceptfds。如果您对这些类型的事件不感兴趣,这些参数中的任何一个或所有参数都可以为NULL选择()返回后,集合中的值将被更改,以显示哪些可以读取或写入,哪些有异常。

第一个参数,n是最高编号的套接字描述符(它们只是int,记得吗?)加1。

最后,最后是结构时间值,超时,这让您可以告诉selc()检查这些集合的时间。它将在超时后返回,或者当事件发生时返回,以先发生者为准。结构时间值有两个字段:tv_sec是秒数,tv_usec加上微秒数(每秒1,000,000微秒)。

辅助宏执行以下操作:

MacroDescriptionFD_SET(int fd, fd_set *set);Add fd to the set.FD_CLR(int fd, fd_set *set);Remove fd from the set.FD_ISSET(int fd, fd_set *set);Return true if fd is in the set.FD_ZERO(fd_set *set);Clear all entries from the set.

Linux用户请注意:Linux的Select()可以返回“准备好阅读”,然后实际上没有准备好阅读,从而导致后续的read()调用被阻止。您可以通过在接收套接字上设置O_NONBLOCK标志来解决这个问题,这样它就会出现EWOULDBLOCK错误,然后在出现错误时忽略这个错误。有关将套接字设置为非阻塞的更多信息,请参阅fcntl()参考页面

9.19.0.3返回值

成功时返回集合中描述符的数量,如果达到超时则返回0,如果出错则返回-1(并相应地设置errno)。此外,还会修改集合以显示哪些套接字已准备就绪。

9.19.0.4例子

int s1, s2, n;fd_set readfds;struct timeval tv;char buf1[256], buf2[256];// pretend we've connected both to a server at this point//s1 = socket(...);//s2 = socket(...);//connect(s1, ...)...//connect(s2, ...)...// clear the set ahead of timeFD_ZERO(&readfds);// add our descriptors to the setFD_SET(s1, &readfds);FD_SET(s2, &readfds);// since we got s2 second, it's the "greater", so we use that for// the n param in select()n = s2 + 1;// wait until either socket has data ready to be recv()d (timeout 10.5 secs)tv.tv_sec = 10;tv.tv_usec = 500000;rv = select(n, &readfds, NULL, NULL, &tv);if (rv == -1) {    perror("select"); // error occurred in select()} else if (rv == 0) {    printf("Timeout occurred! No data after 10.5 seconds.\n");} else {    // one or both of the descriptors have data    if (FD_ISSET(s1, &readfds)) {        recv(s1, buf1, sizeof buf1, 0);    }    if (FD_ISSET(s2, &readfds)) {        recv(s2, buf2, sizeof buf2, 0);    }}

9.19.0.5查看

投票()

9.20 setsokopt(),getsokopt()

为套接字设置各种选项

9.20.0.1简介

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int getsockopt(int s, int level, int optname, void *optval,
                   socklen_t *optlen)
;
    int setsockopt(int s, int level, int optname, const void *optval,
                   socklen_t optlen)
;

9.20.0.2描述

套接字是相当可配置的野兽。事实上,它们是如此可配置,我甚至不打算在这里涵盖所有这些。不管怎样,它可能依赖于系统。但是我将谈谈基础知识。

显然,这些函数获取并设置套接字上的某些选项。在Linux框中,所有套接字信息都在第7节的套接字手册页面中。(键入“man 7套接字”以获取所有这些好东西。)

至于参数,s是您正在谈论的套接字,级别应该设置为SOL_SOCKET。然后您将optname设置为您感兴趣的名称。同样,请参阅您的手册页面了解所有选项,但以下是一些最有趣的选项:

optnameDescriptionSO_BINDTODEVICEBind this socket to a symbolic device name like eth0 instead of using bind() to bind it to an IP address. Type the command ifconfig under Unix to see the device names.SO_REUSEADDRAllows other sockets to bind() to this port, unless there is an active listening socket bound to the port already. This enables you to get around those “Address already in use” error messages when you try to restart your server after a crash.SOCK_DGRAMAllows UDP datagram (SOCK_DGRAM) sockets to send and receive packets sent to and from the broadcast address. Does nothing—NOTHING!!—to TCP stream sockets! Hahaha!

至于参数optval,它通常是指向一个int的指针,指示所讨论的值。对于布尔值,零是假的,非零是真的。这是绝对的事实,除非在你的系统上有所不同。如果没有要传递的参数,optval可以是NULL

最后一个参数optlen应该设置为optval的长度,可能是sizeofint),但是根据选项的不同而变化。请注意,在get门选择()的情况下,这是一个指向socklen_t的指针,它指定了将存储在optval中的最大大小对象(以防止缓冲区溢出)。get门选择()将修改optlen的值以反映实际设置的字节数。

警告:在某些系统(特别是Sun和Windows)上,选项可以是char而不是int,并且被设置为,例如,字符值为'1'而不是int值为1。同样,请使用“man setsokpt”和“man 7套接字”查看您自己的手册页面以获取更多信息!

9.20.0.3返回值

成功时返回零,错误时返回-1(errno将相应地设置)。

9.20.0.4例子

int optval;
int optlen;
char *optval2;

// set SO_REUSEADDR on a socket to true (1):
optval = 1;
setsockopt(s1, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);

// bind a socket to a device name (might not work on all systems):
optval2 = "eth1"// 4 bytes long, so 4, below:
setsockopt(s2, SOL_SOCKET, SO_BINDTODEVICE, optval2, 4);

// see if the SO_BROADCAST flag is set:
getsockopt(s3, SOL_SOCKET, SO_BROADCAST, &optval, &optlen);
if (optval != 0) {
    print("SO_BROADCAST enabled on s3!\n");
}

9.20.0.5另见

FCNTL()

9.21发送(),发送()

通过套接字发送数据

9.21.0.1简介

    #include <sys/types.h>    #include <sys/socket.h>        ssize_t send(int s, const void *buf, size_t len, int flags);    ssize_t sendto(int s, const void *buf, size_t len,                   int flags, const struct sockaddr *to,                   socklen_t tolen);

9.21.0.2说明

这些函数将数据发送到套接字。一般来说,sen()用于TCPSOCK_STREAM连接的套接字,sendto()用于UDPSOCK_DGRAM未连接的数据报套接字。对于未连接的套接字,每次发送数据包时都必须指定数据包的目的地,这就是为什么sendto)的最后一个参数定义数据包的去向。

对于mail()和sendto(),参数s是套接字,buf是要发送数据的指针,len是要发送的字节数,标志允许您指定有关数据如何发送的更多信息。如果您希望它是“正常”数据,请将标志设置为零。以下是一些常用的标志,但请查看您本地的“发送()手册”页面了解更多详细信息:

MacroDescriptionMSG_OOBSend as “out of band” data. TCP supports this, and it’s a way to tell the receiving system that this data has a higher priority than the normal data. The receiver will receive the signal SIGURG and it can then receive this data without first receiving all the rest of the normal data in the queue.MSG_DONTROUTEDon’t send this data over a router, just keep it local.MSG_DONTWAITIf send() would block because outbound traffic is clogged, have it return EAGAIN. This is like a “enable non-blocking just for this send.” See the section on blocking for more details.MSG_NOSIGNALIf you send() to a remote host which is no longer recv()ing, you’ll typically get the signal SIGPIPE. Adding this flag prevents that signal from being raised.

9.21.0.3返回值

返回实际发送的字节数,或错误时的-1(errno将相应设置)。请注意,实际发送的字节数可能小于您要求它发送的字节数!有关帮助函数的解决方法,请参阅处理部分发送()的部分。

此外,如果任何一方都关闭了套接字,调用发送()的进程将获得信号SIGPIPE。(除非MSG_NOSIGNAL标志调用了发送()。)

9.21.0.4实例

int spatula_count = 3490;
char *secret_message = "The Cheese is in The Toaster";

int stream_socket, dgram_socket;
struct sockaddr_in dest;
int temp;

// first with TCP stream sockets:

// assume sockets are made and connected
//stream_socket = socket(...
//connect(stream_socket, ...

// convert to network byte order
temp = htonl(spatula_count);
// send data normally:
send(stream_socket, &temp, sizeof temp, 0);

// send secret message out of band:
send(stream_socket, secret_message, strlen(secret_message)+1, MSG_OOB);

// now with UDP datagram sockets:
//getaddrinfo(...
//dest = ... // assume "dest" holds the address of the destination
//dgram_socket = socket(...

// send secret message normally:
sendto(dgram_socket, secret_message, strlen(secret_message)+10
       (struct sockaddr*)&dest, sizeof dest);

9.21.0.5也看

Recv(), recvfrom()

9.22关机()

停止在套接字上的进一步发送和接收

9.22.0.1简介

    #include <sys/socket.h>
    
    int shutdown(int s, int how);

9.22.0.2描述

就是这样!我受够了!这个套接字上不允许再发送()了,但是我仍然想在上面接收()数据!反之亦然!我该怎么做呢?

当您关闭()一个套接字描述符时,它关闭了用于读写的套接字的两边,并释放了套接字描述符,如果您只是想关闭一边或另一边,您可以使用这个关闭()调用。

至于参数,s显然是您要执行此操作的套接字,可以使用how参数指定该操作。如何SHUT_RD以防止进一步recv()s,SHUT_WR禁止进一步发送()s,或者SHUT_RDWR两者兼而有之。

请注意,Shutdown()不会释放套接字描述符,因此即使套接字已经完全关闭,您最终仍必须关闭()套接字。

这是一个很少使用的系统调用。

9.22.0.3返回值

成功时返回零,错误时返回-1(errno将相应地设置)。

9.22.0.4例子

int s = socket(PF_INET, SOCK_STREAM, 0);

// ...do some send()s and stuff in here...

// and now that we're done, don't allow any more sends()s:
shutdown(s, SHUT_WR);

9.22.0.5另见

关闭()

9.23套接字()

分配套接字描述符

9.23.0.1简介

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int socket(int domain, int type, int protocol);

9.23.0.2说明

返回一个新的套接字描述符,您可以用它来做一些特殊的事情。这通常是编写套接字程序的庞大过程中的第一次调用,您可以使用这个结果进行后续调用,以监听()绑定()接受()或各种其他函数。

在通常的使用中,您可以通过调用getaddrinfo()来获得这些参数的值,如下面的示例所示。但是如果您真的想的话,您可以手工填写它们。

MacroDescriptiondomaindomain describes what kind of socket you’re interested in. This can, believe me, be a wide variety of things, but since this is a socket guide, it’s going to be PF_INET for IPv4, and PF_INET6 for IPv6.typeAlso, the type parameter can be a number of things, but you’ll probably be setting it to either SOCK_STREAM for reliable TCP sockets (send(), recv()) or SOCK_DGRAM for unreliable fast UDP sockets (sendto(), recvfrom()). (Another interesting socket type is SOCK_RAW which can be used to construct packets by hand. It’s pretty cool.)protocolFinally, the protocol parameter tells which protocol to use with a certain socket type. Like I’ve already said, for instance, SOCK_STREAM uses TCP. Fortunately for you, when using SOCK_STREAM or SOCK_DGRAM, you can just set the protocol to 0, and it’ll use the proper protocol automatically. Otherwise, you can use getprotobyname() to look up the proper protocol number.

9.23.0.3返回值

将在后续调用中使用的新套接字描述符,或-1 on false(并且errno将相应地设置)。

9.23.0.4实例

struct addrinfo hints, *res;
int sockfd;

// first, load up address structs with getaddrinfo():

memset(&hints, 0sizeof hints);
hints.ai_family = AF_UNSPEC;     // AF_INET, AF_INET6, or AF_UNSPEC
hints.ai_socktype = SOCK_STREAM; // SOCK_STREAM or SOCK_DGRAM

getaddrinfo("www.example.com""3490", &hints, &res);

// make a socket using the information gleaned from getaddrinfo():
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

9.23.0.5也看

接受``()``,``绑定()``,getaddrinfo(),``听(``)

9.24结构sokaddr和朋友

处理互联网地址的结构

9.24.0.1简介

    #include <netinet/in.h>
    
    // All pointers to socket address structures are often cast to pointers
    // to this type before use in various functions and system calls:
    
    struct sockaddr {
        unsigned short    sa_family;    // address family, AF_xxx
        char              sa_data[14];  // 14 bytes of protocol address
    };
    
    
    // IPv4 AF_INET sockets:
    
    struct sockaddr_in {
        short            sin_family;   // e.g. AF_INET, AF_INET6
        unsigned short   sin_port;     // e.g. htons(3490)
        struct in_addr   sin_addr;     // see struct in_addr, below
        char             sin_zero[8];  // zero this if you want to
    };
    
    struct in_addr {
        unsigned long s_addr;          // load with inet_pton()
    };
    
    
    // IPv6 AF_INET6 sockets:
    
    struct sockaddr_in6 {
        u_int16_t       sin6_family;   // address family, AF_INET6
        u_int16_t       sin6_port;     // port number, Network Byte Order
        u_int32_t       sin6_flowinfo; // IPv6 flow information
        struct in6_addr sin6_addr;     // IPv6 address
        u_int32_t       sin6_scope_id; // Scope ID
    };
    
    struct in6_addr {
        unsigned char   s6_addr[16];   // load with inet_pton()
    };
    
    
    // General socket address holding structure, big enough to hold either
    // struct sockaddr_in or struct sockaddr_in6 data:
    
    struct sockaddr_storage {
        sa_family_t  ss_family;     // address family
    
        // all this is padding, implementation specific, ignore it:
        char      __ss_pad1[_SS_PAD1SIZE];
        int64_t   __ss_align;
        char      __ss_pad2[_SS_PAD2SIZE];
    };

9.24.0.2描述

这些是所有处理互联网地址的系统和函数的基本结构。通常您会使用getaddrinfo()来填写这些结构,然后在必要时阅读它们。

在内存中,结构体sockaddr_in和结构体sockaddr_in6与结构体sokaddr共享相同的开始结构,您可以自由地将一种类型的指针投掷到另一种类型,而不会受到任何伤害,除了可能的宇宙末日。

在宇宙末日的事情上开玩笑...如果宇宙真的在你向一个结构sockaddr_in*投下一个结构时结束了,我向你保证这纯粹是巧合,你甚至不应该担心。

因此,记住这一点,请记住,每当一个函数说它需要一个结构sokaddr*时,您可以轻松安全地将您的结构sockaddr_in*、结构sockaddr_in6*或结构sockadd_storage*转换为该类型。

结构sockaddr_in是用于IPv4地址(例如192.0.2.10)的结构。它持有地址系列(AF_INET)、sin_port端口和sin_addrIPv4地址。

结构体sockaddr_in中还有一个sin_zero字段,有些人声称必须将其设置为零。其他人对此一无所知(Linux文档甚至根本没有提到它),并且将其设置为零似乎实际上没有必要。所以,如果你喜欢,使用memset()将其设置为零。

现在,这个结构in_addr在不同系统上是一个奇怪的野兽。有时它是一个包含各种#定义和其他废话的疯狂联盟。但是你应该做的是只在这个结构中使用s_addr字段,因为许多系统只实现那个。

结构sockadd_in6和结构in6_addr非常相似,只是它们用于IPv6。

结构sockaddr_storage是一个结构,当您试图编写与IP版本无关的代码,并且您不知道新地址是IPv4还是IPv6时,您可以通过该结构接受()或recvfrom()。结构sockaddr_storage结构足够大,可以容纳这两种类型,不像最初的小结构sokaddr。

9.24.0.3实例

// IPv4:

struct sockaddr_in ip4addr;
int s;

ip4addr.sin_family = AF_INET;
ip4addr.sin_port = htons(3490);
inet_pton(AF_INET, "10.0.0.1", &ip4addr.sin_addr);

s = socket(PF_INET, SOCK_STREAM, 0);
bind(s, (struct sockaddr*)&ip4addr, sizeof ip4addr);
// IPv6:

struct sockaddr_in6 ip6addr;
int s;

ip6addr.sin6_family = AF_INET6;
ip6addr.sin6_port = htons(4950);
inet_pton(AF_INET6, "2001:db8:8714:3a90::12", &ip6addr.sin6_addr);

s = socket(PF_INET6, SOCK_STREAM, 0);
bind(s, (struct sockaddr*)&ip6addr, sizeof ip6addr);

9.24.0.4也看

接受()绑定()连接()inet_aton()``,inet_ntoa()

10更多参考资料

你已经走了这么远,现在你尖叫着要更多!你还能去哪里了解更多这些东西?

10.1书籍

对于真正的老式纸质书,试试以下几本好书。这些书会重定向到受欢迎书商的会员链接,给我丰厚的回扣。如果你只是觉得慷慨,你可以向beej@beej.us捐款。:-

Unix网络编程,W. Richard Stevens的第1-2卷。Addison-Wesley专业和PrenticeHall出版。1-2: 978-013141155549, 978-013081081650****卷的ISBN。

**与TCP/IP联网,**Douglas E. Comer第一卷,Pearson出版社,ISBN978-013608530051

TCP/IP插图,第1-3卷,W. Richard Stevens和Gary R. Wright著。Addison Wesley出版。第1、2和3卷的ISBN(和一套3卷的ISBN):978-020163346752, 978-020163354253, 978-020163495254, (978-020177631755)

Craig Hunt的TCP/IP网络管理。O'Reilly&Associates,Inc.出版。ISBN978-059600297856

UNIX环境中的高级编程,W. Richard Stevens著,Addison Wesley出版社,ISBN978-032163773457

10.2网站参考

在网络上:

BSD套接字:一个快速和肮脏的****Primer58(Unix系统编程信息,太!)

Unix套接字常见问题59

TCP/IP****常见问题

Winsock常见问题61

以下是一些相关的维基百科页面:

伯克利****Sockets62

网络之间互连的协议****(IP)63

传输控制协议****(TCP)64

用户数据报协议****(UDP)65

客户端服务器

序列化67(**包装**和拆包数据)

10.3 RFC

RFCs68——真正的污垢!这些是描述分配号码、编程API和互联网上使用的协议的文档。我在这里提供了其中一些的链接,供你享受,所以拿一桶爆米花,戴上你的思考帽:

RFC**** 169——第一个RFC;这让你了解了“互联网”刚刚诞生时的样子,并深入了解了它是如何从头开始设计的。(显然,这个RFC已经完全过时了!)

RFC**** 76870-用户数据报协议(UDP)

RFC**** 79171-网络之间互连的协议(IP)

RFC**** 79372-传输控制协议(TCP)

RFC**** 85473-Telnet协议

RFC**** 95974-文件传输协议(FTP)

RFC**** 135075-琐碎的文件传输协议(TFTP)

RFC**** 145976-互联网中继聊天协议(IRC)

RFC**** 191877-专用互联网的地址分配

RFC**** 213178-动态主机配置协议(DHCP)

RFC**** 261679-超文本传输协议(HTTP)

RFC**** 282180-简单邮件传输协议(SMTP)

RFC**** 333081-特殊用途IPv4地址

RFC**** 349382-IPv6的基本套接字接口扩展

RFC**** 354283-用于IPv6的高级套接字应用程序接口(API)

RFC**** 384984-为文档保留的IPv6地址前缀

RFC**** 392085-可扩展消息和存在协议(XMPP)

RFC**** 397786-网络新闻传输协议(NNTP)

唯一的本地IPv6单播地址

RFC**** 450688-外部数据表示标准(XDR)

IETF有一个很好的在线搜索和浏览RFCs89的工具。

  1. https://www.linux.com/
  2. https://bsd.org/
  3. https://cygwin.com/
  4. https://docs.microsoft.com/en-us/windows/wsl/about?
  5. https://tangentsoft.net/wskfaq/
  6. http://www.catb.org/~esr/faqs/smart-questions.html
  7. https://beej.us/guide/bgnet/examples/telnot.c↩?
  8. https://tools.ietf.org/html/rfc854
  9. https://tools.ietf.org/html/rfc793
  10. https://tools.ietf.org/html/rfc791
  11. https://tools.ietf.org/html/rfc768
  12. https://tools.ietf.org/html/rfc791
  13. https://en.wikipedia.org/wiki/Vint_Cerf↩
  14. https://en.wikipedia.org/wiki/ELIZA
  15. https://www.iana.org/assignments/port-numbers
  16. https://en.wikipedia.org/wiki/Doom_(1993_video_game)↩
  17. https://en.wikipedia.org/wiki/Wilford_Brimley↩
  18. https://tools.ietf.org/html/rfc1918
  19. https://tools.ietf.org/html/rfc4193
  20. https://www.iana.org/assignments/port-numbers
  21. https://beej.us/guide/bgnet/examples/showip.c
  22. https://tools.ietf.org/html/rfc1413
  23. https://beej.us/guide/bgnet/examples/server.c
  24. https://beej. us/guide/bgnet/examples/client. c
  25. https://beej.us/guide/bgnet/examples/listener.c
  26. https://beej.us/guide/bgnet/examples/talker.c
  27. https://libevent.org/
  28. https://beej.us/guide/bgnet/examples/poll.c
  29. https://beej.us/guide/bgnet/examples/pollserver.c
  30. https://libevent.org/
  31. https://beej.us/guide/bgnet/examples/select.c
  32. https://beej.us/guide/bgnet/examples/selectserver.c↩?
  33. https://en.wikipedia.org/wiki/Internet_Relay_Chat↩
  34. https://beej.us/guide/bgnet/examples/pack.c↩?
  35. https://en.wikipedia.org/wiki/IEEE_754
  36. https://beej.us/guide/bgnet/examples/ieee754.c
  37. https://beej.us/guide/url/tpop
  38. https://github.com/protobuf-c/protobuf-c
  39. https://beej.us/guide/bgnet/examples/pack2.c
  40. https://beej.us/guide/bgnet/examples/pack2.c !}
  41. https://tools.ietf.org/html/rfc4506
  42. https://beej.us/guide/bgnet/examples/broadcaster.c
  43. http://www.unpbook.com/src.html
  44. http://www.unpbook.com/src.html !}
  45. https://www.openssl.org/
  46. https://stack over flow. com/question/21323023/
  47. https://www. iana. org/assignments/port-numbers!}
  48. https://www. iana. org/assignments/port-numbers
  49. https://beej.us/guide/url/unixnet1
  50. https://beej.us/guide/url/unixnet2
  51. https://beej.us/guide/url/intertcp1
  52. https://beej.us/guide/url/tcpi1?
  53. https://beej.us/guide/url/tcpi2
  54. https://beej.us/guide/url/tcpi3
  55. https://beej.us/guide/url/tcpi123
  56. https://beej.us/guide/url/tcpna↩?
  57. https://beej.us/guide/url/advunix↩?
  58. https://cis.temple.edu/~giorgio/old/cis307s96/readings/docs/sockets.html
  59. https://developerweb.net/?f=70
  60. http://www.faqs.org/faqs/internet/tcp-ip/tcp-ip-faq/part1/ !}
  61. https://tangentsoft.net/wskfaq/
  62. https://en.wikipedia.org/wiki/Berkeley_sockets↩
  63. https://en.wikipedia.org/wiki/Internet_Protocol↩
  64. https://en.wikipedia.org/wiki/Transmission_Control_Protocol↩
  65. https://en.wikipedia.org/wiki/User_Datagram_Protocol的↩
  66. https://en.wikipedia.org/wiki/Client-server
  67. https://en.wikipedia.org/wiki/Serialization
  68. https://www.rfc-editor.org/
  69. https://tools.ietf.org/html/rfc1
  70. https://tools.ietf.org/html/rfc768
  71. https://tools.ietf.org/html/rfc791
  72. https://tools.ietf.org/html/rfc793
  73. https://tools.ietf.org/html/rfc854
  74. https://tools.ietf.org/html/rfc959
  75. https://tools.ietf.org/html/rfc1350
  76. https://tools.ietf.org/html/rfc1459?
  77. https://tools.ietf.org/html/rfc1918
  78. https://tools.ietf.org/html/rfc2131
  79. https://tools.ietf.org/html/rfc2616
  80. https://tools.ietf.org/html/rfc2821↩?
  81. https://tools.ietf.org/html/rfc3330
  82. https://tools.ietf.org/html/rfc3493
  83. https://tools.ietf.org/html/rfc3542
  84. https://tools.ietf.org/html/rfc3849 !}
  85. https://tools.ietf.org/html/rfc3920
  86. https://tools.ietf.org/html/rfc3977
  87. https://tools.ietf.org/html/rfc4193
  88. https://tools.ietf.org/html/rfc4506
  89. https://tools.ietf.org/rfc/ [↩](

阿酷尔工作室

2021/10/16  阅读:36  主题:默认主题

作者介绍

阿酷尔工作室

恒生研究院