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套接字就此关闭。