UNP v1 第一章:简介

前言

开坑作死小能手在csapp的大坑才刚开始的情况下又来开个新坑了。。。不想像csapp那坑那样作死了。。。此篇以代码为主。。。
UNIX网络编程分为两卷,分别为套接字联网API和进程间通信。

1.1 概述

编写网络通信程序前,需要确定程序通信所用的协议。大多数的网络应用是分为客户(Client)和服务器(Server)来组织的,通常称之为C/S结构。在书中使用一个头文件unp.h,其中包括了需要的头文件、一些错误处理函数以及包裹函数。

1.2 时间获取客户端

下面实现了一个查询当前时间的客户端程序。客户端与服务器建立一个TCP连接后,服务器返回当前的时间与日期。

#include "unp.h"

int main(int argc, char **argv) {
  int sockfd, n;
  char recvline[MAXLINE + 1];
  struct sockaddr_in servaddr;

  if (argc != 2) {    // 使用argv[1]参数传递服务器端地址。
    err_quit("Usage: daytimetcpcli <IPaddress>");
  }

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {   // 创建网际(AF_INET)字节流(SOCK_STREAM)套接字。
    err_sys("socket error");
  }

  bzero(&servaddr, sizeof(servaddr));   // 使用bzero将servaddr清零,也可使用memset(),但memset带3个参数容易犯错。
  servaddr.sin_family = AF_INET;    // 将地址族置为AF_INET。
  servaddr.sin_port = htons(13);    // 服务器端端口号设为13。
  if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {   // 将点分十进制的参数转换为合适的格式,并设置为服务器端的地址。
    err_quit("inet_pton error for %s", argv[1]);
  }

  if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0) {    // 与指定的服务器端建立一个TCP连接。SA是一个宏定义,等同于struct sockaddr。
    err_sys("connect error");
  }

  while ((n = read(sockfd, recvline, MAXLINE)) > 0) {   // 读取服务器的应答,写入recvline中。当返回值为0(对端关闭连接)或为负(发送错误)时结束循环。
    recvline[n] = 0;
    if (fputs(recvline, stdout) == EOF) {
      err_sys("fputs error");
    }
  }

  if (n < 0) {
    err_sys("read error");
  }

  exit(0);
}

在上述代码中用到的err_开头的函数即书中提供的错误处理函数,如err_quit("inet_pton error for %s", argv[1])等同于

fprintf(stderr, "inet_pton error for %s", argv[1]);
exit(1);

1.3 时间获取服务器端

#include "unp.h"
#include <time.h>

int main(int argc, char **argv) {
  int listenfd, connfd;
  struct sockaddr_in servaddr;
  char buff[MAXLINE];
  time_t ticks;

  listenfd = Socket(AF_INET, SOCK_STREAM, 0);

  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   // 将地址设为INADDR_ANY目的是若服务器主机有多个网络接口,服务器程序可以在任意一个接口上接受客户连接。
  servaddr.sin_port = htons(13);

  Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));   // 将端口捆绑到服务器进程的套接字。

  Listen(listenfd, LISTENQ);    // 将套接字转换为监听套接字,常量LISTENQ是允许这个监听描述符排队的最大客户连接数。

  for ( ; ; ) {
    connfd = Accept(listenfd, (SA *) NULL, NULL);   // 接受客户连接
    ticks = time(NULL);
    snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
    Write(connfd, buff, sizeof(buff));    // 发送应答

    Close(connfd);    // 关闭与客户的连接
  }
}

在服务器端程序中使用了包裹函数,它将错误处理函数包含进其中,缩短了程序的长度。如Socket(),它的实现如下

int Socket(int family, int type, int protocol) {
  int n;

  if ((n = socket(family, type, protocol)) < 0) {
    err_sys("socket error");
  }
  return(n);
}

1.4 IPv6版本

上述的两个程序均为IPv4版本,通过稍微修改即可成为IPv6的版本。
在IPv4中,地址族为AF_INET,IPv6中则要修改为AF_INET6。除地址族外,将struct sockaddr_in改为struct sockaddr_in6,并将该结构变量中的sin_开头的成员修改为sin6_。除此外,在服务器程序中,需将servaddr.sin_addr.s_addr = htonl(INADDR_ANY);修改为servaddr.sin6_addr = in6addr_any

1.5 效果

编译客户端与服务器端后在命令行中进行测试,因为我的客户端与服务器端都在同一台电脑上,所以用了环回地址127.0.0.1以及IPv6的换回地址::1作为参数。先启动服务器端,服务器端开启时需要有root权限,下面分别为IPv4与IPv6的服务器端。

$sudo ./daytimetcpsrv
$sudu ./daytimetcpsrvv6

开启服务器端后即可使用客户端程序获取当前时间。

$./daytimetcpcli 127.0.0.1
Sat Oct  3 14:22:04 2015
$./daytimetcpcliv6 ::1
Sat Oct  3 14:23:38 2015
原创文章 29 获赞 31 访问量 7万+

猜你喜欢

转载自blog.csdn.net/ghosind/article/details/48878391