在之前的博客中,有用到了IO多路复用以及TCP网络编程,现在将两者结合一下,就相当于用select、poll监听,以下是IO多路复用以及网络编程相关知识点:
Linux驱动开发 IO模型:多路复用(select/poll/epoll)_凛冬将至__的博客-CSDN博客
Linux应用开发:网络编程(TCP、UDP)_凛冬将至__的博客-CSDN博客
1、select
#define PORT 6666
#define IP "192.168.1.12"
int updateMaxfd(int maxfd, fd_set readfds);
int main(int argc, const char *argv[])
{
//创建流式套接字 socket
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//允许端口快速重用
int reuse = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
//填充地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //IPV4地址族
sin.sin_port = htons(PORT); //端口号的网络字节序,1024~49151
sin.sin_addr.s_addr = inet_addr(IP); //IP地址的网络字节序,终端输入ifconfig
//绑定服务器的地址信息结构体
bind(sfd, (struct sockaddr*)&sin, sizeof(sin));
//将套接字设置为被动监听状态
if(listen(sfd, 10) < 0)
//创建读集合
fd_set readfds, tempfds;
//清空集合
FD_ZERO(&readfds);
FD_ZERO(&tempfds);
//将0号,sfd文件描述符添加到读集合中
FD_SET(0, &readfds);
FD_SET(sfd, &readfds);
//获取最大文件描述符
int maxfd = sfd;
int s_res = -1;
char buf[128] = "";
ssize_t res = -1;
int i = 0;
int newfd = -1;
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
struct sockaddr_in cli[1024-4];
while(1)
{
tempfds = readfds;
s_res = select(maxfd+1, &tempfds, NULL, NULL, NULL);
//能运行到当前位置,说明集合中有文件描述符准备就绪
for(i=0; i<=maxfd; i++)
{
if(FD_ISSET(i, &tempfds) == 0) {
continue;
}
//能运行到当前位置,则表明i所代表的文件描述符在tempfds中
if(i == 0) {
//键盘输入事件
//文件描述符 + 要回复的内容
int sndfd = -1;
bzero(buf, sizeof(buf));
s_res = scanf("%d %s", &sndfd, buf);
while(getchar()!=10);
if(s_res != 2) {
fprintf(stderr, "请输入正确格式:文件描述符 内容\n");
continue;
}
//判断readfds中有没有sndfd,即判断有没有对应客户端
if(!FD_ISSET(sndfd, &readfds)) {
break;
}
if(send(sndfd, buf, sizeof(buf), 0) < 0) {
//关闭文件描述符
close(sndfd);
//将文件描述符从集合中剔除
FD_CLR(sndfd, &readfds);
//更新maxfd
maxfd = updateMaxfd(maxfd, readfds);
break;
}
printf("buf:%s 发送成功\n", buf);
} else if(i == sfd) {
//客户端连接事件
//获取新的文件描述符,当有客户端连接成功,解除阻塞;
newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
//将newfd添加到集合中
FD_SET(newfd, &readfds);
//将地址信息结构体存储在cli数组中,
//数组下标 = newfd-4; (前四个文件描述符不存:0,1,2,sfd)
cli[newfd-4] = cin;
//更新maxfd
maxfd = newfd>maxfd?newfd:maxfd;
} else {
bzero(buf, sizeof(buf));
//读取数据
res = recv(i, buf, sizeof(buf), 0);
}
}
}
//关闭文件描述符
close(sfd);
return 0;
}
int updateMaxfd(int maxfd, fd_set readfds)
{
//从大到小判断文件描述符是否在集合中
int i = maxfd;
for( ; i>0; i--) {
if(FD_ISSET(i, &readfds) == 1) {
return i;
}
}
return 0;
}
2、poll
int main(int argc, const char *argv[])
{
//创建流式套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//填充服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2])); //外部传参输入端口号,需要转换成整型
sin.sin_addr.s_addr = inet_addr(argv[1]); //外部传参输入IP
//连接服务器
connect(sfd, (struct sockaddr*)&sin, sizeof(sin));
//创建文件描述符集合
struct pollfd fds[2];
fds[0].fd = 0; //检测0号文件描述符
fds[0].events = POLLIN; //检测是否有数据可读
fds[1].fd = sfd; //检测sfd号文件描述符
fds[1].events = POLLIN; //检测是否有数据可读
char buf[128] = "";
ssize_t res = -1;
int p_res = -1;
while(1)
{
//阻塞,等待文件描述符有事件产生
p_res = poll(fds, 2, -1);
//判断描述符集合中实际产生的事件是否是指定检测的事件
//POLLIN是revents中的标志位,所以只要判断revents中的指定标志是0还是1即可
if(fds[0].revents & POLLIN) {
//触发键盘输入事件
bzero(buf, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = 0; //将结尾的'\n'修改成'\0'
send(sfd, buf, sizeof(buf), 0);
}
if(fds[1].revents & POLLIN) {
bzero(buf, sizeof(buf));
res = recv(sfd, buf, sizeof(buf), 0);
}
}
//关闭文件描述符
close(sfd);
return 0;
}