APUE学习笔记(十六)网络IPC

16.1 套接字描述符

套接字是通信端点的抽象,用套接字描述符访问套接字。套接字描述符本质上是一个文件描述符,但是不是所有接受文件描述符的函数都接受套接字描述符。

为创建一个套接字,调用socket函数。

套接字通信是双向的,可以采用shutdown函数来禁止一个套接字的I/O。

套接字类型描述
SOCK_DGRAM固定长度、无连接、不可靠的报文传递
SOCK_RAWIP协议的数据报接口
SOCK_SEQPACKET固定长度、有序、可靠、面向连接的报文传递
SOCK_STREAM有序、可靠,双向、面向连接的字节流

16.2 寻址

16.2.1 字节序

大端序,最高有效字节在低地址;小端序,最高有效字节在高地址。

主机序由处理器架构决定,intel的处理器通常是小端序。网络序是大端序。

例子

16进制数值 0x04030201 的最高有效字节是最左边 04,最低有效字节在右边 01
在大端序中,04在低地址,01在高地址;在小端序中则相反
16.2.2 地址格式

AF_INET的地址格式为sockaddr_in,而AF_NET6地址为sockaddr_in6,它们均被强制转换成通用地址格式sockaddr输入到套接字例程。

inet_ntop 将网络字节序的二进制地址转换成文本字符串格式。

inet_pton 将文本字符串格式转换成网络字节序的二进制地址。

有很多的查询函数能用于查询地址信息映射,包括网络名和网络编号,协议编号和协议名,服务名和端口号等。

例子

#include "apue.h"
#include <netdb.h>
#include <arpa/inet.h>

void print_family(struct addrinfo *aip) {
    printf(" family ");
    switch (aip->ai_family) {
        case AF_INET:
            printf("inet");
            break;
        case AF_INET6:
            printf("inet6");
            break;
        case AF_UNIX:
            printf("unix");
            break;
        case AF_UNSPEC:
            printf("unspecified");
            break;
        default:
            printf("unknown");
    }
}

void print_type(struct addrinfo *aip) {
    printf(" type ");
    switch (aip->ai_socktype) {
        case SOCK_STREAM:
            printf("stream");
            break;
        case SOCK_DGRAM:
            printf("datagram");
            break;
        case SOCK_SEQPACKET:
            printf("seqpacket");
            break;
        case SOCK_RAW:
            printf("raw");
            break;
        default:
            printf("unkown type");
    }
}

void print_protocol(struct addrinfo *aip) {
    printf(" protocol ");
    switch (aip->ai_protocol) {
        case 0:
            printf("default");
            break;
        case IPPROTO_TCP:
            printf("TCP");
            break;
        case IPPROTO_UDP:
            printf("UDP");
            break;
        case IPPROTO_RAW:
            printf("raw");
            break;
        default:
            printf("unknown protocol");
    }
}

void print_flags(struct addrinfo *aip) {
    printf("flags");
    if (aip->ai_flags == 0) {
        printf(" 0");
    } else {
        if (aip->ai_flags&AI_PASSIVE)
            printf(" passive");
        if (aip->ai_flags & AI_CANONNAME)
            printf(" canon");
        if (aip->ai_flags & AI_NUMERICSERV)
            printf(" numsrv");
        if (aip->ai_flags & AI_V4MAPPED)
            printf(" v4mapped");
        if (aip->ai_flags & AI_ALL)
            printf(" all");
    }
}

int main(int argc, char *argv[]) {
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    struct sockaddr_in *sinp;
    const char *addr;
    int err;
    char abuf[INET_ADDRSTRLEN];
    char service[1024], host[1024];

    if (argc != 3)
        err_quit("usage: %s nodename service", argv[0]);
    hint.ai_flags = AI_CANONNAME;
    hint.ai_family = 0;
    hint.ai_socktype = 0;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;

    //从服务名主机名获取socket地址,返回的是一个链表
    if ((err = getaddrinfo(argv[1], argv[2], &hint, &ailist)) != 0)
        err_quit("getaddrinfo error: %s", gai_strerror(err));

    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        print_flags(aip);
        print_family(aip);
        print_type(aip);
        print_protocol(aip);
        printf("\n\thost %s", aip->ai_canonname?aip->ai_canonname:"-");

        if (aip->ai_family == AF_INET) {
            sinp = (struct sockaddr_in *)aip->ai_addr;
            addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf, INET_ADDRSTRLEN);
            printf(" address %s", addr?addr:"unknown");
            printf(" port %d", ntohs(sinp->sin_port));
        }
        printf("\n");

        //从socket地址获取主机名和服务名
        if(getnameinfo((struct sockaddr *)aip->ai_addr, sizeof(struct sockaddr), host, 1024, service, 1024, 0) != 0)
            err_sys("getnameinfo error");
        else {
            printf("getnameinfo, host: %s, service: %s\n", host, service);
        }
    }
    exit(0);
}
16.2.3 套接字与地址关联

对于服务器,需要将接收客户端请求的服务器套接字关联一个众所周知的地址。

使用bind函数来关联地址和套接字。调用getsockname函数来发现绑定到套接字上的地址。调用getpeername函数来找到对
方的地址。

指定的地址必须有效;地址中的端口号必须不小于1024。

16.3 建立连接

使用connect函数来建立连接。

如果用SOCK_DGRAM套接字调用connect,传送的报文的目标地址会设置成connect调用中所指定的地址,并且仅能接收来自指定地址的报文。

服务器调用listen函数来宣告它愿意接受连接请求。使用accept函数获得连接请求并建立连接。

16.3.1 有连接的例子

服务器端,运行后成为后台进程。在/etc/services中添加ruptime服务信息,指定一个端口,否则客户端找不到服务。

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <sys/socket.h>

#define BUFLEN	128
#define QLEN 10

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

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

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

  set_cloexec(sockfd);   //fork子进程并执行exec之后,删除无用的fd
  for (;;) {
    if ((clfd = accept(sockfd, NULL, NULL)) < 0) {  //接受请求,返回客户端fd
      syslog(LOG_ERR, "ruptimed: accept error: %s",
             strerror(errno));
      exit(1);
    }
    set_cloexec(clfd);
    if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {  //创建到uptime的管道
      sprintf(buf, "error: %s\n", strerror(errno));
      send(clfd, buf, strlen(buf), 0);
    } else {
      while (fgets(buf, BUFLEN, fp) != NULL)  //从uptime管道读取数据
        send(clfd, buf, strlen(buf), 0);   //发送到客户端
      pclose(fp); //关闭管道
    }
    close(clfd);  //关闭客户端fd
  }
}

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");
  if ((n = sysconf(_SC_HOST_NAME_MAX)) < 0)
    n = HOST_NAME_MAX;	/* best guess */
  if ((host = malloc(n)) == NULL)
    err_sys("malloc error");
  if (gethostname(host, n) < 0)
    err_sys("gethostname error");
  daemonize("ruptimed");   //服务进程设置成守护进程
  memset(&hint, 0, sizeof(hint));
  hint.ai_flags = AI_CANONNAME;  //返回标准名
  hint.ai_socktype = SOCK_STREAM;
  hint.ai_canonname = NULL;
  hint.ai_addr = NULL;
  hint.ai_next = NULL;
  if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {  //查询本机服务信息,这里需要提前添加ruptime到/etc/services
    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) { //初始化服务器监听socket
      serve(sockfd); //接受请求并响应
      exit(0);
    }
  }
  exit(1);
}

客户端程序

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>

#define BUFLEN		128

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

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

  while ((n = recv(sockfd, buf, BUFLEN, 0)) > 0)  //从fd中读取数据,输出到标准输出
    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");
  memset(&hint, 0, sizeof(hint));
  hint.ai_socktype = SOCK_STREAM;
  hint.ai_canonname = NULL;
  hint.ai_addr = NULL;
  hint.ai_next = NULL;
  if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0) //需要知道服务器host,获取地址列表
    err_quit("getaddrinfo error: %s", gai_strerror(err));
  for (aip = ailist; aip != NULL; aip = aip->ai_next) {
    if ((sockfd = connect_retry(aip->ai_family, SOCK_STREAM, 0,
                                aip->ai_addr, aip->ai_addrlen)) < 0) {  //创建连接,返回客户端fd
      err = errno;
    } else {
      print_uptime(sockfd);
      exit(0);
    }
  }
  err_exit(err, "can't connect to %s", argv[1]);
}

选择套接字类型

  • 无连接套接字是无次序的
  • 数据包的最大尺寸不同
  • 无连接套接字可能会丢失
16.3.2 无连接例子

服务器端,在启动之前,要修改/etc/services,添加ruptime的udp服务

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <sys/socket.h>

#define BUFLEN	128
#define MAXADDRLEN 256

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

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

void
serve(int sockfd)
{
  int		n;
  socklen_t alen;
  FILE	*fp;
  char	buf[BUFLEN];
  char abuf[MAXADDRLEN];  //数组内存已经分配好了
  struct sockaddr  *addr = (struct sockaddr *)abuf; //转换为sockaddr类型

  set_cloexec(sockfd);
  for(;;) {
    alen = MAXADDRLEN;
    if ((n = recvfrom(sockfd, buf, BUFLEN, 0, addr, &alen)) <0) { //从监听套接字读取数据,addr保存客户端地址
      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, addr, alen);  //向客户端地址发送数据,sockfd指服务端套接字
    } else {
      if (fgets(buf, BUFLEN, fp) != NULL)
        sendto(sockfd, buf, strlen(buf), 0, addr, 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");
  if ((n = sysconf(_SC_HOST_NAME_MAX)) < 0)
    n = HOST_NAME_MAX;	/* best guess */
  if ((host = malloc(n)) == NULL)
    err_sys("malloc error");
  if (gethostname(host, n) < 0)
    err_sys("gethostname error");
  daemonize("ruptimed");   //服务进程设置成守护进程
  memset(&hint, 0, sizeof(hint));
  hint.ai_flags = AI_CANONNAME;  //返回标准名
  hint.ai_socktype = SOCK_DGRAM;
  hint.ai_canonname = NULL;
  hint.ai_addr = NULL;
  hint.ai_next = NULL;
  if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {  //查询本机服务信息,这里需要提前添加ruptime到/etc/services
    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) { //初始化服务器监听socket
      serve(sockfd); //接受请求并响应
      exit(0);
    }
  }
  exit(1);
}

客户端

#include "apue.h"
#include <sys/socket.h>
#include <netdb.h>
#include <errno.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) //向服务器地址发送数据,服务器向sockfd返回数据
    err_sys("sendto error");
  alarm(TIMEOUT);  //设置定时器
  if ((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) < 0) {  //等待从sockfd返回数据,直到超时
    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");

  memset(&hint, 0, sizeof(hint));
  hint.ai_socktype = SOCK_DGRAM;  //数据报
  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) {  //创建socket
      err = errno;
    } else {
      print_uptime(sockfd, aip);
      exit(0);
    }
  }

  fprintf(stderr, "can't contact %s: %s\n", argv[1], strerror(err));
  exit(1);
}

16.4 套接字选项

套接字可以设置和查询选项,来控制套接字的特性。

例子

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

int initserver(int type, 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;
  //SOL_SOCKET  通用套接字选项
  //IPPROTO_TCP  TCP选项
  //IPPROTO_IP  IP选项
  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)))
  if (bind(fd, addr, alen) < 0) {  //把套接字和传入的地址绑定,传入的地址对应对外提供的服务
    printf("bind error");
    goto errout;
  }

  if (type == SOCK_STREAM || type == SOCK_SEQPACKET) {
    if (listen(fd, qlen) < 0) {  //表示套接字可以进行监听请求,qlen指定监听队列长度
      printf("listen error");
      goto errout;
    }
  }
  return fd;  //返回创建好的套接字
  errout:
  err = errno;
  close(fd);
  errno = err;
  return -1;
}

16.5 带外数据

带外数据是一些通信协议支持的可选功能,比普通数据以更高优先级传输。带外数据先行传输,即使传输队列已经有数据。

TCP支持带外数据,但是UDP不支持。TCP仅支持一个字节的紧急数据,但是允许紧急数据在普通数据传递机制数据流之外传输。

16.6 非阻塞和异步IO

recv 函数没有数据可用时会阻塞等待。当套接字输出队列没有足够空间来发送消息时,send 函数会阻塞。

在套接字非阻塞模式下,行为会改变。

在基于套接字的异步I/O中,当从套接字中读取数据时,或者当套接字写队列中空间变得可用时,可以安排要发送的信号SIGIO。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值