面试题
1.怎么修改文件描述符的标志位
答:fcntl函数,先将旧的文件标志位获取,后加noblock属性
2.udp本地通信需要注意哪些方面
答如果客户端不绑定,系统不会自动绑定一个套接字文件,但是要向某个客户端发消息则需要绑定
3.基于udp的聊天室如何实现数据群发
答:当客户端登入时,服务器创建链表,记录每个客户端的地址信息结构体,有消息需要转发时,遍历链表,将信息逐一发送
4:如何实现并发服务器,并发服务器的实现方式以及有什么异同?
答:多线程
多进程,这两个要求运行在有操作系统的内核上
IO多路复用,select和poll,select 解除阻塞后,会删除文件描述符,poll不会
poll需要定义数组大小
5.tcp连接的三次握:手
1.客户端向服务器发送连接请求
2.服务器接收后,做出应答,对客户端发出连接请求
3.客户端收到后做出应答
6.UDP网络通信模型
poll服务器
#include <myhead.h>
#define SER_PORT 8888
#define SER_IP "192.168.125.234"
//服务器
int main(int argc, const char *argv[])
{
//1.创建用于链接的套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1)
{
perror("socket error");
return -1;
}
printf("sfd=%d\n",sfd);
//端口重用
int reuse =1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))==-1)
{
perror("setsocketopt error");
return -1;
}
printf("端口快速重用成功\n");
//2.给当前套接字绑定ip地址和端口号
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr=inet_addr(SER_IP);
//2.1填充要绑定的地址信息结构体
//2.绑定
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))==-1)
{
perror("bind error");
return -1;
}
printf("bind success%s %s %d\n",__FILE__,__func__,__LINE__);
//3.将套接字设置为监听状态
if(listen(sfd,128)==-1)
{
perror("listen error");
return -1;
}
printf("listen success%s %s %d\n",__FILE__,__func__,__LINE__);
//4.阻塞等待客户端请求
//4.1定义容器接受客户端地址信息
struct sockaddr_in cin;
socklen_t socklen = sizeof(cin);
int newfd =-1;
//11准备一个文件描述符容器
struct pollfd[2];
//22.填充文件描述符
pfd[0].fd=0;
pfd[0].events=POLLIN;
pfd[1].fd=sfd;
pfd[1].events=POLLIN;
//循环
while(1)
{
//使用poll对容器中的文件描述符进行检测
int res = poll(pfd,2,-1)
if(res==-1)
{
perror("poll error");
}else if(res==0)
{
printf("timeout\n");
return -1;
}
//判断是否是文件描述符触发事件
//判断sfd事件
if(pfd[0].revents==POLLIN)
{
newfd=accept(sfd,(struct sockaddr*)&cin,&socklen);
if(newfd==-1)
{
perror("accept error");
return -1;
}
printf("[%s:%d]发来链接请求%s %s %d\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),__FILE__,__func__,__LINE__);
}else
//判断0号文件描述符是否在集合中
if(pfd[1].revents==POLLIN)
{
char wbuf[128]="";
scanf("%s",wbuf);
printf("触发了终端输入事件\n");
if(strcmp(wbuf,"quit")==0)
{
goto END;
}
}else
{
//跟客户端进行消息通信
char buf[128]="";
//将数组清空
bzero(buf,sizeof(buf));
//读取客户端发来的消息
int res = recv(cli,buf,sizeof(buf),0);
if(res==0)
{
printf("客户端已经下线\n");
//关闭当前通信套接字
close(cli);
continue;
}
printf("[%s:%d]:%s\n",inet_ntoa(cin_arr[cli].sin_addr),ntohs(cin_arr[cli].sin_port),buf);
strcat(buf,"*-*");
send(cli,buf,sizeof(buf),0);
printf("发送成功\n");
}
}
}
//是客户端链接
//是0号文件描述符
//是跟客户端通信
//关闭套接字
END:
close(sfd);
return 0;
}
select客户端
#include <myhead.h>
#define SER_PORT 8888
#define SER_IP "192.168.125.234"
#define CLI_PORT 6666
#define CLI_IP "192.168.125.234"
int main(int argc, const char *argv[])
{
//1创建通信套接字
int cfd =-1;
cfd = socket(AF_INET,SOCK_STREAM,0);
if(cfd==-1)
{
perror("socket error");
return -1;
}
printf("cfd=%d\n",cfd);
//地址信息结构体
struct sockaddr_in cin;
cin.sin_family=AF_INET;
cin.sin_port=htons(CLI_PORT);
cin.sin_addr.s_addr=inet_addr(CLI_IP);
//2.绑定
if(bind(cfd,(struct sockaddr*)&cin,sizeof(cin))==-1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
3.链接服务器
//3.1填充服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_port=htons(SER_PORT);
sin.sin_addr.s_addr=inet_addr(SER_IP);
//3.2链接
if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin))==-1)
{
perror("connect error");
return -1;
}
printf("connect success\n");
//11.准备一个文件描述容器
fd_set readfds,tempfds;
FD_ZERO(&readfds);
FD_SET(0,&readfds);
FD_SET(sfd,&readfds);
int maxfd=sfd;
struct sockaddr_in cin_arr[1024];
while(1)
{
tempfds=readfds;
//阻塞监测是否有事件产生
int res =select(maxfd+!,&tempfds,NULL,NULL,NULL);
if(res ==-1)
{
perror("poll error");
return -1;
}else if(res==0)
{
printf("time out\n");
return -1;
}
//已经有事件发生
//0号文件描述符产生事件
for(int cli=0;cli<=maxfd;cli++)
{
if(cli==0)
{
//清空数组
bzero(buf,sizeof(buf));
//终端输入数据
fgets(buf,sizeof(buf),stdin);
//将换行改为\0
buf[strlen(buf)-1]=0;
//发送服务器
send(cfd,buf,sizeof(buf),0);
printf("发送成功\n");
if(strcmp(buf,"quit")==0)
{
break;
}
}
//判断cfd产生事件
if(cli==cfd)
{
recv(cfd,buf,sizeof(buf),0);
printf("[%s:%d]:%s\n",SER_IP,SER_PORT,buf);
}
}
}
//5.关闭套接字
close(cli);
close(cfd);
return 0;
}