自己做网站用什么软件,镇江网站排名优化价格,中信建设有限责任公司待遇,网站后台清空文章目录 3.linux网络涉及到的协议栈4.三个版本的服务器4.1响应式4.2命令式4.3交互式1.启动程序2.运行结果 3.linux网络涉及到的协议栈
Linux网络协议栈是一个复杂而强大的系统#xff0c;它负责处理网络通信的各种细节。下面是对Linux网络协议栈的详细介绍#xff1a;
套接… 文章目录 3.linux网络涉及到的协议栈4.三个版本的服务器4.1响应式4.2命令式4.3交互式1.启动程序2.运行结果 3.linux网络涉及到的协议栈
Linux网络协议栈是一个复杂而强大的系统它负责处理网络通信的各种细节。下面是对Linux网络协议栈的详细介绍
套接字层Socket Layer
这是用户空间和内核空间之间的接口提供了对底层网络通信的抽象。应用程序通过调用套接字API如socket(), bind(), connect(), send(), recv()等来与协议栈进行交互。 套接字层支持多种类型的套接字如流式套接字TCP、数据报套接字UDP和原始套接字直接访问网络层协议。 传输层Transport Layer
传输层负责在源端和目的端之间建立可靠的或不可靠的数据传输。主要的传输层协议包括TCP传输控制协议和UDP用户数据报协议。 TCP提供面向连接的、可靠的、字节流的服务通过序列号、确认和重传机制确保数据的完整性和顺序性。 UDP则提供无连接的、不可靠的数据报服务不保证数据的顺序性和完整性但具有较低的开销和较高的传输效率。 网络层Network Layer
网络层负责将数据包从源主机路由到目的主机。主要的网络层协议包括IP互联网协议和ICMP互联网控制消息协议。 IP协议负责在主机之间传输数据包通过路由算法确定数据包的最佳路径。 ICMP协议用于在主机和路由器之间传递控制消息如错误报告和路由查询。 数据链路层Data Link Layer
数据链路层负责在相邻节点之间传输数据帧。主要的协议包括以太网、PPP点对点协议等。 数据链路层还负责处理错误检测和流量控制等问题确保数据帧在物理层上的可靠传输。 物理层Physical Layer
物理层负责将数据帧转换为电信号或光信号以便在物理介质如光纤、双绞线等上进行传输。 物理层还负责处理信号的编码、解码和调制等问题以确保数据的正确传输。 除了上述各层之外Linux网络协议栈还包括一些辅助模块和子系统如网络设备驱动程序、网络地址转换NAT、防火墙等它们共同协作以实现复杂的网络通信功能。
在Linux内核中网络协议栈的实现涉及大量的数据结构和算法如套接字数据结构、路由表、缓冲区管理等。此外Linux还提供了丰富的配置和调试工具如ifconfig、netstat、tcpdump等以帮助开发人员和运维人员更好地理解和控制网络行为。
总之Linux网络协议栈是一个功能强大、灵活且可扩展的系统它为用户提供了高效、可靠的网络通信服务。 协议栈和tcp/ip及osi模型有区别吗 协议栈、TCP/IP模型以及OSI模型在网络通信中各自扮演着重要的角色但它们之间确实存在一些关键的区别。
协议栈又称协议堆叠是计算机网络协议套件的一个具体的软件实现。每个协议模块通常都要和上下两个其他协议模块通信可以想象成是协议栈中的层。协议栈反映了网络中文件传输的过程并使得每个网络层能够处理特定的通信任务。
TCP/IP模型是一种网络通信协议模型由传输控制协议TCP和互联网协议IP组成是互联网通信的基础。它将网络通信分为四个层次网络接口层、网络层、传输层和应用层。每一层都有其特定的功能和协议共同协作以实现数据的可靠传输。
OSI开放式系统互连模型即OSI参考模型是ISO组织在1985年研究的网络互连模型。它定义了网络互连的七层框架物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。OSI模型提供了一个对网络通信的全面描述并为不同厂商和开发者提供了一个统一的开发标准。
从区别上来看
TCP/IP模型与OSI模型的主要区别在于层数不同以及第三层的协议支持不同。TCP/IP模型仅支持IP协议而OSI模型支持所有网络层协议。此外TCP/IP协议栈支持跨层封装这在某些情况下可以加快收敛速度和提高效率。 协议栈与TCP/IP模型和OSI模型的关系在于协议栈是实现这些模型的具体软件形式。也就是说TCP/IP模型和OSI模型为网络通信提供了理论框架和分层结构而协议栈则是这些模型在实际网络环境中的软件实现。 总的来说协议栈、TCP/IP模型和OSI模型各有其特点和功能它们共同构成了现代网络通信的基础。在实际应用中这些模型和协议栈相互协作以实现高效、可靠的网络通信。 在Linux下进行网络编程时先调用socket然后bind然后调用其他接口就能进行服务器和客户端通信了。原理是什么socketbind调用完之后发生了什么为什么这样就能通信 在Linux下进行网络编程时确实需要按照特定的顺序调用函数来设置和建立服务器与客户端之间的通信。这个过程涉及到底层的网络协议和操作系统的支持。下面我将解释socket和bind调用后发生的事情以及为什么它们能够支持网络通信。
socket调用 当你调用socket函数时你实际上是在请求操作系统为你创建一个新的套接字socket。这个套接字是一个数据结构它包含了进行网络通信所需的所有信息比如协议类型TCP、UDP等、本地和远程的地址等。
创建套接字后操作系统会为你分配一个唯一的文件描述符用于后续对套接字的操作。这个文件描述符就像一个句柄你可以通过它来读写套接字上的数据。
bind调用 bind函数用于将套接字与本地的一个地址IP地址和端口号关联起来。对于服务器来说这一步是必须的因为服务器需要监听一个特定的端口来接收客户端的连接请求。
当你调用bind时操作系统会检查你指定的地址是否可用并将其与套接字关联起来。这样当客户端发送连接请求或数据时操作系统就能知道应该将请求或数据发送到哪个套接字上。
其他接口调用 在socket和bind之后服务器通常会调用listen来开始监听连接请求。当有客户端连接时服务器会调用accept来接受连接并返回一个新的套接字用于与客户端的通信。
对于客户端来说它会在socket之后调用connect来尝试连接到服务器。一旦连接建立成功客户端和服务器就可以通过各自的套接字来发送和接收数据了。
原理与通信能力 之所以能够通信是因为操作系统和网络协议栈在背后默默地做了大量的工作。当你调用这些网络编程接口时你实际上是在与操作系统的网络协议栈进行交互。
网络协议栈它负责实现各种网络协议如TCP、UDP、IP等并将你的数据按照协议格式进行封装和解封装。这样数据就能在网络中正确地传输和识别。 路由与转发操作系统还负责根据目标地址选择合适的路径来发送数据。这涉及到路由表的查询和数据的转发。 错误处理与重传对于可靠的协议如TCP网络协议栈还负责处理各种错误情况比如数据丢失或乱序。它会自动进行数据的重传和排序以确保数据的完整性和顺序性。 因此当你调用socket和bind等函数时你实际上是在利用操作系统和网络协议栈的功能来建立和管理网络通信。这些函数为你提供了高层次的接口使得你可以更加方便地进行网络编程而不需要关心底层的细节。 udp的全双工是如何实现的 UDPUser Datagram Protocol用户数据报协议是一个无连接的、不可靠的传输层协议它提供了全双工通信的能力。全双工通信意味着数据可以在同一时刻双向流动即发送方和接收方都可以同时发送和接收数据。UDP实现全双工通信主要依赖于以下几个关键点
无连接性UDP不需要在通信双方之间建立连接。因此发送方和接收方都可以随时开始发送数据无需等待对方的确认或建立连接的过程。这种无连接特性使得UDP非常适合于需要快速响应和低延迟的应用场景。
独立的数据报UDP将每个数据报视为独立的单元每个数据报都包含源端口号、目的端口号、数据长度和校验和等信息。这意味着每个数据报都可以独立地路由和传输无需与其他数据报保持顺序或关联。因此发送方和接收方可以同时发送和接收多个数据报实现全双工通信。
异步传输UDP的异步传输特性意味着数据的发送和接收是异步进行的即发送方发送数据后不需要等待接收方的确认或响应可以继续发送其他数据。同样地接收方也可以随时接收数据无需等待发送方的进一步指示。这种异步性使得UDP能够支持并发通信和实时交互。
需要注意的是虽然UDP提供了全双工通信的能力但它并不保证数据的可靠传输。由于UDP是无连接的它不提供流量控制、数据排序或重传机制。因此在使用UDP进行通信时应用程序需要自行处理这些问题以确保数据的完整性和正确性。
总之UDP通过其无连接性、独立的数据报和异步传输特性实现了全双工通信。这使得UDP成为许多需要快速响应和低延迟的应用场景的首选传输层协议。然而在使用UDP时应用程序需要仔细考虑如何处理数据的可靠性和完整性。 回顾静态成员函数 静态成员函数是C中的一个重要特性它们与普通的成员函数有一些显著的差异。静态成员函数属于类本身而不是类的实例这意味着它们可以在没有创建类的对象的情况下被调用。静态成员函数只能访问静态成员变量和其他静态成员函数不能访问类的非静态成员。
静态成员函数的应用场景、目的和意义主要包括以下几个方面
应用场景
工具函数当某些函数仅与类有关但并不涉及类的具体实例时可以将其设计为静态成员函数。例如一个表示数学运算的类可能有一个静态成员函数来计算两个数的和这个函数并不依赖于类的任何特定实例。 访问静态成员静态成员函数常常用于访问和修改类的静态成员变量。由于静态成员变量不属于任何特定的对象实例因此需要通过静态成员函数进行访问。 工厂方法在设计模式中静态成员函数经常用作工厂方法用于创建类的实例。这种方法允许我们控制对象的创建过程例如实现单例模式或实现对象的池化。 目的
减少耦合通过将某些功能设计为静态成员函数可以减少类与其使用者之间的耦合使得类的使用更加灵活和独立。 提供类级别的操作静态成员函数允许我们在不创建类实例的情况下执行某些操作这对于那些只需要在类级别执行的操作非常有用。 意义
提高代码复用性静态成员函数可以被类的所有实例共享提高了代码的复用性。 简化代码设计通过将某些功能设计为静态成员函数可以简化类的设计使得类的职责更加清晰和明确。 增强代码可读性通过合理使用静态成员函数可以使代码的结构更加清晰提高代码的可读性。 总的来说静态成员函数是C中一种强大的工具它们允许我们在类级别执行操作提高代码的复用性和可读性并简化代码设计。然而我们也需要注意合理使用静态成员函数避免过度使用导致代码结构混乱或增加不必要的复杂性。
4.三个版本的服务器
4.1响应式
#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP#include Log.hpp
#include iostream
#include unordered_map
#include cstdio
#include string
#include cerrno
#include cstring
#include cstdlib
#include strings.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include unistd.h
#include queue#define SIZE 1024class UdpServer
{
public:// 这里填空串 下面三目运算分配INADDR_ANY;填指定串 server以指定串作为ipUdpServer(uint16_t port, std::string ip 127.0.0.1): _ip(ip),_port(port),_socketFd(-1){}bool initServer(){// 新系统调用完成网络功能// 1. socket创建套接字 协议用IPv4 套接字类型用数据报套接字_socketFd socket(AF_INET, SOCK_DGRAM, 0); // #define AF_INET PF_INET 二者相同if (_socketFd 0){logMsg(FATAL, socket::%d:%s, errno, strerror(errno));exit(2); // 正规写法规定每个退出码代表什么意思}// 2. bind绑定端口号 将一个套接字绑定到一个特定的ip和portstruct sockaddr_in svr_sockAddr;bzero(svr_sockAddr, sizeof(svr_sockAddr));svr_sockAddr.sin_family AF_INET;// #define INADDR_ANY ((in_addr_t) 0x00000000) 字符串--32位整数--网络字节序svr_sockAddr.sin_addr.s_addr _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());svr_sockAddr.sin_port htons(_port); //主机字节序--网络字节序// int bind(int __fd, const sockaddr *__addr, socklen_t __len)if (bind(_socketFd, (struct sockaddr *)svr_sockAddr, sizeof(svr_sockAddr)) 0){logMsg(FATAL, bind::%d:%s, errno, strerror(errno));exit(2);}logMsg(NORMAL, init udp server done ... %s, strerror(errno));return true;}void Start(){// 响应式服务器:原封地不动返回client发送的消息char buffer[SIZE];while (true){// clt_sockAddr 纯输出型参数struct sockaddr_in clt_sockAddr;bzero(clt_sockAddr, sizeof(clt_sockAddr));// clint_addrlen: 输入输出型参数socklen_t clint_addrlen sizeof(clt_sockAddr); // unsigned int// 读取数据ssize_t bytes_read recvfrom(_socketFd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)clt_sockAddr, clint_addrlen);if (bytes_read -1){logMsg(FATAL, recvfrom::%d:%s, errno, strerror(errno));exit(3);}// 谁 发送的 什么信息if (bytes_read 0){buffer[bytes_read] 0; // 数据当做字符串使用uint16_t client_port ntohs(clt_sockAddr.sin_port); // 网络字节序 -- 主机字节序std::string cli_ip inet_ntoa(clt_sockAddr.sin_addr); // 4字节的网络序列的IP-本主机字符串风格的IPprintf([%s:%d]# %s\n, cli_ip.c_str(), client_port, buffer);}// 回显数据sendto(_socketFd, buffer, strlen(buffer), 0, (struct sockaddr *)clt_sockAddr, clint_addrlen);}}~UdpServer(){if (_socketFd 0)close(_socketFd);}private:// ip和portstd::string _ip;uint16_t _port;int _socketFd;
};#endif
4.2命令式
#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP#include Log.hpp
#include iostream
#include unordered_map
#include cstdio
#include string
#include cerrno
#include cstring
#include cstdlib
#include strings.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include unistd.h
#include queue#define SIZE 1024class UdpServer
{
public:UdpServer(uint16_t port, std::string ip 127.0.0.1): _ip(ip),_port(port),_socketFd(-1){}bool initServer(){// 新系统调用完成网络功能// 1. socket创建套接字 协议用IPv4 套接字类型用数据报套接字_socketFd socket(AF_INET, SOCK_DGRAM, 0); // #define AF_INET PF_INET 二者相同if (_socketFd 0){logMsg(FATAL, socket::%d:%s, errno, strerror(errno));exit(2); // 正规写法规定每个退出码代表什么意思}// 2. bind绑定端口号 将一个套接字绑定到一个特定的地址和端口struct sockaddr_in svr_sockAddr;bzero(svr_sockAddr, sizeof(svr_sockAddr));svr_sockAddr.sin_family AF_INET;svr_sockAddr.sin_addr.s_addr _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());svr_sockAddr.sin_port htons(_port);// int bind(int __fd, const sockaddr *__addr, socklen_t __len)if (bind(_socketFd, (struct sockaddr *)svr_sockAddr, sizeof(svr_sockAddr)) 0){logMsg(FATAL, bind::%d:%s, errno, strerror(errno));exit(2);}logMsg(NORMAL, init udp server done ... %s, strerror(errno));return true;}void Start(){char cmdBuf[SIZE];while (true){// client_addr 纯输出型参数struct sockaddr_in client_addr;bzero(client_addr, sizeof(client_addr));// clint_addrlen: 输入输出型参数socklen_t clint_addrlen sizeof(client_addr); // unsigned int// 读取数据ssize_t bytes_read recvfrom(_socketFd, cmdBuf, sizeof(cmdBuf) - 1, 0, (struct sockaddr *)client_addr, clint_addrlen);if (bytes_read -1){logMsg(FATAL, recvfrom::%d:%s, errno, strerror(errno));exit(3);}// 谁 发送的 什么信息char partMsg[256];std::string cmdOutput;if (bytes_read 0){cmdBuf[bytes_read] 0; // 数据当做字符串使用// 不允许客户端执行rm命令if (strcasestr(cmdBuf, rm) ! nullptr || strcasestr(cmdBuf, rmdir) ! nullptr){std::string err_msg 大坏蛋不准删除;std::cout client send: cmdBuf but is blocked! std::endl;sendto(_socketFd, err_msg.c_str(), err_msg.size(), 0, (struct sockaddr *)client_addr, clint_addrlen);continue;}FILE *fp popen(cmdBuf, r);if (nullptr fp){logMsg(ERROR, popen:%d:%s, errno, strerror(errno));continue;}//popen把cmdBuf的命令执行的结果存入到fp文件中while (fgets(partMsg, sizeof(partMsg), fp) ! nullptr){cmdOutput partMsg;}fclose(fp);sendto(_socketFd, cmdOutput.c_str(), cmdOutput.size(), 0, (struct sockaddr *)client_addr, clint_addrlen);}}}~UdpServer(){if (_socketFd 0)close(_socketFd);}private:// ip和portstd::string _ip;uint16_t _port;int _socketFd;
};#endif4.3交互式
#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP#include Log.hpp
#include iostream
#include unordered_map
#include cstdio
#include string
#include cerrno
#include cstring
#include cstdlib
#include strings.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include unistd.h
#include queue#define SIZE 1024class UdpServer
{
public:UdpServer(uint16_t port, std::string ip ): _ip(ip),_port(port),_socketFd(-1){}bool initServer(){// 新系统调用完成网络功能// 1. socket创建套接字 协议用IPv4 套接字类型用数据报套接字_socketFd socket(AF_INET, SOCK_DGRAM, 0); // #define AF_INET PF_INET 二者相同if (_socketFd 0){logMsg(FATAL, %d:%s, errno, strerror(errno));exit(2); // 正规写法规定每个退出码代表什么意思}// 2. bind绑定端口号 将一个套接字绑定到一个特定的地址和端口// 将用户设置的ip和port在内核中和我们当前的进程强关联struct sockaddr_in svr_socketAddr;bzero(svr_socketAddr, sizeof(svr_socketAddr));svr_socketAddr.sin_family AF_INET;svr_socketAddr.sin_addr.s_addr _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());svr_socketAddr.sin_port htons(_port);// int bind(int __fd, const sockaddr *__addr, socklen_t __len)if (bind(_socketFd, (struct sockaddr *)svr_socketAddr, sizeof(svr_socketAddr)) 0){logMsg(FATAL, %d:%s, errno, strerror(errno));exit(2);}logMsg(NORMAL, init udp server done ... %s, strerror(errno));return true;}void Start(){char buffer[SIZE];while (true){// client_addr 纯输出型参数struct sockaddr_in client_addr;bzero(client_addr, sizeof(client_addr));// clint_addrlen: 输入输出型参数socklen_t clint_addrlen sizeof(client_addr); // unsigned int// 读取数据ssize_t bytes_read recvfrom(_socketFd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)client_addr, clint_addrlen);if (bytes_read -1){logMsg(FATAL, %d:%s, errno, strerror(errno));exit(3);}//一旦收到新用户的信息 就把新用户记录下来char client[64];if (bytes_read 0){buffer[bytes_read] 0; // 数据当做字符串使用std::string cli_ip inet_ntoa(client_addr.sin_addr); // 4字节的网络序列的IP-本主机字符串风格的IPuint16_t client_port ntohs(client_addr.sin_port); // 网络字节序 -- 主机字节序snprintf(client, sizeof(client), %s-%u, cli_ip.c_str(), client_port); // 127.0.0.1-8080logMsg(NORMAL, client: %s, client);std::string client_str client;auto it _clients.find(client_str);if (it _clients.end()){logMsg(NORMAL, add new client : %s, client);_clients.insert({client_str, client_addr});}}//svr把一个客户端发送过来的信息发送给了他所记录的所有的clientfor (auto iter : _clients){std::string sendMsg client;sendMsg # ;sendMsg buffer; // 127.0.0.1-8080# 你好logMsg(NORMAL, push message to %s, iter.first.c_str());sendto(_socketFd, sendMsg.c_str(), sendMsg.size(), 0, (struct sockaddr *)(iter.second), sizeof(iter.second));}}}~UdpServer(){if (_socketFd 0)close(_socketFd);}private:// ip和portstd::string _ip;uint16_t _port;int _socketFd;std::unordered_mapstd::string, struct sockaddr_in _clients;/*for test: PC模型多线程服务端: 解耦服务端 -- PC模型A线程只读数据且把数据推送到队列B线程只取数据且把数据发给客户端std::queuestd::string msgQueue;如果消息是任务 还可以把任务交给线程池处理*/
};#endif
1.启动程序
当你执行以下命令序列时
./server 0.0.0.0 8080
./client 127.0.0.1 8080
./client 127.0.0.1 8080会发生以下事情
服务器启动 ./server 0.0.0.0 8080 命令启动了一个服务器程序该服务器绑定到所有可用的网络接口上的8080端口由于使用了0.0.0.0地址。服务器现在准备接收发送到8080端口的UDP数据包。
第一个客户端启动 ./client 127.0.0.1 8080 命令启动了第一个客户端程序。该客户端配置为向本地机器上的8080端口发送UDP数据包目标地址是127.0.0.1回环地址。客户端发送的数据将被路由到同一台机器上运行的服务器。
第二个客户端启动 几乎同时./client 127.0.0.1 8080 命令又启动了第二个客户端程序。这个客户端与第一个客户端完全相同它也向本地机器上的8080端口发送UDP数据包。
现在服务器上有一个监听在8080端口的套接字准备接收数据。两个客户端都在尝试向这个端口发送数据。由于UDP是无连接的所以两个客户端可以独立地、同时地或几乎同时地向服务器发送数据。
服务器将分别接收来自两个客户端的数据包并根据其程序逻辑处理这些数据。因为UDP是无状态的所以服务器不会知道或关心数据是从一个客户端还是两个客户端发送的它只处理到达其套接字的数据包。
需要注意的是UDP不保证数据包的顺序、可靠性或重复检测所以可能会有数据包丢失、乱序或重复到达的情况。此外如果服务器没有设计为并发处理多个客户端的请求它可能会在处理一个客户端的数据时阻塞另一个客户端的数据这取决于服务器的实现细节。
在实际应用中你通常需要确保服务器能够处理来自多个客户端的并发请求以避免性能瓶颈或数据丢失。此外如果数据包的内容需要按照特定的顺序处理或者需要确保数据的可靠性那么你可能需要考虑使用TCP而不是UDP。
2.运行结果