网络io与select
我们知道网络IO模型一共有5种,这里我们主要讨论同步IO和select多路复用的情况。
我们先从一个简单的TCP服务器的代码出发,来讨论一下这个是怎么实现的。
一个十分简单的TCP服务器
一个简单的TCP的服务器的建立流程是这样
- 建立SOCKET
- 绑定端口
- 监听
- 接受连接
- 接受消息
- 发送消息
- 关闭连接
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include <unistd.h>
#define BUFFER_LENGTH 128
int main()
{
//socket有两个参数,第一个参数指定我们要使用IPV4,还是IPV6,第二个参数表明我们要使用套接字类型,这里我们使用的是流格式的套接字,第三个参数就是我们需要使用传输协议
//这里使用0,表示让系统自动推导我们需要使用的传输协议。
int listenfd= socket(AF_INET,SOCK_STREAM,0);
//如果返回值为-1,说明我们创建SOCKET失败,直接返回。
if (listenfd==-1)
{
return -1;
}
//我们需要绑定的信息
struct sockaddr_in serveraddr;
//使用IPV4
serveraddr.sin_family=AF_INET;
//我们需要绑定的IP地址,INADDR_ANY 就是0.0.0.0 ,就是所有网卡的所有IP段都可以连接到我们的创建的TCP服务器上。
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
//我们需要绑定的端口,这里我们绑定的端口为9999
serveraddr.sin_port=htons(9999);
//第一个参数我们创建的套接字,第二个是我们填写的绑定信息,最后是我们的绑定信息结构体的大小。
if (-1==bind(listenfd,(const sockaddr*)&serveraddr,sizeof(serveraddr)))
{
return -2;
}
//监听我们创建的套接字,请求的队列数量,这里我们填写为10个
listen(listenfd,10);
//定义客户端的socket
struct sockaddr_in client;
//客户端结构体的长度
socklen_t len=sizeof(client);
//等待接受连接
//第一个参数服务器的套接字,第二个接收到的客户端的socket,第三个函数就是结构体的长度
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
//接受的缓冲区大小
unsigned char buffer[BUFFER_LENGTH]={0};
//收函数
//第一个参数客户端的套接字,第二个参数,接受的缓冲区,第三个参数缓冲区的大小,第4个参数接收到的字节数
int ret = recv(clientfd,buffer,BUFFER_LENGTH,0);
if (ret==0)
{
close(clientfd);
}
printf("buffer: %s , ret : %d\n",buffer,ret);
//发函数
//第一个参数客户端的套接字,第二个参数,发送的缓冲区,第三个参数发送的字节数,第4个参数实际发送的字节数
ret = send(clientfd,buffer,ret,0);
}
上面的代码已经把每个函数的参数的作用,还有 参数的意义都已经注释上了,
运行一下上面的代码,并使用我们的网络调试助手,发现我们的客户端已经可以收发数据了。
现在就遇到了一个问题,如果我们想一直接受和发送数据,我们需要做什么处理那?
可能大多数人都可以想到,我们添加 一个while循环的就可以一直接受数据了,因此我们改变一下我们的代码。
让它可以一直收发数据,知道我们的客户端退出为止。
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include <unistd.h>
#define BUFFER_LENGTH 128
int main()
{
//socket有两个参数,第一个参数指定我们要使用IPV4,还是IPV6,第二个参数表明我们要使用套接字类型,这里我们使用的是流格式的套接字,第三个参数就是我们需要使用传输协议
//这里使用0,表示让系统自动推导我们需要使用的传输协议。
int listenfd= socket(AF_INET,SOCK_STREAM,0);
//如果返回值为-1,说明我们创建SOCKET失败,直接返回。
if (listenfd==-1)
{
return -1;
}
//我们需要绑定的信息
struct sockaddr_in serveraddr;
//使用IPV4
serveraddr.sin_family=AF_INET;
//我们需要绑定的IP地址,INADDR_ANY 就是0.0.0.0 ,就是所有网卡的所有IP段都可以连接到我们的创建的TCP服务器上。
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
//我们需要绑定的端口,这里我们绑定的端口为9999
serveraddr.sin_port=htons(9999);
//第一个参数我们创建的套接字,第二个是我们填写的绑定信息,最后是我们的绑定信息结构体的大小。
if (-1==bind(listenfd,(const sockaddr*)&serveraddr,sizeof(serveraddr)))
{
return -2;
}
//监听我们创建的套接字,请求的队列数量,这里我们填写为10个
listen(listenfd,10);
//定义客户端的socket
struct sockaddr_in client;
//客户端结构体的长度
socklen_t len=sizeof(client);
//等待接受连接
//第一个参数服务器的套接字,第二个接收到的客户端的socket,第三个函数就是结构体的长度
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
//接受的缓冲区大小
unsigned char buffer[BUFFER_LENGTH]={0};
while (1)
{
//收函数
//第一个参数客户端的套接字,第二个参数,接受的缓冲区,第三个参数缓冲区的大小,第4个参数接收到的字节数
int ret = recv(clientfd,buffer,BUFFER_LENGTH,0);
if (ret<=0)
{
close(clientfd);
break;
}
printf("buffer: %s , ret : %d\n",buffer,ret);
//发函数
//第一个参数客户端的套接字,第二个参数,发送的缓冲区,第三个参数发送的字节数,第4个参数实际发送的字节数
ret = send(clientfd,buffer,ret,0);
}
return 0;
}
这样我们就达到了我们的要求,那么还有新的问题,就是我创建一个TCP server不能只使用一个。
下面就是如果我这个TCPserver想要连接多个客户端我应该怎么去做?
有两个可行的方案 ,供我们使用:
- 多线程、多进程的方式
- select,poll,epoll多路复用的方式。
我们首先看一下第一种的方式
多进程的方式,创建一个服务器,实现一个TCP 服务器可以连接多个TCP 客户端
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include <unistd.h>
#define BUFFER_LENGTH 128
int main()
{
unsigned char buffer[BUFFER_LENGTH]={0};
//socket有两个参数,第一个参数指定我们要使用IPV4,还是IPV6,第二个参数表明我们要使用套接字类型,这里我们使用的是流格式的套接字,第三个参数就是我们需要使用传输协议
//这里使用0,表示让系统自动推导我们需要使用的传输协议。
int listenfd= socket(AF_INET,SOCK_STREAM,0);
//如果返回值为-1,说明我们创建SOCKET失败,直接返回。
if (listenfd==-1)
{
return -1;
}
//我们需要绑定的信息
struct sockaddr_in serveraddr;
//使用IPV4
serveraddr.sin_family=AF_INET;
//我们需要绑定的IP地址,INADDR_ANY 就是0.0.0.0 ,就是所有网卡的所有IP段都可以连接到我们的创建的TCP服务器上。
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
//我们需要绑定的端口,这里我们绑定的端口为9999
serveraddr.sin_port=htons(9999);
//第一个参数我们创建的套接字,第二个是我们填写的绑定信息,最后是我们的绑定信息结构体的大小。
if (-1==bind(listenfd,(const sockaddr*)&serveraddr,sizeof(serveraddr)))
{
return -2;
}
//监听我们创建的套接字,请求的队列数量,这里我们填写为10个
listen(listenfd,10);
//定义客户端的socket
;
while (1)
{
struct sockaddr_in client;
//客户端结构体的长度
socklen_t len=sizeof(client);
//等待接受连接
//第一个参数服务器的套接字,第二个接收到的客户端的socket,第三个函数就是结构体的长度
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if (clientfd<0)
{
close(listenfd);
return -3;
}
pid_t id=fork();
if (id<0)
{
perror("fork");
}else if (id==0)
{
close(listenfd);
pid_t idd= fork();
if (idd<0)
{
perror("second fork");
_exit(5);
}
else if (idd==0)
{
while (1)
{
//收函数
//第一个参数客户端的套接字,第二个参数,接受的缓冲区,第三个参数缓冲区的大小,第4个参数接收到的字节数
int ret = recv(clientfd,buffer,BUFFER_LENGTH,0);
if (ret<=0)
{
close(clientfd);
break;
}
printf("buffer: %s , ret : %d\n",buffer,ret);
//发函数
//第一个参数客户端的套接字,第二个参数,发送的缓冲区,第三个参数发送的字节数,第4个参数实际发送的字节数
ret = send(clientfd,buffer,ret,0);
}
}else{
_exit(6);
}
}
#if 0
//接受的缓冲区大小
unsigned char buffer[BUFFER_LENGTH]={0};
while (1)
{
//收函数
//第一个参数客户端的套接字,第二个参数,接受的缓冲区,第三个参数缓冲区的大小,第4个参数接收到的字节数
int ret = recv(clientfd,buffer,BUFFER_LENGTH,0);
if (ret<=0)
{
close(clientfd);
break;
}
printf("buffer: %s , ret : %d\n",buffer,ret);
//发函数
//第一个参数客户端的套接字,第二个参数,发送的缓冲区,第三个参数发送的字节数,第4个参数实际发送的字节数
ret = send(clientfd,buffer,ret,0);
}
#endif
}
return 0;
}
然后我们在看看通过第一种方式的多线程的方式来完成我们的TCP服务器
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include <unistd.h>
#include<thread>
#define BUFFER_LENGTH 128
void routine(void *arg) {
int clientfd = *(int *)arg;
while (1) {
unsigned char buffer[BUFFER_LENGTH] = {0};
int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
if (ret == 0) {
close(clientfd);
break;
}
printf("buffer : %s, ret: %d\n", buffer, ret);
ret = send(clientfd, buffer, ret, 0); //
}
}
int main()
{
unsigned char buffer[BUFFER_LENGTH]={0};
//socket有两个参数,第一个参数指定我们要使用IPV4,还是IPV6,第二个参数表明我们要使用套接字类型,这里我们使用的是流格式的套接字,第三个参数就是我们需要使用传输协议
//这里使用0,表示让系统自动推导我们需要使用的传输协议。
int listenfd= socket(AF_INET,SOCK_STREAM,0);
//如果返回值为-1,说明我们创建SOCKET失败,直接返回。
if (listenfd==-1)
{
return -1;
}
//我们需要绑定的信息
struct sockaddr_in serveraddr;
//使用IPV4
serveraddr.sin_family=AF_INET;
//我们需要绑定的IP地址,INADDR_ANY 就是0.0.0.0 ,就是所有网卡的所有IP段都可以连接到我们的创建的TCP服务器上。
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
//我们需要绑定的端口,这里我们绑定的端口为9999
serveraddr.sin_port=htons(9999);
//第一个参数我们创建的套接字,第二个是我们填写的绑定信息,最后是我们的绑定信息结构体的大小。
if (-1==bind(listenfd,(const sockaddr*)&serveraddr,sizeof(serveraddr)))
{
return -2;
}
//监听我们创建的套接字,请求的队列数量,这里我们填写为10个
listen(listenfd,10);
//定义客户端的socket
;
while (1)
{
struct sockaddr_in client;
//客户端结构体的长度
socklen_t len=sizeof(client);
//等待接受连接
//第一个参数服务器的套接字,第二个接收到的客户端的socket,第三个函数就是结构体的长度
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if (clientfd<0)
{
close(listenfd);
return -3;
}
std::thread t{&routine,&clientfd};
t.detach();
}
#if 0
//接受的缓冲区大小
unsigned char buffer[BUFFER_LENGTH]={0};
while (1)
{
//收函数
//第一个参数客户端的套接字,第二个参数,接受的缓冲区,第三个参数缓冲区的大小,第4个参数接收到的字节数
int ret = recv(clientfd,buffer,BUFFER_LENGTH,0);
if (ret<=0)
{
close(clientfd);
break;
}
printf("buffer: %s , ret : %d\n",buffer,ret);
//发函数
//第一个参数客户端的套接字,第二个参数,发送的缓冲区,第三个参数发送的字节数,第4个参数实际发送的字节数
ret = send(clientfd,buffer,ret,0);
}
#endif
return 0;
}
这样我们的就完成了我们TCP 单进程多线程服务器的创建,这里会有一个问题:如果我们就想要通过单线程完成我们的可以接受多个客户端的连接,我们应该怎么去实现那?
在这里,我大概讲一下我自己的理解,可能理解的不对,也是刚开始学,我是这样去理解,当我们使用单线程只能接受一个客户端的TCP服务器的时候,当我们连接第2个客户端的时候,ACCEPT是可以接收的,但是没有办法进行消息的收发,因此我们就有这样的一个思路去完成我们的需求,就是通过类似哈希表一样的结构,有一个新的连接连入我们的时候,我们就插入这个表,然后我们开始轮询遍历这个表中的连接,如果有读写的事件产生,我们就调用recv和send函数进行调用,完成我们的需求。

类似图片中的轮询,下面我们看一下使用select函数的代码是怎么样的。
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include <unistd.h>
#define BUFFER_LENGTH 128
int main()
{
unsigned char buffer[BUFFER_LENGTH]={0};
int ret=0;
//socket有两个参数,第一个参数指定我们要使用IPV4,还是IPV6,第二个参数表明我们要使用套接字类型,这里我们使用的是流格式的套接字,第三个参数就是我们需要使用传输协议
//这里使用0,表示让系统自动推导我们需要使用的传输协议。
int listenfd= socket(AF_INET,SOCK_STREAM,0);
//如果返回值为-1,说明我们创建SOCKET失败,直接返回。
if (listenfd==-1)
{
return -1;
}
//我们需要绑定的信息
struct sockaddr_in serveraddr;
//使用IPV4
serveraddr.sin_family=AF_INET;
//我们需要绑定的IP地址,INADDR_ANY 就是0.0.0.0 ,就是所有网卡的所有IP段都可以连接到我们的创建的TCP服务器上。
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
//我们需要绑定的端口,这里我们绑定的端口为9999
serveraddr.sin_port=htons(9999);
//第一个参数我们创建的套接字,第二个是我们填写的绑定信息,最后是我们的绑定信息结构体的大小。
if (-1==bind(listenfd,(const sockaddr*)&serveraddr,sizeof(serveraddr)))
{
return -2;
}
//监听我们创建的套接字,请求的队列数量,这里我们填写为10个
listen(listenfd,10);
//定义客户端的socket
//定义可读序列和可写序列
fd_set rfds,wfds,rset,wset;
//清空序列
FD_ZERO(&rfds);
//设置读的序列
FD_SET(listenfd,&rfds);
//清空可写的序列
FD_ZERO(&wfds);
int maxfd=listenfd;
while (1)
{
//开始进行序列的赋值,
rset=rfds;
wset=wfds;
//select开始多路服用
//第一个参数是所有文件描述符的范围,第二个参数监控读的文件描述符的序列,第三个参数监控写的文件描述符的序列, 第4个参数监控异常的序列,第5个参数等待的时间,0是指无限等待
int nready=select(maxfd+1,&rset,&wset,NULL,NULL);
//判断listenfd服务器socket是否被设置,设置代表有效。
if (FD_ISSET(listenfd,&rset))
{
printf("listen --> \n");
struct sockaddr_in client;
socklen_t len = sizeof(client);
//开始接受客户端得连接
int clientfd = accept(listenfd, (struct sockaddr*)&client, &len);
//添加可读列表中
FD_SET(clientfd, &rfds);
//如果客户端的文件描述符的序号大于最大的文件描述符的编号
if (clientfd > maxfd) maxfd = clientfd;
}
int i = 0;
//开始轮询监听的已经连接的客户端的socket
for (i = listenfd+1; i <= maxfd;i ++) {
//如果有可读的事件
if (FD_ISSET(i, &rset)) { //
//开始接受信息
ret = recv(i, buffer, BUFFER_LENGTH, 0);
if (ret <= 0) {
close(i);
FD_CLR(i, &rfds);
} else if (ret > 0) {
//打印接受到信息
printf("buffer : %s, ret: %d\n", buffer, ret);
//设置可写的事件
FD_SET(i, &wfds);
}
//如果有可写的事件
} else if (FD_ISSET(i, &wset)) {
//开发发送消息
ret = send(i, buffer, ret, 0); //
//清空可写的事件
FD_CLR(i, &wfds);
//设置可读事件,因为并没有断开连接
FD_SET(i, &rfds);
}
}
}
return 0;
}
这样就完成了我们的select选择模型的代码。今天我们就介绍到这里。
推荐一个零声学院免费教程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:
服务器
音视频
dpdk
Linux内核
网络io与select的更多相关文章
- 五种网络IO模型以及多路复用IO中select/epoll对比
下面都是以网络读数据为例 [2阶段网络IO] 第一阶段:等待数据 wait for data 第二阶段:从内核复制数据到用户 copy data from kernel to user 下面是5种网络 ...
- 网络编程socket 结合IO多路复用select; epool机制分别实现单线程并发TCP服务器
select版-TCP服务器 1. select 原理 在多路复用的模型中,比较常用的有select模型和epoll模型.这两个都是系统接口,由操作系统提供.当然,Python的select模块进行了 ...
- 网络IO之阻塞、非阻塞、同步、异步总结
网络IO之阻塞.非阻塞.同步.异步总结 1.前言 在网络编程中,阻塞.非阻塞.同步.异步经常被提到.unix网络编程第一卷第六章专门讨论五种不同的IO模型,Stevens讲的非常详细,我记得去年看第一 ...
- Linux Network IO Model、Socket IO Model - select、poll、epoll
目录 . 引言 . IO机制简介 . 阻塞式IO模型(blocking IO model) . 非阻塞式IO模型(noblocking IO model) . IO复用式IO模型(IO multipl ...
- 转:Linux网络IO并行化技术概览
转:http://codinginet.com/articles/view/201605-linux_net_parallel?simple=1&from=timeline&isapp ...
- 5种网络IO模型
5种网络IO模型(有图,很清楚) 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到 ...
- Lost connection to MySQL server during query,MySQL设置session,global变量及网络IO与索引
Navicat导出百万级数据时,报错:2013 - Lost connection to MySQL server during query 网上一番搜索,修改mysql如下几处配置文件即可: sel ...
- 多路复用 阻塞/非阻塞IO模型 网络IO两个阶段
1.网络IO的两个阶段 waitdata copydata send 先经历:copydata阶段 recv 先经历:waitdata阶段 再经历 copydata阶段 2.阻塞的IO模型 之前写的都 ...
- IO多路复用select/poll/epoll详解以及在Python中的应用
IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...
- 网络io模式(服务器请求应答模式)
2014年1月19日 22:07:41 这几天看nginx 和 Apache的视频教程(马哥和邹老师)了解到了一些网络io模式(nginx的相关配置项为sendfile) 这里简单记录下来以备后用 A ...
随机推荐
- CORS小结
1.说明 https://www.cnblogs.com/xuanyuan/p/12979841.html 该文基于故事图文并茂地讲述了跨域的前生今世,因为文章是故事形式,里面的一些要点都只是一提而过 ...
- 探索C语言的数据类型:解密编程世界的核心秘密
欢迎大家来到贝蒂大讲堂 养成好习惯,先赞后看哦~ 所属专栏:C语言学习 贝蒂的主页:Betty's blog 1. 常量与变量 1.1 常量 (1) 常量的概念 常量顾名思义就是无法改变的量,比如一周 ...
- js根据输入字符长度自动调整textarea高度
1.编写html <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...
- letcode-两数相除
题解 设未知数: Br= 125 / 3,拆进行如下拆解: Br = 125 / 3 Br = (29 + 96)/3 Br = 29/3 + (32 * 3) / 3 Br = 29/3 + (2 ...
- [BUUCTF][Web][极客大挑战 2019]EasySQL 1
打开靶机对应的url 界面显示需要输入账号和密码 分别在两个输入框尝试加单引号尝试是否有sql注入的可能,比如 123' 发现两个框可以注入,因为报了个错误信息 You have an error i ...
- mongodb(2022)
了解 文档数据库MongoDB用于记录文档结构的数据,如JSON.XML结构的数据.一条文档就是一条记录(含数据和数据结构),一条记录里可以包含若干个键值对.键值对由键和值两部分组成,键又叫做字段.键 ...
- 【LeetCode剑指offer#05】回文链表的两种解法+删除链表中间节点(链表的基本操作)
回文链表 给你一个单链表的头节点 head ,请你判断该链表是否为回文链表.如果是,返回 true :否则,返回 false . 示例 1: 输入:head = [1,2,2,1] 输出:true 示 ...
- Xilinx GTH 简介 ,CoaXpress FPGA PHY 部分
什么是GTH GTH 是Xilinx UltraScale系列FPGA上高速收发器的一种类型,本质上和其它名称如GTP, GTX等只是器件类型不同.速率有差异:GTH 最低速率在500Mbps,最高在 ...
- 07、Etcd 中Raft算法简介
本篇内容主要来源于自己学习的视频,如有侵权,请联系删除,谢谢. 思考: etcd是如何基于Raft来实现高可用.数据强-致性的? 1.什么是Raft算法 Raft 算法是现在分布式系统开发首选的共识算 ...
- Go中响应式编程库github.com/ReactiveX/RxGo详细介绍
最近的项目用到了 RxGo ,因为之前从没有接触过,特意去学了学,特此记录下.文章很多内容是复制了参考资料或者官方文档.如果涉及侵权,请联系删除,谢谢. 1.RxGo简介 1.1 基础介绍 RxGo是 ...