1、select的TCP服务器
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#define PORT 8888
#define IP "192.168.1.5"
int deal_keyboardEvent(fd_set readfs,struct sockaddr_in pSaveCin[]);
int deal_CliConnectEvent(int sfd,struct sockaddr_in* saveCin,fd_set* preadfds,int* pmaxfd);
int deal_CliTalkEvent(int i,struct sockaddr_in saveCin[],fd_set* preadfds, int* pmaxfd);
int main(int argc, const char *argv[])
{
//创建流式套接字文件
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd<0)
{
fprintf(stderr,"line:%d",__LINE__);
perror("scoket");
return -1;
}
printf("创建流式套接字成功 sfd=%d __%d__\n",sfd,__LINE__);
//允许端口号被快速重复使用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
fprintf(stderr, "line:%d ", __LINE__);
perror("setsockopt");
return -1;
}
//填充服务器的地址信息结构体,真实的地址信息结构体根据地址族制定
//AF_INET--->man 7 IP
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET;
sin.sin_port = htons(PORT);//端口号的网络字节序 1024~49151
sin.sin_addr.s_addr=inet_addr(IP);//服务器要运行的主机的IP的网络地址
//ifconfig 查找本机的IP地址
//绑定服务器自身的地址信息--->>必须绑定
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
fprintf(stderr,"line:%d",__LINE__);
perror("bind");
return -1;
}
printf("bind success __%d__\n",__LINE__);
//将套接字设置为被动监听状态
if(listen(sfd,128)<0)
{
fprintf(stderr,"line:%d ",__LINE__);
perror("listen");
return -1;
}
printf("listen success __%d__\n",__LINE__);
//创建一个读集合
//由于读集合中的成员是一个整形数组,若不初始化集合中为随机值
//由此时会导致readfds中随机到一个有效的文件描述符编号.但是该文件描述符我不需要监测
//所以readfds必须清空
fd_set readfds,tempfds;
FD_ZERO(&readfds);
//要监测的文件描述符加入到读集合中
FD_SET(0,&readfds);
FD_SET(sfd,&readfds);
int maxfd = sfd;
int s_res;
char buf[128];
ssize_t res;
int newfd=-1;
struct sockaddr_in saveCin[1024-3]; //将新连接成功的客户端信息存储到newfd-3的下标位置
while(1)
{
tempfds = readfds;
//监测集合中的文件描述符是否准备就绪
s_res = select(maxfd+1,&tempfds,NULL,NULL,NULL);
if(s_res<0)
{
fprintf(stderr,"line:%d",__LINE__);
perror("select");
return -1;
}
else if (0== s_res)
{
printf("time out ....\n");
break;
}
printf("__%d__\n",__LINE__);
//能运行到当前位置,则代表集合中有文件描述符准备就绪
//判断是那个文件描述符准备就绪,走对应的处理函数即可
//当select函数解除阻塞后,集合中会只剩下产生事件的文件描述符,例如:
//0准备就绪,则集合中会只剩下0
//sfd准备就绪,则集合中只会剩下sfd
//0和sfd准备就绪,则集合中只剩下0和sfd
//所以,只需要判断集合中剩下哪个文件描述符,就可以知道哪个文件描述符准备就绪
//若文件描述符对应的空间中一直存在数据,则该文件描述符一直处于被触发状态
for(int i=0;i<=maxfd;i++)
{
if(FD_ISSET(i,&tempfds)==0)
continue;
//能运行到当前位置,则说明i代表的文件描述符是在集合中的
//接下来只需要判断i代表的文件描述符是什么,触发的是什么事件即可
if(0==i){
printf("触发键盘输入事件\n");
deal_keyboardEvent(readfds,saveCin);
}
else if(i==sfd)
{
printf("触发客户端了连接事件\n");
deal_CliConnectEvent(sfd,saveCin,&readfds,&maxfd);
}
else
{
printf("触发客户端交互事件\n");
deal_CliTalkEvent(i,saveCin,&readfds,&maxfd);
}
}
}
//关闭文件描述符
close(sfd);
return 0;
}
/*
* function: 处理客户端的交互事件
* @param [ in]
* @param [out]
* @return
*/
int deal_CliTalkEvent(int i,struct sockaddr_in saveCin[],fd_set *preadfds, int *pmaxfd)
{
char buf[128]="";
bzero(buf,sizeof(buf));
//接收
ssize_t res=recv(i,buf,sizeof(buf),0);
if(res<0)
{
fprintf(stderr,"line:%d",__LINE__);
perror("recv");
return -1;
}
else if(0==res)
{
printf("[%s:%d]客服端下线 newfd=%d__%d__\n",\
inet_ntoa(saveCin[i-3].sin_addr),ntohs(saveCin[i-3].sin_port),i,__LINE__);
close(i);
FD_CLR(i,preadfds); //将newfd从readfds集合中剔除
//从最大值开始遍历,判断文件描述符是否在集合中
for(;*pmaxfd>0;*pmaxfd--){
if(FD_ISSET(*pmaxfd,preadfds)){
break;
}
}
return -1;
}
printf("[%s:%d] newfd=%d %s__%d__\n",\
inet_ntoa(saveCin[i-3].sin_addr),ntohs(saveCin[i-3].sin_port),i,buf,__LINE__);
//发送
strcat(buf,"*_*");
if(send(i,buf,sizeof(buf),0)<0)
{
fprintf(stderr,"line:%d ",__LINE__);
perror("send");
return -1;
}
printf("send success __%d__\n",__LINE__);
return 0;
}
/*
* function: 处理客户端连接事件
* @param [ in]
* @param [out]
* @return
*/
int deal_CliConnectEvent(int sfd,struct sockaddr_in* saveCin,fd_set* preadfds,int* pmaxfd)
{
struct sockaddr_in cin; //存储客服端信息的
socklen_t addrlen = sizeof(cin);
int newfd = accept(sfd,(struct sockaddr*)&cin,&addrlen);
if(newfd<0)
{
fprintf(stderr,"line:%d",__LINE__);
perror("select");
return -1;
}
printf("[%s:%d]客服端连接成功 newfd=%d __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd,__LINE__);
saveCin[newfd-3]=cin;
FD_SET(newfd,preadfds); //将生成的newfd添加到集合中
*pmaxfd = newfd>*pmaxfd ? newfd:*pmaxfd; //更新maxfd
return 0;
}
/*
* function: 键盘输入事件
* @param [ in]
* @param [out]
* @return
*/
int deal_keyboardEvent(fd_set readfs,struct sockaddr_in pSaveCin[])
{
int sndfd;
char buf[128];
int res = scanf("%d %s",&sndfd ,buf);
while(getchar()!='\n');
if(res!=2)
{
printf("请输入正确格式:fd string \n");
return -1;
}
if(sndfd<0 || sndfd>1203 || FD_ISSET(sndfd,&readfs)==0) //判断终端输入的文件描述符是否合法
{
printf("请输入正确的文件描述符\n");
return -1;
}
if(send(sndfd,buf,sizeof(buf),0)<0)
{
perror("send");
return -1;
}
printf("发送给[%s:%d] fd=%d客服端成功 __%d__\n",\
inet_ntoa(pSaveCin[sndfd-3].sin_addr),ntohs(pSaveCin[sndfd-3].sin_port),sndfd,__LINE__);
return 0;
}
2、poll
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
#define PORT 8888
#define IP "192.168.1.5"
int main(int argc, const char *argv[])
{
//创建流式套接字
int cfd = socket(AF_INET, SOCK_STREAM, 0);
if(cfd < 0)
{
fprintf(stderr, "line:%d ", __LINE__);
perror("socket");
return -1;
}
printf("create socket success, cfd=%d __%d__\n", cfd, __LINE__);
//绑定客户端自身的地址信息结构体--->非必须绑定
//若不绑定,则操作系统会自动给客户端绑定客户端所运行主机的IP及随机端口:49152~65535
//填充服务器的地址信息,给connect函数连接服务器的时候使用
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET;
sin.sin_port = htons(PORT); //服务器绑定的端口号
sin.sin_addr.s_addr = inet_addr(IP); //服务器绑定的IP地址
//0.0.0.0
//连接指定的服务器
if(connect(cfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
fprintf(stderr, "line:%d ", __LINE__);
perror("connect");
return -1;
}
//创建集合
struct pollfd fds[2];
fds[0].fd = 0; //指定监测0号文件描述符
fds[0].events = POLLIN; //指定监测读事件
fds[1].fd = cfd;
fds[1].events = POLLIN;
char buf[128] = "";
ssize_t res;
int p_res;
while(1)
{
//poll,让内核监测集合中是否有文件描述符准备就绪
p_res = poll(fds, 2, -1);
if(p_res < 0)
{
fprintf(stderr, "line:%d ", __LINE__);
perror("poll");
return -1;
}
else if(0 == p_res)
{
printf("time out....\n");
break;
}
//能运行到当前位置,则代表集合中有文件描述符产生事件了
//需要遍历判断集合中文件描述符实际产生的事件中是否有POLLIN事件。
//需要将revents & POLLIN判断结果是否为0
if((fds[0].revents & POLLIN) != 0)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = '\0';
//发送
if(send(cfd, buf, sizeof(buf), 0) < 0)
{
fprintf(stderr, "line:%d ", __LINE__);
perror("send");
return -1;
}
printf("send success __%d__\n", __LINE__);
}
if(fds[1].revents & POLLIN)
{
//接收
bzero(buf, sizeof(buf));
res = recv(cfd, buf, sizeof(buf), 0);
if(res < 0)
{
fprintf(stderr, "line:%d ", __LINE__);
perror("recv");
return -1;
}
else if(0 == res)
{
printf("服务器下线 __%d__\n", __LINE__);
break;
}
printf(":%s __%d__\n", buf, __LINE__);
}
}
//关闭套接字
close(cfd);
return 0;
}