目录
[htons() htonl()](#htons() htonl())
为每个客户端都开一个线程虽然能够提高实时性, 但是非常浪费资源并且具备一定风险!
所以使用多路 IO 复用:
IO复用(Input/Output Multiplexing)是一种高效的技术,用于在单个线程中管理多个输入/输出通道。它允许程序同时监听多个IO事件(如网络套接字、文件描述符等),并在有事件发生时进行处理,而无需为每个通道创建独立的线程或进程。
通过IO复用,程序可以避免阻塞在单一IO操作上,从而提升系统的并发性能
select()接口
函数的原型:
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| int select ( int nfds **, //**所有被监控的 fd 中, 最大值 + 1 fd_set * readfds , // 可读文件描述符集合 fd_set * writefds , // 可写文件描述符集合 fd_set * exceptfds , // 异常事件文件描述符集合 struct timeval * timeout // 超时时间,填NULL表示无限阻塞,直到有事件发生 ); |
返回值:
|--------------------------------------|
| 成功,返回所有集合中就绪 fd 的总数 超时,返回 0 失败,返回 -1 |
附带操作fd_set的四个宏
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| void FD_SET**(int fd,fd_set *set); // 往集合里面添加一个文件描述符 void FD_CLR(int fd,fd_set*set); //从集合里面 删除一个指定的文件描述符 int FD_ISSET(int fd,**fd_set ***set); //查看集合里面是否存在这个文件描述符 void FD_ZERO(**fd_set *set); // 清空整个集合,使用前调用 |
select版TCP版服务器代码示例
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "stdio.h"
#include "stdint.h"
#include "stdlib.h"
#include "string.h"
#include "fcntl.h"
#include "sys/socket.h"
#include "netinet/in.h"
#include <arpa/inet.h>
#include <sys/stat.h>
int main(void)
{
// 1.创建套接字
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == socket_fd)
{
perror("socket_fd");
return -1;
}
// 2.设置端口复用
int opt = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(socket_fd, SOL_SOCKET, 15, &opt, sizeof(opt));
// 3.绑定IP和端口号
struct sockaddr_in addr = {0};
addr.sin_addr.s_addr = 0; // 万用IP
addr.sin_family = AF_INET; // IPV4
addr.sin_port = htons(6000); // 端口号
int result = bind(socket_fd, (struct sockaddr*)&addr, sizeof addr);
if(result < 0) // 成功result= 0,失败result = -1
{
perror("bind:");
close(socket_fd);
return -1;
}
// 4.设置监听数
listen(socket_fd, 16);
printf("服务器已开启\r\n");
//------------------------------------------------------------------
// 1. 创建一个读表
int max_fd = 0;
max_fd = socket_fd; // 目前服务器套接字为最大的描述符
fd_set readfds, backupsfds; // 读表和备份
// 2. 清空读表
FD_ZERO(&readfds);
// 3. 把服务器套接字添加进读表
FD_SET(socket_fd, &readfds);
// 4. 备份读表
backupsfds = readfds;
// 5. 循环:阻塞检测
while(1)
{
readfds = backupsfds;
result = select(max_fd+1, &readfds, NULL, NULL, NULL);
if(result >= 1) // 至少有一个监测的文件发生了事件
{
for(int i = socket_fd; i <= max_fd; i++) // 遍历读表
{
if(FD_ISSET(i, &readfds)) // 找到出现事件的文件
{
if(i == socket_fd) // 服务器套接字出现事件
{
// 接受客户端的连接
int client_fd = accept(socket_fd, NULL, NULL);
// 将连接的客户端的套接字描述符加入读表
FD_SET(client_fd, &backupsfds);
max_fd = max_fd < client_fd ? client_fd : max_fd;
printf("客户端%d连接!\n", client_fd);
}
else // 客户套接字事件
{
char buf[1024] = {0};
int len = read(i, buf, sizeof buf);
if(0 == len) // 客户端离线
{
close(i);
FD_CLR(i, &backupsfds);
if(max_fd == i) max_fd--;
printf("客户端%d离线...\n", i);
continue; //
}
printf("客户端%d->%s\n", i , buf);
}
}
}
}
}
return 0;
}
poll()接口
函数原型:
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #include <poll.h> struct pollfd { int fd; /* 要监控的文件描述符 */ short events; /* 输入参数:需要监测的事件 */ short revents; /* 输出参数:内核返回的、实际发生的事件 */ }; int poll(struct pollfd *fds, // 指向pollfd结构体数组的数组名 nfds_t nfds, // 第一个参数数组的元素个数,即要监控多少个描述符 int timeout // 等待的超时时间,单位是ms, 填-1为死等 ); |
返回值:
|-----------------------------------|
| 成功:返回有事件发生的文件描述符个数 超时:返回0 失败:返回-1 |
poll版TCP版服务器代码示例
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "stdio.h"
#include "stdint.h"
#include "stdlib.h"
#include "string.h"
#include "fcntl.h"
#include "sys/socket.h"
#include "netinet/in.h"
#include <arpa/inet.h>
#include <sys/stat.h>
#include <poll.h>
#define SERVER_IP "192.168.61.130"
#define SERVER_PORT 60000
int main(void)
{
// 1. 创建服务器套接字
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd < 0)
{
perror("socket_fd");
return -1;
}
// 2. 绑定地址信息
struct sockaddr_in addr = {0};
addr.sin_addr.s_addr = inet_addr(SERVER_IP);
addr.sin_port = htons(SERVER_PORT);
addr.sin_family = AF_INET;
int result = bind(socket_fd, (struct sockaddr*)&addr, sizeof addr);
if(result != 0)
{
perror("bind");
return -1;
}
// 3. 监听
listen(socket_fd, 10);
printf("服务器已开启 IP->%s PORT->%d\n", SERVER_IP, SERVER_PORT);
//--------------------------------------------------------------------
// 1. 创建一个poll需要的监测结构体数组
struct pollfd fd_array[10] = {0};
uint8_t fd_array_itemCount = 0; // 数组中成员的总数
// 2. 添加服务器套接字描述符至数组
fd_array[0].fd = socket_fd;
fd_array[0].events = POLLIN; // 有数据可读(有数据输入)
fd_array[0].revents = 0; // 实际上发生的事件类型
fd_array_itemCount++;
// 3.轮训监测
while(1)
{
result = poll(fd_array, fd_array_itemCount, -1);
if(result > 0) // 监测的文件有事件发生
{
for(int i = 0; i < fd_array_itemCount; i++)
{
// 找到触发事件的文件
if(fd_array[i].events == fd_array[i].revents)
{
if(fd_array[i].fd == socket_fd) // 如果是服务器
{
// 接受客户端的连接
int client_fd = accept(socket_fd, NULL, NULL);
// 添加客户端套接字描述符至结构体数组
fd_array[fd_array_itemCount].fd = client_fd;
fd_array[fd_array_itemCount].events = POLLIN;
fd_array[fd_array_itemCount].revents = 0;
fd_array_itemCount++;
printf("客户端%d连接成功..\n", client_fd);
}
else // 客户端
{
char buf[1024] = {0};
int len = read(fd_array[i].fd, buf, sizeof buf);
if(len == 0) // 客户端离线
{
printf("客户端%d离线...\n", fd_array[i].fd);
// 1. 关闭客户端套接字
close(fd_array[i].fd);
// 2. 从数组中删除客户端套接字描述符
for(int j = i; j < fd_array_itemCount; j++)
{
fd_array[j] = fd_array[j+1];
}
// 3. 更新数组中成员的总数
fd_array_itemCount--;
}
else // 正常收到消息
{
printf("收到客户端%d信息->%s\n", fd_array[i].fd, buf);
}
}
}
}
}
}
return 0;
}
网络编程常用函数
socket()
作用:创建一个socket套接字
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #include <sys/socket.h> int socket( int domain, // 协议族(AF_INET 用于 IPv4,AF_INET6 用于 IPv6) int type, // 套接字类型(SOCK_STREAM 用于 TCP,SOCK_DGRAM 用于 UDP) int protocol // 通常填 0,让系统自动选择协议 ); 返回值: 成功返回文件描述符(>=0),失败返回 -1 |
bind()
作用 :用于绑定套接字的 IP 属性和端口号
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #include <sys/types.h> #include <sys/socket.h> int bind( int sockfd, // 已经创建但尚未绑定的套接字 const struct sockaddr *addr, // 要绑定的 IP 地址和端口号 socklen_t addrlen // addr结构体的长度,一般用sizeof() ); 返回值: 0: 绑定成功 -1:绑定失败 |
示例:
// 2. 绑定地址信息
struct sockaddr_in addr = {0};
addr.sin_addr.s_addr = inet_addr("192.168.130.31);
addr.sin_port = htons(55555);
addr.sin_family = AF_INET;
int result = bind(socket_fd, (struct sockaddr*)&addr, sizeof addr);
if(result != 0)
{
perror("bind");
return -1;
}
inet_addr()
inet_addr() 是一个将点分十进制 IPv4 地址字符串(如 "192.168.1.1")转换为 32 位网络字节序整数的函数
函数原型:
|--------------------------------------------------------------------------------------------------------------|
| #include <arpa/inet.h> // Linux 下的头文件 in_addr_t inet_addr(const char *cp); 返回值: 将字符串IP装换为大端的32位数字IP |
示例:
addr.sin_addr.s_addr = inet_addr("192.168.130.31);
htons() htonl()
将 16 或32 位整数从主机字节序转换为网络字节序
host to network short / long
函数原型:
|----------------------------------------------------------------------------------------------------------------------------------------------------|
| #include <arpa/inet.h> // Linux #include <winsock2.h> // Windows uint16_t htons(uint16_t hostshort); uint32_t htonl(uint32_t hostlong); |
listen()
listen() 开启监听且设置最大监听数量
函数原型:
|---------------------------------------------------------------------------------------------------------------|
| #include <sys/types.h> #include <sys/socket.h> int listen(int sockfd, int backlog); 返回值: 0:监听成功 -1:失败 |
accept()
函数功能:用于接受客户端的连接。
函数原型:
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| int accept ( int sockfd , // 服务器的套接字 struct sockaddr * addr , // 存放客户端地址信息的结构体地址 socklen_t * addrlen // 上一个参数的大小,sizeof(addr) ) 返回值: 成功: 返回客户的 通信套接字 失败:错误码 |
如有错误或不完善之处, 请不吝赐教。