Socket网络编程--聊天程序(5)

  上一小节我们讲了使用select来避免使用多进程的资源浪费问题。上次只是实现了从多个客户端发送数据给服务器端,接下来就要实现从服务器端发送数据给各个服务器。

  使用select多路转换处理聊天程序2

  client.c 使用上一节用的那个,在那个基础上修改下面几句

 66     //send-recv 一些返回指没有判断,具体可以看server.c
 67     if((pid=fork())<0)
 68     {
 69         perror("fork error\n");
 70     }
 71     else if(pid==0)/*child*/
 72     {
 73         while(1)
 74         {
 75             fgets(sendBuf,MAX_BUF,stdin);
 76             if(send(sockfd,sendBuf,strlen(sendBuf),0)==-1)
 77             {
 78                 perror("fail to receive datas.");
 79             }
 80             memset(sendBuf,0,sizeof(sendBuf));
 81         }
 82     }
 83     else
 84     {
 85         while(1)
 86         {
 87             if((recvSize=recv(sockfd,recvBuf,MAX_BUF,0)==-1))
 88             {
 89                 printf("Server maybe shutdown!");
 90                 break;
 91             }
 92             printf("Server:%s\n",recvBuf);
 93             memset(recvBuf,0,sizeof(recvBuf));
 94         }
 95         kill(pid,SIGKILL);
 96     }
 97
 98     close(sockfd);
 99
100
101     return 0;
102 }

  server.c 我们在上一小节的基础上加上一个处理服务器stdin读取数据,然后遍历所有fd_A里面还连接着的客户端,然后一个一个的发送数据。

111     while(1)
112     {
113         FD_ZERO(&servfd);//清空所有server的fd
114         FD_ZERO(&recvfd);//清空所有client的fd
115         FD_SET(sockfd,&servfd);
116         //timeout.tv_sec=30;//可以减少判断的次数
117         switch(select(max_servfd+1,&servfd,NULL,NULL,&timeout))
118         {
          ...141         }
142         //FD_COPY(recvfd,servfd);
143         for(i=0;i<MAX_CON_NO;i++)//最大队列进行判断,优化的话,可以使用链表
144         {
145             if(fd_A[i]!=0)
146             {
147                 FD_SET(fd_A[i],&recvfd);
148             }
149         }
150
151         switch(select(max_recvfd+1,&recvfd,NULL,NULL,&timeout))
152         {
          ...182         }
183
184         /*用于检测stdin是否有数据*/
185         FD_ZERO(&servfd);
186         FD_SET(STDIN_FILENO,&servfd);
187
188         switch(select(STDIN_FILENO+1,&servfd,NULL,NULL,&timeout))
189         {
190             case -1:
191                 break;
192             case 0:
193                 break;
194             default:
195                 /*send datas to client*/
196                 if(FD_ISSET(STDIN_FILENO,&servfd))
197                 {
198                     fgets(sendBuf,MAX_DATA_SIZE,stdin);
199                     for(i=0;i<MAX_CON_NO;i++)
200                     {
201                         if(fd_A[i]!=0)
202                         {
203                             printf("数据发往%d,",fd_A[i]);
204                             if((sendSize=send(fd_A[i],sendBuf,strlen(sendBuf),0))!=strlen(sendBuf))
205                             {
206                                 perror("fail");
207                                 exit(1);
208                             }
209                             else
210                             {
211                                 printf("Success\n");
212                             }
213                         }
214                     }
215                     memset(sendBuf,0,MAX_DATA_SIZE);
216                     fflush(stdin);
217                 }
218                 break;
219         }
220     }
221     return 0;
222 }

  废话不多说,直接上运行时的截图

  程序运行的顺序是,运行server,然后运行client1,server发送data1,client2连接server,server发送data2,client3连接server,server发送data3,此时client1退出连接,server获取fd 4 close,发送data4的时候没有发送给fd4。然后client4连接上去。分配为fd7。(虽然fd4已经关闭了,按说可以给client4分配fd4,不过太麻烦我这里就没有实现。)

  实现聊天室功能

  接下来就是实现聊天室功能。上面server.c中有三个select,第一个是处理随时可能来的客户端连接,第二个select是处理随时可能从客户器端来的数据,第三个select是处理随时可能从控制台输入数据,并send到各个客户端。要实现聊天室功能,就对第二个和第三个select进行合并。具体不多说,直接上代码

  client.c 基本不变

  server.c

  1 #include <stdio.h>
   ...
 36 int main(int argc,char *argv[])
 37 {
    ...
111     while(1)
112     {
113         FD_ZERO(&servfd);//清空所有server的fd
114         FD_ZERO(&recvfd);//清空所有client的fd
115         FD_SET(sockfd,&servfd);
116         //timeout.tv_sec=30;//可以减少判断的次数
117         switch(select(max_servfd+1,&servfd,NULL,NULL,&timeout))
118         {
119             ...141         }
       ...
151         switch(select(max_recvfd+1,&recvfd,NULL,NULL,&timeout))
152         {
153             case -1:
154                 //select error
155                 break;
156             case 0:
157                 //timeout
158                 break;
159             default:
160                 for(i=0;i<conn_amount;i++)
161                 {
162                     if(FD_ISSET(fd_A[i],&recvfd))
163                     {
164                         /*receive datas from client*/
165                         if((recvSize=recv(fd_A[i],recvBuf,MAX_DATA_SIZE,0))==-1 || recvSize==0)
166                         {
167                             //perror("fail to receive datas");
168                             //表示该client是关闭的
169                             printf("fd %d close\n",fd_A[i]);
170                             FD_CLR(fd_A[i],&recvfd);
171                             fd_A[i]=0;
172                         }
173                         else//客户端发送数据过来,然后这里进行转发
174                         {
175                             /*send datas to client*/
176                             for(j=0;j<MAX_CON_NO;j++)
177                             {
178                                 if(fd_A[j]!=0&&i!=j)
179                                 {
180                                     printf("数据发往%d,",fd_A[j]);
181                                     if((sendSize=send(fd_A[j],recvBuf,strlen(recvBuf),0))!=strlen(recvBuf))
182                                     {
183                                         perror("fail");
184                                         exit(1);
185                                     }
186                                     else
187                                     {
188                                         printf("Success\n");
189                                     }
190                                 }
191                             }
192                             //可以判断recvBuf是否为bye来判断是否可以close
193                             memset(recvBuf,0,MAX_DATA_SIZE);
194                         }
195                     }
196                 }
197                 break;
198         }
199     }
200     return 0;
201 }

  直接上运行时图片

  各个程序运行的循序是先运行server,然后运行client1,client2,然后cli1发送数据data1,此时cli2接收到server发来的转发数据,然后... ...

  好了,到现在为止已经完成了聊天室功能了,还可以随时进入随时出来。好像还不错的样子。下一节就介绍引入用户名登陆的字符界面(由于我不会图形化界面,所以就不花时间去学那个了,至于字符界面下有个curses.h据说可以弄得很好看,我这里就不弄了。有兴趣的可以去弄一下),还有一些人性化的中文提示,然后整理一下代码。由于基本没有什么技术含量,所以如果到时候篇幅不够就再加个数据库设计,考虑使用数据库来保存用户(为了方便就使用mysql吧)。

  小小剧透一下,如果接下来有时间将会实现以下功能 用户功能,指令功能,私聊功能,vip功能,文件发送功能,多服务器问题

  本文地址: http://www.cnblogs.com/wunaozai/p/3871563.html

Socket网络编程--聊天程序(5)

时间: 2024-10-20 02:32:15

Socket网络编程--聊天程序(5)的相关文章

Socket网络编程--聊天程序(6)

这一小节将增加一个用户的结构体,用于保存用户的用户名和密码,然后发给服务器,然后在服务器进行判断验证.这里就有一个问题,以前讲的就是发送字符串是使用char类型进行传输,然后在服务器进行用同样是字符串进行接收.然而作为一个结构体是不是也可以呢?如果有看send或recv的函数定义就知道第二个参数是void *类型,也就是说这两个函数对传入的类型其实是不做要求的,只是要你传输个地址,然后后面接一个大小就可以了.就是要send在这个地址取值,去大小为size个,然后传输.学过TCP/IP就知道,我们

Socket网络编程--聊天程序(9)

这一节应该是聊天程序的最后一节了,现在回顾我们的聊天程序,看起来还有很多功能没有实现,但是不管怎么说,都还是不错的.这一节我们将讲多服务器问题(高大上的说法就是负载问题了.)至于聊天程序的文件发送(也即二进制文件发送例如图片)和单点登陆(就是多加一个数组fd_L[],用来记录是否已经登陆过了.),这些问题就不讨论了. 支持多服务器实现负载问题的聊天程序 今天才知道原来我们一直使用的select来处理IO多路复用的这个函数最多只能有1024个连接,因为内部实现里面的数组就是只有1024,多了不行.

Socket网络编程--聊天程序(8)

上一节已经完成了对用户的身份验证了,既然有了验证,那么接下来就能对不同的客户端进行区分了,所以这一节讲实现私聊功能.就是通过服务器对客户端的数据进行转发到特定的用户上, 实现私聊功能的聊天程序 实现的技术细节是:对客户端发送的数据增加一个标识头,由于我们处理的是纯文本,所以为了讲解的方便就把标识头加到聊天信息的前面,然后在服务器中判断.如果是要在做成产品的话,因为要考虑传送纯文本,图片,文件,特定的结构体等等其他非纯文本信息,那么我们可以对一次聊天信息,发送两次数据,第一次用TCP发送一个结构体

Socket网络编程--聊天程序(7)

接上一小节,本来是计划这一节用来讲数据库的增删改查,但是在实现的过程中,出现了一点小问题,也不是技术的问题,就是在字符界面上比较不好操作.比如要注册一个帐号,就需要弄个字符界面提示,然后输入数字表示选择,在依次输入信息.(这一点,用C写过什么管理系统就知道,很是麻烦.)考虑到本程序讲的是网络编程,就不弄增删改查了,就判断一个用户名密码是否正确就行了. 首先是安装mysql和创建数据库,所用到的命令如下: 1 su root //使用root用户 2 yum install mysql-devel

Socket网络编程--聊天程序(3)

上一小节,已经讲到可以每个人多说话,而且还没有限制,简单的来说,我们已经完成了聊天的功能了,那么接下来我们要实现什么功能呢?一个聊天程序至少应该支持一对多的通讯吧,接下来就实现多个客户端往服务器发送数据,和服务器向多个客户端发送数据. 多对一,单向,各个客户端都可以向服务器发送数据 close函数 #include <unistd.h> int close(int sockfd); //用于关闭所打开的Socket套接字 返回值:如果为0表示成功,-1表示失败 处理的办法是每一个客户端连接到服

Socket网络编程--聊天程序(4)

上一小节讲到可以实现多客户端与服务器进行通讯,对于每一个客户端的连接请求,服务器都要分配一个进程进行处理.对于多用户连接时,服务器会受不了的,而且还很消耗资源.据说有个select函数可以用,好像还很NB的样子. 使用select多路转换处理聊天程序 下面摘取APUE 14.5小结 I/O多路转接 当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中循环中使用阻塞I/O: while((n = read(STDIN_FILENO, buf, BUFFSIZ))>0) if(writ

Socket网络编程--聊天程序(2)

上一节简单如何通过Socket创建一个连接,然后进行通信.只是每个人只能说一句话.而且还是必须说完才会接收到信息,总之是很不方便的事情.所以这一小节我们将对上一次的程序进行修改,修改成每个人可以多说话,主要是通过Linux下多进程fork实现的. 一对一,server和client是每个人都可以多说几句话 由于控制台度数据的函数fgets是阻塞函数,要每个人都可以多说话,这里我使用的是读取和发送都在不同的进程里面,使之互相没有影响. fork函数 #include <unistd.h> pid

Socket网络编程--小小网盘程序(1)

这个系列是准备讲基于Linux Socket进行文件传输.简单的文件传输就是客户端可以上传文件,可以从服务器端下载文件.就这么两个功能如果再加上身份验证,就成了FTP服务器了,如果对用户的操作再加上一些功能(如分享),就可以作为一个最简单的网盘了.想想是不是有点小激动啊. 我这一小节就不讲那么高级的东西,就先了解文件怎么传输,我们以前的聊天程序传输数据都是一次发送就完成本次的发送,因为一个sendBuf是足够的.但是对于二进制文件来说,文件的大小就不一定了,有可能很大,所以我们的Buf是不知道要

Socket网络编程--小小网盘程序(4)

在这一小节中实现了文件的下载,具体的思路是根据用户的uid和用户提供的文件名filename联合两张表,取得md5唯一标识符,然后操作这个标识符对应的文件发送给客户端. 实现下载的小小网盘程序 client.cpp增加下面这个函数以实现文件的下载. 1 int file_pull(struct Addr addr,struct User user,char *filenames) 2 { 3 struct sockaddr_in servAddr; 4 struct hostent *host;