首页 >> 大全

Linux网络编程5——多路IO转接服务器

2023-11-18 大全 30 作者:考证青年

学习视频链接

黑马程序员-Linux网络编程_哔哩哔哩

目录

一、多路 IO 服务器

1.1 分类

1. 原理图

二、 函数

2.1 传入、传出参数

2.2 函数和后续分析

2.3 配套函数

2.4 代码

2.5 linux和通信

2.的优缺点

2.7添加一个自己定义数组提高效率

一、多路 IO 服务器 1.1 分类

模型

poll 模型

epoll 模型

1. 原理图

这里 不是等待客户端连接,而是客户端想要连接需要需要通知 ,然后 把信息交给主函数,主函数执行 (),然后由 负责去写数据。涉及到阻塞、非阻塞轮询、响应式——多路 IO 转接,上述例子是多路 IO 转接

二、 函数 2.1 传入、传出参数

程序启动起来后 0 1 2 这 3 个文件描述符就被占用了。后面的文件描述符 3 被 lfd 占用,用于监听,后面的连接依次占用文件描述符 4、5、6 ...

客户端连接 上后没有发送信息的话,就不需要 读监听,想监听的话就把对应的文件描述符放入读集合 (r) 就可以了。写和异常的监听放在另外两个集合里面

现阶段没有这两种需求,所有传入 NULL 就行了

2.2 函数和后续分析

1、函数

int (int nfds, *, *, *, *);

nfds: 监控的文件描述符集里最大文件描述符加 1,因为此参数会告诉内核检测前多少个文件描述符的状态

: 监控有读数据到达文件描述符集合,传入传出参数

: 监控写数据到达文件描述符集合,传入传出参数

: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数

: 定时阻塞监控时间,3种情况。

(1) NULL,永远等下去

(2) 设置 ,等待固定时间。

(3) 设置 里时间均为 0,检查描述字后立即返回,轮询

2、分析

函数最后一个参数设置轮询的时间,在监听期间有发送信息的话,会放入到传出参数中,传出参数同样使用位图

一开始监听 3、5、6 文件描述符对应的读 (r),3 在监听的时候没有读 (r),所以传出中的位图没有 3 了

2.3 配套函数

void ( *set); —— 清空一个文件描述符集合

rset;

(&rset);

void (int fd, *set); —— 将待监听的文件描述符,添加到监听集合中

(3, &rset);

(5, &rset);

(6, &rset);

void (int fd, fd_ set *set); —— 将一个文件描述符从监听集合中移除

linux多线程服务器编程__编程器刷路由

(4, &rset);

int (int fd, *set); —— 判断一个文件描述符是否在监听集合中

返回值: 在返回1;不在返回0

(4, &rset);

2.4 代码

进入 while 前,监听的位图 就是上图这个,maxfd 对应的值是 3

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include #define SERV_PORT 6666void sys_err(const char* str) 
{perror(str);exit(1);
}int main(void)
{int i, j, n, nready;int maxfd = 0;int listenfd, connfd;char buf[BUFSIZ];struct sockaddr_in clie_addr, serv_addr;socklen_t clie_addr_len;listenfd = socket(AF_INET, SOCK_STREAM, 0);     // 创建一个socket, 得到lfd  int opt = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 设置端口复用bzero(&serv_addr, sizeof(serv_addr));           // 地址结构清零  serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 指定本地任意IP  serv_addr.sin_port = htons(SERV_PORT);          // 指定端口号   bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));  // 绑定  listen(listenfd, 128);     // 设置同一时刻链接服务器上限数  fd_set rset, allset;       // rset读事件文件描述符 allset用来暂存maxfd = listenfd;FD_ZERO(&allset);FD_SET(listenfd, &allset); // 构造select监控文件描述符集while (1) {rset = allset;         // 每次循环时都要重新设置select监控信号集nready = select(maxfd + 1, &rset, NULL, NULL, NULL);if (nready < 0) {sys_err("select error");}if (FD_ISSET(listenfd, &rset)) {  // 说明有新的客户端链接请求clie_addr_len = sizeof(clie_addr);connfd = accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);  // accept不会阻塞  FD_SET(connfd, &allset);      // 向监控文件描述符集合allset添加新的文件描述法connfdif (maxfd < connfd) {maxfd = connfd;}		if (0 == --nready) {continue;}}for (i = listenfd + 1; i <= maxfd; i++) {  // 检测哪个客户端有数据就绪if (FD_ISSET(i, &rset)) {if ((n = read(i, buf, sizeof(buf))) == 0) {  // 当客户端关闭链接时,服务器也关闭对应链接close(i);FD_CLR(i, &allset);}else if (n > 0) {for (j = 0; j < n; j++) {buf[j] = toupper(buf[j]);}write(i, buf, n);}}}}close(listenfd);return 0;
}

2.5 linux和通信

代码

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS#include 
#include 
#include #pragma comment(lib, "ws2_32.lib")using namespace std;int main()
{WORD ver = MAKEWORD(2, 2);  // 版本号,启动windows socket 2.x环境WSADATA dat;  // 数据结构,用来存储被WSAStartup函数调用后返回的Windows Sockets数据。// 它包含Winsock.dll执行的数据。WSAStartup(ver, &dat);// 1.建立一个socketSOCKET _sock = socket(AF_INET, SOCK_STREAM, 0);// 2.连接服务器sockaddr_in _sin = {};_sin.sin_family = AF_INET;_sin.sin_port = htons(6666);_sin.sin_addr.S_un.S_addr = inet_addr("192.168.244.137");int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));if (SOCKET_ERROR == ret) {cout << "绑定网络端口失败" << endl;}else {cout << "绑定网络端口成功" << endl;}char recvBuf[256] = {};int nlen = recv(_sock, recvBuf, 256, 0);if (nlen > 0) {cout << "接收到数据: " << recvBuf << endl;}// 4.关闭套接字closesocketclosesocket(_sock);getchar();// 清除windows socket环境WSACleanup();return 0;
}

拓展一下思路,其他的 TCP 通信同样可以和服务端连接

2.的优缺点

1、缺点

监听上限受文件描述符限制,最大 1024 个

检测满足条件的 fd,自己添加业务逻辑提高小。提高了编码难度。

2、优点

跨平台

2.7添加一个自己定义数组提高效率

#include 
#include 
#include 
#include 
#include 
#include 
#include "wrap.h"#define SERV_PORT 6666int main(int argc, char *argv[])  
{  int i, j, n, maxi;  int nready, client[FD_SETSIZE];          // 自定义数组client, 防止遍历1024个文件描述符  FD_SETSIZE默认为1024 */  int maxfd, listenfd, connfd, sockfd;  char buf[BUFSIZ], str[INET_ADDRSTRLEN];  // #define INET_ADDRSTRLEN 16 struct sockaddr_in clie_addr, serv_addr;  socklen_t clie_addr_len;  fd_set rset, allset;                     // rset 读事件文件描述符集合 allset用来暂存listenfd = Socket(AF_INET, SOCK_STREAM, 0);  int opt = 1;  setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  bzero(&serv_addr, sizeof(serv_addr));  serv_addr.sin_family= AF_INET;  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  serv_addr.sin_port= htons(SERV_PORT);  Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));  Listen(listenfd, 128);  maxfd = listenfd;           // 起初 listenfd 即为最大文件描述符maxi = -1;                  // 将来用作client[]的下标, 初始值指向0个元素之前下标位置for (i = 0; i < FD_SETSIZE; i++) {  client[i] = -1;         // 用-1初始化client[]  }FD_ZERO(&allset);  FD_SET(listenfd, &allset);  // 构造select监控文件描述符集  while (1) {     rset = allset;          // 每次循环时都重新设置select监控信号集 nready = select(maxfd+1, &rset, NULL, NULL, NULL);  // 2  1--lfd  1--connfd  if (nready < 0) {  perr_exit("select error");}  if (FD_ISSET(listenfd, &rset)) {                    // 说明有新的客户端链接请求  clie_addr_len = sizeof(clie_addr);  connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);  // Accept 不会阻塞 */  printf("received from %s at PORT %d\n",  inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),  ntohs(clie_addr.sin_port));  for (i = 0; i < FD_SETSIZE; i++) if (client[i] < 0) {     // 找client[]中没有使用的位置  client[i] = connfd;  // 保存accept返回的文件描述符到client[]里 break;  }if (i == FD_SETSIZE) {    // 达到select能监控的文件个数上限 1024  fputs("too many clients\n", stderr);  exit(1);  }FD_SET(connfd, &allset);  // 向监控文件描述符集合allset添加新的文件描述符connfdif (connfd > maxfd)  maxfd = connfd;       // select第一个参数需要if (i > maxi) {  maxi = i;             // 保证maxi存的总是client[]最后一个元素下标 }if (--nready == 0) {continue;  }}   for (i = 0; i <= maxi; i++) {                             // 检测哪个clients 有数据就绪 		  if ((sockfd = client[i]) < 0) {continue;  }if (FD_ISSET(sockfd, &rset)) {  if ((n = Read(sockfd, buf, sizeof(buf))) == 0) {  // 当client关闭链接时,服务器端也关闭对应链接 */  Close(sockfd);  FD_CLR(sockfd, &allset);                      // 解除select对此文件描述符的监控client[i] = -1;  } else if (n > 0) {  for (j = 0; j < n; j++)  buf[j] = toupper(buf[j]);  Write(sockfd, buf, n);  Write(STDOUT_FILENO, buf, n);  }	  if (--nready == 0) {break;   // 跳出for, 但还在while中}}  }  }  Close(listenfd);  return 0;  
}  

wrap.h

#ifndef WRAP_H 
#define WRAP_H #include  
#include  
#include  void perr_exit(const char *s); int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); 
void Bind(int fd, const struct sockaddr *sa, socklen_t salen); 
void Connect(int fd, const struct sockaddr *sa, socklen_t salen); 
void Listen(int fd, int backlog); int Socket(int family, int type, int protocol); 
void Close(int fd); ssize_t Readline(int fd,void *vptr,size_t maxlen);
ssize_t Read(int fd, void *ptr, size_t nbytes); 
ssize_t Write(int fd, const void *ptr, size_t nbytes); ssize_t Readn(int fd, void *vptr, size_t n); 
ssize_t Writen(int fd, const void *vptr, size_t n); #endif 

wrap.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "sys/socket.h"
#include "ctype.h"
#include "arpa/inet.h"
#include "wrap.h" 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((ECONNABORTED == errno) || (EINTR == errno)) goto again; else perr_exit("accept error"); } return n; 
} void Bind(int fd, const struct sockaddr *sa, socklen_t salen) 
{ if(bind(fd, sa, salen) < 0) perr_exit("bind error"); 
} void Connect(int fd, const struct sockaddr *sa, socklen_t salen) 
{ if(connect(fd, sa, salen) < 0) perr_exit("connect error"); 
} void Listen(int fd, int backlog) 
{ if(listen(fd, backlog) < 0) perr_exit("listen error"); 
} int Socket(int family, int type, int protocol) 
{ int n; 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(EINTR == errno) goto again; else 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(EINTR == errno) goto again; else return -1; } return n; 
} void Close(int fd) 
{ if(close(fd) == -1) perr_exit("close error");    
} ssize_t Readn(int fd, void *vptr, size_t nbytes) 
{ size_t nleft; size_t nread; char *ptr; ptr = vptr; nleft = nbytes; while(nleft > 0) { if((nread = read(fd, ptr, nleft)) < 0) { if(EINTR == errno)     nread = 0; else return -1; } else if(nread == 0) break; nleft -= nread; ptr += nread; } return (nbytes-nleft); 
} 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;}else if(read_cnt == 0)return 0;read_ptr = read_buf;}read_cnt--;*ptr = *read_ptr++;return 1;
}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;}else if(rc == 0){*ptr = 0;return n - 1;}else{return -1;}}*ptr = 0;return n;
}ssize_t Writen(int fd, const void *vptr, size_t nbytes) 
{ size_t nleft; size_t nwritten; const char *ptr; ptr = vptr; nleft = nbytes; while(nleft > 0) { if((nwritten = write(fd, ptr, nleft)) <= 0) {    if(nwritten < 0 && EINTR == errno) nwritten = 0; else return -1; } nleft -= nwritten; ptr += nwritten; } return nbytes; 
}

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了