一、TCP可靠性分析
二、 scoket 属性设置
1、socket 属性设置表
NAME
getsockopt, setsockopt - get and set options on sockets
获取 和 设置 套接字属性
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd:网络描述符
level: 层次 👉SOL_SOCKET
optname:属性
optval:可变数据,根据不同的属性类型会改变
optlen:可变数据的大小
返回值: 设置/获取 成功 0 设置/获取 失败 -1
2,开启地址复用属性
// 使套接字sockfd关联的地址在套接字关闭后立即释放
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
SO_REUSEADDR:端口与地址复用属性
on = 1:开启 on = 0:关闭
3.UDP 广播属性
只有给192.168.63.255 IP发送UDP广播数据包,当前局域网的所有相同端口的主机都可以接收到数据!
// 设定套接字的广播属性为真
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
SO_BROADCAST:广播属性 💡只有UDP通信协议才支持广播
on = 1:开启 on = 0:关闭
----------------------------UDP发送广播例子--------------------------
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
// 1.创建UDP 通信对象
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_socket < 0)
{
perror("创建UDP对象失败\n");
return -1;
}
else
{
printf("创建UDP对象成功\n");
}
// 开启广播功能
int on = 1;
int ret = setsockopt(udp_socket, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
if (ret != 0)
{
printf("开启广播失败\n");
return -1;
}
else
{
printf("开启广播成功\n");
}
// 2.发送UDP数据包
struct sockaddr_in addr;
addr.sin_family = AF_INET; // IPV4网络协议
addr.sin_port = htons(7777); // 设置端口号
addr.sin_addr.s_addr = inet_addr("192.168.63.255"); // 设置广播地址
char buf[1024] = {"hello"};
int size = sendto(udp_socket, buf, 5, 0, (struct sockaddr *)&addr, sizeof(addr));
printf("发送广播数据包成功: %d\n", size);
// 3.关闭UDP 对象
close(udp_socket);
}
4.发送/接收缓存区属性
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
// 1.创建UDP 通信对象
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_socket < 0)
{
perror("创建UDP对象失败\n");
return -1;
}
else
{
printf("创建UDP对象成功\n");
}
// UDP发送缓存区大小:163840 UDP接收缓存区大小:163840
// 设置发送与接收缓存区大小
int rev_size = 2048;
int snd_size = 4096;
setsockopt(udp_socket, SOL_SOCKET, SO_RCVBUF, &rev_size, sizeof(rev_size)); // 获取接收缓存区大小
setsockopt(udp_socket, SOL_SOCKET, SO_SNDBUF, &snd_size, sizeof(rev_size)); // 获取发送缓存区大小
// 获取网络发送与接收缓存区大小
int len = sizeof(rev_size);
getsockopt(udp_socket, SOL_SOCKET, SO_RCVBUF, &rev_size, &len); // 获取接收缓存区大小
int len1 = sizeof(snd_size);
getsockopt(udp_socket, SOL_SOCKET, SO_SNDBUF, &snd_size, &len1); // 获取发送缓存区大小
printf("UDP发送缓存区大小:%d UDP接收缓存区大小:%d\n", snd_size, rev_size);
// 3.关闭UDP 对象
close(udp_socket);
//tip:设置的缓存区大小,Linux系统会自动 * 2
}
5.超时属性设置
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
// 1.创建UDP 通信对象
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_socket < 0)
{
perror("创建UDP对象失败\n");
return -1;
}
else
{
printf("创建UDP对象成功\n");
}
// 设置UDP超时属性
struct timeval val;
val.tv_sec = 3; // 设置3秒
val.tv_usec = 0; // 设置0毫秒
if (setsockopt(udp_socket, SOL_SOCKET, SO_RCVTIMEO, &val, sizeof(val)) != 0)
{
perror("超时属性设置失败\n");
return -1;
}
else
{
printf("超时属性设置成功3秒\n");
}
// 2.发送UDP数据包
struct sockaddr_in addr;
addr.sin_family = AF_INET; // IPV4网络协议
addr.sin_port = htons(7777); // 设置端口号
addr.sin_addr.s_addr = INADDR_ANY; // 设置广播地址
while (1)
{
char buf[1024] = {0};
printf("等待客户端发送数据包.....\n");
int size = recvfrom(udp_socket, buf, 1024, 0, NULL, NULL);
printf("size=%d\n", size);
}
// 3.关闭UDP 对象
close(udp_socket);
}
二、多路复用
多路复用的作用:实现Linux多个阻塞IO接口的数据读取操作,无需多线程或多进行处理阻塞IO,提高处理效率。
1.Linux系统的阻塞IO
阻塞IO就是在读写文件描述时会产生阻塞的状态,这种文件描述符就是阻塞IO。
1.从键盘获取数据 scanf
2.读取管道文件 read -> pipe
3.读取网络socket read -> tcp_socekt/udp_socket
4.接收客户端连接请求 accpet
......
--------💡如何解决多个IO的阻塞问题-------
1.利用并发技术(多线程/多进程),每一个阻塞IO都开一个线程或者进程去读取。 ✔️
2.把IO接口设置为非阻塞状态,并轮询读取
long state = fcntl(sockfd, F_GETFL);
state |= O_NONBLOCK; //设置为非阻塞状态
fcntl(sockfd, F_SETFL, state);
while(1)
{
read(socket,); //频繁系统调用,浪费资源
read(socket1,);
read(socket2,);
}
3.异步信号,注册一个信号处理函数,当有数据可读时,发送信号,触发读取函数。
4.select 多路复用去监听,活跃的描述符,当一个描述符活跃时,则读取数据。 ✔️
2.多路复用的设计流程图
NAME
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing
SYNOPSIS
#include <sys/select.h>
//开启多路监听
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
nfds:监听的最大描述符 + 1
readfds:读集合
writefds:写集合
exceptfds:可执行集合
timeout:超时检测
返回值: 成功 大于 0
超时 等于 0
失败 小于 0
void FD_CLR(int fd, fd_set *set); //把一个文件描述符从监听集合删除
int FD_ISSET(int fd, fd_set *set);//判断一个文件描述符是否活跃
void FD_SET(int fd, fd_set *set); //把一个文件描述符添加到监听集合中
void FD_ZERO(fd_set *set); //清空监听集合
例子:多路复用设计TCP客户端
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
int main()
{
// 1.创建TCP通信对象
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_socket < 0)
{
perror("创建对象失败\n");
return -1;
}
else
{
printf("创建对象成功\n");
}
// 2.设置服务器地址信息
struct sockaddr_in addr;
addr.sin_family = AF_INET; // IPV4网络协议
addr.sin_port = htons(7777); // 设置端口号
addr.sin_addr.s_addr = inet_addr("192.168.63.1"); // 设置IP地址
int ret = connect(tcp_socket, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0)
{
perror("连接失败\n");
return -1;
}
else
{
printf("连接成功\n");
}
#if 0
// 读取网络数据
while (1)
{
char buf[1024] = {0};
read(tcp_socket, buf, 1024); // 阻塞-> tcp_socket描述 3
printf("recv:%s\n", buf);
}
// 发送数据到网络中
while (1)
{
printf("请输入需要发送的网络数据\n");
char buf[1024] = {0};
scanf("%s", buf); // 阻塞 -> 标准输入描述 0
write(tcp_socket, buf, strlen(buf));
}
#endif
while (1)
{
// 1.清空集合
fd_set set;
FD_ZERO(&set);
// 2.添加阻塞IO描述符到集合中
FD_SET(0, &set); // 添加标准输入描述符
FD_SET(tcp_socket, &set); // 添加网络描述符 3
// 3.select 开启监听
printf("开启监听集合中的描述符......\n");
int ret = select(tcp_socket + 1, &set, NULL, NULL, NULL); // 监听读集合
if (ret > 0) // 有活跃描述符 /描述符有数据可读
{
printf("描述符有数据,请处理\n");
// 判断是否为输入描述符活跃
if (FD_ISSET(0, &set))
{
printf("0号描述符活跃\n");
char buf[1024] = {0};
read(0, buf, 1024);
// 发送数据到网络中
write(tcp_socket, buf, strlen(buf));
}
// 判断是否为TCP_SOCKET活跃
if (FD_ISSET(tcp_socket, &set))
{
printf("tcp_socket描述符活跃\n");
char buf[1024] = {0};
read(tcp_socket, buf, 1024);
printf("recv:%s\n", buf);
}
}
}
}
3.select超时检测
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
int main()
{
// 1.创建TCP服务器对象
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_socket < 0)
{
perror("创建对象失败\n");
return -1;
}
else
{
printf("创建对象成功\n");
}
// 2.设置服务器地址信息
struct sockaddr_in addr;
addr.sin_family = AF_INET; // IPV4网络协议
addr.sin_port = htons(1688); // 设置端口号
// addr.sin_addr.s_addr = inet_addr("192.168.63.1"); // 设置IP地址
addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡
int ret = bind(tcp_socket, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0)
{
perror("绑定失败\n");
return -1;
}
else
{
printf("绑定成功\n");
}
// 3.开启监听
ret = listen(tcp_socket, 10);
if (ret < 0)
{
perror("监听失败\n");
return -1;
}
else
{
printf("监听成功\n");
}
while (1)
{
// 1.清空集合
fd_set set;
FD_ZERO(&set);
// 2.添加阻塞IO描述符到集合中
FD_SET(tcp_socket, &set); // 添加服务器描述到集合中
// 设置监听的时间
struct timeval timeout;
timeout.tv_sec = 10; // 设置10秒
timeout.tv_usec = 0;
// 3.开始监听
int ret = select(tcp_socket + 1, &set, NULL, NULL, &timeout);
if (ret == 0) // 超时
{
printf("超时未有客户端连接\n");
}
if (ret > 0) // 有客户端连接
{
if (FD_ISSET(tcp_socket, &set))
{
printf("有新客户端连接,开启处理\n");
int new_scoekt = accept(tcp_socket, NULL, NULL);
printf("新客户端 %d\n", new_scoekt);
}
}
}
}