5系统调用或崩溃
这是我们进入系统调用(和其他库调用)的部分,这些调用允许您访问Unix框或任何支持套接字应用编程接口的框的网络功能(BSD、视窗、Linux、苹果、你有什么)。)当你调用其中一个函数时,内核会自动接管并为你完成所有工作。
大多数人被困在这里的地方是调用这些东西的顺序。在这种情况下,手册页是没有用的,正如你可能已经发现的那样。为了帮助解决这个可怕的情况,我试图在下面的部分中以与你在程序中调用它们完全``*(大约*``)相同的顺序列出系统调用。
再加上到处都是一些示例代码,一些牛奶和饼干(我担心你必须自己提供),以及一些原始的勇气和勇气,你会像乔恩·波斯特之子一样在互联网上传播数据!
(请注意,为了简洁起见,下面的许多代码片段不包括必要的错误检查。它们通常假设调用getaddrinfo()的结果*成功*
,并返回链接列表中的有效条目。不过,这两种情况在独立程序中都得到了适当的解决,所以请将它们作为一个模型。)
5.1 getaddrinfo()-准备
发射!
这是一个有很多选项的函数的真正主力,但是用法实际上非常简单。它有助于设置您以后需要的结构
。
一点历史:过去,你会使用一个叫做gethostbyname(
)的函数来进行DNS查找。然后你会手工将这些信息加载到一个结构sockaddr_in
中,并在你的调用中使用它。
谢天谢地,这不再是必要的。(如果你想编写适用于IPv4和IPv6的代码,这也不是可取的!)在当今时代,你现在有了getaddrinfo()
函数,它可以为你做各种各样的好事,包括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);
你给这个函数三个输入参数,它给你一个指向结果链表res的指针。
节点
参数是要连接的主机名或IP地址。
接下来是参数服务
,它可以是端口号,如80,或者特定服务的名称(在IANA端口列表20或Unix机器上的 /etc/services
文件中找到),如超文本传输协议、ftp、telnet或smtp或其他任何名称。
最后,提示
参数指向您已经用相关信息填写的结构addrinfo
。
如果您是一个想要监听主机IP地址端口3490的服务器,下面是一个示例调用。请注意,这实际上并没有进行任何监听或网络设置;它只是设置了我们稍后将使用的结构:
int status;
struct addrinfo hints;
struct addrinfo *servinfo; // will point to the results
memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
exit(1);
}
// servinfo now points to a linked list of 1 or more struct addrinfos
// ... do everything until you don't need servinfo anymore ....
freeaddrinfo(servinfo); // free the linked-list
请注意,我将ai_family
设置为AF_
UNSPEC,从而表明我不在乎我们使用的是IPv4还是IPv6。如果您特别想要其中之一,您可以将其设置为AF_INET或AF_INET6
。
此外,您将看到AI_PASSIVE
标志;这告诉getaddrinfo()
将本地主机的地址分配给套接字结构。这很好,因为这样您就不必硬编码它。(或者您可以将特定的地址作为getaddrinfo()的第一个参数,我目前在上面有NULL
。)
然后我们进行调用。如果有错误(getaddrinfo()返回非零),我们可以使用函数gai_strerror()
打印出来,如您所见。但是,如果一切正常,servinfo
将指向一个结构
地址的链表,每个链表都包含一个我们以后可以使用的结构
地址!漂亮!
最后,当我们最终完成getaddrinfo()
如此慷慨地为我们分配的链接列表时,我们可以(也应该)通过调用fre
addrinfo()来释放它。
如果你是一个想要连接到特定服务器的客户端,这里有一个示例调用,比如“www.example.net”端口3490。同样,这实际上并没有连接,但它设置了我们稍后将使用的结构:
int status;
struct addrinfo hints;
struct addrinfo *servinfo; // will point to the results
memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
// get ready to connect
status = getaddrinfo("www.example.net", "3490", &hints, &servinfo);
// servinfo now points to a linked list of 1 or more struct addrinfos
// etc.
我一直在说servinfo是一个包含各种地址信息
的链表。让我们写一个快速演示程序来展示这些信息。这个简短的程序21将打印你在命令行指定的任何主机的IP地址:
/*
** 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;
}
正如您所看到的,代码调用getaddrinfo()
,无论您在命令行上传递什么,它都会填写res
所指向的链表,然后我们可以遍历该列表并打印出来,或者做任何事情。
(这里有一点丑陋,我们必须根据IP版本深入研究不同类型的结构sokadd
rs。对此我很抱歉!我不确定有更好的方法。)
样本运行!每个人都喜欢截图:
$ showip www.example.net
IP addresses for www.example.net:
IPv4: 192.0.2.88
$ showip ipv6.example.com
IP addresses for ipv6.example.com:
IPv4: 192.0.2.101
IPv6: 2001:db8:8c00:22::171
现在我们已经控制住了,我们将使用从getaddrinfo(
)获得的结果传递给其他套接字函数,最后,建立我们的网络连接!继续阅读!
5.2套接字()-获取
文件描述符!
我想我不能再推迟了——我必须谈谈套接字
()系统调用。下面是细分:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
但是这些参数是什么呢?它们允许您说出您想要什么样的套接字(IPv4或IPv6、流或数据报以及TCP或UDP)。
过去人们会硬编码这些值,你现在绝对可以这样做。(域是PF_INET
或PF_INET6
,类型
是SOCK_STREAM
或SOCK_DGRAM
,协议
可以设置为0
来为给定类型选择合适的协议。或者
你可以调用
get原型名()来查找你想要的协议,“tcp”或“udp”。)
(这个PF_INET的东西是AF_INET
的近亲,您可以在初始化结构sockaddr_in中的sin_family
字段时使用它。事实上,它们关系如此密切,以至于它们实际上具有相同的值,许多程序员会调用套接字
()并将AF_INET作为第一个参数,而不是PF_INET
。现在,得到一些牛奶和饼干,因为是时候讲一个故事了。很久很久以前,人们认为一个地址族(AF_INET中的“AF
”代表什么)可能支持他们的协议族(PF_INET中的“PF”代表什么)引用的几个协议。但这并没有发生。从那以后,他们都过着幸福的生活,结束。所以最正确的做法是在你的结构sockaddr_in中使用AF_INET
,在你对套接字
()的调用中使用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);
套接字
()只返回一个套接字描述符,您可以在以后的系统调用中使用它,或者在出错时返回-1
。全局变量errno被设置为错误的
值(有关更多详细信息,请参阅errno手册页,以及在多线程程序中使用errno
的快速注释)。
好吧,好吧,好吧,但是这个套接字有什么好处呢?答案是它本身真的没有好处,你需要继续阅读并进行更多的系统调用,这样它才有意义。
5.3 bind()
-我在哪个端口?
一旦你有了一个套接字,你可能必须将该套接字与本地机器上的端口相关联。(如果你要在特定端口上监听()
传入连接,这通常是这样做的——多人网络游戏在告诉你“连接到192.168.5.10端口3490”时会这样做。)端口号被内核用来将传入的数据包与某个进程的套接字描述符相匹配。如果您只做一个连接()
(因为您是客户端,而不是服务器),这可能是不必要的。无论如何都要阅读它,只是为了好玩。
下面是bind(
)系统调用的概要:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
Sockfd
是套接字
()返回的套接字文件描述符。my_addr
是指向包含您的地址(即端口和IP地址)信息的结构Sockaddr
的指针。addrlen是
该地址的字节长度。
咻。这在一个块中有点难吸收。让我们举一个例子,将套接字绑定到程序运行的主机,端口3490:
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地址,请删除AI_PASSIVE,并在getaddrinfo()
的第一个参数中放入一个IP地址。
bind(
)在错误时也返回-1
,并将errno
设置为错误的值。
许多旧代码在调用bind()之前手动打包sockaddr_in的结构
。显然这是IPv4特有的,但是没有什么能阻止你对IPv6做同样的事情,除了使用getaddrinfo(
)通常会更容易。不管怎样,旧代码看起来像这样:
// !!! THIS IS THE OLD WAY !!!
int sockfd;
struct sockaddr_in my_addr;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero);
bind(sockfd, (struct sockaddr *)&my_addr, sizeof my_addr);
在上面的代码中,如果您想绑定到本地IP地址(如上面的AI_PASSIVE
标志),您还可以将INADDR_ANY分配给s_addr字段。IPv6版本的INADDR_ANY是一个全局变量in6addr_any,分配到您的结构sockaddr_in6的sin6_addr字段中。(还有一个宏IN6ADDR_ANY_INIT
,您可以在变量初始化器中使用。)
调用bind()
时需要注意的另一件事是:不要使用端口号。所有低于1024的端口都是保留的(除非你是超级用户)!你可以有任何高于1024的端口号,直到65535(前提是它们还没有被其他程序使用)。
有时,您可能会注意到,您试图重新运行服务器,但bind()
失败,声称“地址已在使用中”。这是什么意思?嗯,一点连接的套接字仍然挂在内核中,它占用了端口。您可以等待它清除(大约一分钟),或者向您的程序添加代码,允许它重用端口,如下所示:
int yes=1;
//char yes='1'; // Solaris people use this
// lose the pesky "Address already in use" error message
if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof yes) == -1) {
perror("setsockopt");
exit(1);
}
关于bind()的最后一个小注意事项:有时您不一定要调用它。如果您正在连接()
到远程机器,并且您不关心您的本地端口是什么(就像telnet
的情况一样,您只关心远程端口),您可以简单地调用连接()
,它将检查套接字是否未绑定,并在必要时将它绑定到未使用的本地端口。
5.4连接()-嘿
,你!
让我们假装几分钟你是一个telnet应用程序。你的用户命令你(就像电影TRON中一样*)*获取一个套接字文件描述符。你遵从并调用套接字
()。接下来,用户告诉你连接到端口“23”
(标准telnet端口)上的“10.12.110.57”
。哟!你现在怎么办?
你很幸运,程序,你现在正在细读关于连接()
的部分——如何连接到远程主机。所以请继续阅读!没时间浪费了!
连接()
调用如下:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
Sockfd
是我们友好的邻域套接字文件描述符,由套接字
()调用返回,serv_addr
是包含目标端口和IP地址的结构Sockaddr,addrlen
是服务器地址结构的长度(以字节为单位)。
所有这些信息都可以从getaddrinfo()调用的结果中收集到,
该调用非常有用。
这开始更有意义了吗?我在这里听不到你,所以我只能希望是这样。让我们举一个例子,我们在这里建立一个套接字连接到端口3490
的“www.example.com”
:
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);
同样,老式程序填写了自己的结构sockaddr_ins
传递给连接()
。如果你愿意,你可以这样做。参见上面bind(
)部分的类似注释。
一定要检查连接()的返回值-它会
返回-1
的错误,并设置变量errno
。
另外,请注意我们没有调用bind()
。基本上,我们不关心我们的本地端口号;我们只关心我们要去哪里(远程端口)。内核会为我们选择一个本地端口,我们连接的站点会自动从我们这里获取这些信息。不用担心。
5.5听()
-有人能打电话给我吗?
好的,是时候改变一下节奏了。如果你不想连接到远程主机怎么办。比方说,只是为了好玩,你想等待传入的连接,并以某种方式处理它们。这个过程有两个步骤:首先你听()
,然后你接受()(
见下文)。
听()
调用相当简单,但需要一点解释:
int listen(int sockfd, int backlog);
ockfd
是套接字
()系统调用中常用的套接字文件描述符。积压
是传入队列中允许的连接数。这是什么意思?传入的连接将在这个队列中等待,直到您接受()
它们(见下文),这是可以排队的数量限制。大多数系统默默地将这个数字限制在大约20个;您可能可以将它设置为5
或10
而不受影响。
同样,像往常一样,听()
返回-1
,并设置errno
错误。
正如你可能想象的那样,我们需要先调用bind()
,然后再调用List(),这样服务器就可以在特定的端口上运行。(你必须能够告诉你的朋友要连接到哪个端口!)
所以如果你要监听传入的连接,你将进行的系统调用的顺序是:
getaddrinfo();
socket();
bind();
listen();
/* accept() goes here */
我将把它放在示例代码中,因为它是相当不言自明的。(下面接受()
部分的代码更完整。)整个sha-ang中真正棘手的部分是调用接受()。
5.6接受()-"
感谢您拨打端口3490。"
准备好——接受()
调用有点奇怪!会发生这样的事情:远处的某个人会试图在你正在监听()的端口上连接()
到你的机器。他们的连接会排队等待接受()
。你调用接受()
,并告诉它获取挂起的连接。它会返回一个全新的套接字文件描述符给你,用于这个单一的连接!没错,突然你有了两个套接字文件描述符,一个的价格!原来的那个还在监听更多的新连接,新创建的那个终于准备好发送()
和recv(
)了。我们到了!
电话如下:
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
Sockfd
是听()ing
套接字描述符。很简单。addr
通常是指向本地结构sockaddr_storage
的指针。这是关于传入连接的信息的去处(通过它,您可以确定哪个主机从哪个端口调用您)。addrlen
是一个本地整数变量,在其地址传递给接受()
之前,应该设置为sizeof(结构sockaddr_storage
)。接受()
不会在addr
中放入超过那么多字节。如果它放入的少,它会改变addrlen的值来反映这一点。
你猜怎么着?接受()
返回-1
,如果发生错误,设置errno
。Betcha没有想到这一点。
像以前一样,这是一个可以在一个块中吸收的集合,所以这里有一个示例代码片段供您阅读:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#define MYPORT "3490" // the port users will be connecting to
#define BACKLOG 10 // 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!
.
.
.
同样,请注意,我们将使用套接字描述符new_fd
所有的发送()
和recv()
调用。如果您只得到一个连接,如果您愿意,您可以关闭()
侦听ocockfd
,以防止同一端口上有更多的传入连接。
5.7发送()和回复()
-跟我说话,宝贝!
这两个函数用于通过流套接字或连接的数据报套接字进行通信。如果您想使用常规的未连接数据报套接字,您需要看到下面关于sendto()
和recvfrom()的部分。
发送()
调用:
int send(int sockfd, const void *msg, int len, int flags);
Sockfd
是您要发送数据到的套接字
描述符(无论它是通过套接字()返回的还是通过接受()获得的)。msg
是您要发送的数据的指针,len
是该数据的长度,以字节为单位。只需将标志设置
为0
。(有关标志的详细信息,请参阅发送()
手册页。)
一些示例代码可能是:
char *msg = "Beej was here!";
int len, bytes_sent;
.
.
.
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
.
.
.
发送()
返回实际发送的字节数——这可能比你告诉它发送的字节数要少!看,有时候你告诉它发送一大堆数据*,它就是*
无法处理。它会尽可能多地发送数据,并相信你稍后会发送其余的数据。记住,如果*发送()*
返回的值与len中的值不匹配,就由你来发送字符串的其余部分。好消息是:如果数据包很小(小于1K左右),它可能会一次性发送所有内容。同样,-1
在错误时返回,errno
被设置为错误号。
recv(
)调用在许多方面类似:
int recv(int sockfd, void *buf, int len, int flags);
Sockfd
是要读取的套接字描述符,buf
是要读取信息的缓冲区,len是
缓冲区的最大长度,标志可以
再次设置为0
。(有关标志信息,请参阅recv()
手册页。)
recv()
返回实际读入缓冲区的字节数,或-1(相应地设置errno
)。
等待!recv()可以返回0
。这只意味着一件事:远程端已经关闭了您的连接!返回值为0
是recv()
让您知道发生了这种情况的方式。
这很简单,不是吗?你现在可以在流套接字上来回传递数据了!哇!你是一个Unix网络程序员!
5.8 sendto()和recvfrom()
-跟我说话,DGRAM风格
“这一切都很好,”我听到你说,“但是这给我留下了没有连接的数据报套接字吗?”没问题,朋友。我们正好有东西。
由于数据报套接字没有连接到远程主机,猜猜我们在发送数据包之前需要给出哪条信息?没错。目的地地址!下面是独家新闻:
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, socklen_t tolen);
正如你所看到的,这个调用基本上是相同的调用发送()添加了另外两条信息。to是一个指针,指向一个结构sokaddr(这可能是另一个结构sockaddr_in或结构sockaddr_in6或结构sockaddr_storage,你在最后一分钟投),其中包含目标IP地址和端口。tolen,一个int深层,可以简单地设置为sizeof*to或sizeof(结构sockaddr_storage)。
要获得目标地址结构,您可能要么从getaddrinfo()或下面的recvfrom()
获得,要么手工填写。
就像在发送()
中一样,sendto()返回实际发送的字节数(同样,可能少于您告诉它发送的字节数!),或者在错误时返回-1
。
recv()
和recvfrom()同样相似。recvfrom()的概要是:
int recvfrom(int sockfd, void *buf, int len, unsigned int flags,
struct sockaddr *from, int *fromlen);
同样,这就像recv()添加了几个字段。from是一个指向本地结构sockaddr_storage的指针,该结构将填充原始机器的IP地址和端口。Fromlen是一个指向本地int的指针,该int应该初始化为sizeof*from或sizeof(结构sockaddr_storage)。当函数返回时,Fromlen将包含实际存储在from中的地址的长度。
recvfrom()
返回接收到的字节数,或-1
(相应地设置errno
)。
所以,这里有一个问题:为什么我们使用结构sockaddr_storage
作为套接字类型?为什么不结构sockaddr_in
?因为,你看,我们不想把自己束缚在IPv4或IPv6上。所以我们使用通用的结构sockaddr_storage我们知道它足够大。
(所以…这里还有另一个问题:为什么结构Sockaddr
本身对任何地址来说都不够大?我们甚至将通用结构sockaddr_storage
转换为通用结构Sockaddr!看起来是多余的。答案是,它不够大,我想在这一点上改变它会有问题。所以他们做了一个新的。)
请记住,如果您连接了一个数据报套接字()
,那么您可以简单地对所有事务使用发送()
和recv()。套接字本身仍然是一个数据报套接字,数据包仍然使用UDP,但是套接字接口会自动为您添加目的地和源信息。
5.9关闭()
和关闭()-滚出我的脸!
咻!你一整天都在发送()ing
和recv()ing
数据,你已经受够了。你已经准备好关闭套接字描述符上的连接了。这很容易。你可以只使用常规的Unix文件描述符关闭()
函数:
close(sockfd);
这将阻止对套接字的任何读写。任何试图在远程端读写套接字的人都会收到错误。
如果您想对套接字如何关闭有更多的控制,您可以使用关闭()
函数。它允许您切断某个方向的通信,或者双向的通信(就像关闭()
一样)。简介:
int shutdown(int sockfd, int how);
Sockfd是
您要关闭的套接字文件描述符,以及如何关闭以下内容之一:
howEffect0Further receives are disallowed1Further sends are disallowed2Further sends and receives are disallowed (like close())
Shutdown()在成功时返回0
,在错误时返回-1(
相应地设置errno
)。
如果您屈尊在未连接的数据报套接字上使用关闭(),它只会使套接字无法用于进一步的发送()
和recv()调用(
请记住,如果您连接()
您的数据报套接字,您可以使用这些)。
需要注意的是,Shutdown()
实际上并没有关闭文件描述符–它只是改变了文件描述符的可用性。要释放套接字描述符,您需要使用关闭()
。
没什么可说的。
(除了要记住,如果你使用的是Windows和Winsock,你应该调用闭包()而不是关闭()。
)
5.10 getpeername()-
你是谁?
这个功能太简单了。
太简单了,我几乎没有给它自己的部分。但不管怎样,它在这里。
函数getpeername()将
告诉您谁在连接的流套接字的另一端。概要:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
Sockfd
是连接的流套接字的描述符,addr
是一个指向结构Sockaddr(或结构sockaddr_in)
的指针,它将保存关于连接另一侧的信息,addrlen是
一个指向int
的指针,它应该初始化为sizeof*addr或sizeof
(结构
Sockaddr)。
函数在错误时返回-1
,并相应地设置errno
。
一旦您有了他们的地址,您可以使用inet_ntop()、getnameinfo()或gethostbyaddr()来打印或获取更多信息。不,您无法获得他们的登录名。(好的,好的。如果另一台计算机正在运行一个ident守护进程,
这是可能的。但是,这超出了本文档的范围。查看RFC 141322了解更多信息。)
5.11地名()-
我是谁?
比getpeername()更简单的是函数gethostname()。它返回运行程序的计算机的名称。下面的gethostbyname(
)可以使用该名称来确定本地计算机的IP地址。
还有什么比这更有趣的呢?我可以想到一些事情,但是它们与套接字编程无关。不管怎样,下面是分类:
#include <unistd.h>
int gethostname(char *hostname, size_t size);
参数很简单:host name是指向在函数返回时包含主机名
的字符数组的指针,size
是主机名
数组的字节长度。
该函数在成功完成时返回0
,在错误时返回-1
,并像往常一样设置errno
。