数据报套接字提供了对称数据交换接口,无需建立连接。每条消息都带有目标地址。下图显示了服务器与客户机之间的通信流。
服务器的 bind(3SOCKET) 步骤为可选步骤。
图 8-2 使用数据报套接字的无连接通信
按照创建套接字中所述创建数据报套接字。如果需要特定本地地址,必须在首次数据传输之前执行 bind(3SOCKET) 操作。否则,系统会在首次发送数据时设置本地地址或端口。使用 sendto(3SOCKET) 发送数据。
sendto(s, buf, buflen, flags, (struct sockaddr *) &to, tolen);
s、buf、 buflen 和 flags 参数与面向连接的套接字中的相应参数相同。to 和 tolen 值指示消息预期接受者的地址。在本地检测到的错误情况(如无法访问网络)会导致返回 -1,并将 errno 设置为错误号。
recvfrom(s, buf, buflen, flags, (struct sockaddr *) &from, &fromlen);
要在数据报套接字上接收消息,请使用 recvfrom(3SOCKET)。在调用之前,fromlen 将会设置为 from 缓冲区的大小。返回时, fromlen 将会设置为接收数据报的地址的大小。
数据报套接字还可以使用 connect(3SOCKET) 调用将套接字与特定目标地址关联。然后,此套接字便可以使用 send(3SOCKET) 调用。如果在未显式指定目标地址的套接字上发送数据,则目标地址为已连接的对等方。只传送从该对等方接收的数据。一个套接字一次只能有一个已连接的地址。再次调用 connect(3SOCKET) 会更改目标地址。将立即返回数据报套接字上的连接请求。系统将记录对等方的地址。不能将 accept(3SOCKET) 或 listen(3SOCKET) 用于数据报套接字。
连接数据报套接字之后,便可以从先前的 send(3SOCKET) 调用中异步返回错误。此套接字可以在后续套接字操作中报告这些错误。或者,此套接字可以使用 getsockopt(3SOCKET) 的选项 SO_ERROR 来询问错误状态。
以下示例代码说明如何通过创建一个套接字、将名称绑定到该套接字以及将消息发送到该套接字来发送 Internet 调用。
示例 8-5 发送 Internet 系列数据报
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#define DATA "The sea is calm, the tide is full . . ."
/*
* Here I send a datagram to a receiver whose name I get from
* the command line arguments. The form of the command line is:
* dgramsend hostname portnumber
*/
main(int argc, char *argv[])
{
int sock, errnum;
struct sockaddr_in6 name;
struct hostent *hp;
/* Create socket on which to send. */
sock = socket(AF_INET6,SOCK_DGRAM, 0);
if (sock == -1) {
perror("opening datagram socket");
exit(1);
}
/*
* Construct name, with no wildcards, of the socket to ``send''
* to. getinodebyname returns a structure including the network
* address of the specified host. The port number is taken from
* the command line.
*/
hp = getipnodebyname(argv[1], AF_INET6, AI_DEFAULT, &errnum);
if (hp == (struct hostent *) 0) {
fprintf(stderr, "%s: unknown host\n", argv[1]);
exit(2);
}
bzero (&name, sizeof (name));
memcpy((char *) &name.sin6_addr, (char *) hp->h_addr,
hp->h_length);
name.sin6_family = AF_INET6;
name.sin6_port = htons(atoi(argv[2]));
/* Send message. */
if (sendto(sock,DATA, sizeof DATA ,0,
(struct sockaddr *) &name,sizeof name) == -1)
perror("sending datagram message");
close(sock);
exit(0);
}
以下样例代码说明如何通过创建套接字,将名称绑定到套接字,然后从套接字进行读取来读取 Internet 调用。
示例 8-6 读取 Internet 系列数据报
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
/*
* This program creates a datagram socket, binds a name to it, then
* reads from the socket.
*/
main()
{
int sock, length;
struct sockaddr_in6 name;
char buf[1024];
/* Create socket from which to read. */
sock = socket(AF_INET6, SOCK_DGRAM, 0);
if (sock == -1) {
perror("opening datagram socket");
exit(1);
}
/* Create name with wildcards. */
bzero (&name, sizeof (name));
name.sin6_family = AF_INET6;
name.sin6_addr = in6addr_any;
name.sin6_port = 0;
if (bind (sock, (struct sockaddr *)&name, sizeof (name)) == -1) {
perror("binding datagram socket");
exit(1);
}
/* Find assigned port value and print it out. */
length = sizeof(name);
if (getsockname(sock,(struct sockaddr *) &name, &length)
== -1) {
perror("getting socket name");
exit(1);
}
printf("Socket port #%d\n", ntohs(name.sin6_port));
/* Read from the socket. */
if (read(sock, buf, 1024) == -1 )
perror("receiving datagram packet");
/* Assumes the data is printable */
printf("-->%s\n", buf);
close(sock);
exit(0);
}