⟅UNIX网络编程⟆⦔简介以及获取服务端时间实例

说在前面

  • 环境: ubuntu16.04
  • 参考: UNIX网络编程

概述

  • 编写网络通信程序首先要明确使用的通信协议。同时,需要在高层次(例如应用层?)确定发起通信的程序以及响应的时间。(如Web客户端与Web服务器之间的通信,服务器需要长时间保持运行,以响应随时可能出现的客户端请求。)
    通常使用客户/服务端模型(C/S模型)。
    在这里插入图片描述
    一个客户端可能与多个服务器通信;一个服务器也可能同时与多个客户端通信。
  • 客户端程序与服务端之间的通信通常涉及多个网络层协议。
    在这里插入图片描述

实例-获取时间

  • 实验环境说明

    由于该实验需要运行客户端以及服务端程序,所以可能会有以下实验环境(假定操作系统均为Linux,不同环境的处理见后面的内容):

    • 局域网下两台机器分别运行
      随意选择运行客户端还是服务端。
    • 宿主机+虚拟机(如VMware)
      随意选择运行客户端还是服务端。Windows环境可以[运行两台虚拟机]或者[宿主机使用WSL+一台虚拟机]
    • 一台主机+云服务器
      云服务器(拥有公网IP)运行服务端程序,主机运行客户端程序。
    • 同一台主机上运行客户端以及服务端
      不讨论这种情况
  • 客户端

    • 一般流程
      在这里插入图片描述

    • 创建套接字

      if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
      	err_sys("socket error");
      

      通过socket函数创建网际(AF_INET)字节流(SOCK_STREAM)套接字。函数返回一个整数,用于标识该套接字。

      补充说明: 在linux下,我们可以将所有的设备都看作文件来处理,并使用一个文件描述符(FileDescriptor,一个整型数)来标识它们。

      err_sys函数标准函数,用于出错处理,主要功能为打印错误信息并终止程序。具体实现见代码。

    • 建立连接

      struct sockaddr_in	servaddr;
      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, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
      	err_sys("connect error");
      
      • 连接服务端需要知道目的IP以及端口号,我们使用结构体sockaddr_in来存储。

      • 定义servaddr变量后使用bzero函数(该函数可以使用memset函数代替)清零。

      • 然后将地址族sin_family置为AF_INET,网络序的端口号13填入到sin_port

        htonl函数,主机序转换为网络序;ntohl函数,网络序转换为主机序。

      • inet_pton函数可以将点分十进制串(如192.168.12.1)转换为适合sin_addr的格式,并且如果转换出错(即字符串不符合点分十进制格式)会返回负值。

      • 准备完成后使用connect函数与servaddr指向的服务器建立一个TCP连接。若建立连接失败,返回负值。

    • 读/写

      while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
      	recvline[n] = 0;
      	if (fputs(recvline, stdout) == EOF)
      		err_sys("fputs error");
      }
      

      使用read函数读取服务器返回的数据。函数返回值为实际读取的字节数;若未读取到数据,返回负值。

      从使用read函数也可以看出,linux将通信设备看作"文件"来处理,服务器返回的数据会先存放在该"文件"中,通过read系统调用来读取这些数据。在发送数据时,我们通过wirte系统调用往某类似"文件"中写入数据,再由操作系统将"文件"中的数据发送出去。

    • 关闭套接字

      if(close(sockfd) == -1)
      	err_sys("close error");
      
    • 不同实验环境
      主要的差异在于IP地址。
      前两种环境下,绑定的ip即主机或虚拟机的ip,可以使用ifconfig命令

      ifconfig
      

      在这里插入图片描述
      第三种环境下,绑定的为服务器的公网ip,同时注意防火墙是否打开了对应的端口(或者直接关闭防火墙)

  • 服务端

    • 一般流程
      在这里插入图片描述

    • 创建套接字

      if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
      	err_sys("socket error");
      
    • 绑定

      bzero(&servaddr, sizeof(servaddr));
      servaddr.sin_family      = AF_INET;
      servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
      servaddr.sin_port        = htons(13);
      
      if(bind(listenfd, (struct sockaddr_in *) &servaddr, sizeof(servaddr)) < 0)
          err_sys("bind error");
      

      同样,现将结构体变量清零,同时将地址族sin_family置为AF_INET,ip地址置sin_addr.s_addrINADDR_ANY(即0x00000000;一个主机可能有多个网络接口,例如多块网卡,这样我们可以接受任意网络接口上的连接),sin_port为13。

      sin_addr实际为一个(in_addr类型)结构体变量,只有一个成员s_addr。
      struct in_addr {
      in_addr_t s_addr;//32位
      };

      使用bind函数将13号端口捆绑至已创建的套接字上。

    • 监听

      if(listen(listenfd, LISTENQ) < 0)
              err_sys("listen error");
      

      使用listen函数将套接字转换为监听套接字。

    • 接受连接

      for ( ; ; ) {
      	if( (connfd = accept(listenfd, (struct sockaddr_in *) NULL, NULL)) < 0) {
      		if(errno == EPROTO || errno == ECONNABORTED)
      			continue;
      		else
      			err_sys("accept error");
      	}
      	// 、、、
      }
      

      通常,进程在调用accept函数进入睡眠,等待客户端连接。函数返回值是一个称为已连接描述符(connected descriptor)的新描述符。accept为每个连接的客户端分配一个新描述符。通过该描述符,可以与客户端进行通信。

      补充: 在accept函数被调用时,可能会出现非致命错误(ECONNABORTED/EPROTO),这时只需再次调用accept函数即可解决。

    • 读/写

      ticks = time(NULL);
      snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
      if( write(connfd, buff, strlen(buff)) != strlen(buff))
      	err_sys("write error");
      

      获取服务器时间,并用write函数将该时间发送给客户端。

    • 关闭套接字

      if (close(connfd) == -1)
      	err_sys("close error");
      
    • 不同实验环境
      三种不同实验环境下没有啥区别,注意防火墙以及对应的端口即可。

  • UNIX网络编程一书中的基本流程

在这里插入图片描述

贴图

  • 作为server的WSL,关闭防火墙。
    在这里插入图片描述
  • 作为client的另一台主机
    在这里插入图片描述

代码

  • github
  • 编译
    cd getdaytime
    make
    
发布了106 篇原创文章 · 获赞 41 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_33446100/article/details/103136123
今日推荐