网络编程之IO复用机制(多路IO转接)之lect实现IO复用的代码实现03

更新时间:2023-06-19 07:38:04 阅读: 评论:0

⽹络编程之IO复⽤机制(多路IO转接)之lect实现IO复⽤的代码实现03
1 思路图
上⼀篇我们说过了lect的代码思路,如果你清楚的话可以不看。但是这个图还是很有必要去了解的,虽然lect不建议深⼊。
1)我们先看图⼀,图⼀是我们调⽤accept去进⾏通信的最简单的例⼦。我们知道,accept函数每⼀次只能与⼀个客户端进⾏建⽴连接,要想与多个客户端进⾏连接,必须开启多个rver,所以这是不符合我们的需求的。
2)所以,伟⼤的程序员们就想到了IO复⽤机制即lect,poll,epoll去借助内核帮我们实现⼀个rver与多个客户端连接并进⾏通信。本节只讲lect。看图⼆,我们将accept函数替换成了lect,lect是⼀个⾮阻塞的函数,可以设置超时返回。它的作⽤是:将预先创建好的lfd监听想要连接的客户端和想要通信的客户端,然后进⾏对应操作。
3)这⾥强调⼀点,lect只需要监听读事件集合即可。因为相对于服务器,你客户端不管请求连接还是请求通信(即写内容到套接字的缓冲中),针对于服务器都是读事件,所以服务器只需要读事件集合即可完成想要的读写操作返回给客户端。⽽写事件其实意义不⼤,因为针对于服务器,你想写就写,不需要
什么条件来满⾜才能写,这个写事件可以参考libevent库,它对写事件也是实⽤性不⼤,所以这个事件只需要传NULL即可,⽆需了解。⽽异常事件⼀般是lect借助内核时可能触发的⼀些异常,这⾥我们也不需要理,因为发⽣异常你也搞不来,传NULL即可。
图⼀:
图⼆lect:
2 lect代码实战
我们简单写⼀下lect的实战,服务器的作⽤是,当客户端连接时,将客户端写过来的内容转成⼤写发回给客户端。
注意:下⾯开头部分的函数是封装好出错处理的函数,⽅便观察逻辑。
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#define SERV_PORT 6666
void perr_exit(const char*s)
{
perror(s);
exit(-1);
}
int Accept(int fd,struct sockaddr *sa, socklen_t *salenptr)
{
int n;
again:
if((n =accept(fd, sa, salenptr))<0){
if((errno == ECONNABORTED)||(errno == EINTR))
goto again;
el
perr_exit("accept error");
}
心碎的网名
return n;
}
int Bind(int fd,const struct sockaddr *sa, socklen_t salen)
{
int n;
if((n =bind(fd, sa, salen))<0)
perr_exit("bind error");
return n;
}
int Connect(int fd,const struct sockaddr *sa, socklen_t salen) {
int n;
if((n =connect(fd, sa, salen))<0)
perr_exit("connect error");
return n;
}
int Listen(int fd,int backlog)
{
int n;
if((n =listen(fd, backlog))<0)
perr_exit("listen error");
return n;
}
int Socket(int family,int type,int protocol)
{
int n;
if((n =socket(family, type, protocol))<0)
青筋突出
if((n =socket(family, type, protocol))<0)
perr_exit("socket error");
return n;
}
ssize_t Read(int fd,void*ptr, size_t nbytes)
{
ssize_t n;
again:
if((n =read(fd, ptr, nbytes))==-1){
if(errno == EINTR)
goto again;
el
return-1;
}
return n;
}
ssize_t Write(int fd,const void*ptr, size_t nbytes)
{
ssize_t n;
again:
if((n =write(fd, ptr, nbytes))==-1){
if(errno == EINTR)
goto again;
el
return-1;
}
return n;
}
int Clo(int fd)
{
int n;
if((n =clo(fd))==-1)
perr_exit("clo error");
return n;
}
/*参三: 应该读取的字节数*/
ssize_t Readn(int fd,void*vptr, size_t n)
{
size_t  nleft;//usigned int 剩余未读取的字节数 ssize_t nread;//int 实际读到的字节数
char*ptr;
ptr = vptr;
重阳节诗歌nleft = n;
while(nleft >0){
if((nread =read(fd, ptr, nleft))<0){
if(errno == EINTR)
nread =0;
上班时间通知el
return-1;
}el if(nread ==0)
break;
四季的风作文nleft -= nread;
ptr += nread;
}
return n - nleft;
return n - nleft;
}
ssize_t Writen(int fd,const void*vptr, size_t n)西红柿炖牛腩怎么做好吃又烂
{
size_t nleft;
ssize_t nwritten;
const char*ptr;
ptr = vptr;
nleft = n;
while(nleft >0){
if((nwritten =write(fd, ptr, nleft))<=0){
if(nwritten <0&& errno == EINTR)
nwritten =0;
el
return-1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
static ssize_t my_read(int fd,char*ptr)
{
static int read_cnt;
static char*read_ptr;
static char read_buf[100];
if(read_cnt <=0){
again:
if((read_cnt =read(fd, read_buf,sizeof(read_buf)))<0){ if(errno == EINTR)
goto again;
return-1;
}el if(read_cnt ==0)
return0;
read_ptr = read_buf;
另辟蹊径什么意思
}
read_cnt--;
*ptr =*read_ptr++;
return1;
}
ssize_t Readline(int fd,void*vptr, size_t maxlen)
{
ssize_t n, rc;
char    c,*ptr;
ptr = vptr;
for(n =1; n < maxlen; n++){
if((rc =my_read(fd,&c))==1){
*ptr++= c;
if(c  =='\n')
break;
}el if(rc ==0){
*ptr =0;
return n -1;
}el
return-1;
}
*ptr  =0;
return n;
}
int main(){
int nready, i, j, n;
落花流水的意思
int maxfd, lfd, cfd;
socklen_t clie_addr_len;
char buf[BUFSIZ];
fd_t rt, allt;/* allt为保存所有读事件⽂件描述符集合,rt只是⽤来暂存每次调⽤lect后,满⾜读事件的描述符 */
lfd =socket(AF_INET, SOCK_STREAM,0);/*参1为协议族,参2为套接字类型,参数默认0即可*/
if(lfd ==-1){
perr_exit("socket failed.");
}
/*sockaddr_in以0填充8个字节,变成14字节的结构体,⽅便与下⾯的sockaddr结果转换,
两者实际是⼀样的,只不过我们使⽤sockaddr_in时有ip和端⼝具体成员,⽽sockaddr没有,⽅便我们赋值*/
struct sockaddr_in clie_addr, rv_addr;
bzero(&rv_addr,sizeof(rv_addr));
rv_addr.sin_family= AF_INET;
rv_addr.sin_addr.s_addr =htonl(INADDR_ANY);
rv_addr.sin_port=htons(SERV_PORT);
int ret =bind(lfd,(struct sockaddr *)&rv_addr,sizeof(rv_addr));/*参2为sockaddr类型的服务器地址*/
if(ret ==-1){
perr_exit("bind failed.");
}
if(listen(lfd,128)==-1){/*允许连接的最⼤数,并⾮字⾯意思监听,监听实际上是下⾯的accept监听*/
perr_exit("listen failed.");
}
/*先将lfd添加到allt读事件集合,这样才能监听到客户端的连接请求和通信请求*/
FD_ZERO(&allt);
FD_SET(lfd,&allt);
maxfd = lfd;
/* while⾥完完全全就是维护allt和maxfd,并且while内只处理lfd和cfd请求的事情 */
while(1){
rt = allt;/* 满⾜读事件的使⽤rt,allt只保存所有⽂件描述符集合,注意lect与open时不⼀样,open默认会占3个描述符,所以只有1021可⽤,⽽lect有1024 */
nready =lect(maxfd+1,&rt,NULL,NULL,NULL);
if(nready <0){/* lect的返回⼀般只⽤于判断出错 */
perr_exit("lect error.");
}
//处理客户端请求连接的事情
if(FD_ISSET(lfd,&rt)){
clie_addr_len =sizeof(clie_addr);
cfd =Accept(lfd,(struct sockaddr *)&clie_addr,&clie_addr_len);/* Accept 不会阻塞 */
FD_SET(cfd,&allt);/* 向保存所有⽂件描述符集合的allt添加新的⽂件描述符cfd,它像open⽂件描述符时⼀样,默认插⼊(open为分配)到下标最⼩的位图位置,以⾄于当第⼀次超过1023(因为0-1023)后,cfd不会⼀直被插⼊到allt的末尾⽽覆盖正在通信的描述符 */ if(cfd > maxfd){/* 更新maxfd,防⽌下⼀次lect后遍历不完整导致出错,并且lect第⼀个参数需要 */
maxfd = cfd;
}
if(nready ==1){/* 只有lfd不需要执⾏下⾯的通信cfd的事情 */
continue;
}
/* ⾄此这⾥处理完lfd连接请求的事情 */
}
/* 处理客户端请求通信的事情 */
for(i = lfd +1; i <= maxfd; i++){/* 遍历所有⽂件描述符,判断是否在传出rt请求的集合中 */
if(FD_ISSET(i,&rt)){
if((n =Read(i, buf,sizeof(buf)))==0){/* 当client关闭链接时,服务器端也关闭对应链接 */

本文发布于:2023-06-19 07:38:04,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1045269.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:客户端   事件   连接   请求   集合   函数
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图