一个简单的时间获取客户程序

本文介绍了一个TCP当前时间查询服务的实现,包括服务器和客户端代码。服务器接收连接请求,返回当前时间和日期,而客户端则发起连接并显示服务器返回的时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

让我们考虑一个具体例子,引入将本书中遇到的许多概念和说法。实现一个TCP当前时间查询客户程序的实现。该客户程序与其服务器建立一个TCP连接后,服务器直观可读格式简单地送回当前时间和日期。
获取时间的服务器端代码

#include        <sys/types.h>
#include        <sys/socket.h>
#include        <arpa/inet.h>
#include        <unistd.h>
#include        <time.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#define  MAX    1024
 
int main(int argc, char **argv)
{
        int                     listenfd, connfd;
        struct sockaddr_in      servaddr;
        char                    buff[MAX];
        time_t                  ticks;
 
        if((listenfd = socket(AF_INET, SOCK_STREAM, 0))<0)
        {
                perror("socket fail");
                return 1;
        }
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family      = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port        = htons(13);   /* daytime server */
 
        bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
 
        listen(listenfd, 10);
 
        while(1) 
        {
                connfd = accept(listenfd, (struct sockaddr *) NULL, NULL);
                ticks = time(NULL);
                snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
                write(connfd, buff, strlen(buff));
 
                close(connfd);
        }
        return 0;
}

客户端的代码

#include        <sys/types.h>
#include        <sys/socket.h>
#include        <unistd.h>
#include        <arpa/inet.h>
#include        <stdio.h>
 
#define PORT    8888
#define MAXLINE 1024
int main(int argc, char **argv)
{
        int                     sockfd, n;
        char                    recvline[MAXLINE + 1];
        struct sockaddr_in      servaddr;
 
        if (argc != 2)
                puts("usage: t.out <IPaddress>");
 
        if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
                perror("socket error");
 
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port   = htons(PORT);
        if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
                printf("inet_pton error for %s", argv[1]);
 
        if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
                perror("connect error");
 
        while ( (n = read(sockfd, recvline, MAXLINE)) > 0) 
        {             
                recvline[n] = 0;
                if (fputs(recvline, stdout) == EOF)
                perror("fputs error");
        }
        if (n < 0)
                perror("read error");
 
        exit(0);
}

执行客户端的可以执行文件的时候执行方式是

./a.out 127.0.0.1

创建TCP套接字
socket函数创建一个网际(AF_INET)字节流(SOCK_STREAM)套接字,它是TCP套接字的花哨名字。该函数返回一个小整数描述符,以后的所有函数调用(如随后的connect和read)就用该描述符来标识这个套接字。

指定服务器的IP地址和端口
我们把服务器的IP地址和端口号填入一个网际套接字地址结构(一个名为servaddr的sockaddr_in结构变量)。使用bzero把整个结构清零后置地址族为AF_INET,端口号为8888,IP地址为第一个命令行参数的值(argv[1])。网际套接字地址结构中IP地址和端口号这两个成员必须使用特定格式,为此我们调用函数htons(“主机到网络短整型”)去转换二进制端口号,又调用库函数inet_pton(“呈现形式到数值”)去把ASCII命令行参数转换成合适的形式

建立与服务器的连接
connect函数应用于一个TCP套接字时,将与由它的第二个参数指向的套接字地址结构指定的服务器建立一个TCP连接。该套接字地址结构的长度也必须作为函数的第三个参数指定,对于网际套接字地址结构,我们总是使用C语言的sizeof操作符由编译器来计算长度

读入并输出服务器的应答
我们使用read函数读取服务器应答,并使用标准的I/O函数fputs输出结果。使用TCP时必须小心,因为TCP是一个没有记录边界的字节流协议。服务器的应答通常如下格式的26字节字符串
Mon May 26 20::58:40 2003\r\n
其中,\r时ASCII回车符,\n时ASCII换行符。使用字节流协议的情况下,这26个字节可以有多种返回方式:既可以时包含所有26个字节的单个TCP分节,也可以是每个分节只包含1个字节的26个TCP分节,还可以时总共26个字节的任何其他组合。通常服务器返回包含所有26个字节的单个分节,但是如果数据量很大,我们就不能确保一个read调用能返回服务器的整个应答。因此从TCP套接字读取数据时,我们总是需要把read编写在某个循环中,当read返回0(表明对端关闭连接)或负值(表明发生错误)时终止循环
本例中服务器关闭表征记录的结束。HTTP超文本传送协议也采用这个技术。还可以用其他的技术标记和记录结束。例如,SMTP(简单邮件传送协议)使用由ASCII回车符后跟换行符构成的2字节序列标记记录的结束 ;sun远程过程调用以及域名系统在使用TCP承载应用数据时,在每个要发送的记录之前放置一个二进制的计数值,给出这个记录的长度。这里的重要概念时TCP本身并不提供记录的结束标志:如果应用程序需要确定记录的边界,它就要自己去实现已有的一些常用的方法可供选择
终止程序
exit终止程序。unix在一个进程终止时总是关闭该进程所有打开的描述符,我们的TCP套接字 就此被关闭

服务器端代码解析:
创建TCP套接字
TCP套接字的创建和客户端相同

把服务器的众所周知的端口和套接字捆绑
通过填写一个网际套接字地址结构并调用bind函数,服务器的众所周知的端口(8888)被捆绑到所创建的套接字。我们指定IP地址为INADDR_ANY,这样要时服务器主机有多个网络接口,服务器进程就可以在任意网络接口上接受客户端的连接。以后我们将了解怎么样限定服务器进程只在单个网络接口上接受客户端连接

把套接字转换成监听套接字
调用listen函数把该套接字转换成监听套接字,这样来自客户端的外来连接就可在该套接字上由内核接受。socket、bind、listen这3个调用步骤是任何TCP服务器准备所谓的监听描述符的正常步骤

接受客户连接,发送应答
通常情况下,服务器进程在accept调用中被投入睡眠,等待某个客户连接到达并被内核接受。TCP连接使用所谓的三路握手来建立。握手完成时accept返回,其返回值是一个称为已连接描述符的新描述符。该描述符用于与新的那个客户通信。accept为每个连接到本服务器的客户返回一个新描述符

终止连接
服务器通过调用close关闭与客户的连接。该调用引发正常的TCP连接终止序列:每个方向上发送一个FIN,每个FIN又由各自对端的确认

本服务器一次只能处理一个客户。如果多个客户连接差不多同时到达,系统内核在某个最大数目的限制下把它们排入队列,然后每次返回一个给accept函数。本服务器只需调用time和ctime函数,运行速度很快。然而如果服务器需要较多的时间(譬如说几秒钟或者几分钟)服务每个客户,那么我们必须以某种方式重叠对各个客户的服务器。迭代服务,因为对于每个客户都迭代执行一次。同时能处理多个客户的并发服务器有多种编写技术。最简单的技术就是fork,或在服务器启动时预先fork一定数量的子进程
如果从shell命令行启动本例这样的服务器,我们也许想要它运行很长时间,因为服务器往往在系统工作期间一直运行。这要求我们往往服务器程序中添加代码,以便它能够作为一个Unix守护进程–能在后台运行且不跟任何终端关联的进程运行
当大家出现这种错误的时候

connect error: Connection refused
read error: Transport endpoint is not connected

可以先调试一下端口和ping一下地址是否通,注意端口不能太小,因为小的会被系统保留

ping 地址
例如:ping 127.0.0.1
telnet 地址 端口
例如:telnet 127.0.0.1 8888
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值