TCP时间获取客户程序
//自定义编写的unp.h的头文件
//该头文件包含了大部分网络程序都需要的许多系统头文件
//并定义了所用到的各种常值(MAXLINE)
#include "unp.h"
int main(int argc,char *argv[])
{
int sockfd,n;
struct sockaddr_in servaddr;
//在头文件unp.h 中定义了MAXLINE = 4096
char recvline[MAXLINE+1];
//socket函数创建一个网际(AF_INET)字节流(SOCK_STREAM)套接字
//返回一个小整数描述符
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(argc != 2)
err_quit("usage: a.out <IPaddress>");//自定义的退出函数
//printf("usage:IP \r\n");
if(sockfd < 0)
printf("error exit!\r\n");
//把服务器的IP地址和端口号填入一个网际套接字地址结构(
//一个名为servaddr的sockaddr_in的结构变量)
//使用bzero把整个结构清零后,置地址族为AF_INET 端口号为13
//网际套接字地址结构中IP地址和端口号这两个成员必须使用特定格式
//为此我们调用库函数htons(“主机到网络短整数”)去转换二进制端口号
//调用库函数inet_pton(“呈现形式到数值”)去把ASCII命令行参数
//(例如:127.0.0.1)转换为合适的格式
bzero(& servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5000); /*daytime server*/ //13
//IP地址为第一个命令行参数的值(argv[1])
if(inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr)<=0)
/*if(inet_pton(AF_INET,argv[1],&servaddr,sin_addr)<=0)//终端运行时以命令行
//第2个参数作为ip地址
*/
err_quit("inet_pton error for %s",argv[1]);
//connect函数应用于一个套接字时,
//将与由它的第二个参数指向的套接字地址结构指定的服务器建立一个TCP连接。
//该套接字地址结构的长度也必须作为该函数的第三个参数指定
//对于网际套接字地址结构,我们总是使用C语言的sizeof操作符由编译器来计算这个长度
// struct sockaddr 把其缩减为SA
// 在头文件unp.h 中,我们使用#define把SA定义为struct sockaddr
if(connect(sockfd,(SA *) & servaddr, sizeof (servaddr)) <0)
err_sys("connect error");
/*执行一个shell命令行时通常会自动打开三个标准文件,即标准输入文件(stdin)
*通常对应终端 标准输出文件(stdout)和标准错误输出文件(stderr) 这两个文件
*对应终端的屏幕
*/
/*读入并输出服务器的应答,如果数据量比较大就不能保证一次性读完,所以需要写在某个循环里
*当read返回0(表明对端关闭连接)或负值(表明发生错误)时终止循环
*/
while( (n = read(sockfd,recvline, MAXLINE)) >0 ){
recvline[n] = 0; /*null terminate*/
if(fputs(recvline,stdout) == EOF)//stdout (Standardoutput) 标准输出,指屏幕;
//EOF表示输出失败
err_sys("fputs error");
}
if (n <0 )
err_sys("read error");
/* exit终止程序运行。
* Unix在一个进程终止时总是关闭该进程所有打开的描述符,我们的TCP套接字就此被关闭
*/
exit(0);
}
/*
* 这里面的所有函数都是包裹函数
* 什么是包裹函数?
* 因为每一个程序都必须检查每个函数调用是否返回错误。
* 当错误发生时,就调用一个函数输出错误消息并且终止程序的运行。
* 既然发生错误时终止程序是普遍的情况,我们可以通过定义包裹函数(wrapper function)来缩短程序
*
* 在本书中,只要你遇到一个首字母大写的函数名,它就是我们定义的某个包裹函数,
* 它调用的实际函数的名字与包裹函数名字相同,不过以对应的小写字母开头
*
* 只要一个Unix函数中有错误发生,全局变量errno就会被置为一个指明该错误类型的正值
* 函数本身则通常返回-1
* err_sys 查看errno变量的值并输出相应的错误消息
*/
tcp时间获取服务器程序
#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;
//TCP套接字的创建与客户程序相同
listenfd = socket(AF_INET,SOCK_STREAM,0);
//把服务器的众所周知的端口捆绑到套接字
//我们指定IP地址为INADDR_ANY 这样要是服务器主机上有多个网络接口
//服务器进程就可以在任意网络接口上接受客户连接
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(5000);/*daytime server*/
//13 书上用的13端口,需要在root权限下运行服务器
Bind(listenfd, (SA *) & servaddr,sizeof(servaddr));
//调用listen函数把套接字转化为监听套接字,这样来自于客户的外来连接就可在该套解字上由内核接受
//LISTENQ = 1024 在unp.h头文件中定义 指定系统内核允许在这个监听描述符上排队的最大客户连接数
Listen(listenfd,LISTENQ);
//接受客户连接,发送应答
//通常情况下,服务器进程在accept调用中被投入睡眠,等待某个客户连接的到达并被内核接受
//TCP连接使用所谓的三路握手(three-way handshake)来建立连接。
//握手完毕时accept返回,其返回值是一个称为已连接描述符的新描述符。(本例子中为connfd)
//该描述符用于与新近连接的那个客户通信 accept为每个连接到本服务器的客户返回一个新描述符
/*
* 当前时间和日期由库函数time返回 下一个库函数ctime把该整数值转换为只管可读的时间格式
*/
for( ; ; ){
connfd = accept(listenfd,(SA *)NULL,NULL);
ticks = time(NULL);
//snprintf函数在这个字符串末尾添加一个回车符和一个回行符
//writ函数把结果字符串写给客户
snprintf(buff,sizeof(buff),"%.24s %s\r\n",ctime(&ticks),"中国");
write(connfd,buff,strlen(buff));
//服务器通过调用close关闭与客户的连接。
//该调用引发正常的TCP连接终止序列,每个方向上发送一个FIN 每个FIN又又各自的对端确认
Close(connfd);
}
}
/* 像上面这种的服务器称作称为迭代服务器(iterative server),因为对于每个客户,它都是迭代执行一次。
* 同时能处理多个客户称为并发服务器(concurrent server),可以用fork,多线程,
* 预先fork一定数量的子进程来实现。
* 如果我们想要一个服务器长时间运行,这要求我们往服务器中添加代码,
* 以便它能够作为一个守护进程(能在后台运行且不跟任何终端关联的进程)运行。
*/
结果展示