main函数主体

#include "unp.h"
int main(int argc, char** argv){
  int    sockfd,n;
  char     recvline[MAXLINE + 1];
  struct sockaddr_in    servaddr;

  if (argc != 2)
      err_quit("usage: a.out <IPaddress>");
  if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
      err_sys("socket error");

  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port   = htons(13);    /* daytime server */
  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)
      err_sys("connect error");
  while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
      recvline[n] = 0;    /* null terminate */
      if (fputs(recvline, stdout) == EOF)
      err_sys("fputs error");
  }
  if (n < 0)
      err_sys("read error");
  exit(0);
}

7-10行

判断输入格式

if (argc != 2)
    err_quit("usage: a.out <IPaddress>");

当输入不符合规范时,执行该if语句,并给出规范输入。
其中err_quit是作者自定义的函数,源码如下

void err_quit(const char *fmt, ...)
{
        va_list         ap;
        va_start(ap, fmt);
        err_doit(0, LOG_ERR, fmt, ap);
        va_end(ap);
        exit(1);
}
void err_sys(const char *fmt, ...)
{
       va_list         ap;

       va_start(ap, fmt);
       err_doit(1, LOG_ERR, fmt, ap);
       va_end(ap);
       exit(1);
} 

在C语言中对于可变参数,需要特殊处理,需要头文件<stdarg.h>以及va_list类型和va_start、va_arg、va_end 3个宏读取传递到函数中的参数值。更详细的内容可参考博客,此处暂且不表。
https://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html

判断socket调用成功/失败

int socket(int domain, int type, int protocol)

domain是协议族,type是协议类型,protocol的套接字文件描述符是协议编号
domain=AF_INET,PF_INET时,代表ipv4协议; PF_INET6,代表ipv6协议; PF_UNIX,PF_LOCAL,代表本地通信
type用于设置套接字通信的类型,主要有流式SOCK_STREAM(TCP连接)和数据包套接字SOCK_DGRAM(UDP连接)
socket函数调用失败时返回-1

if(socket()<0)
    err_sys("socket error");

当函数调用失败时,调用err_sys放弃程序运行,并输出出错消息,例如常见的socket错误“Protocol not supported(协议不受支持)”,然后终止进程。

12-16行

指定服务器IP地址和端口号

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port   = htons(13);    /* daytime server */
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
    err_quit("inet_pton error for %s", argv[1]);

把服务器IP地址和端口填入网际套接字地址结构中(结构体sockaddr_in,变量servaddr)。
bzero函数把servaddr结构清零,填入地址组为AF_INET,端口为13。
NOTE : DAYTIME协议是基于TCP的应用,返回当前时间和日期,在13端口)

格式转换

调用库函数htons("主机->网络短整数")转换二进制端口号,inet_pton库函数转换ASCII命令行参数为指定格式。

17-18行

建立与服务器的连接

//在unp.h文件中,用#define将SA定义为struct sockaddr,也就是通用套接字地址结构
if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
    err_sys("connect error");

connect函数用于TCP套接字,与它第二个参数指向的套接字指定的服务器建立一个TCP连接。该套接字地址结构的长度也必须作为第三个参数,用sizeof来计算。

19-25行

读入并输出服务器的应答

//read函数,返回读取字节数,若失败返回-1,若到达文件末尾返回0
while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
    recvline[n] = 0;    /* null terminate */
    if (fputs(recvline, stdout) == EOF)
    err_sys("fputs error");
}
if (n < 0)
    err_sys("read error");

使用read函数读取服务器的应答,并用标准的I/O函数fputs输出结果。
(NOTE:因为TCP是一个没有记录边界的字节流协议,服务器应答通常为如下格式的26字节字符串:
Mon May 26 20:58:40 2003\r\n
通常服务器返回包含所有26个字节的TCP分节,但是当数据量很大时一次read无法返回服务器的整个应答,因此从TCP套接字读取数据时习惯把read编写至某个循环中,read返回0或负值即关闭连接或发生错误时终止循环)

26行

终止程序

exit终止程序运行。UNIX在程序终止时,会关闭该程序打开的所有描述符,TCP套接字就此关闭。