C++跨平台socket编程
- 一、概述
-
- [1.1 TCP协议](#1.1 TCP协议)
-
- [1.1 TCP 的主要特性](#1.1 TCP 的主要特性)
- [1.2 TCP报文格式](#1.2 TCP报文格式)
- UDP报文格式
- IP协议
- 使用windows编辑工具直接编辑Linux上代码
- 二、系统socket库
-
- 1.windows上加载socket库
- 2.创建socket
-
- [2.1 windows下](#2.1 windows下)
- [2.2 linux下](#2.2 linux下)
- 3.网络字节序
- 4.bind端口
- 5.listen监听并设置最大连接数
- 6.accept读取用户连接信息
- 7.服务器通过recv接收客户端发送信息
- 8.服务器send回应客户端数据
- 9.服务器开启多线程并发处理客户端连接
-
- [9.1 windows下](#9.1 windows下)
- [9.2 linux下](#9.2 linux下)
- 三、封装跨平台XTcp类
-
- 1.windows下测试
- 2.linux下测试
- 3.创建并测试XTcp的dll动态链接库
-
- [3.1 创建](#3.1 创建)
- [3.2 测试](#3.2 测试)
- 4.创建并测试XTcp的so动态链接库
-
- [4.1 创建](#4.1 创建)
- [4.2 测试](#4.2 测试)
- 四、TcpClient编写与tcp编程总结
-
- 1.connect与三次握手的过程
-
- [1.1 为什么需要三次握手?](#1.1 为什么需要三次握手?)
- [2 connect客户端连接服务器](#2 connect客户端连接服务器)
- 3.tcp编程总结
- 五、高并发服务器开发和测试
-
- 1.Windows中设置socket阻塞和非阻塞
- 2.Linux中设置socket阻塞和非阻塞
- 3.通过select实现connect的超时处理
- 4.并发测试工具ab
- 5.基于epoll的高性能服务器
- [6.epoll、select 和阻塞 accept 之间的关系](#6.epoll、select 和阻塞 accept 之间的关系)
-
- 1.监听套接字队列
- [2.**accept 函数**](#2.accept 函数)
- [3.I/O 多路复用机制](#3.I/O 多路复用机制)
- 六、http协议
- 七、UDP协议
-
- 1.创建UDPServer监听端口
- 2.接受客户端数据
- 3.客户端和服务端互相发送数据
- 4.移植到linux
- 5.UDP广播包发送接受
- 6.syslog项目
-
- [6.1 配置系统日志](#6.1 配置系统日志)
- [6.2 封装XUdp类](#6.2 封装XUdp类)
- [6.3 使用udp接受linux日志](#6.3 使用udp接受linux日志)
- [6.4 使用正则表达式分析syslog日志并警告](#6.4 使用正则表达式分析syslog日志并警告)
一、概述
1.1 TCP协议
TCP协议提供可靠、有序、且无差错的数据传输服务。
1.1 TCP 的主要特性
面向连接:
- 在传输数据之前,TCP 需要在通信双方之间建立一个连接,这个过程称为三次握手(Three-way Handshake)。
可靠传输:
- TCP 通过确认(ACK)、序列号、超时重传等机制保证数据的可靠传输。
有序传输:
- TCP 确保数据按序到达目标,即使数据包乱序到达,TCP 也会重新排序。
流量控制:
- TCP 使用滑动窗口机制来控制数据流量,避免发送方发送数据过快,超过接收方的处理能力。
拥塞控制:
- TCP 有内置的拥塞控制算法,通过调整发送速率避免网络拥塞。
1.2 TCP报文格式
UDP报文格式
IP协议
使用windows编辑工具直接编辑Linux上代码
ubuntu: apt-get install samba
centos7:sudo yum install samba samba-client samba-common -y
配置环境:
vim ../etc/samba/smb.conf
文件末尾加入共享目录:
重启一下服务:
在根目录下创建code目录:
设置权限:
添加Samba用户,设置密码:
检查服务是否都启动了(ubuntu跳过):
如果服务没有启动(centos):
shell
sudo systemctl enable smb nmb
sudo systemctl start smb nmb
如果服务没有启动(ubuntu):
shell
sudo service smbd restart
在windows系统中连接共享目录:
如果再次连接samba,并尝试进入共享文件夹,出现报错你没有权限访问,可能是防火墙问题,输入命令:
sudo setenforce 0
sudo iptables -F
二、系统socket库
套接字(Socket)是网络编程中用于在计算机之间进行通信的端点。它提供了一种进程间通信的机制,可以在同一台计算机上或不同计算机之间进行数据交换。套接字的概念和实现是网络通信的重要基础。
套接字是一个主机本地应用程序创建的,被操作系统所控制的接口("门")。
应用程序通过这个接口,使用传输层提供的服务,跨网络发送(接收)消息到(从)其他应用进程。
C/S模式的通信接口------套接字接口。
1.windows上加载socket库
在Windows中,Winsock库不是自动加载的。在使用网络功能之前,程序必须显式地初始化Winsock库,以便系统可以分配资源并为网络通信做好准备。初始化之后,程序就可以创建套接字并使用网络功能了。
每次编写涉及套接字编程的应用程序时,都需要在程序的开头部分添加这两行代码,并在程序结束时调用 WSACleanup()
来清理资源。
2.创建socket
2.1 windows下
socket(AF_INET, SOCK_STREAM, 0)
:创建一个套接字。
AF_INET
指定使用IPv4地址族,SOCK_STREAM
指定使用流式套接字(TCP),0
表示使用默认的协议(对于 SOCK_STREAM
即TCP协议)。socket
函数成功时返回一个非负整数表示创建的套接字,失败时返回 -1
。
2.2 linux下
因为Linux下各种库和windows不一样,个别函数也不一样,因此使用条件编译的方法。
我们一次性创建两千个socket。
在windows下,可以正常创建。
在Linux下,因为系统默认一个进程最多创建1024个socket,因此1024之后的创建不了。
我们可以通过ulimit -n修改最大可创建socket个数。
此时再运行就可以了。
3.网络字节序
网络字节序(Network Byte Order)是指在网络传输中,数据的字节排列顺序。它采用的是大端字节序(Big-Endian),即最高有效字节(Most Significant Byte,MSB)在前,最低有效字节(Least Significant Byte,LSB)在后。
c++
#include <arpa/inet.h>
//将主机字节序转换为网络字节序
unit32_t htonl (unit32_t hostlong);
unit16_t htons (unit16_t hostshort);
//将网络字节序转换为主机字节序
unit32_t ntohl (unit32_t netlong);
unit16_t ntohs (unit16_t netshort);
4.bind端口
获取端口号
配置地址结构
绑定端口
在Linux中编译发现报错,说明有头文件不一致。
5.listen监听并设置最大连接数
c++
int listen(int sockfd, int backlog);
参数说明
sockfd
:这是一个已经通过socket
和bind
函数创建并绑定到特定地址和端口的套接字描述符。backlog
:指定等待连接队列的最大长度,即在accept
函数被调用之前,可以有多少个连接请求处于等待状态。
当一个服务器套接字处于监听状态时,它会创建一个队列来存储那些尚未被服务器接受(accept
)的传入连接请求。当完全连接队列已满且有新的连接到达时,新连接可能会被拒绝(客户端将收到一个错误)。这个队列实际上包含两个部分:
-
半连接队列:存放已经完成三次握手中的第一步(SYN)但尚未完成整个握手过程的连接请求。
-
完全连接队列 :存放已经完成三次握手的连接请求,等待应用程序调用
accept
。
在实际应用中,backlog
的值会影响服务器在高并发环境下的表现。设置过低的 backlog
值可能导致拒绝合法的连接请求,而设置过高的值可能会消耗更多的系统资源。
6.accept读取用户连接信息
accept
函数是套接字编程中用于从监听队列中提取第一个连接请求的系统调用。它会创建一个新的套接字 用于与客户端进行通信。accept
函数通常与服务器端的监听套接字配合使用,在客户端发起连接请求并经过 listen
函数处理后,由 accept
函数接受该请求。
c++
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明
sockfd
:已绑定到本地地址并处于监听状态的套接字描述符。addr
:指向sockaddr
结构体的指针,用于存储客户端的地址信息。可以是NULL
。addrlen
:指向一个socklen_t
变量的指针,用于存储addr
结构体的大小。调用前应设置为addr
结构体的大小,调用后包含实际的地址长度。可以是NULL
。
返回值
- 成功:
accept
函数成功时返回一个新的套接字描述符,这个新的套接字用于与客户端进行通信。- 返回值是一个非负整数,表示新的连接套接字。
- 失败:
accept
函数失败时返回-1
,并设置errno
以指示错误类型。
7.服务器通过recv接收客户端发送信息
c++
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数说明
sockfd
:一个已经连接的套接字描述符。buf
:指向用于存放接收到的数据的缓冲区。len
:缓冲区的长度,即可以接收的数据的最大字节数。flags
:接收操作的标志,可以是0或以下值的组合:MSG_OOB
:接收带外数据。MSG_PEEK
:窥视操作,将数据复制到缓冲区但不从输入队列中删除。MSG_WAITALL
:等待完整的数据,直到缓冲区满或发生错误。MSG_DONTWAIT
:非阻塞操作,如果没有数据可读,立即返回。
服务器循环接收客户端数据
不过现在有一个新的问题,如果有第二个、第三个...多个客户端连接,只有当第一个客户端退出,服务器才能收到其他客户端的消息,这是因为我们是在单线程中处理消息。
8.服务器send回应客户端数据
9.服务器开启多线程并发处理客户端连接
9.1 windows下
9.2 linux下
三、封装跨平台XTcp类
为什么需要XTcp类?
与系统相关的头文件、socket的初始化、绑定等函数容易出错,同时写起来很麻烦,因此包装成一个类,直接调用类的方法。
- 构造函数
- 创建和关闭socket
- 关于为什么提供CloseSocket而不是直接析构中调用closesocket?
- 析构中调用WSACleanup()同理,目前先不做析构。
- 关于为什么提供CloseSocket而不是直接析构中调用closesocket?
- 绑定与监听
- 接收客户端连接
- 发送、接收数据
- main 和 Tcpserver
1.windows下测试
2.linux下测试
3.创建并测试XTcp的dll动态链接库
3.1 创建
注意,在Windows系统上,套接字编程需要链接 Ws2_32.lib
库。确保在项目设置中包含这个库。
如果程序需要执行,则要配置工作目录为dll所在目录。
3.2 测试
4.创建并测试XTcp的so动态链接库
4.1 创建
运行makefile会发现有报错,windows的导出宏在linux没有,还有头文件只能包含一次也没有。
4.2 测试
再把测试程序也编译一下。
根据提示修改makefile文件
在Linux中,运行程序时确实需要确保动态库的路径在系统的库搜索路径中,否则程序将无法找到并加载所需的动态库。
四、TcpClient编写与tcp编程总结
1.connect与三次握手的过程
1.1 为什么需要三次握手?
确保双方都有能力发送和接收数据:通过三次握手,客户端和服务器可以确认双方都能够发送和接收数据包。
同步初始序列号:三次握手的过程还可以确保双方同步初始序列号,以防止因网络传输延迟或重传而导致的数据包混乱。
防止旧的重复连接初始化:三次握手可以防止旧的、重复的连接请求在网络中滞留并意外创建连接。
如果只进行两次,客户端收到服务器发送的ack后,知道服务器能收到客户端消息,所以客户端能保证服务器收到正确数据。但是如果客户端不回复服务器这一次的syn,那么服务器不知道客户端能否正确收到服务器的消息。
2 connect客户端连接服务器
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
- sockfd :由
socket
函数返回的套接字文件描述符。它标识一个套接字。 - addr :指向
struct sockaddr
类型的指针,包含了要连接的服务器的IP地址和端口号。 - addrlen :结构体
sockaddr
的长度。
返回值:
- 成功:返回0。
- 失败 :返回-1,并设置
errno
以指示错误原因。
3.tcp编程总结
五、高并发服务器开发和测试
1.Windows中设置socket阻塞和非阻塞
阻塞模式:
- 套接字在创建时默认是阻塞模式。
- 调用套接字函数(如
accept
、connect
、recv
、send
等)时,如果操作无法立即完成,函数会阻塞(即挂起执行)直到操作完成或发生错误。非阻塞模式:
- 调用套接字函数时,如果操作无法立即完成,函数立即返回一个错误(如
EWOULDBLOCK
),而不是阻塞等待。- 程序通常需要轮询套接字状态或使用事件驱动机制(如
select
、poll
或epoll
)时使用非阻塞模式。
我们的目的是:在建立连接的时候是非阻塞模式,在接收数据的时候是阻塞模式,因为接收数据我们是多线程方式,所以设置为阻塞不影响别的线程运行。
ioctlsocket
是 Windows 操作系统中用于控制套接字行为的函数。
c++
int ioctlsocket(
SOCKET s,
long cmd,
u_long *argp
);
参数说明:
- s: 套接字的描述符。
- cmd: 控制命令,指定要执行的操作。
- argp : 命令参数,根据
cmd
指定不同的含义。
FIONBIO: 设置或清除非阻塞模式。
- 如果
*argp
为非零值,则套接字设置为非阻塞模式。 - 如果
*argp
为零,则套接字设置为阻塞模式。
返回值:
- 如果函数成功,返回 0。
2.Linux中设置socket阻塞和非阻塞
3.通过select实现connect的超时处理
为什么使用select实现connect的超时处理?
- 阻塞
connect
无法设置超时 :connect
是一个阻塞操作,当服务器不可达时,可能会阻塞很长时间。这会导致程序响应变慢,尤其是在高并发环境下。
- 非阻塞
connect
:- 通过将套接字设置为非阻塞模式,
connect
会立即返回并设置errno
为EINPROGRESS
,表示连接正在进行中。此时,程序不会被阻塞,可以继续处理其他任务。
- 通过将套接字设置为非阻塞模式,
在非阻塞模式下使用 select
实现 connect
的超时处理是一个常见的做法,因为直接使用阻塞的 connect
函数无法设置超时。
select是一个用于多路复用的系统调用,用来监视多个文件描述符,等待其中的一个或多个文件描述符变为"就绪"状态,也就是可以进行I/O操作(如读或写)而不会阻塞。
在 select 函数中,有三个文件描述符集合用于监听不同类型的事件:
- 读集合(readfds) :用于监听可读事件。读事件是指文件描述符上有数据可以读取,或者连接已经关闭,或有一个新的连接请求(对于监听套接字)。当某个文件描述符触发读事件时,select会在
readfds
集合中设置该文件描述符。 - 写集合(writefds) :用于监听可写事件。写事件是指文件描述符可以执行写操作而不会阻塞。通常在以下情况下触发写事件:当套接字的发送缓冲区有空间时,select会在
writefds
集合中设置该文件描述符。 - 异常集合(exceptfds):用于监听异常事件。
文件描述符集合:
在 select
中,文件描述符集合使用 fd_set
结构。可以通过以下宏来操作文件描述符集合:
- FD_ZERO(fd_set *set):清空集合。
- FD_SET(int fd, fd_set *set):将文件描述符加入集合。
- FD_CLR(int fd, fd_set *set):将文件描述符从集合中删除。
- FD_ISSET(int fd, fd_set *set):检查文件描述符是否在集合中。
我们可以将需要监听的套接字放入套接字文件描述符集合,由该集合负责帮我们监听该文件描述符表中这些套接字文件描述符对应的套接字的缓冲区中是否有数据需要处理。
这个监听集合的大小为1024(默认最大值),但需要注意的是,虽然这个集合的大小为1024,但实际能帮我们监听的客户端套接字只有1020个,因为前1-3个分别用于监听标准输入、标准输出和标准出错,第四个用于存放服务器套接字。通过这个监听集合,我们就可以实现对多个socket的同时监听。
在非阻塞模式下调用 connect
函数时,它不会阻塞等待连接完成,而是立即返回。如果连接不能立即完成,errno
会被设置为 EINPROGRESS
,表示连接正在进行中。
当套接字连接成功或失败时,套接字会变为可写,因此应该监听写事件。
代码如下:
4.并发测试工具ab
5.基于epoll的高性能服务器
epoll
是 Linux 特有的 I/O 多路复用机制,适用于处理大量并发连接。它比传统的select
和poll
更高效,能够处理更高的并发数。
epoll
支持两种触发模式:边缘触发(Edge Triggered, ET)和水平触发(Level Triggered, LT)。
水平触发(LT):
这是
epoll
的默认模式,类似于select
和poll
的工作方式。当文件描述符处于就绪状态时,epoll_wait
会返回该文件描述符,每次调用epoll_wait
都会返回,直到事件被处理。
优点:
简单直接,每次都有数据时都会通知。
缺点:
当不处理事件时,
epoll_wait
会不断返回就绪状态,可能导致重复处理同一事件。边缘触发(ET):
在边缘触发模式下,当文件描述符状态发生变化(如从不可读变为可读)时,
epoll_wait
会返回该文件描述符。事件只会在状态变化时触发,处理后不会再次触发,直到状态再次变化。
优点:
高效,减少了不必要的系统调用。
适用于处理高并发连接,避免重复处理同一事件。
缺点:
复杂,需要确保每次事件处理时读取或写入尽可能多的数据,避免丢失事件。
水平触发(level-trggered)
- 只要文件描述符关联的读内核缓冲区非空,有数据可以读取,就一直发出可读信号进行通知。
- 当文件描述符关联的内核写缓冲区不满,有空间可以写入,就一直发出可写信号进行通知。
边缘触发(edge-triggered)
当文件描述符关联的读内核缓冲区由空转化为非空的时候,则发出可读信号进行通知。
当文件描述符关联的内核写缓冲区由满转化为不满的时候,则发出可写信号进行通知。
水平触发是只要读缓冲区有数据,就会一直触发可读信号,而边缘触发仅仅在空变为非空的时候通知一次。
epoll的主要组件:
epoll_create
:创建一个 epoll 实例。epoll_ctl
:控制 epoll 实例,注册、修改或删除感兴趣的文件描述符事件。epoll_wait
:等待事件的发生,并返回已经准备好的文件描述符。
注意
使用apache测试工具进行测试,发现进度卡在900条。
主要问题在于accept部分,如果同时有多个连接到来,并且都是请求与服务器进行的连接,我们只accept一次,其他的连接就丢失了,因为我们是边缘触发,只通知一次。
但是accept是阻塞函数,如果调用的时候没有新的连接到来了,就会一直阻塞着,因此我们需要首先设置套接字为非阻塞。
处理速度是5000多次每秒,是之前的八倍。
在if(events[i].data.fd == tcp.sock)判断条件下,可能会有多个客户端同时请求服务器,但是最开始只accept一次,会导致一些请求被丢失或者延迟处理。关于为什么设置tcp.sock为非阻塞,因为没法直接判断监听套接字队列是否为空,因此通过不停调用accept来间接判断,如果tcp.sock是阻塞,那么当队列为空,就会阻塞整个流程,导致后续只有新连接到来才会停止阻塞,反而tcp.sock为非阻塞,可以根据accept的返回值判断是否队列为空。
6.epoll、select 和阻塞 accept 之间的关系
1.监听套接字队列
当服务器调用 listen
函数将套接字设置为监听模式时,内核会为该套接字分配一个连接队列。
这个队列包含所有已完成三次握手但尚未被服务器 accept 的客户端连接。
队列与 I/O 多路复用机制(如 epoll、select 或阻塞的 accept)是独立的。
2.accept 函数
accept
函数用于从监听队列中提取一个已完成的连接,并为该连接创建一个新的套接字。
如果队列为空,阻塞模式下的 accept
函数会阻塞,直到有新的连接可用。非阻塞模式下的 accept
会立即返回,并设置 errno
为 EAGAIN
或 EWOULDBLOCK
。
3.I/O 多路复用机制
I/O 多路复用机制(如 select
、poll
和 epoll
)用于监视多个文件描述符,查看它们是否准备好进行 I/O 操作(如读或写)。
六、http协议
HTTP/1.0(短连接):
- 每个请求/响应对都要创建一个新的TCP连接,服务器在发送完响应后立即关闭连接。
HTTP/1.1(持续连接):
- 支持持久连接,即默认情况下,TCP连接会保持打开,允许在同一个连接上发送多个请求和响应。
- 支持分块传输编码,可以在响应主体的长度未知时逐块传输。
HTTP/2(二进制协议):
- HTTP/2 使用二进制格式传输数据,而不是HTTP/1.1 的文本格式。
- 在一个TCP连接上可以发送多个请求和响应,彼此互不干扰。
- 使用HPACK压缩算法减少头部大小,降低带宽消耗。
1.请求格式
请求方法 请求地址 协议版本 回车
2.响应格式
3.接受浏览器发送的请求
4.响应浏览器的请求
如果将协议长度改为6呢?
5.通过正则表达式分析浏览器请求
5.1正则表达式
贪婪模式
非贪婪模式
6.获取请求文件大小并生成http协议头
c
int fseek(FILE *stream, long int offset, int whence);
参数:
FILE *stream
:指向文件的指针,该文件是通过fopen
打开的。long int offset
:偏移量,表示从whence
开始移动的字节数。int whence
:决定偏移的起始位置,有三个可能的值:SEEK_SET
:文件开头。SEEK_CUR
:当前位置。SEEK_END
:文件末尾。
返回值:
fseek
成功时返回 0,失败时返回非零值。
c
long int ftell(FILE *stream);
ftell
是 C 标准库中的一个函数,用于获取文件指针的当前位置。它与 fseek
配合使用,可以实现对文件的随机访问和定位。
参数:
FILE *stream
:指向文件的指针,该文件是通过fopen
打开的。
返回值:
- 成功时:返回当前文件指针的位置(相对于文件开头的字节数)。
- 失败时:返回 -1L,并设置
errno
。
c
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
fread
是 C 标准库中的一个函数,用于从文件中读取数据。它在文件指针当前位置开始读取指定数量的数据块,并将数据存储到指定的内存位置。
参数:
void *ptr
:指向存储读取数据的内存位置的指针。size_t size
:每个数据块的大小(以字节为单位)。size_t nmemb
:要读取的数据块的数量。FILE *stream
:指向文件的指针,该文件是通过fopen
打开的。
返回值:
- 成功时:返回实际读取的数据块数量(
nmemb
)。 - 失败时:如果读取时发生错误或到达文件末尾,则返回值可能小于
nmemb
。
7.发送用户请求的页面和图片
试试请求一个完整的网页。
8.完成HTTP1.1协议并移植到windows
我们目前的程序是一次连接发送一次数据,如果想再次请求页面,就要重新建立连接。
在创建线程执行Main之前都是没问题的,但是Main里我们每次处理完一次连接请求就会退出线程,因此需要改为不停地保持连接处理直到客户端单方面关闭连接。
这里将所有处理代码放入循环中,同时在buf的有效数据最后一位加入'\0',以免上一次读的数据影响这一次结果。
9.通过正则表达式获取请求页面文件后缀php
10.调用php-cgi完成php脚本解析
11.完成php脚本解析并返回给浏览器
在上一节我们实现的php脚本解析会将消息报头部分也发送给客户端,在这一节我们修改一下程序只发送正文部分。
12.支持给php传递基于URLGET变量
当我们想给php传递参数的时候,目前的程序是不支持的,我们需要先修改正则表达式,然后修改发送给php的命令。
13.封装XhttpServer
入口不应该包含业务逻辑。
关于之前与创建服务器相关的代码可以封装到XhttpServer类中。
此外在XHTtpServer入口函数中,我们要接受客户端消息,分析客户端消息,给客户端发消息,因此需要再拆分成两个类,一个类负责与客户端进行通信,一个类负责解析客户端的消息。
因此一共需要三个类来重构。
- 首先创建XhttpServer类,我们可以创建多个httpserver,绑定不同的端口。
14.封装XhttpComm处理单个请求的接口
15.封装Xhttp协议响应类XHttpResponse
16.总结
原始代码存在的问题:
- 单一职责原则 :
- 原始代码中的
Main()
函数兼顾了接收客户端请求、解析请求、处理文件、发送响应等多个功能。这导致函数功能单一,代码量庞大,难以理解和维护。
- 原始代码中的
- 缺乏模块化 :
- 所有的功能都堆积在一个函数中,没有清晰的模块划分,难以单独测试和重用每个功能模块。
- 可扩展性差 :
- 在原始代码中,增加新的功能或者修改现有功能会影响到整个函数,风险较大。
通过将原始代码拆分成三个类,每个类负责不同的功能模块,可以有效解决上述问题,并带来以下优势:
- 模块化和清晰的责任划分 :
XHttpServer
类:负责服务器的启动和请求的接收,处理与客户端的连接管理。XHttpComm
类 :负责处理每个客户端连接的通信,包括接收请求、调用XHttpResponse
处理请求和发送响应。XHttpResponse
类:负责解析 HTTP 请求、打开和读取文件,生成并发送 HTTP 响应。
- 单一职责原则 :
- 每个类专注于自己的职责,代码更加清晰,易于理解和维护。
- 代码复用性 :
XHttpResponse
类中的文件操作和 HTTP 响应生成逻辑可以被多个客户端连接共享,避免重复编写相同的代码片段。
- 可扩展性 :
- 每个类可以独立地进行扩展和修改,不会对其他部分造成影响。例如,如果需要增加新的功能(如支持 POST 请求、多线程并发处理等),可以在相应的类中进行扩展。
类之间的协作关系:
XHttpServer
类 :- 负责服务器的启动和接收客户端连接。
- 每次接收到客户端连接后,创建一个
XHttpComm
对象处理该连接。
XHttpComm
类 :- 负责接收客户端请求、调用
XHttpResponse
处理请求和发送响应。 - 在收到请求后,创建一个
XHttpResponse
对象,调用其方法处理请求,并将生成的响应发送回客户端。
- 负责接收客户端请求、调用
XHttpResponse
类 :- 负责解析 HTTP 请求、读取相应的文件内容、生成 HTTP 响应头和发送响应正文。
16.移植到linux并解决中断问题
在linux会出现频繁刷新网页然后程序崩溃的情况,原因是当服务器调用send发送网页内容给客户端的时候,客户端关闭了sock,会触发操作系统产生SIGPIPE中断。SIGPIPE信号是在尝试向已关闭的socket连接发送数据时产生的。默认情况下,SIGPIPE信号会导致程序终止。这在网络编程中特别常见,尤其是在客户端突然断开连接时。
用gdb命令运行程序,然后频繁刷新网页,会发现程序闪退然后gdb提示是SIGPIPE信号导致的。
七、UDP协议
特点和主要用途:
- 面向无连接 :
- UDP 是一种无连接的协议,不需要在传输数据前建立连接,也不维护连接状态。每个 UDP 数据包都是独立的,发送端和接收端之间没有直接的持久性关系。
- 不可靠性 :
- UDP 不保证数据报的可靠传输。它不提供数据的重传、错误检测与恢复机制,也不保证数据报的顺序性。如果数据报在传输过程中丢失或损坏,接收端将无法得知,并且不会发生重传。
- 高效性 :
- UDP 没有 TCP 那样的拥塞控制和流量控制机制,因此在传输数据时不会因为额外的控制信息而产生额外的开销,传输效率相对较高。
- 适用场景 :
- UDP 适合于一些对传输速度要求较高、对实时性要求较强的应用场景,例如音频、视频流传输、在线游戏等。在这些应用中,即使偶尔丢失一些数据也不会对应用造成很大影响,而高效的传输速度和低延迟更为重要。
- 应用层协议 :
- 在网络编程中,UDP 通常用于实现一些应用层协议,如 DNS(域名系统)、DHCP(动态主机配置协议)、SNMP(简单网络管理协议)等。
UDP 数据包的基本结构如下:
- 源端口号:2 字节,表示发送端的端口号。
- 目的端口号:2 字节,表示接收端的端口号。
- 长度:2 字节,指示 UDP 数据包的长度,包括头部和数据部分。
- 检验和:2 字节,用于检测 UDP 数据包在传输过程中是否出现了错误。
UDP 的头部固定长度为 8 字节,没有额外的选项字段,头部中只有必需的信息,因此 UDP 的头部开销很小。
1.创建UDPServer监听端口
2.接受客户端数据
c++
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数说明:
sockfd
是要接收数据的套接字文件描述符。buf
是用于存放接收数据的缓冲区的指针。len
是缓冲区的长度,即能接收的最大字节数。flags
是可选的标志,通常设置为0。src_addr
是用于存放发送端地址的sockaddr
结构体指针。addrlen
是src_addr
结构体的大小的指针,用来接收实际地址长度。
3.客户端和服务端互相发送数据
c++
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数说明:
sockfd
是要发送数据的套接字文件描述符。buf
是包含要发送数据的缓冲区的指针。len
是要发送的数据的字节数。flags
是可选的标志,通常设置为0。dest_addr
是目标地址的sockaddr
结构体指针。addrlen
是dest_addr
结构体的大小。
服务器:
客户端:
4.移植到linux
5.UDP广播包发送接受
UDP的广播(Broadcast)是一种数据包发送方式,用于将数据发送给同一个网络中的所有主机,而无需知道每个主机的具体地址。广播是UDP协议的一个重要特性,它允许单个发送者发送数据包到网络中的所有接收者。
特点和用途:
- 单向发送:广播是单向的,只有一个发送者,但可以有多个接收者。
- 目标地址 :广播数据包的目标地址通常是本地网络的广播地址,例如IPv4的广播地址为
255.255.255.255
,IPv6的广播地址为ff02::1
。 - 局限性:广播通常局限于局域网(LAN),因为广播包的传播会受到网络设备(如路由器)的限制和配置影响,一般不会被路由器转发到其他子网。
- 应用场景:广播常用于局域网中的服务发现、通知和配置等,比如局域网内的设备发现、DHCP(动态主机配置协议)中的地址分配通知、一些多播视频流的发现等。
6.syslog项目
开发一个Syslog服务器涉及到接收和处理来自各种网络设备和应用程序的Syslog消息。Syslog是一种用于日志记录的标准协议,它允许各种设备和应用程序将日志信息发送到远程的Syslog服务器,通常通过UDP协议进行通信。
6.1 配置系统日志
6.2 封装XUdp类
6.3 使用udp接受linux日志
6.4 使用正则表达式分析syslog日志并警告
- 警告登录失败
首先制造一条登陆失败的消息,看看是什么格式。
用正则表达式打印登陆超级管理员失败的用户名。