IO多路复用
客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#define IP "192.168.0.105"
#define PORT 6666
int main(int argc, const char *argv[])
{
//创建套接字
int cfd = socket(AF_INET,SOCK_STREAM,0);
if(cfd < 0)
{
perror("cfd");
return -1;
}
printf("套接字文件描述符cfd=%d\n",cfd);
//启用端口快速重用
int optval =1;
if(setsockopt(cfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) < 0)
{
perror("setsockopt");
return -1;
}
printf("端口快速重用启用成功\n");
//定义通用地址结构变量填充所需数据
struct sockaddr_in cin;
cin.sin_family =AF_INET;
cin.sin_port =htons(PORT);
cin.sin_addr.s_addr=inet_addr(IP);
//开始向服务器发送链接数据
if(connect(cfd,(struct sockaddr*)&cin,sizeof(cin)) == -1)
{
perror("connect");
return -1;
}
//设置一个读集合(一个用来写入,一个用来执行)
fd_set readfds,tmpfds;
//将随机值清空
FD_ZERO(&readfds);
FD_ZERO(&tmpfds);
//添加需要的文件描述符(cfd,0)
FD_SET(0,&readfds);
FD_SET(cfd,&readfds);
//开始发送数据
char buf[128]="";
ssize_t res = 0;
int s_res = 0;
while(1)
{
//重新赋予集合初始值
tmpfds = readfds;
printf("请输入信息-----:");
fflush(stdout);
s_res = select(cfd+1,&tmpfds,NULL,NULL,NULL);
if(s_res < 0)
{
perror("select");
}else if( 0 == s_res)
{
printf("time out ....\n");
}
//发送数据程序体
if(FD_ISSET(0,&tmpfds))
{
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
if(send(cfd,buf,sizeof(buf),0) <0)
{
perror("send");
return -1;
}
printf("信息发送成功\n");
}
//接收数据结构体
if(FD_ISSET(cfd,&tmpfds))
{
res = recv(cfd,buf,sizeof(buf),0);
if(res < 0 )
{
perror("res");
return -1;
}
else if(0 == res)
{
printf("cfd=%d 服务器下线或者宕机\n",cfd);
break;
}
printf("\ncfd=%d 收到信息:%s\n",cfd,buf);
}
}
//关闭文件描述符
close(cfd);
return -1;
}
服务器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#define IP "192.168.0.199"
#define PORT 6666
int main(int argc, const char *argv[])
{
//创建TCP套接字文件描述符
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd <0 )
{
perror("socket");
return -1;
}
printf("套接字文件描述符sfd=%d\n",sfd);
//启用端口快速重用
/* SO_DEBUG 打开或关闭排错模式
SO_REUSEADDR 允许在bind()过程中本地地址可重复使用
SO_TYPE 返回socket形态。
SO_ERROR 返回socket已发生的错误原因
SO_DONTROUTE 送出的数据包不要利用路由设备来传输。
SO_BROADCAST 使用广播方式传送
SO_SNDBUF 设置送出的暂存区大小
SO_RCVBUF 设置接收的暂存区大小
SO_KEEPALIVE 定期确定连线是否已终止。
SO_OOBINLINE 当接收到OOB 数据时会马上送至标准输入设备
SO_LINGER 确保数据安全且可靠的传送出去。*/
int optval =1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval))<0)//成功返回0;失败返回-1
{
perror("setsockopt");
return -1;
}
printf("端口快速重用启用成功\n");
//定义通用地址结构变量
struct sockaddr_in sin;
//填充结构体
sin.sin_family =AF_INET;//IPV4
sin.sin_port =htons(PORT);//端口号(将主机字节序转到网络字节序然后赋值给通用地址结构体)
sin.sin_addr.s_addr=inet_addr(IP);//IP字符地址(将主机字节序转到网络字节序然后赋值给通用地址结构体)
//将IP和端口号绑定在套接字上
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
{
perror("bind");
return -1;
}
printf("绑定成功\n");
//将填入目标套接字设置为监听状态
if(listen(sfd,128) == -1)
{
perror("listen");
return -1;
}
printf("监听成功\n");
//存储链接成功的客户端地址信息
struct sockaddr_in cin;
socklen_t size_cin = sizeof(cin);
//设置一个读集合、
fd_set readfds,tmpfds;
//将readfds 及tmpfds中的随机值清空
FD_ZERO(&readfds);
FD_ZERO(&tmpfds);
//添加需要的文件描述符
FD_SET(0,&readfds);
FD_SET(sfd,&readfds);
//最大的文件描述符
int maxfd = sfd;
char buf[128]="";
int newfd =-1;
//接收recv的返回值
int s_res = 0;
ssize_t res =0;
//存储程序中被覆盖的地址信息
struct sockaddr_in save_cin[1024-4];
while(1)
{
//每次循环开头都将集合中的值重新赋值
tmpfds = readfds;
//定义多路复用函数
s_res = select(maxfd+1,&tmpfds,NULL,NULL,NULL);
if(s_res < 0)
{
perror("select");
return -1;
}
else if(0 == s_res)
{
printf("time out ...\n");
return -1;
}
//在所有的文件符遍历符合符合要求的文件描述符
for(int i = 0;i<=maxfd;i++)
{
if( !FD_ISSET(i,&tmpfds))//如果不在集合中,则直接继续向后遍历
continue;
//能运行到这说明i存在在集合中
if( 0 == i)
{
int sndfd = -1;
printf("键盘触发事件----");
res = scanf("%d %s",&sndfd,buf);
while(getchar()!=10 );
if(res != 2)
{
perror("请输入正确的格式\n");
continue;
}
if(sndfd <= sfd || sndfd >=1024 || !FD_ISSET(sndfd,&readfds))
{
fprintf(stderr,"sndfd=%d 文件描述符错误\n",sndfd);
continue;
}
if(send(sndfd,buf,sizeof(buf),0) < 0)
{
perror("send");
return -1;
}
}
else if(sfd == i)
{
printf("客户端触发事件----\n");
// fflush(stdout);
newfd = accept(sfd,(struct sockaddr*)&cin,&size_cin);
if(-1 ==newfd )
{
perror("accept");
return -1;
}
printf("[地址:%s 端口:%d] newfd:%d 客户链接服务器成功\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);
save_cin[newfd-4]=cin;
//将newfd添加到读集合中
FD_SET(newfd,&readfds);
//更新maxfd
maxfd = maxfd>newfd ? maxfd:newfd;
}
else
{
bzero(buf,sizeof(buf));
res = recvfrom(i,buf,sizeof(buf),0,(struct sockaddr*)&cin,&size_cin);
if(res < 0)
{
perror("recv");
return -1;
}
else if(0 == res)
{
printf("[地址:%s 端口:%d] newfd:%d 客户下线\n",inet_ntoa(save_cin[i-4].sin_addr),ntohs(save_cin[i-4].sin_port),newfd);
//关闭文件描述符
close(i);
//从集合中剔除该文件描述符
FD_CLR(i,&readfds);
//更新maxfd
//从目前最大的文描述符往最小的判断
int j =0;
for(j=maxfd;j >=0;j--)
{
if(FD_ISSET(j,&readfds))
{
maxfd =j;
break;
}
}
if(j < 0)//若集合中没有要监测的文件描述符了,则让maxfd = -1
maxfd = -1;
continue;
}
printf("[地址:%s 端口:%d] newfd:%d send:%s \n",inet_ntoa(save_cin[i-4].sin_addr),ntohs(save_cin[i-4].sin_port),newfd,buf);
//发送信息
strcat(buf,"--- ");//改变信息
if(send(i,buf,sizeof(buf),0)<0)//成功发送返回字节数,失败返回-1
{
perror("send");
return -1;
}
printf("回执信息发送成功\n");
}
}
}
/*
while(1)
{
//读取执行体
//bzero(temp,sizeof(temp));
res = recv(newfd,temp,sizeof(temp),0);//以非阻塞形式
//发送执行体
}
*/
//关闭打开的文件描述符
close(sfd);
close(newfd);
return 0;
}