UNIX网络编程-简介

devtools/2024/10/18 11:31:12/

概述


要编写通过计算机网络通信的程序,首先要确定这些程序相互通信所用的协议(protocal)。

大多数网络应用划分为客户端(client)和服务器(server)。在设计网络应用时,确定总是由客户发起请求往往能够简化协议和程序本身。当然一些较为复杂的网络应用还需要异步回调通信,也就是由服务器向客户端发起请求。

通常客户端每次只与一个服务器通信,但一个服务器可以同时与多个客户端通信。一个服务器同时处理多个客户端请求:

可认为客户端与服务器之间是通过某个网络协议通信的,但实际上这样的通信通常涉及多个网络协议层。本书的焦点是TCP/IP协议族,也称为网际协议族。

客户端和服务器处于同一局域网:

Web客户端与服务器之间使用TCP通信,TCP又转而使用IP通信,IP再通过某种形式的数据链路层通信。如果客户端与服务器处于同一个以太网(以太网是一种局域网技术),则通信层次如下:

注意:

  1. 客户端与服务器之间的信息流在其中一端是向下通过协议栈的,跨越网络后,在另一端则是向上通过协议栈的。
  2. 客户端与服务器通常是用户进程,而TCP和IP协议通常是内核中协议栈的一部分。
  3. 上述所说的IP协议是指IPv4(自20世纪80年代早期以来一直在使用),在20世纪90年代中期开发了IPv6,将来可能会取代IPv4。

客户端和服务器处于不同局域网:

如果客户端和服务端处于两个局域网(LAN)下,则需要使用路由器把这两个局域网连接到广域网(WAN)中。

注意:

路由器是广域网的架构设备,当今最大的广域网是因特网。许多公司也构建自己的广域网,而这些私用的广域网既可以连接到因特网,也可以不连接因特网。

时间获取客户端程序


IPv4协议相关

下述代码是从服务器获取时间的客户端程序,该客户端与服务器建立TCP连接后,服务器以直观可读格式返回当前时间和日期。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h> /* basic socket definitions */#define MAXLINE     4096
#define SA  struct sockaddr    int main(int argc, char **argv)
{int                 sockfd, n;char                recvline[MAXLINE + 1];struct sockaddr_in  servaddr;if (argc != 2)exit(1);/* --------------------------------------------- *///1) 创建一个TCP连接套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {printf("socket error");return -1;}/* --------------------------------------------- *///2) 指定服务器的IP地址和端口bzero(&servaddr, sizeof(servaddr));         // 初始化内存servaddr.sin_family = AF_INET;              // 地址族servaddr.sin_port   = htons(13);            // 时间获取服务器端口为13// 注意:此处的IP和端口是服务器的IP和端口// 把点分十进制的IP地址(如:206.168.112.96)转化为合适的格式if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {printf("inet_pton error for %s", argv[1]);return -1;}/* --------------------------------------------- *///3) 建立客户端(sockfd)与服务器(servaddr)的连接,TCP连接if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0) {printf("connect error");return -1;}/* --------------------------------------------- *///4) 读取服务器应答// read读取服务器应答,如果数据量较大,不能确保一次read调用能返回服务器整个应答// 因此,通常需要把read放到一个循环中while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {recvline[n] = 0;    // 读取数据后,在末尾加空字符来确保字符数组合法// 输出结果if (fputs(recvline, stdout) == EOF) {printf("fputs error");return -1;}}if (n < 0)printf("fputs error");/* --------------------------------------------- *///5) 终止程序运行,关闭该进程打开的所有描述符和TCP套接字exit(0);
}

注意:上述获取时间的客户端代码是与IPv4协议相关的!

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("usage: 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);   /* daytime server */// 第五处修改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;    /* null terminate */if (fputs(recvline, stdout) == EOF)err_sys("fputs error");}if (n < 0)err_sys("read error");exit(0);
}

注意:

  1. IPv4协议相关和IPv6协议相关客户端代码,只有五处修改!
  2. 上述两个程序,用户必须以点分十进制格式给出服务器的IP地址,如206.168.112.96!

时间获取服务器程序


TCP时间获取服务器程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
#include <arpa/inet.h>#define MAXLINE     4096
#define LISTENQ     1024
#define SA  struct sockaddrint main(int argc, char **argv)
{int                 listenfd, connfd;struct sockaddr_in  servaddr;char                buff[MAXLINE];time_t              ticks;/* --------------------------------------------- *///1) 创建一个TCP连接套接字listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd < 0) {printf("socket error");return -1;}/* --------------------------------------------- *///2) 把服务器对应端口绑定到套接字 bzero(&servaddr, sizeof(servaddr));servaddr.sin_family      = AF_INET;     // 地址族// 指定IP地址为INADDR_ANY,这样要是服务器主机有多个网络接口,服务器进程就可以在任意网络接口上接受客户端连接servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port        = htons(13);   // 时间获取服务器端口为13if (bind(listenfd, (SA *) &servaddr, sizeof(servaddr)) < 0) {printf("bind error");return -1;}/* --------------------------------------------- *///3) 把套接字转换为监听套接字// LISTENQ表示系统内核允许在这个监听描述符上排队的最大客户端连接数if(listen(listenfd, LISTENQ) < 0) {printf("listen error");return -1;}/* --------------------------------------------- *///4) 接受客户端连接,发送应答for ( ; ; ) {// connfd为已连接描述符,用于和客户端进行通信connfd = accept(listenfd, (SA *) NULL, NULL);if(connfd < 0) {printf("accept error");return -1;}ticks = time(NULL);snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));/* --------------------------------------------- *///5) 把结果(时间)写给客户端if (write(connfd, buff, strlen(buff)) != strlen(buff)) {printf("write error");return -1;}/* --------------------------------------------- *///6) 关闭与客户端的连接if (close(connfd) == -1) {printf("close error");return -1;}}
}

问:使用snprintf函数代替sprintf函数有哪些好处?

答:sprintf函数无法检查缓冲区是否溢出,而snprintf要求第二个参数指定目的缓冲区的大小,因此可以确保该缓冲区不溢出。

注:

  1. 许多网络入侵是由黑客通过发送数据,导致服务器对sprintf的调用使其缓冲区溢出而发生的。
  2. 使用fgets、strncat、strncpy代替gets、strcat、strcpy也是同样道理。

 测试

// 编译
gcc -o daytimetcpcli daytimetcpcli.c
gcc -o daytimetcpsrv daytimetcpsrv.c// 启动服务端程序
./daytimetcpsrv// 启动客户端程序,同时需要加上ip、port等参数
./daytimetcpcli 127.0.0.1
结果:Thu Aug 22 20:02:59 2024// 连接服务端方式二
telnet 127.0.0.1 13

OSI模型


描述一个网络中各个协议层的常用方法是使用国际标准化组织(International Organization for Standardization,IOS)的计算机通信开发系统互连(open systems interconnection,OSI)模型。

这是一个七层模型,如图所示,图中还给出了它与国际协议族的近似映射:

 各层含义如下:

  1. 物理层和数据链路层:OSI模型的物理层和数据链路层是随系统提供的设备驱动程序和网络硬件。通常情况下,除需知道数据链路层的某些特性(如1500字节以太网的MTU大小),不必关心这两层的情况。
  2. 网络层:网络层由IPv4和IPv6这两个协议处理。
  3. 传输层:传输层通常由TCP和UDP协议处理,上图中TCP和UDP之间留有间隙,表明网络应用绕过传输层直接使用IPv4或IPv6是有可能的。这就是所谓的原始套接字。
  4. 会话层、表示层和应用层:这三层合称应用层。这就是Web客户(浏览器)、Telnet客户、Web服务器、FTP服务器和其他我们在使用网络应用所在的层。对于网际协议,OSI模型的顶上三层协议几乎没有区别。

问:为什么套接字提供的是从OSI模型的顶上三层进入传输层的接口?

答:有以下两个理由:

  1. 顶上三层处理具体网络应用(如FTP、Telnet或HTTP)的所有细节,却对通信细节了解很少;底下四层对具体网络应用了解不多,却处理所有的通信细节,如发送数据、等待确认,给无序到达的数据排序、计算并验证校验和等等。
  2. 顶上三层通常构成所谓的用户进程,底下四层却通常作为操作系统内核的一部分提供。

Unix与其他现代操作系统都提供分隔用户进程与内核的机制。由此可见,第4层和第5层之间的接口是构建API的自然位置。


http://www.ppmy.cn/devtools/126708.html

相关文章

线性代数 行列式

一、行列式 1、定义 一个数学概念&#xff0c;主要用于 线性代数中&#xff0c;它是一个可以从方阵&#xff08;即行数和列数相等的矩阵&#xff09;形成的一个标量&#xff08;即一个单一的数值&#xff09; 2、二阶行列式 &#xff0c;像这样将一个式子收缩称为一个 2*2 的…

2d 数字人实时语音聊天对话使用案例;支持asr、llm、tts实时语音交互

参考: https://github.com/lyz1810/live2dSpeek 下载live2dSpeek项目 ## 下载live2dSpeek git clone https://github.com/lyz1810/live2dSpeek cd live2dSpeek-main ## 运行live2dSpeek npm install -g http-server http-server .更改新的index.html页面 index.html

Android从上帝视角来看PackageManagerService

戳蓝字“牛晓伟”关注我哦&#xff01; 用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章&#xff0c;技术文章也可以有温度。 前言 阅读该篇之前&#xff0c;建议先阅读下面的系列文章&#xff1a; Android深入理解包管理–PackageManagerService和它的“小伙伴…

《拿下奇怪的前端报错》:1比特丢失导致的音视频播放时长无限增长-浅析http分片传输核心和一个坑点

问题背景 在一个使用MongoDB GridFS实现文件存储和分片读取的项目中&#xff0c;同事遇到了一个令人困惑的问题&#xff1a;音频文件总是丢失最后几秒&#xff0c;视频文件也出现类似情况。更奇怪的是&#xff0c;播放器显示的总时长为无限大。这个问题困扰了团队成员几天&…

【RS】GEE(Python):栅格计算

在遥感影像处理中&#xff0c;栅格计算是一项至关重要的操作。栅格数据代表了地球表面特定范围内的物理量信息&#xff0c;利用栅格计算可以进行多种分析操作&#xff0c;比如计算植被指数、分类、过滤、组合波段&#xff0c;甚至执行复杂的空间分析任务。本篇教程将详细介绍遥…

【学习笔记】MongoDB 概念

文章目录 MongoDB 概念MongoDb 的应用场景什么时候会选择MongoDB&#xff1f; MongoDB 概念 MongoDb 的应用场景 传统的关系型数据库(如MySQL)&#xff0c;在数据操作的三高需求以及应对Web2.0的网站需求面前&#xff0c;显得力不从心。 那什么是“三高”&#xff1f; 高血…

rollup.js 插件实现原理与自定义

Rollup.js 是一个JavaScript模块打包器&#xff0c;它主要用于将小块代码编译成大块复杂的库或应用程序。相较于Webpack&#xff0c;Rollup更专注于代码的ES模块转换和优化&#xff0c;特别适合构建库或者那些对代码体积、执行效率有严格要求的应用。Rollup的核心特性之一就是它…

鸿蒙NEXT开发-知乎评论小案例(基于最新api12稳定版)

注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注&#xff0c;博主会一直更新鸿蒙next相关知识 专栏地址: https://blog.csdn.net/qq_56760790/…