字地址[置顶] 《UNIX环境编程》第十六章--网络IPC:套接字

题记:写这篇博客要主是加深自己对字地址的认识和总结实现法算时的一些验经和训教,如果有错误请指出,万分感谢。

    Contents

    

    

套接字口接

    套接字口接是一组用来合结UNIX I/O函数行进程进间通信的函数,大多数系统上都实现了它,括包各种UNIX变种、Windows和Mac系统。

    

/media/note/2012/05/18/linux-socket/fig1.png

套接字口接

    

套接字描述符

    套接字是通信端点的抽象,用使套接字描述符拜访套接字,Linux用文件描述符实现套接字描述符,很多理处文件描述符的函数也可以用于套接字描述符。

    用使 socket 函数建创套接字。

#include <sys/socket.h>

/* 建创套接字
 * @return      胜利返回文件描述符,错出返回-1 */
int socket(int domain, int type, int protocol);

    参数明说:

    

domain

肯定通信的性特,括包地址格式,每一个域都有自己的地址格式。POSIX.1指定的域括包:

  • AF_INET :IPv4地址域。
  • AF_INET6 :IPv6地址域。
  • AF_UNIX :UNIX域。
  • AF_UNSPEC :未指定,可以代表任何域。
type

肯定套接字的型类。POSIX.1定义的套接字型类有:

  • SOCK_SEQPACKET :度长牢固、有序、靠可的面向连接报文递传。
  • SOCK_STREAM :有序、靠可、双向的面向连接节字流。
  • SOCK_DGRAM :度长牢固、不靠可的无连接报文递传。
  • SOCK_RAW :IP协议的据数报口接。
protocol
平日为0,示表按给定的域和套接字型类选择默许协议。在 domain 和 type 给定的情况下如果有多个协议,可以用 protocol 指定协议。

    在 AF_INET 通信域, SOCK_SEQPACKET 套接字型类的默许协议是SCTP, SOCK_STREAM 套接字型类的默许协议是TCP, SOCK_DGRAM 套接字型类的默许协议是UDP。

    面向连接的协议通信可比作打电话。在交换据数前,要求在地本套接字和近程套接字之间建立一个逻辑连接,即连接是端到端的通信道信。话会中不包括地址息信,它隐含在连接中。

    SOCK_STREAM 套接字供提节字流服务,从套接字读取据数时可能须要多次函数调用。 SOCK_SEQPACKET 套接字供提报文服务,从套接字收接的据数量和对方发送的分歧。

    据数报供提了无连接服务,它是一种自含报文,发送据数报可比作寄件邮。可以发送很多据数报,但不证保它们的次序,而且可能会丧失,据数报包括收接地址。

    SOCK_RAW 套接字供提了一个据数报口接用于直接拜访网络层(IP层),用使它时须要自己造构协议部首。建创 SOCK_RAW 套接字须要超级用户特权。

    尽管套接字描述符是文件描述符,担不是全部用使文件描述符的函数都能理处套接字描述符,上面是有关函数的支撑情况:

    

close释放套接字
dup dup2畸形复制
fcntl支撑一些命令,如 F_DUPFD 、 F_GETFD 、 F_GETFL 、 F_GETOWN 、 F_SETFD 、 F_SETFL、 F_SETOWN
fstat支撑一些 stat 结构成员,由实现定义
ioctl支撑分部命令,依赖于底层设备驱动
poll畸形用使
read readv等价于无标记位的 recv
select畸形用使
write writev等价于无标记位的 send

    close 直到套接字的最后一个描述符闭关时才释放网络端点。

    可以用 shutdown 函数来止禁套接字上的输入/出输。

#include <sys/socket.h>

/* 闭关套接字上的输入/出输
 * @return      胜利返回0,错出返回-1 */
int shutdown(int sockfd, int how);

    how 可以取:

    

  • SHUT_RD ,闭关读端,即没法从套接字读取据数。
  • SHUT_WR ,闭关写端,即没法用套接字发送据数。
  • SHUT_RDWR ,闭关读写,同时没法读取和发送据数。

    

寻址

    程进标识肯定标目通信程进,它有两分部:算计机的网络地址肯定算计机,服务肯定算计机上的特定程进。

节字序

CPU有大端节字序和小端节字序两种节字示表次序。为使不同的算计机可以畸形交换息信,网络协议指定了节字序。

TCP/IP协议栈用采大端节字序。可以用上面的函数理处机主节字序和网络节字序之间的转换。

#include <arpa/inet.h>

/* 将机主节字序的32位整型数转换为网络节字序 */
uint32_t htonl(uint32_t hostlong);
/* 将机主节字序的16位整型数转换为网络节字序 */
uint16_t htons(uint16_t hostshort);
/* 将网络节字序的32位整型数转换为机主节字序 */
uint32_t ntohl(uint32_t netlong);
/* 将网络节字序的16位整型数转换为机主节字序 */
uint16_t ntohs(uint16_t netshort);

地址格式

地址标识特定通信域中的套接字端点,地址格式和特定通信域相干。为兼容不同格式的地址,地址被强制转换为通用的地址结构 sockaddr ,Linux系统中该结构定义如下:

struct sockaddr {
    sa_family_t sa_family;      /* 地址型类 */
    char        sa_data[14];    /* 变长地址 */
};

因特网地址定义在 <netinet/in.h> 中。

AF_INET 域中,套接字地址结构如下:

struct in_addr {
    in_addr_t       s_addr;         /* IPv4地址 */
};

struct sockaddr_in {
    sa_family_t     sin_family;     /* 地址型类 */
    in_port_t       sin_port;       /* 端口号 */
    struct in_addr  sin_addr;       /* IPv4地址 */
    unsigned char   sin_zero[8];    /* 0填充 */
};

AF_INET6 域中,套接字地址结构如下:

struct in6_addr {
    uint8_t         s6_addr[16];    /* IPv6地址 */
};

struct sockaddr_in6 {
    sa_family_t     sin6_family;    /* 地址型类 */
    in_port_t       sin6_port;      /* 端口号 */
    uint32_t        sin6_flowinfo;  /* 传输类和流息信 */
    struct in6_addr sin6_addr;      /* IPv6地址 */
    uint32_t        sin6_scope_id;  /* 用作域的口接集 */
};

in_port_t 定义为 uint16_t , in_addr_t 定义为 uint32_t 。

可以用 inet_ntop 和 inet_pton 函数对IPv4和IPv6地址作二进制和点分十进制字符串之间的转换。相似的还有 inet_addr 和 inet_ntoa 等函数,但它们只能用于IPv4地址。

#include <arpa/inet.h>

/* 将网络节字序的二进制地址转换为字符串格式
 * @return      胜利返回地址字符串针指,错出返回NULL */
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
/* 将字符串格式转换为网络节字序的二进制地址
 * @return      胜利返回1,格式无效返回0,错出返回-1 */
int inet_pton(int af, const char *src, void *dst);

af 只支撑 AF_INET 和 AF_INET6 。

size 指定字符串缓冲区 dst 的巨细,可用 INET_ADDRSTRLEN 和 INET6_ADDRSTRLEN 来为IPv4和IPv6地址的字符串设置足够大的空间。

地址查询

用使 gethostent 函数可以找到给定算计机的机主息信。

#include <netdb.h>

/* 得获文件的下一个hostent结构
 * @return      胜利返回指向hostent结构的针指,错出返回NULL */
struct hostent *gethostent(void);
/* gethostent的可重入版本,自设缓冲 */
int gethostent_r(struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop);
/* 回到文件扫尾 */
void sethostent(int stayopen);
/* 闭关文件 */
void endhostent(void);

/* 通过机主名或地址查询hostent结构
 * @return      胜利返回指向hostent结构的针指,错出返回NULL */
struct hostent *gethostbyname(const char *name);
#include <sys/socket.h>       /* for AF_INET */struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);

结构 hostent 的定义如下:

struct hostent {
    char  *h_name;            /* 机主名 */
    char **h_aliases;         /* 机主名别表列 */
    int    h_addrtype;        /* 机主地址型类 */
    int    h_length;          /* 地址度长 */
    char **h_addr_list;       /* 地址表列 */
};

其中的地址用采网络节字序。

gethostbyname 和 gethostbyaddr 函数经已时过。

用使 getnetent 函数可以得获网络名和网络号。

#include <netdb.h>

struct netent *getnetent(void);
void setnetent(int stayopen);
void endnetent(void);

struct netent *getnetbyname(const char *name);
struct netent *getnetbyaddr(uint32_t net, int type);

结构 netent 的定义如下:

struct netent {
    char      *n_name;     /* 网络名 */
    char     **n_aliases;  /* 网络名别表列 */
    int        n_addrtype; /* 网络地址型类 */
    uint32_t   n_net;      /* 网络号 */
};

网络号按网络节字序返回。地址型类为 AF_XX 的地址族常量。

用使 getprotoent 可以得获协议名字和对应协议号。

#include <netdb.h>

struct protoent *getprotoent(void);
void setprotoent(int stayopen);
void endprotoent(void);

struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);

结构 protoent 的定义如下:

struct protoent {
    char  *p_name;       /* 协议名 */
    char **p_aliases;    /* 协议名别表列 */
    int    p_proto;      /* 协议号 */
};

服务由地址的端口号分部示表,每种服务由一个唯一和熟知的端口号供提。用使 getservent 可以得获服务名字和对应端口号。

#include <netdb.h>

struct servent *getservent(void);
void setservent(int stayopen);
void endservent(void);

struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);

结构 servent 的定义如下:

struct servent {
    char  *s_name;       /* 服务名 */
    char **s_aliases;    /* 服务名别表列 */
    int    s_port;       /* 端口号 */
    char  *s_proto;      /* 用使的协议 */
};

getaddrinfo 函数可以将一个机主名字和服务名字映射到一个地址, getnameinfo 的用作相反,用它们换替旧函数 gethostbyname 和 gethostbyaddr 。 freeaddrinfo 用来释放 addrinfo 结构链表。

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

/* 得获地址息信
 * @return      胜利返回0,错出返回非0错误码 */
int getaddrinfo(const char *node, const char *service,
                const struct addrinfo *hints, struct addrinfo **res);
/* 释放一个或多个addrinfo结构的链表 */
void freeaddrinfo(struct addrinfo *res);
/* 将错误码转换为错误息信 */
const char *gai_strerror(int errcode);

/* 得获机主名或/和服务名
 * @return      胜利返回0,错出返回非0值 */
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
                char *host, size_t hostlen,
                char *serv, size_t servlen, int flags);

须要供提机主名或/和服务名,机主名可是以节点名或点分十进制字符串示表的机主地址。

结构 addrinfo 的定义如下:

struct addrinfo {
    int              ai_flags;      /* 自定义行为 */
    int              ai_family;     /* 地址型类 */
    int              ai_socktype;   /* 套接字型类 */
    int              ai_protocol;   /* 协议 */
    size_t           ai_addrlen;    /* 地址度长 */
    struct sockaddr *ai_addr;       /* 地址 */
    char            *ai_canonname;  /* 机主规范名 */
    struct addrinfo *ai_next;       /* 链表中的下一个结构 */
};

可以用 hints 来过滤到得的结构,它只用使 ai_flags 、 ai_family 、 ai_socktype 、 ai_protocol 字段,其他字段必须设为0或 NULL 。

ai_flags 用来指定如何理处地址和名字,可用标记有:

  • AI_ADDRCONFIG :查询配置的地址型类(IPv4或IPv6)。
  • AI_ALL :找查IPv4和IPv6地址,仅用于 AI_V4MAPPED 。
  • AI_CANONNAME :须要一个规范名而不是名别。
  • AI_NUMERICHOST :以字数格式返回机主地址。
  • AI_NUMERICSERV :以端口号返回服务。
  • AI_PASSIVE :套接字地址用于听监绑定。
  • AI_V4MAPPED :如果没找到IPv6地址,返回映射到IPv6格式的IPv4地址。

flags 参数指定一些转换的控制方法,有:

  • NI_DGRAM :服务基于据数报而非基于流。
  • NI_NAMEREQD :如果找不到机主名,将其视作错误。
  • NI_NOFQDN :对于地本机主,仅返回全完定限域名的节点名分部。
  • NI_NUMERICHOST :以字数情势返回机主地址。
  • NI_NUMERICSERV :以端口号返回服务地址。

绑定地址

    对于服务器,须要给收接户客端请求的套接字绑定一个尽人皆知的地址。户客端则让系统选择一个默许地址可即。

    可以用 bind 函数将地址绑定到一个套接字。

#include <sys/socket.h>

/* 将地址绑定到套接字
 * @return      胜利返回0,错出返回-1 */
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    对于地址,要求:

    

  • 地址必须有效,不能指定其他器机的地址。
  • 地址必须和套接字的地址族所支撑的格式匹配。
  • 如果不是超级用户,端口号不能小于1024。
  • 一般只有套接字端点可以和地址绑定。

    对于因特网域,如果指定IP地址为 INADDR_ANY ,套接字端点可以被绑定到全部的系统网络口接,这样它可以收到这个系统的全部卡网的据数包。

    getsockname 函数可以得获绑定到套接字的地址。

#include <sys/socket.h>

/* 得获绑定到套接字的地址
 * @return      胜利返回0,错出返回-1 */
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    addrlen 指定 addr 的缓冲区巨细,返回时会被设置为返回地址的巨细,如果地址过大,则会被断截而不报错。

    套接字经已和对方连接时,可以用 getpeername 函数得获对方的地址。

#include <sys/socket.h>

/* 得获绑定到套接字的地址
 * @return      胜利返回0,错出返回-1 */
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    

建立连接

    面向连接的网络服务在交换据数前须要首先在请求服务的套接字(户客端)和供提服务的套接字(服务器)之间建立连接。可以用 connect 函数建立连接。

#include <sys/socket.h>

/* 建立连接
 * @return      胜利返回0,错出返回-1 */
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    addr 为服务器的地址。如果 sockfd 还没有绑定地址,函数会给它绑定一个默许地址。

    由于服务器的负载变更等题问, connect 可能会返回错误,平日会选择重连,如上面的数指补偿法算。

#include <unistd.h>
#include <sys/socket.h>

#define MAXSLEEP 128

int connect_retry(int sockfd, const struct sockaddr *addr, socklen_t alen)
{
    int nsec;

    /* Try to connect with exponential backoff. */
    for (nsec = 1; nsec <= MAXSLEEP; nsec <<= 1) {
        if (connect(sockfd, addr, alen) == 0) {
            /* Connection accepted. */
            return(0);
        }
        /* Delay before trying again. */
        if (nsec <= MAXSLEEP/2)
            sleep(nsec);
    }
    return(-1);
}

    connect 也可用于无连接网络服务。全部发送报文的标目地址被设为 addr ,并且只能收接来自 addr 的报文。

    服务器用使 listen 函数来明说可以接受连接请求。

#include <sys/socket.h>

/* 明说可以接受连接
 * @return      胜利返回0,错出返回-1 */
int listen(int sockfd, int backlog);

    backlog 议建连接请求的列队巨细,际实值由系统定决,上限为 SOMAXCONN ,该值为128。列队满后,系统会拒绝过剩的连接请求。

    后之用 accept 函数得获连接请求并建立连接。

#include <sys/socket.h>

/* 得获连接请求,建立连接
 * @return      胜利返回文件描述符,错出返回-1 */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    accept 会将户客端地址设置到 addr 指向的缓冲区并更新 addrlen 指向的值,若不心关户客端可将它们设为 NULL 。

    函数返回连接到调用 connect 的户客端的套接字描述符,它和 sockfd 有同相的套接字型类和地址族,sockfd 会被持保可用状态接受其他连接请求。

    如果没有连接请求, accept 会阻塞直到有请求到来,如果 sockfd 为非阻塞模式,则返回-1,并设 errno为 EAGAIN 或 EWOULDBLOCK (两者等价)。服务器也可以用 poll 或 select 来等待请求,请求套接字会被作为可读的。

    

据数传输

    首先,因为套接字端点用文件描述符示表,所以可以用 read 和 write 函数来交换据数,这会带来兼容性上的利益。但如果想得获更多的能功,须要用使 send 和 recv 函数族。

    send 函数族用于发送据数。

#include <sys/types.h>
#include <sys/socket.h>

/* 发送据数
 * @return      胜利返回发送的节字数,错出返回-1 */
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
        const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

    send 函数相似 write ,但它支撑额定的参数 flags ,可用标记有:

    

  • MSG_DONTROUTE :勿将据数由路出地本网络。
  • MSG_DONTWAIT :答应非阻塞操纵,等价于用 O_NONBLOCK 。
  • MSG_EOR :如果协议支撑,此为录记结束。
  • MSG_OOB :如果协议支撑,发送带外据数。

    send 胜利返回只代表据数经已确正发送到网络上,不示表连接另一端的程进收接据数。对于支撑为报文设限的协议,如果报文巨细超越协议支撑的最大值, send 失败并设 errno 为 EMSGSIZE 。对于节字流协议,send 会阻塞直到个整据数被传输。

    sendto 和 send 的区别是它支撑在无连接的套接字上指定标目地址。无连接的套接字如果在调用 connect时没有设置标目地址,则不能用 send 。

    sendmsg 相似于 writev ,可以指定多重缓冲区来传输据数。结构 msghdr 的定义如下:

struct msghdr {
    void         *msg_name;       /* 可选地址 */
    socklen_t     msg_namelen;    /* 地址巨细 */
    struct iovec *msg_iov;        /* I/O缓冲区数组 */
    size_t        msg_iovlen;     /* 数组中元素数 */
    void         *msg_control;    /* 帮助据数 */
    size_t        msg_controllen; /* 帮助据数巨细 */
    int           msg_flags;      /* 收接到的据数的标记 */
};

    应相地, recv 函数族用于收接据数。

#include <sys/types.h>
#include <sys/socket.h>

/* 收接据数
 * @return      胜利返回收接的消息节字数,无可用消息或对方已序按结束返回0,错出返回-1 */
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
        struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

    recv 函数相似 read ,但一样它支撑额定的参数 flags ,可用标记有:

    

  • MSG_WAITALL :等待直到全部据数可用,只对 SOCK_STREAM 有效。
  • MSG_TRUNC :返回报文的际实度长,即使被断截。
  • MSG_PEEK :返回报文容内但不取走报文。
  • MSG_OOB :如果协议支撑,收接带外据数。

    SOCK_DGRAM 和 SOCK_SEQPACKET 套接字型类一次读取就返回个整报文,所以 MSG_WAITALL 对它们没有用作。

    如果发送者用 shutdown 结束传输,或网络协议支撑次序闭关且发送端已闭关,则据数收接完后之 recv 返回0。

    recvfrom 可以到得发送者的源地址,平日用于无连接套接字。

    recvmsg 相似于 readv ,可以将收接到的据数放入多个缓冲区,也可用于想收接帮助据数的情况。 msghdr结构面前经已明说,从 recvmsg 返回时会设置 msg_flags 字段来示表收接的据数的性特,可能的值有:

    

  • MSG_DONTWAIT : recvmsg 处于非阻塞模式。
  • MSG_TRUNC :一般据数被断截。
  • MSG_CTRUNC :控制据数被断截。
  • MSG_EOR :收接到录记结束符。
  • MSG_OOB :收接到带外据数。

    

套接字项选

    有三种型类的套接字项选:

    

  1. 通用项选,适用于全部套接字型类。
  2. 特定套接字的项选,会依赖于上层协议。
  3. 特定协议的项选。

    用 getsockopt 和 setsockopt 函数得获和设置套接字项选。

#include <sys/socket.h>

/* 得获和设置套接字项选
 * @return      胜利返回0,错出返回-1 */
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

    参数明说:

    每日一道理
那蝴蝶花依然花开花落,而我心中的蝴蝶早已化作雄鹰飞向了广阔的蓝天。

    

level
指定项选应用的协议。如果项选是通用的套接字项选,设为 SOL_SOCKET ,否则设为控制该项选的协议号。对TCP项选设为 IPPROTO_TCP ,对UDP项选设为 IPPROTO_UDP ,对IP项选设为 IPPROTO_IP 。
optname

指定套接字项选。通用套接字项选括包:

项选 参数 optval 型类 明说
SO_ACCEPTCONN int 返回息信示指该套接字是否能听监,仅getsockopt
SO_BROADCAST int 如果 *optval 非0,广播据数包
SO_DEBUG int 如果 *optval 非0,启动网络驱动调试能功
SO_DONTROUTE int 如果 *optval 非0,绕过平日由路
SO_ERROR int 返回起挂的套接字错误并清除,仅 getsockopt
SO_KEEPALIVE int 如果 *optval 非0,启动周期性keep-alive消息
SO_LINGER struct linger 当有未发送消息并且套接字闭关时,延迟时间
SO_OOBINLINE int 如果 *optval 非0,将带外据数放到通普据数中
SO_RCVBUF int 收接缓冲区的节字巨细
SO_RCVLOWAT int 收接调用中返回的据数最小节字数
SO_RCVTIMEO struct timeval 套接字收接调用的超时值
SO_REUSEADDR int 如果 *optval 非0,重用 bind 中的地址
SO_SNDBUF int 发送缓冲区的节字巨细
SO_SNDLOWAT int 发送调用中发送的据数最小节字数
SO_SNDTIMEO struct timeval 套接字发送调用的超时值
SO_TYPE int 标识套接字型类,仅 getsockopt
optval
根据项选的不同指向一个据数结构或整数。

    平日TCP不答应绑定一同个地址,可以用 SO_REUSEADDR 来过越这个制限。

#include <errno.h>
#include <sys/socket.h>

int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen)
{
    int fd, err;
    int reuse = 1;

    if ((fd = socket(addr->sa_family, type, 0)) < 0)
        return(-1);
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) < 0) {
        err = errno;
        goto errout;
    }
    if (bind(fd, addr, alen) < 0) {
        err = errno;
        goto errout;
    }
    if (type == SOCK_STREAM || type == SOCK_SEQPACKET) {
        if (listen(fd, qlen) < 0) {
            err = errno;
            goto errout;
        }
    }
    return(fd);
errout:
    close(fd);
    errno = err;
    return(-1);
}

    

带外据数

    带外据数是一些通信协议支撑的可选性特,答应更高优先级的据数比通普据数优先传输。TCP支撑带外据数,UDP不支撑。

    在TCP中带外据数称为急紧据数,TCP只支撑1节字的急紧据数,答应急紧据数在通普据数据数流以外传输。在 send 函数族中指定 MSG_OOB 标记,就会发生急紧据数,取最后1个节字。

    用 SO_OOBINLINE 套接字项选可以在通普据数中收接急紧据数,可以在TCP的通普据数流中看查急紧据数的位置,即急紧标记。

#include <sys/socket.h>

/* 下一个要读的节字在标记处返回1,没在标记处返回0,错出返回-1 */
int sockatmark(int sockfd);

    在读取列队涌现带外据数时, select 函数返回文件描述符并异常起挂。可在通普据数流或用加 MSG_OOB 标记的 recv 函数族收接急紧据数,前面的急紧据数会盖覆面前的。

    

UNIX域套接字

    UNIX域套接字用于一同台器机上的程进间通信。因特网域套接字也可以实现但UNIX域套接字效率更高,后者只复制据数而不理处协和议其他和网络相干的作工。

    UNIX域套接字有流和据数报两种口接,它的据数报服务是靠可的。

    可以用 socketpair 函数建创一对非定名的、相互连接的UNIX域套接字。

#include <sys/socket.h>

/* 建创一对无名的相互连接的UNIX域套接字
 * @return      胜利返回0,错出返回-1 */
int socketpair(int domain, int type, int protocol, int sv[2]);

    例:

#include <sys/socket.h>

/* Returns a full-duplex "stream" pipe (a UNIX domain socket) with the two file descriptors returned in fd[0] and fd[1]. */
int s_pipe(int fd[2])
{
    return(socketpair(AF_UNIX, SOCK_STREAM, 0, fd));
}

    也可以用 socket 建创定名的UNIX域套接字,用标准的 bind 、 listen 、 accept 、 connect 行进理处。

    UNIX域套接字的地址定义在 <sys/un.h> 中,由 sockaddr_un 结构示表:

struct sockaddr_un {
    sa_family_t sun_family;     /* AF_UNIX */
    char        sun_path[108];  /* 路径名 */
};

    可以用 offsetof 函数得获 sun_path 的偏移量再加上 sun_path 的度长到得绑定地址的度长。

    将地址绑定到UNIX域套接字时,系统会用该路径名建创一个 S_IFSOCK 型类的文件。它只用于向户客程进告知套接字名字,不能打开,也不能由应用程序用于通信。如果文件经已存在,则 bind 会失败。闭关套接字时,不会主动删除文件,因此还须要在程序中手动删除。

    

面向连接的ruptime

    户客端:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>
#include "error.h"

#define MAXADDRLEN  256
#define BUFLEN      128

extern int connect_retry(int, const struct sockaddr *, socklen_t);

void print_uptime(int sockfd)
{
    int     n;
    char    buf[BUFLEN];

    while ((n = recv(sockfd, buf, BUFLEN, 0)) > 0)
        write(STDOUT_FILENO, buf, n);
    if (n < 0)
        err_sys("recv error");
}

int main(int argc, char *argv[])
{
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    int             sockfd, err;

    if (argc != 2)
        err_quit("usage: ruptime hostname");
    hint.ai_flags = 0;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_STREAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
        err_quit("getaddrinfo error: %s", gai_strerror(err));
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        if ((sockfd = socket(aip->ai_family, SOCK_STREAM, 0)) < 0)
            err = errno;
        if (connect_retry(sockfd, aip->ai_addr, aip->ai_addrlen) < 0) {
            err = errno;
        } else {
            print_uptime(sockfd);
            exit(0);
        }
    }
    fprintf(stderr, "can't connect to %s: %s\n", argv[1], strerror(err));
    exit(1);
}

    服务器( popen 版本):

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <sys/socket.h>
#include "error.h"

#define BUFLEN  128
#define QLEN 10

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int, struct sockaddr *, socklen_t, int);

void serve(int sockfd)
{
    int     clfd;
    FILE    *fp;
    char    buf[BUFLEN];

    for (;;) {
        clfd = accept(sockfd, NULL, NULL);
        if (clfd < 0) {
            syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
            exit(1);
        }
        if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {
            sprintf(buf, "error: %s\n", strerror(errno));
            send(clfd, buf, strlen(buf), 0);
        } else {
            while (fgets(buf, BUFLEN, fp) != NULL)
                send(clfd, buf, strlen(buf), 0);
            pclose(fp);
        }
        close(clfd);
    }
}

int main(int argc, char *argv[])
{
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    int             sockfd, err, n;
    char            *host;

    if (argc != 1)
        err_quit("usage: ruptimed");
#ifdef _SC_HOST_NAME_MAX
    n = sysconf(_SC_HOST_NAME_MAX);
    if (n < 0)  /* best guess */
#endif
        n = HOST_NAME_MAX;
    host = malloc(n);
    if (host == NULL)
        err_sys("malloc error");
    if (gethostname(host, n) < 0)
        err_sys("gethostname error");
    daemonize("ruptimed");
    hint.ai_flags = AI_CANONNAME;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_STREAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {
        syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err));
        exit(1);
    }
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr,
          aip->ai_addrlen, QLEN)) >= 0) {
            serve(sockfd);
            exit(0);
        }
    }
    exit(1);
}

    服务器( fork 版本):

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include "error.h"

#define QLEN 10

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int, struct sockaddr *, socklen_t, int);

void serve(int sockfd)
{
    int     clfd, status;
    pid_t   pid;

    for (;;) {
        clfd = accept(sockfd, NULL, NULL);
        if (clfd < 0) {
            syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
            exit(1);
        }
        if ((pid = fork()) < 0) {
            syslog(LOG_ERR, "ruptimed: fork error: %s", strerror(errno));
            exit(1);
        } else if (pid == 0) {  /* child */
            /*
             * The parent called daemonize ({Prog daemoninit}), so
             * STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO
             * are already open to /dev/null.  Thus, the call to
             * close doesn't need to be protected by checks that
             * clfd isn't already equal to one of these values.
             */
            if (dup2(clfd, STDOUT_FILENO) != STDOUT_FILENO ||
              dup2(clfd, STDERR_FILENO) != STDERR_FILENO) {
                syslog(LOG_ERR, "ruptimed: unexpected error");
                exit(1);
            }
            close(clfd);
            execl("/usr/bin/uptime", "uptime", (char *)0);
            syslog(LOG_ERR, "ruptimed: unexpected return from exec: %s",
              strerror(errno));
        } else {        /* parent */
            close(clfd);
            waitpid(pid, &status, 0);
        }
    }
}

int main(int argc, char *argv[])
{
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    int             sockfd, err, n;
    char            *host;

    if (argc != 1)
        err_quit("usage: ruptimed");
#ifdef _SC_HOST_NAME_MAX
    n = sysconf(_SC_HOST_NAME_MAX);
    if (n < 0)  /* best guess */
#endif
        n = HOST_NAME_MAX;
    host = malloc(n);
    if (host == NULL)
        err_sys("malloc error");
    if (gethostname(host, n) < 0)
        err_sys("gethostname error");
    daemonize("ruptimed");
    hint.ai_flags = AI_CANONNAME;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_STREAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {
        syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err));
        exit(1);
    }
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr,
          aip->ai_addrlen, QLEN)) >= 0) {
            serve(sockfd);
            exit(0);
        }
    }
    exit(1);
}

    

无连接的ruptime

    户客端:

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>
#include "error.h"

#define BUFLEN      128
#define TIMEOUT     20

void sigalrm(int signo) {}

void print_uptime(int sockfd, struct addrinfo *aip)
{
    int     n;
    char    buf[BUFLEN];

    buf[0] = 0;
    if (sendto(sockfd, buf, 1, 0, aip->ai_addr, aip->ai_addrlen) < 0)
        err_sys("sendto error");
    alarm(TIMEOUT);
    if ((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) < 0) {
        if (errno != EINTR)
            alarm(0);
        err_sys("recv error");
    }
    alarm(0);
    write(STDOUT_FILENO, buf, n);
}

int main(int argc, char *argv[])
{
    struct addrinfo     *ailist, *aip;
    struct addrinfo     hint;
    int                 sockfd, err;
    struct sigaction    sa;

    if (argc != 2)
        err_quit("usage: ruptime hostname");
    sa.sa_handler = sigalrm;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGALRM, &sa, NULL) < 0)
        err_sys("sigaction error");
    hint.ai_flags = 0;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_DGRAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
        err_quit("getaddrinfo error: %s", gai_strerror(err));
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        if ((sockfd = socket(aip->ai_family, SOCK_DGRAM, 0)) < 0) {
            err = errno;
        } else {
            print_uptime(sockfd, aip);
            exit(0);
        }
    }
    fprintf(stderr, "can't contact %s: %s\n", argv[1], strerror(err));
    exit(1);
}

    服务器:

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <sys/socket.h>
#include "error.h"

#define BUFLEN      128
#define MAXADDRLEN  256

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int, struct sockaddr *, socklen_t, int);

void serve(int sockfd)
{
    int         n;
    socklen_t   alen;
    FILE        *fp;
    char        buf[BUFLEN];
    char        abuf[MAXADDRLEN];

    for (;;) {
        alen = MAXADDRLEN;
        if ((n = recvfrom(sockfd, buf, BUFLEN, 0,
          (struct sockaddr *)abuf, &alen)) < 0) {
            syslog(LOG_ERR, "ruptimed: recvfrom error: %s", strerror(errno));
            exit(1);
        }
        if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {
            sprintf(buf, "error: %s\n", strerror(errno));
            sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)abuf, alen);
        } else {
            if (fgets(buf, BUFLEN, fp) != NULL)
                sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)abuf, alen);
            pclose(fp);
        }
    }
}

int main(int argc, char *argv[])
{
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    int             sockfd, err, n;
    char            *host;

    if (argc != 1)
        err_quit("usage: ruptimed");
#ifdef _SC_HOST_NAME_MAX
    n = sysconf(_SC_HOST_NAME_MAX);
    if (n < 0)  /* best guess */
#endif
        n = HOST_NAME_MAX;
    host = malloc(n);
    if (host == NULL)
        err_sys("malloc error");
    if (gethostname(host, n) < 0)
        err_sys("gethostname error");
    daemonize("ruptimed");
    hint.ai_flags = AI_CANONNAME;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_DGRAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {
        syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err));
        exit(1);
    }
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        if ((sockfd = initserver(SOCK_DGRAM, aip->ai_addr,
          aip->ai_addrlen, 0)) >= 0) {
            serve(sockfd);
            exit(0);
        }
    }
    exit(1);
}

 

    

    

文章结束给大家分享下程序员的一些笑话语录: 话剧:程序员过沟
  本剧内容纯属虚构,如有雷同……HEHE……俺也没办法了。
  话说某市街道改建,某某软件公司门口横七竖八挖了几条大沟。一群程序员(SDK程序员赵某,VB程序员钱某,VC程序员孙某,DELPHI程序员李某)下班从公司里出来,看到门前的几条沟,于是各显神通……门前第一条沟也就半米来宽,SDK程序员赵某二话没说,轻轻一跃跳了过去,看到其它人纷纷把随身携带的公文包(类库)横在沟上踩着过沟,不屑地说,这么小一条沟,犯得着小题大做用那个吗?看我多么轻松多么洒脱多么……多么……(众人皆怒目横视之……)
  接着第二条沟有点宽度。SDK程序员赵某还是还是一马当先,飞跃而起……不好,还差一点才到……幸好凭着多年的(跳远?编程?)经验,单手抓住沟沿,颤巍巍地爬了上来,嘴里还念念有词“高手就是高手啊,虽然差一点就……不过毕竟……HEHE……跳远是过沟的基础嘛,有基础(SDK)就有一切的说……”(众人作瞠目结舌状……)看到别人跳过去了,可自己又跳不了那么远,只好再想办法了……VB程序员钱某,DELPHI程序员李某打开手提,连上手机,开始上网找可供过沟的控件……VC程序员孙某却不慌不忙,打开公文包,把几块衬板拆了下来,然后三下五除二拼成一个简易木桥……“虽然这几个板子(类)做得不怎么样,不过先把这个项目应付过去,有时间我自己做一个好了……”于是踩着板子过了沟。
  这时钱某和李某也分别找到了合适的东东。钱某找到的是“钢丝绳.ocx”,安装简单,使用方便,拉出一头,对孙某说“大虾,顺手拉兄弟一把……”,于是把绳子系在沟两边的绿化树木上,踩着钢丝就过了沟。刚刚站稳就四方作揖,“小生这里有礼了”。这时一戴着黄袖圈的老太太跳了出来,抓住钱某,“破坏绿化树木,罚款XXXX元,交钱,交钱,交钱!”(老人家作双枪老太婆怒视伪军状
……钱某被逼无奈,只好边掏钱,边对着后台叫道“导演,我这可是因公牺牲,不给个烈士称号也得报销”,后台一个臭鸡蛋飞出,“叫什么叫,我这个月的粮饷还不知哪里去领呢,都什么时代了,你不下岗都不错了……”)
  李某看着刚刚好不容易从台湾拖回来的“铝条.ZIP”

转载于:https://www.cnblogs.com/jiangu66/archive/2013/04/24/3041146.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值