最近学习网络编程相关,学习资源在b站搜索【linux网络编程】。本文章记录相关学习心得。
思路
利用select()
函数监听信息,accept()
函数非阻塞的建立连接。
相关API
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
readfds: 监控有读数据到达文件描述符集合,传入传出参数
writefds: 监控写数据到达文件描述符集合,传入传出参数
exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout: 定时阻塞监控时间,3种情况
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd清0
int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
流程
- 创建一个
socket
命名为listen_fd
,这个listen_fd
负责监听是否有客户端想要建立连接。 bind()
函数将listen_fd
和ser_addr
绑定listen()
函数设置允许同时建立连接的上限,默认为128- 定义
cli
数组,用于记录所有的已经建立连接的客户端的fd,并初始化为-1
,表示没有存储fd - 将
listen_fd
添加到allfds
中,其中allfds
是一个位图,记录listen_fd
和所有已经建立连接的客户端的fd - 调用
select()
函数,返回值为满足条件的数量,readfds
为传入传出参数,传出满足条件的可读集合,即相应的文件描述符有写入的集合。 - 如果
listen_fd
在readfds
中,则代表有建立连接的请求- 利用
accep()
函数和有建立连接的请求的客户端记为connect_fd
建立连接 - 更新
cli
数组 - 添加
connect_fd
到allset
- 利用
- 检测
cli
数组中有没有数据就绪,如果有就读写数据
代码
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <ctype.h>
#include <arpa/inet.h> // sockaddr_in
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#define PORT 6666
int main() {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
int maxfd = listen_fd;
struct sockaddr_in ser_addr;
bzero(&ser_addr, sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(PORT);
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY); //
int opt = 1; //端口复用,解决2msl的等待时间问题
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bind(listen_fd, (struct sockaddr*)&ser_addr, sizeof(ser_addr));
listen(listen_fd, 128);
fd_set allfds;
FD_ZERO(&allfds);
FD_SET(listen_fd, &allfds);
int maxi = -1;
int cli[FD_SETSIZE];
for (int i = 0; i < FD_SETSIZE; i++) { //初始化为-1
cli[i] = -1;
}
struct sockaddr_in cli_addr;
socklen_t cli_addr_len = sizeof(cli_addr);
bzero(&cli_addr, sizeof(cli_addr));
char cli_addr_str[BUFSIZ];
fd_set readfds;
int nready;
int i;
int n;
char buf[BUFSIZ];
int connect_fd;
while(1) {
readfds = allfds; // allfds用于保存readfds集合,select函数中readfds会变成满足条件的集合
nready = select(maxfd + 1, &readfds, NULL, NULL, NULL);
if (FD_ISSET(listen_fd, &readfds)) { // 如果有建立连接的请求
connect_fd = accept(listen_fd, (struct sockaddr*)&cli_addr, &cli_addr_len); //与该客户端建立连接
printf("client ip : %s, port : %d\n", inet_ntop(AF_INET, &cli_addr.sin_addr.s_addr, cli_addr_str, sizeof(cli_addr_str)),ntohs(cli_addr.sin_port));
for (i = 0; i < FD_SETSIZE; i++) {
if (cli[i] == -1) {
cli[i] = connect_fd; //更新cli数组
FD_SET(connect_fd, &allfds); //将该客户端的fd添加到allfds集合
if (connect_fd > maxfd) maxfd = connect_fd;
if (i > maxi) maxi = i;
break;
}
}
if (i == FD_SETSIZE) { //超出文件描述符上限
printf("too many clients.\n");
exit(1);
}
if (--nready <= 0) continue; // return while
}
for (i = 0; i <= maxi; i++) { //有数据就绪
int socket_fd = cli[i]; //遍历所有的fd,查看是否满足条件(有数据写入),即在readfds集合中
if (FD_ISSET(socket_fd, &readfds)) {
n = read(socket_fd, buf, sizeof(buf));
if (n == 0) { //代表对端关闭
close(socket_fd);
FD_CLR(socket_fd, &allfds);
cli[i] = -1;
} else if (n > 0) { //数据处理
for (i = 0; i < n; i++) {
buf[i] = toupper(buf[i]);
}
write(socket_fd, buf, n);
write(STDOUT_FILENO, buf, n); //输出到屏幕,主观大小为n而不是BUFSIZ
}
if (--nready <= 0) break;
}
}
}
close(listen_fd); // 关闭服务器端
return 0;
}