Linux C/C++编程:利用Socket编写获取时间服务器

OSI七层模型

  • TCP在第四层----》传输层,传输层的数据叫做segment
  • IP在第三层—》网络层,网络层的数据叫做Packet
  • ARP在第二层–》数据链路层,数据链路层的数据叫做``Frame`

程序中的数据会先打包在TCP的segment,然后TCP的segment会被打包到IP的Packet,然后再到以太网EthernetFrame,传递到对端后,各个层解析自己的协议,然后把数据交给更高层的协议处理

Linux C/C++编程:利用Socket编写获取时间服务器_第1张图片

  • 最下面两层是随系统提供的设备驱动程序和网络硬件。我们基本上不太需要关注它们
  • 网络层是IPv4.Ipv6。我们可以彻底绕过IP层直接读写数据链路层的帧
  • 传输层可以选择TCP或者UDP,当然,我们也可以使用原始套接字 绕开它们
  • 最上面三层可以合并为一层,简称应用层,就是web服务端、web服务器等所在的层

用户进程和内核之间通过系统提供的API来交流

服务器 与客户端

  1. 客户段与服务器使用TCP在同一个以太网(局域网)中通信
  • 客户端和服务器一般是用户进程,而TCP/I[协议通常是内核中协议栈的一部分
    Linux C/C++编程:利用Socket编写获取时间服务器_第2张图片
  1. 客户段与服务器使用TCP在不同以太网(局域网)中通信
  • 这两个局域网是使用路由器(router)连接到广域网(WAN)的
  • 路由器是广域网的架设设备
  • 当今最大的广域网是因特网

Linux C/C++编程:利用Socket编写获取时间服务器_第3张图片

获取时间

时间获取客户端

用于获取时间服务

IPV4版本

#include 
#include 
#include 
#include 
#include 
#include
#include 
#include 

#define	MAXLINE		4096
int main(int argc, char **argv){
    int                sockfd, n;
    struct sockaddr_in servaddr;
    char				recvline[MAXLINE + 1];

    if (argc != 2){
        printf("usage: a.out ");
        exit(-1);
    }

    // 创建套接字
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        printf("socket error");
        exit(-1);
    }

    // 设置要连接的服务器属性
    bzero(&servaddr, sizeof(servaddr));   // 清零
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(13);   // 时间获取服务器的端口都是13
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){ // 将argv[1]转换为合适的格式: ipv4/ipv6
        printf("inet_pton error for %s: %s", argv[1], strerror(errno));
        exit(-1);
    }


    // 使用tcp套接字与指定服务器建立一个tcp连接
    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
        printf("connect error: %s", strerror(errno));
        exit(-1);
    }


    // read读取服务器应答: read返回0(表示对端关闭)/负数(表示读取错误)
    while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
        recvline[n] = 0;	/* null terminate */
        if (fputs(recvline, stdout) == EOF){  // 使用fputs输出到标准输出端
            printf("fputs error");
            exit(-1);
        }

    }
    if (n < 0){
        printf("read error");
        exit(-1);
    }

    exit(0);
}

Linux C/C++编程:利用Socket编写获取时间服务器_第4张图片

在这里插入图片描述

IPV6版本

#include 
#include 
#include 
#include 
#include 
#include
#include 
#include 

#define	MAXLINE		4096
int main(int argc, char **argv){
    int                  sockfd, n;
    struct sockaddr_in6  servaddr;
    char				 recvline[MAXLINE + 1];

    if (argc != 2){
        printf("usage: a.out ");
        exit(-1);
    }

    // 创建套接字
    if ((sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0){
        printf("socket error");
        exit(-1);
    }

    // 设置要连接的服务器属性
    bzero(&servaddr, sizeof(servaddr));   // 清零
    servaddr.sin6_family   = AF_INET6;
    servaddr.sin6_port     = htons(13);   // 时间获取服务器的端口都是13
    if (inet_pton(AF_INET, argv[1], &servaddr.sin6_addr) <= 0){ // 将argv[1]转换为合适的格式: ipv4/ipv6
        printf("inet_pton error for %s: %s", argv[1], strerror(errno));
        exit(-1);
    }


    // 使用tcp套接字与指定服务器建立一个tcp连接
    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
        printf("connect error: %s", strerror(errno));
        exit(-1);
    }


    // read读取服务器应答: read返回0(表示对端关闭)/负数(表示读取错误)
    while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
        recvline[n] = 0;	/* null terminate */
        if (fputs(recvline, stdout) == EOF){  // 使用fputs输出到标准输出端
            printf("fputs error");
            exit(-1);
        }

    }
    if (n < 0){
        printf("read error");
        exit(-1);
    }

    exit(0);
}

Linux C/C++编程:利用Socket编写获取时间服务器_第5张图片

时间获取服务器

IPV4版本



#include 
#include 
#include 
#include 
#include 
#include
#include 
#include 
#include	
#define	MAXLINE		4096
#define	LISTENQ		1024

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_ANY);  // 这里指定了IP地址为INADDR_ANY,意思是如果服务器主机有多个网络接口,服务器进程就可以在任意网络上接受客户连接
    servaddr.sin_port        = htons(13);
    printf("%d", htons(13));

    if( bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
        printf("bind error, %s", strerror(errno));
        exit(0);
    }

    if(listen(listenfd, LISTENQ) < 0){
        printf("listen error, %s", strerror(errno));
        exit(0);
    } // 监听listenfd,这样来自客户端的连接就可以在该套接字上由内核接受。 LISTENQ表示内核允许在这个监听描述符上排队的最大客户连接数

    for ( ; ; ) {
        if((connfd = accept(listenfd, (struct sockaddr *) NULL, NULL)) < 0){
            printf("accept error, %s", strerror(errno));
            continue;
        }// 当前服务器进程会在accept上睡眠阻塞,直到某个客户断连接唤醒:TCP连接使用三次握手来建立连接,握手完毕accept返回一个已连接的描述符,我们就可以使用这个描述符与客户端通信了

        //sleep(60);
        ticks = time(NULL);  // time返回一个int秒数
        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); // ctime将int转换成直观可读的时间格式,然后使用snprintf复制到buff中(最大不能超过sizeof(buff)个字符)
        write(connfd, buff, strlen(buff)); // 将消息通过connfd写回给客户端


        close(connfd); //关闭connfd: 该调用引发正常的TCP连接终止序列:每个方向上发送一个FIN,每个FIN又由各自的对端确认
        printf("客户端连接关闭\n");
    }
}

  1. 这个服务器,一次只能处理一个请求。 如果有多个客户端同时到达,系统内核在某个最大数目的限制下把它们排入队列
    Linux C/C++编程:利用Socket编写获取时间服务器_第6张图片
  2. 对于一个服务器来讲,在建立连接之后(之前也一样),客户端是否提前关闭不影响服务器的行为,服务器还是会继续自己的处理
    Linux C/C++编程:利用Socket编写获取时间服务器_第7张图片
  3. 在服务器将消息写到了通道中,然后就死掉了;但是此时客户端还没有来的及将消息读走,那此时消息还在吗?
    还在

问:如果我们想要知道客户端的IP地址和端口,该怎么改写?


#include 
#include 
#include 
#include 
#include 
#include
#include 
#include 
#include	
#define	MAXLINE		4096
#define	LISTENQ		1024

int main(int argc, char **argv){
    int					listenfd, connfd;
    struct sockaddr_in	servaddr, cliaddr;
    char				buff[MAXLINE];
    time_t				ticks;
    socklen_t           len;


    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  // 这里指定了IP地址为INADDR_ANY,意思是如果服务器主机有多个网络接口,服务器进程就可以在任意网络上接受客户连接
    servaddr.sin_port        = htons(13);


    if( bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
        printf("bind error, %s", strerror(errno));
        exit(0);
    }

    if(listen(listenfd, LISTENQ) < 0){
        printf("listen error, %s", strerror(errno));
        exit(0);
    } // 监听listenfd,这样来自客户端的连接就可以在该套接字上由内核接受。 LISTENQ表示内核允许在这个监听描述符上排队的最大客户连接数

    for ( ; ; ) {
        len = sizeof(cliaddr);
        if((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &len)) < 0){
            printf("accept error, %s", strerror(errno));
            continue;
        }
        printf("connectio from %s, port %d\n",
               inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, buff, sizeof(buff)),
               ntohs(cliaddr.sin_port));
        //sleep(60);
        ticks = time(NULL);  // time返回一个int秒数
        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); // ctime将int转换成直观可读的时间格式,然后使用snprintf复制到buff中(最大不能超过sizeof(buff)个字符)
        write(connfd, buff, strlen(buff)); // 将消息通过connfd写回给客户端


        close(connfd); //关闭connfd: 该调用引发正常的TCP连接终止序列:每个方向上发送一个FIN,每个FIN又由各自的对端确认
        printf("客户端连接关闭\n");
    }
}

你可能感兴趣的:(Unix/Linux编程,服务器,linux,c语言)