JavaScript is required to for searching.
跳过导航链接
退出打印视图
编程接口指南     Oracle Solaris 10 1/13 Information Library (简体中文)
search filter icon
search icon

文档信息

前言

1.  内存和 CPU 管理

2.  用于 Solaris Cluster 的远程共享内存 API

3.  会话描述协议 API

4.  进程调度程序

5.  地址组 API

6.  输入/输出接口

7.  进程间通信

8.  套接字接口

SunOS 4 二进制兼容性

套接字概述

套接字库

套接字类型

接口组

套接字基础知识

创建套接字

绑定本地名称

建立连接

连接错误

数据传输

关闭套接字

连接流套接字

输入/输出多路复用

数据报套接字

标准例程

主机和服务名称

主机名-hostent

网络名称-netent

协议名-protoent

服务名-servent

其他例程

客户机/服务器程序

套接字和服务器

套接字和客户机

无连接服务器

高级套接字主题

带外数据

非阻塞套接字

异步套接字 I/O

中断驱动套接字 I/O

信号和进程组 ID

选择特定的协议

地址绑定

套接字选项

inetd 守护进程

广播及确定网络配置

使用多播

发送 IPv4 多播数据报

接收 IPv4 多播数据报

发送 IPv6 多播数据报

接收 IPv6 多播数据报

流控制传输协议

SCTP 栈实现

SCTP 套接字接口

sctp_bindx()

sctp_opt_info()

sctp_recvmsg()

sctp_sendmsg()

sctp_send()

分叉关联

sctp_getpaddrs()

sctp_freepaddrs()

sctp_getladdrs()

sctp_freeladdrs()

SCTP 用法代码示例

9.  使用 XTI 和 TLI 编程

10.  包过滤钩子

11.  传输选择和名称到地址映射

12.  实时编程和管理

13.  Solaris ABI 和 ABI 工具

A.  UNIX 域套接字

索引

套接字基础知识

本节介绍基本套接字接口的用法。

创建套接字

socket(3SOCKET) 调用创建指定系列和指定类型的套接字。

s = socket(family, type, protocol);

如果未指定协议,则系统将选择支持所需套接字类型的协议。将返回套接字句柄。套接字句柄即为文件描述符。

familysys/socket.h 中定义的一个常量指定。名为 AF_suite 的常量指定要在解释名称中使用的地址格式:

AF_APPLETALK

Apple Computer Inc. Appletalk 网络

AF_INET6

适用于 IPv6 和 IPv4 的 Internet 系列

AF_INET

仅适用于 IPv4 的 Internet 系列

AF_PUP

Xerox Corporation PUP internet

AF_UNIX

UNIX 文件系统

套接字类型在 sys/socket.h 中定义。AF_INET6 AF_INETAF_UNIX 支持 SOCK_STREAMSOCK_DGRAMSOCK_RAW 这些类型。以下示例创建 Internet 系列的流套接字:

s = socket(AF_INET6, SOCK_STREAM, 0);

此调用生成流套接字。TCP 协议提供底层通信。在大多数情况下,将 protocol 参数设置为缺省值 0。可以指定缺省协议之外的其他协议,如高级套接字主题中所述。

绑定本地名称

创建套接字时不指定名称。只有在套接字绑定到地址之后,远程进程才能引用此套接字。用于通信的进程通过地址连接。在 Internet 系列中,连接由本地和远程地址以及本地和远程端口组成。不能存在重复排序组,如 protocollocal address local portforeign addressforeign port。在大多数系列中,连接必须唯一。

使用 bind(3SOCKET) 接口,进程可以指定套接字的本地地址。此接口组成 local addresslocal port 组。connect(3SOCKET)accept(3SOCKET) 通过添加地址元组的远程部分来完成套接字的关联。bind(3SOCKET) 调用的用法如下:

bind (s, name, namelen);

套接字句柄为 s。绑定名称是由支持协议解释的字节字符串。Internet 系列名称包含 Internet 地址和端口号。

本示例说明如何绑定 Internet 地址。

#include <sys/types.h>
#include <netinet/in.h>
...
struct sockaddr_in6 sin6;
...
s = socket(AF_INET6, SOCK_STREAM, 0);
bzero (&sin6, sizeof (sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_addr.s6_addr = in6addr_arg;
sin6.sin6_port = htons(MYPORT);
bind(s, (struct sockaddr *) &sin6, sizeof sin6);

地址 sin6 的内容在讨论 Internet 地址绑定的地址绑定中介绍。

建立连接

通常以非对称形式建立连接,一个进程用作客户机,而另一个进程则用作服务器。服务器将套接字绑定到与服务关联的已知地址,并阻塞在套接字上等待连接请求。然后,不相关的进程便可连接到此服务器。客户机通过启动到服务器套接字的连接,向服务器请求服务。在客户机端,connect(3SOCKET) 调用启动连接。在 Internet 系列中,此连接可能如下所示:

struct sockaddr_in6 server;
...
connect(s, (struct sockaddr *)&server, sizeof server);

如果在连接调用期间未绑定客户机的套接字,则系统会自动选择一个名称并将其绑定到套接字。有关更多信息,请参见地址绑定。这种自动选择是将本地地址绑定到客户机端套接字的常规方法。

要接收客户机的连接,服务器必须在绑定其套接字之后执行两个步骤。第一步是说明可以排队多少连接请求。第二步接受连接。

struct sockaddr_in6 from;
...
listen(s, 5);                /* Allow queue of 5 connections */
fromlen = sizeof(from);
newsock = accept(s, (struct sockaddr *) &from, &fromlen);

套接字句柄 s 是绑定到连接请求要发送到的地址的套接字。listen(3SOCKET) 的第二个参数指定可以对其进行排队的最大未完成连接数。from 结构使用客户机的地址进行填充。可能传递 NULL 指针。fromlen 为此结构的长度。

通常,accept(3SOCKET) 例程阻塞进程。accept(3SOCKET) 返回一个连接到请求客户机的新套接字描述符。fromlen 的值将更改为此地址的实际大小。

服务器无法指示其只接受来自特定地址的连接。服务器可以检查由 accept(3SOCKET) 返回的 from 地址并关闭与不可接受的客户机之间的连接。服务器可以接受多个套接字上的连接,或者避免在调用 accept(3SOCKET) 时阻塞。高级套接字主题中介绍了这些技术。

连接错误

如果连接失败,则会返回错误,但是由系统绑定的地址保持不变。如果连接成功,则套接字与服务器关联,并且可以开始数据传输。

下表列出在连接尝试失败时返回的一些比较常见的错误。

表 8-1 套接字连接错误

套接字错误
错误说明
ENOBUFS
支持调用的可用内存不足。
EPROTONOSUPPORT
请求未知协议。
EPROTOTYPE
请求不支持的套接字类型。
ETIMEDOUT
未在指定时间内建立连接。当目标主机关闭或由于丢失传输而导致网络问题时,会发生此错误。
ECONNREFUSED
主机拒绝服务。当服务器进程未在请求地址中显示时,会发生此错误。
ENETDOWNEHOSTDOWN
这些错误是由底层通信接口传送的状态信息造成的。
ENETUNREACHEHOSTUNREACH
由于不存在到网络或主机的路由,因此可能会发生这些操作错误。这些错误还可能是由中间网关或切换节点所返回的状态信息造成的。返回的状态信息并不总是足以区分网络故障和主机故障。

数据传输

本节介绍用于发送和接收数据的接口。可以使用常规 read(2)write(2) 接口来发送或接收消息。

write(s, buf, sizeof buf);
read(s,  buf, sizeof buf);

还可以使用 send(3SOCKET)recv(3SOCKET)

send(s, buf, sizeof buf, flags);
recv(s, buf, sizeof buf, flags);

send(3SOCKET)recv(3SOCKET) 非常类似于 read(2)write(2),但是 flags 参数至关重要。如果需要一个或多个以下项,则可以将 sys/socket.h 中定义的 flags 参数指定为非零值:

MSG_OOB

发送和接收带外数据

MSG_PEEK

查看数据而不读取

MSG_DONTROUTE

发送数据而不路由包

带外数据特定于流套接字。使用 recv(3SOCKET) 调用指定 MSG_PEEK 之后,所有显示的数据均返回给用户,但是仍视为不可读取。套接字上的下一个 read(2)recv(3SOCKET) 调用将返回相同数据。当前只有路由表管理进程使用发送数据而不路由包的选项(应用于传出包)。

关闭套接字

可以通过 close(2) 接口调用废弃 SOCK_STREAM 套接字。如果在 close(2) 接口调用之后数据排队到保证可靠传送的套接字,则协议会继续尝试传输数据。如果数据在任意时间之后还不能传送,则会将其废弃。

shutdown(3SOCKET) 可正常关闭 SOCK_STREAM 套接字。这两个进程均可确认不再发送。此调用的形式为:

shutdown(s, how);

其中 how 定义为

0

禁止进一步接收数据

1

禁止进一步传输数据

2

禁止进一步传输和接收

连接流套接字

以下两个示例说明如何启动和接受 Internet 系列流连接。

图 8-1 使用流套接字的面向连接的通信

image:此图使用接受/连接和读取/写入函数对说明客户机和服务器之间的数据流。

以下是针对服务器的示例程序。服务器创建套接字并将名称绑定到此套接字,然后显示端口号。此程序调用 listen(3SOCKET) 将套接字标记为可以接受连接请求并初始化请求队列。此程序的其余部分为一个死循环。每次循环都接受一个新的连接并将其从队列中删除,从而创建一个新的套接字。服务器读取并显示此套接字中的消息,然后关闭此套接字。地址绑定中介绍了 in6addr_any 的用法。

示例 8-1 接受 Internet 流连接(服务器)

#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#define TRUE 1   
/*
 * This program creates a socket and then begins an infinite loop.
 * Each time through the loop it accepts a connection and prints
 * data from it. When the connection breaks, or the client closes
 * the connection, the program accepts a new connection.
*/
main() {
    int sock, length;
    struct sockaddr_in6 server;
    int msgsock;
    char buf[1024];
    int rval;
    /* Create socket. */
    sock = socket(AF_INET6, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("opening stream socket");
        exit(1);
    }
    /* Bind socket using wildcards.*/
    bzero (&server, sizeof(server));
    server.sin6_family = AF_INET6;
    server.sin6_addr = in6addr_any;
    server.sin6_port = 0;
    if (bind(sock, (struct sockaddr *) &server, sizeof server)
            == -1) {
        perror("binding stream socket");
        exit(1);
    }
    /* Find out assigned port number and print it out. */
    length = sizeof server;
    if (getsockname(sock,(struct sockaddr *) &server, &length)
            == -1) {
        perror("getting socket name");
        exit(1);
    }
    printf("Socket port #%d\n", ntohs(server.sin6_port));
    /* Start accepting connections. */
    listen(sock, 5);
    do {
        msgsock = accept(sock,(struct sockaddr *) 0,(int *) 0);
        if (msgsock == -1)
            perror("accept");
        else do {
            memset(buf, 0, sizeof buf);
            if ((rval = read(msgsock,buf, sizeof(buf))) == -1)
                perror("reading stream message");
            if (rval == 0)
                printf("Ending connection\n");
            else
                /* assumes the data is printable */
                printf("-->%s\n", buf);
        } while (rval > 0);
        close(msgsock);
    } while(TRUE);
    /*
     * Since this program has an infinite loop, the socket "sock" is
     * never explicitly closed. However, all sockets are closed
     * automatically when a process is killed or terminates normally.
     */
    exit(0);
}

要启动连接,示例 8-2 中的客户机程序会创建一个流套接字,然后调用 connect(3SOCKET),指定用于连接的套接字地址。如果存在目标套接字,并且接受了请求,则连接会完成。现在,程序可以发送数据。数据按顺序传送,并且没有消息边界。此连接会在任何一个套接字关闭时销毁。 有关此程序中的数据表示例程(如 ntohl(3SOCKET)ntohs(3SOCKET)htons(3SOCKET)htonl(3XNET))的更多信息,请参见 byteorder(3SOCKET) 手册页。

示例 8-2 Internet 系列流连接(客户机)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#define DATA "Half a league, half a league . . ."   
/* 
 * This program creates a socket and initiates a connection with 
 * the socket given in the command line. Some data are sent over the 
 * connection and then the socket is closed, ending the connection.
 * The form of the command line is: streamwrite hostname portnumber 
 * Usage: pgm host port 
 */ 
main(int argc, char *argv[])
{
    int sock, errnum, h_addr_index;
    struct sockaddr_in6 server;
    struct hostent *hp;
    char buf[1024];
    /* Create socket. */
    sock = socket( AF_INET6, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("opening stream socket");
        exit(1);
    }
    /* Connect socket using name specified by command line. */
    bzero (&server, sizeof (server));
    server.sin6_family = AF_INET6;
    hp = getipnodebyname(argv[1], AF_INET6, AI_DEFAULT, &errnum);
/*
 * getipnodebyname returns a structure including the network address
 * of the specified host.
 */
    if (hp == (struct hostent *) 0) {
        fprintf(stderr, "%s: unknown host\n", argv[1]);
        exit(2);
    }
    h_addr_index = 0;
    while (hp->h_addr_list[h_addr_index] != NULL) {
        bcopy(hp->h_addr_list[h_addr_index], &server.sin6_addr,
                    hp->h_length);
        server.sin6_port = htons(atoi(argv[2]));
        if (connect(sock, (struct sockaddr *) &server,
                    sizeof (server)) == -1) {
            if (hp->h_addr_list[++h_addr_index] != NULL) {
                /* Try next address */
                continue;
            }
            perror("connecting stream socket");
            freehostent(hp);
            exit(1);
        }
        break;
    }
    freehostent(hp);
    if (write( sock, DATA, sizeof DATA) == -1)
        perror("writing on stream socket");
    close(sock);
    freehostent (hp);
    exit(0);
}

可以将一对一 SCTP 连接支持添加到流套接字。以下示例代码将 -p 添加到现有程序,使此程序可以指定要使用的协议。

示例 8-3 将 SCTP 支持添加到流套接字

#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>

int
main(int argc, char *argv[])
{
    struct protoent *proto = NULL;
    int c;
    int s;
    int protocol;

    while ((c = getopt(argc, argv, "p:")) != -1) {
        switch (c) {
        case 'p':
            proto = getprotobyname(optarg);
            if (proto == NULL) {
                fprintf(stderr, "Unknown protocol: %s\n",
                        optarg);
                return (-1);
            }
            break;
        default:
            fprintf(stderr, "Unknown option: %c\n", c);
            return (-1);
        }
    }

    /* Use the default protocol, which is TCP, if not specified. */
    if (proto == NULL)
        protocol = 0;
    else
        protocol = proto->p_proto;

    /* Create a IPv6 SOCK_STREAM socket of the protocol. */
    if ((s = socket(AF_INET6, SOCK_STREAM, protocol)) == -1) {
        fprintf(stderr, "Cannot create SOCK_STREAM socket of type %s: "
                "%s\n", proto != NULL ? proto->p_name : "tcp",
                strerror(errno));
        return (-1);
    }
    printf("Success\n");
    return (0);
}

输入/输出多路复用

请求可以在多个套接字或多个文件之间多路复用。使用 select(3C) 进行多路复用:

#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
...
fd_set readmask, writemask, exceptmask;
struct timeval timeout;
...
select(nfds, &readmask, &writemask, &exceptmask, &timeout);

select(3C) 的第一个参数是列表中由接下来的三个参数指向的文件描述符数。

select(3C) 的第二、第三以及第四个参数指向三组文件描述符:一组描述符用于读取,一组用于写入,一组用于接受例外情况。带外数据是唯一的例外情况。可以将其中任一指针指定为已正确强制转换的空指针。每一组都是包含长整数位掩码数组的结构。可以使用 select.h 中定义的 FD_SETSIZE 来设置数组的大小。此数组的长度足以为每个 FD_SETSIZE 文件描述符存储一个位。

FD_SET (fd, & mask) 和 FD_CLR (fd, & mask) 分别在组 mask 中添加和删除文件描述符 fd。此组应该在使用之前设置为零,并且宏 FD_ZERO (&mask) 将清除组 mask

select(3C) 的第五个参数用于指定超时值。如果 timeout 指针为 NULL,则 select(3C) 将阻塞,直到可以选择描述符或收到信号为止。如果 timeout 中的字段均设置为 0,则 select(3C) 会进行轮询并立即返回。

select(3C) 例程通常返回所选的文件描述符数,或者在已超时的情况下返回零。对于错误或中断,select(3C) 例程返回 -1,并且 errno 中的错误号以及文件描述符掩码保持不变。对于成功的返回,三个组指示哪些文件描述符可以读取、写入或具有暂挂的例外情况。

使用 FD_ISSET (fd, &mask) 宏在选择掩码中测试文件描述符的状态。如果 fd 位于组 mask 中,则此宏会返回非零值。否则,此宏会返回零。针对读取组先后使用 select(3C)FD_ISSET ( fd, &mask) 宏,以便检查套接字上的排队连接请求。

以下示例显示如何针对可读性在侦听套接字上做出选择,以确定何时可以通过调用 accept(3SOCKET) 接受新的连接。程序将接受连接请求,读取数据,并在单个套接字上断开连接。

示例 8-4 使用 select(3C) 检查暂挂连接

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time/h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#define TRUE 1
/*
 * This program uses select to check that someone is
 * trying to connect before calling accept.
 */
main() {
    int sock, length;
    struct sockaddr_in6 server;
    int msgsock;
    char buf[1024];
    int rval;
    fd_set ready;
    struct timeval to;
    /* Open a socket and bind it as in previous examples. */
    /* Start accepting connections. */
    listen(sock, 5); 
    do {
        FD_ZERO(&ready);
        FD_SET(sock, &ready);
        to.tv_sec = 5;
        to.tv_usec = 0;
        if (select(sock + 1, &ready, (fd_set *)0, 
                   (fd_set *)0, &to) == -1) {
            perror("select");
            continue;
        }
        if (FD_ISSET(sock, &ready)) {
            msgsock = accept(sock, (struct sockaddr *)0, (int *)0);
            if (msgsock == -1)
                perror("accept");
            else do {
                memset(buf, 0, sizeof buf);
                if ((rval = read(msgsock, buf, sizeof(buf))) == -1)
                    perror("reading stream message");
                else if (rval == 0)
                    printf("Ending connection\n");
                else
                    printf("-->%s\n", buf);
            } while (rval > 0);
            close(msgsock);
        } else
            printf("Do something else\n");
        } while (TRUE);
    exit(0);
}

在早期版本的 select(3C) 例程中,其参数是指向整数的指针,而不是指向 fd_sets 的指针。在文件描述符数小于整数中的位数时,此风格的调用仍然有效。

select(3C) 例程提供一种同步多路复用方案。高级套接字主题中介绍的 SIGIO SIGURG 信号提供有关输出完成、输入可用性以及例外情况的异步通知。