简单的时间获取程序

通过一个简单的时间获取程序来了解网络编程流程

//客户程序
#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);
    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;
        if(fputs(recvline,stdout)==EOF)
            err_sys("fputs error");

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

这里边涉及了几个关键函数
socket()
bzero()
inet_pton()
connect()
read()

还有一个关键的结构
sockaddr_in

下面是客户程序的流程
1 创建TCP套接字
socket函数创建一个网际(AF_INET)字节流(SOCK_STREAM)套接字,该函数返回一个小整数描述符,以后的所有函数调用就用这个描述符来标识这个套接字。
2 指定服务器的IP地址和端口
创建一个网际套接字地址结构sockaddr_in,使用bzero把结构清零,置地址族为AF_INET,端口号为13.网际套接字地址结构中IP地址和端口号这两个成员必须使用特定的格式。
使用htons转换端口号,使用inet_pton 将命令行中点分十进制转换为二进制。
3 建立与服务器的连接
connect函数第一个参数是一个TCP套接字,第二个参数是通用套接字地址结构,每当一个套接字函数需要一个指向某个套接字地址结构的指针时,这个指针必须强制类型转换成一个指向通用套接字地址结构的指针。这里的SA是struct sockaddr的别名。第三个参数是套接字结构的长度。
4 读入并输出服务器的应答
这里使用read函数读取服务器的应答,并用fputs输出结果。
使用TCP必须要小心,因为TCP是一个没有记录边界的字节流协议。
TCP的PDU可能包含所有字节,也可能一个PDU只包含一个字节,通常服务器返回包含所有字节的单个PDU,但是如果数据量很大,我们不能确保一次read调用能返回服务器的整个应答。因此从TCP套接字读取数据时,总是需要把read编写在某个循环中,当read返回0或负值时终止循环。
5 终止程序
exit终止程序运行。

解释一下PDU:
计算机网络各层对等实体间交换的单元称为协议数据单元PDU,除了物理层外,每层PDU通过由紧邻下层提供给本层的服务接口,作为下层的服务数据单元SDU传递给下层,并由下层间接完成本层的PDU交换。如果本层的PDU大小超过紧邻下层的最大SDU限制,那么本层还要事先把PDU划分成若干个合适的片段让本层分开载送,再在相反方向把这些片段重组成PDU。每层PDU除用于承载紧邻上层的PDU外,也用于承载本层协议内部通信所需的控制信息。
应用层实体间交换的PDU称为应用数据,TCP应用进程之间交换的是没有长度限制的单个双向字节流,在UDP应用进程之间交换的是其长度不超过UDP发送缓冲区大小的单个记录,在SCTP应用进程之间交换的是没有总长度限制的单个或多个双向记录流。传输层实体间交换的PDU称为消息,其中TCP的PDU特称为分节。
在TCP传输层中,发送端TCP把来自应用进程的字节流数据(即由应用进程通过一次次输出操作写出到发送端TCP套接字中的数据)按顺序经分割后封装在各个分节中传送给接收端TCP,其中每个分节所封装的数据即可能是发送端应用进程单次输出操作的结果,也可能是连续数次输出操作的结果,而且每个分节所封装的单次输出操作的结果或者首尾两次输出操作的结果即可能是完整的,也可能是不完整的,具体取决于可在连接建立阶段由对端通告的最大分节大小MSS以及外出接口的最大传输单元MTU或外出路径的路径MTU
分节除了用于承载应用数据外,也用于建立连接SYN分节,终止连接FIN分节,中止连接RST分节,确认数据接收ACK分节,刷送待发数据PSH分节和携带紧急数据指针URG分节。
网络层实体间交换的PDU称为IP数据报,链路层实体间交换的PDU称为帧。

下边的程序能在IPv6上运行

#include"unp.h"
int main(int argc,char**argv)
{
    int sockfd,n;
    char recvline[MAXLINE+1];
    struct sockaddr_in6 servaddr;
    if(argc !=2)
        err_quit("usageL:a.out<IPaddress>");
    if((sockfd=socket(AF_INET6,SOCK_STREAM,0))<0)
        err_sys("socket error");
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin6_family=AF_INET6;
    servaddr.sin6_port=htons(13);
    if(inet_pton(AF_INET6,argv[1],&servaddr.sin6_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;
        if(fputs(recvline,stdout)==EOF)
            err_sys("fputs error");
    }
    if(n<0)
        err_sys("read error");
    exit(0);
}

定义错误包裹函数
一般我们使用socket,inet_pton,connect,read和fputs函数都会判断是否会发生错误,这样我们可以定义一个错误包裹函数,简化程序
例如

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

这样使用 sockfd=Socket(AF_INET,SOCK_STREAM,0)就可以简化程序。

线程函数遇到错误时并不设置标准Unix的errno变量,而是把errno的值作为函数返回值返回调用者。这以为着每次调用以pthread_开头的某个函数时,我们必须分配一个变量来存放函数返回值,以便在调用err_sys前把errno变量设置成该值。

unix errno 值
只要一个Unix函数中有错误发生,全局变量errno就被置为一个指明该错误类型的正值,函数本身则通常返回-1,err_sys查看errno变量的值并输出相应的出错信息。
errno通常放在

服务器程序

#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_ANT);
    servaddr.sin_port=htons(13);
    Bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
    Listen(listenfd,LISTNQ);
    for(;;)
    {
        connfd=Accept(listenfd,(SA*)NULL,NULL);
        ticks=time(NULL);
        snprintf(buff,sizeof(buff),"%.24s\r\n",ctime(&ticks));
        Write(connfd,buff,strlen(buff));
        Close(connfd);
    }
}

服务器程序流程
1 创建TCP套接字
2 把服务器端口捆绑到套接字
通过填写一个网际套接字地址结构并调用bind函数,服务器的端口被捆绑到所创建的套接字。我们指定IP地址为INADDR_ANY,这样要是服务器主机有多个网络接口,服务器进程就可以在任意网络接口上接受客户连接。
3 把套接字转换成监听套接字
调用listen函数把套接字转换成一个监听套接字,这样来自客户的外来连接就可在该套接字上由内核接受。
socket,bind和listen这三个调用步骤是任何TCP服务器监听的步骤。
LISTENO是指定系统内核允许在这个监听描述符上排队的最大客户连接数。
4 接受客户连接,发送应答
服务器进程在accept调用中被投入睡眠,等待某个客户连接的到达并被内核接受。
TCP连接使用三路握手来建立连接。
握手完毕时accept返回,其返回值是一个称为已连接描述符的新描述符。
该描述符用于与新近连接的那个客户通信。accept为每个连接到本服务器的客户返回一个新描述符。
时间和日期是由库函数time返回,它返回的是unxi标准秒数,由库函数ctime把这个整数值转换为可读的时间格式。
write函数把结果字符串写给客户。
5 终止连接
服务器通过调用close关闭与客户的连接。该调用引发正常的TCP连接终止序列:每个方向上发送一个FIN,每个FIN又由各自的对端确认。

如果多个客户连接差不多同时到达,系统内核在某个最大数目的限制下把它们排入队列,然后每次返回一个给accept函数。如果服务器需要较多时间服务每个客户,那么我们必须以某种方式重叠对各个客户的服务。这种服务器叫迭代服务器,因为对于每个客户它都迭代执行一次,同时能处理多个客户的并发服务器又多种编写技术,最简单的技术是调用fork函数,为每个客户创建一个子进程。其他技术包括使用线程代替fork,或在服务器启动时预先fork一定数量的子进程。

如果我们想让这个服务器进程能在系统工作期间一直运行,那么可以在服务器程序中添加代码,以便它能够作为一个unix守护进程。

我们要观察自己电脑上的关于网络配置的信息,可以使用
netstat -ni 可以查看网络接口
netstat -nr 可以查看路由表的信息
有了网络接口的名字,就可以使用ifconfig获得每个接口的详细信息。
//例如:获得eth0接口的详细信息
ifconfig eth0
找出本地网络中众多主机的ip地址的方法是,对本地接口的广播地址执行ping命令。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值