昨天对于那个socket程序进行了小小的修改,使它可以在2个主机之间互相通话,一个很简单的聊天方式。(利用了select)
—————————————————————————————————————————————————————————————————————————————
先上图看看效果吧:
—————————————————————————————————————————————————————————————————————————————
先说一下我的思路:(只是2台主机通信,这个服务器我设置了只能和一台主机通信,与多台有待研究)
服务器端:当accept到一个客户端后获得了客户端的socket fd在进入while(1)循环,一直用select监听着我的文件描述符,然后检测到底是我输入信息还是接受信息,再去执行具体函数,最后输入quit退出,或者对方输入quit退出。
客户端:跟客户端一样,while(1)循环在connect后面
我先说一下 select这个函数:
使用select函数的过程一般是:
先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,
接着调用函数select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。
====================================================================================================================================
理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。
(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
(3)若再加入fd=2,fd=1,则set变为0001,0011
(4)执行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。
====================================================================================================================================
我代码中的的运用:
FD_ZERO(&rfds);
FD_SET(0,&rfds);
FD_SET(connfd,&rfds);
ret=select(connfd+1,&rfds,NULL,NULL,NULL);
首先清空rfds,然后将0和connfd加进去,0是stdin标准输入,当我们发送信息的时候就会输入文字在标准输入上(屏幕),然后这个文件描述符里面就有可读内容了select就能监听到,然后调用send函数;connfd就是我们客户端的fd,当它发送信息的时候我们也可以监听到,然后调用recv函数。
这里我想对超时时间timeout说一下,就是select最后一个参数,你可以设置一个超时参数,比如6秒,那么select就会帮你监听这2个文件6秒钟,当有文件可读的时候返回一个大于0的数时候表示有文件可读,再用FD_ISSET这个宏去判断到底是哪些文件可读,在这个程序中就是--到底是向对方发送信息,还是接受对方信息。如果超过6秒,还没有信息,那么返回0,这个时候你可以选择继续往下执行,也可以退出,反正依靠自己功能,不过在这个程序中,我就是继续往下执行。
(我这里设置的是NULL,就是阻塞式的,直到有文件可读,才往下执行。因为程序比较简单,所以这里并没有体现出阻塞和非阻塞的太多区别,设置成0也可以)
if(ret>=0)
{
if(FD_ISSET(connfd,&rfds))//当对方有消息的时候往下执行
{
bzero(buff,MAXLINE);
n = recv(connfd, buff, MAXLINE, 0);
buff[n] = '\0';
if(n>0)
{
printf("recv msg from client: %s\n", buff);
}
else if(n==0)//这里是因为我在客户端那边的操作是,当输入quit的时候就直接退出关闭了套接字,没有执行send函数,所以recv收到的字节数为0
{
printf("=================Chat end===============\n");
break;
}
}
if(FD_ISSET(0,&rfds))//当你想输入消息的时候往下执行
{
bzero(buff,MAXLINE);
fgets(buff,MAXLINE,stdin);
printf("send mesg to clint.(\"quit\"to end):%s\n",buff);
if(!strncmp(buff,"quit",4))//先判断我输入的是不是quit,如果是的话,则直接退出了
break;
if( send(connfd,buff,strlen(buff),0)<0)
{
printf("send msg error:%s(errno:%d)\n",strerror(errno),errno);
exit(1);
}
}
}
}
OK~以上就是我对代码的理解,下面还是贴出我的代码,然后有时间我再修改一下,实现更多的功能看看。
ps:由于这个程序只是2个主机之间通信。 还想想改成多线程的程序,服务器能同时接受多个服务器传来的消息,明天再上传吧。(mark:continue是结束这一次循环,break是结束整个循环)
服务端:(9月28号又改了一下代码,这个代码的功能是客户端退出后,服务端可以选择不退出,而且服务端在和客户端交互信息的时候可以再连客户端,不过要等上一个客户端退出后信息才会我们才能从屏幕上看到信息)<strncmp可以改成strncasecmp忽略大小写,而且还有一个BUG就是如果你输入quitxx也会退出>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr,c_addr;
char buff[MAXLINE];
int n;
int len;
fd_set rfds;
int ret;
int max_fd;
if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )
{
printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);
if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)
{
printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
if( listen(listenfd, 10) == -1)
{
printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
printf("======waiting for client's request======\n");
while(1)
{
len=sizeof(struct sockaddr_in);
if( (connfd = accept(listenfd, (struct sockaddr*)&c_addr, &len)) == -1)
{
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
}
else
printf("=================connect success!================\n");
printf("the clint port is: %x\n",ntohs(c_addr.sin_port));
/* ================================================================================= */
while(1)
{
FD_ZERO(&rfds);
FD_SET(0,&rfds);
FD_SET(connfd,&rfds);
//max_fd=0;
//if(max_fd<connfd)
// max_fd=connfd;//这里是判断最大描述符的,因为我是stdin 默认就是0,所以没必要比较,如果是多个socket的话就需要比较了,因为select第一个参数需要最大描述符
ret=select(connfd+1,&rfds,NULL,NULL,NULL);
if(ret>=0)
{
if(FD_ISSET(connfd,&rfds))
{
bzero(buff,MAXLINE);
n = recv(connfd, buff, MAXLINE, 0);
buff[n] = '\0';
if(n>0)
{
printf("recv msg from client: %s\n", buff);
}
else if(n==0)
{
printf("====client quit===== \n do you want to quit?(y)\n");
break;
}
}
if(FD_ISSET(0,&rfds))
{
bzero(buff,MAXLINE);
fgets(buff,MAXLINE,stdin);
printf("send mesg to clint.(\"quit\"to end):%s\n",buff);
if(!strncmp(buff,"quit",4))
break;
if( send(connfd,buff,strlen(buff),0)<0)
{
printf("send msg error:%s(errno:%d)\n",strerror(errno),errno);
exit(1);
}
}
}
}
close(connfd);
printf("==============quit server?================\n");
bzero(buff,MAXLINE);
fgets(buff,MAXLINE,stdin);
if(!strncmp(buff,"y",1))
{
printf("=========================QUIT=============================\n");
break;
}
}
close(listenfd);
return 0;
}
客户端:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int sockfd, n;
char recvline[4096], sendline[4096];
struct sockaddr_in c_addr;
fd_set rfds;
int ret;
int max_fd;
int rcv_len;
int send_len;
if( argc != 2){
printf("usage: ./client <ipaddress>\n");
exit(0);
}
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
exit(0);
}
memset(&c_addr, 0, sizeof(c_addr));
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(6666);
if( inet_pton(AF_INET, argv[1], &c_addr.sin_addr) <= 0)
{
printf("inet_pton error for %s\n",argv[1]);
exit(0);
}
if( connect(sockfd, (struct sockaddr*)&c_addr, sizeof(c_addr)) < 0)
{
printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
else
printf("connect success!\n");
/*================================================================================*/
while(1)
{
FD_ZERO(&rfds);
FD_SET(0,&rfds);//0 stdin
FD_SET(sockfd,&rfds);
//max_fd=0;
//if(max_fd<sockfd)
// max_fd=sockfd;
ret=select(sockfd+1,&rfds,NULL,NULL,NULL);
if(ret>=0)
{
if(FD_ISSET(0,&rfds))
{
memset(sendline,0,sizeof(sendline));
fgets(sendline, MAXLINE, stdin);
send_len=send(sockfd,sendline,strlen(sendline),0);
if( send_len<0)
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(1);
}
else
{
printf("send msg to server(\"quit\" to end):%s\n",sendline);
if(!strncmp(sendline,"quit",4))
{
printf(" End \n");
break;
}
}
}
/* ========================================================================== */
if(FD_ISSET(sockfd,&rfds))
{
memset(recvline,0,sizeof(recvline));
rcv_len=recv(sockfd,recvline,MAXLINE,0);
if(rcv_len<0)
{ printf("recv msg error:%s(errno:%d)\n",strerror(errno),errno);;
exit(1);
}
else if(rcv_len==0)
{
printf("============chat End================\n");
break;
}
else
printf("recv from server:%s\n",recvline);
}
}
}
close(sockfd);
return 0;
}
—————————————————————————————————————————————————————————————————————————————
帖一下修改后的图:
我们用3个终端测试,一个服务端,2个客户端:
大家可以看出当我第一个客户端退出后第个客户端才能连接,因为我代码中用了2个while循环嘛
第1个客户端退出:
第2个客户端退出: