本文最后更新于:2020年7月2日 晚上
* 趁着刚刚总结完select的内核源码。。。现在就来应用一下。。。→_→ *
深入理解select底层原理请戳传送门——IO复用——select内核源代码剖析
本次网络通信socket套接字是基于TCP协议的可靠传输,因为只开了一台虚拟机。。。所以服务器IP地址采用回环地址——127.0.0.1,服务器端口号port为6000
server
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAX_FD 10 //设定最多监听的文件描述符的数量为10
int fds[MAX_FD]; //存储多个文件描述符的数组
//初始化文件描述符数组
void fds_init()
{
int i = 0;
for(;i<MAX_FD;i++)
{
fds[i] = -1; //将所有的元素初始化为-1
}
}
//在文件描述符数组中寻找第一个值为-1的元素,即此元素存放新添加的文件描述符
void fds_add(int fd)
{
int i = 0;
for(;i<MAX_FD;i++)
{
if(fds[i] == -1)
{
fds[i] = fd;
break;
}
}
}
//在文件描述符数组中寻找指定值的元素,将其删除,即将该元素置为-1
void fds_del(int fd)
{
int i = 0;
for(;i<MAX_FD;i++)
{
if(fds[i] == fd)
{
fds[i] = -1;
break;
}
}
}
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
assert(res != -1);
listen(sockfd,5);
//这里只对读事件进行验证,所以只定义一个fd_set结构
fd_set fdset;
//初始化存放文件描述符的数组
fds_init();
fds_add(sockfd); //将socket也作为要监听的文件描述符
//服务器开始进入while循环
while(1)
{
//因为调用select后,有可能改变文件描述符在fdset位图中的标志,所以每一次开始监听之前都要重新轻灵标志
FD_ZERO(&fdset);
int maxfd = -1; //记录当前要监听的文件描述符的最大值
int i = 0;
for(; i < MAX_FD; ++i)
{
if(fds[i] != -1) //如果文件描述符数组中的元素不为-1,就将此元素设置到fds位图中
{
FD_SET(fds[i], &fdset);
if(fds[i] > maxfd)
{
maxfd = fds[i];
}
}
}
//设置浅睡眠时间为5毫秒整
struct timeval tv = {5,0};
//返回值为一共就绪事件的总和
int n = select(maxfd + 1, &fdset, NULL, NULL, &tv);
if(n == -1) //如果返回值为-1,表示出错
{
perror("select error");
continue;
}
else if(n == 0) //如果返回值为0,表示睡眠期间没有事件就绪,继续循环监听
{
printf("time out\n");
continue;
}
else
{
int i = 0;
//再一次遍历文件描述符数组,查看哪一个是就绪事件
for(; i < MAX_FD; ++i)
{
if(fds[i] == -1)
{
continue;
}
//测试此文件描述符元素在位图中对应的位是否被设置
if(FD_ISSET(fds[i], &fdset))
{
//如果就绪的是sockfd,表示有了新连接
if(sockfd == fds[i])
{
int len = sizeof(caddr);
int c = accept(sockfd, (struct sockaddr *)&caddr, &len);
if(c < 0)
{
continue;
}
fds_add(c);
}
else //否则就读取就绪事件的数据
{
char buff[128] = {0};
//如果返回值小于等于0,表示连接中断或出错
if(recv(fds[i], buff, 127, 0) <= 0)
{
//此时需要关闭该连接
close(fds[i]);
//并从文件描述符数组中删除此元素
fds_del(fds[i]);
printf("one client over\n");
continue;
}
printf("read:%s\n",buff);
//读取全部数据后,向客户端发送"ok"
send(fds[i], "ok", 2, 0);
}
}
}
}
}
return 0;
}
client
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
listen(sockfd,5);
while(1)
{
printf("input:\n");
//输入缓冲区
char buff[128] = {0};
fgets(buff,128,stdin);
//如果输入的数据为"end",表示要关闭客户端,此时跳出循环
if(strncmp(buff,"end",3 ) == 0)
{
break;
}
//否则就发送所输入的数据
send(sockfd,buff,strlen(buff),0);
//再将缓冲区清零
memset(buff,0,128);
//接收服务器端的回复
recv(sockfd,buff,127,0);
printf("buff = %s\n",buff);
}
//关闭该连接
close(sockfd);
return 0;
}
运行结果
* 其实这些代码是之前写的。。。一会就来总结poll机制。。。→_→ *
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!