思维导图
1.select实现客户端
#include
#define SER_PORT 8888 //服务器端口号
#define SER_IP "192.168.122.70" //服务器客户端IP
#define CLI_PORT 6666
#define CLI_IP "192.168.122.70"
int main(int argc, const char *argv[])
{
//1.创建用于通信的套接字文件描述符
int cfd=socket(AF_INET,SOCK_STREAM,0);
if(cfd==-1)
{
perror("socket error");
return -1;
}
printf("cfd=%d\n",cfd);
//2.可不绑定
//2.1 填充地址信息结构体
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.2 绑定
if(bind(cfd,(struct sockaddr*)&cin,sizeof(cin))==-1)
{
perror("bind error");
return -1;
}
puts("bind success");
fd_set writefds;//11.准备1个文件描述符容器
FD_ZERO(&writefds);//22.清空容器
FD_SET(0,&writefds);//33.将要检测的文件描述符放入集合
FD_SET(cfd,&writefds);
//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;
}
while(1)
{
int res=select(cfd+1,&writefds,NULL,NULL,NULL);
if(res==-1)
{
perror("select error");
return -1;
}
else if(res==0)
{
puts("timeout");
return -1;
}
//判断0号文件描述符是否存在容器中
if(FD_ISSET(0,&writefds))
{
//终端输入
char wbuf[128]="";
scanf("%s",wbuf);
puts("触发了终端输入事件……");
//将信息发给服务器端
sendto(cfd,wbuf,sizeof(wbuf),0,(struct sockaddr*)&cin,sizeof(cin));
if(strcmp(wbuf,"quit")==0)
goto END;
}
else
{
//4.收发数据
char buf[128]="";
//清空数组
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin);//从终端获取数据
buf[strlen(buf)-1]='\0';
send(cfd,buf,sizeof(buf),0);//发送给服务器
if(strcmp(buf,"quit")==0)
break;
printf("发送成功\n");
recv(cfd,buf,sizeof(buf),0);//接收服务器发来的信息
printf("[%s:%d]:%s\n",SER_IP,SER_PORT,buf);
}
}
END:
//5.关闭客户端
close(cfd);
return 0;
}
2.poll实现服务器端
#include
#define SER_PORT 8888 //服务器端口号
#define SER_IP "192.168.122.70" //服务器客户端IP
int main(int argc, const char *argv[])
{
//1.创建套接字
int sfd=socket(AF_INET,SOCK_STREAM,0);
//参数1:通讯域,表明使用的是IPv4通信
//参数2:表示使用TCP通信
//参数3:协议,0表示之前已经指定的协议 IPPROTO_TCP
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("reuse error");
return -1;
}
puts("服务器快速重启成功");
//2.绑定IP地址和端口号
//2.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);//IP地址
//2.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);//用于接收地址信息的大小
//4.2阻塞形式接收客户端连接请求
int 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__);
//11.定义一个等待文件描述符结构体数组
struct pollfd pfd[2];
//22.填充要等待的文件描述符及事件
pfd[0].fd=0;//将0号文件描述符放入检测集合中
pfd[0].events=POLLIN;//表示检测该文件描述符的读事件
pfd[1].fd=sfd;//将sfd文件描述符放入检测集合中
pfd[1].events=POLLIN;//表示检测该文件描述符的读事件
//5.跟客户端进行通信
char buf[128]="";
while(1)
{
int res=poll(pfd,2,-1);//-1表示一直阻塞
if(res==-1)
{
perror("poll error");
return -1;
}
else if(res==0)
{
puts("timeout");
return -1;
}
//判断是0号文件描述符产生的事件
if(pfd[0].revents==POLLIN)
{
bzero(buf,sizeof(buf));//清空数组
int res=recv(newfd,buf,sizeof(buf),0);//读取客户端发来的信息
if(res==0)
{
puts("客户端已下线");
goto END;
}
printf("[%s:%d]:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),buf);
//给客户端发送信息
strcat(buf,"*_*");
send(newfd,buf,sizeof(buf),0);
puts("发送成功");
}
}
END:
//6.关闭服务器
close(sfd);
close(newfd);
return 0;
}
模拟面试:
1.怎么修改文件描述符的标志位?
答:fcntl函数,先将旧的文件标识位获取下来,然后在此基础上加上O_NONBLOCK 属性,然后再设置回去。
2.UDP本地通信需要注意哪些方面?
答:对于客户端而言,如果不绑定,则系统不会自动绑定一个套接字文件,服务器端接收该客户端的信息没有问题,如果服务器端想要向该客户端发送消息,则要求客户端必须绑定IP地址信息结构体。
3.基于UDP的网络聊天室如何实现群发?
答:当客户端登录服务器时,会给服务器发送一个登录信息,服务器端收到该登录信息后记录该客户端的地址信息到创建的链表中,当客户端给服务器端发送信息后,服务器端将遍历整个链表,然后将该信息逐个发送给所有其他在线用户。
4.如何实现并发服务器,并发服务器的属性方式及有什么异同?
5.TCP连接时的三次握手机制
6.UDP网络通信模型
答:服务器端:socket()--->bind()--->sendto()/recvfrom()--->close()
客户端:socket()--->(bind())--->sendto()/recvfrom()--->close