上一节已经完成了对用户的身份验证了,既然有了验证,那么接下来就能对不同的客户端进行区分了,所以这一节讲实现私聊功能。就是通过服务器对客户端的数据进行转发到特定的用户上,

  实现私聊功能的聊天程序

  实现的技术细节是:对客户端发送的数据增加一个标识头,由于我们处理的是纯文本,所以为了讲解的方便就把标识头加到聊天信息的前面,然后在服务器中判断。如果是要在做成产品的话,因为要考虑传送纯文本,图片,文件,特定的结构体等等其他非纯文本信息,那么我们可以对一次聊天信息,发送两次数据,第一次用TCP发送一个结构体,该结构体包含接下来要接收的信息的格式,大小等信息,然后第二次就发送真正的数据块。

  关于第二次发送为什么要用UDP呢?这个是学腾讯qq的,因为标识结构体比较小,而且是必须要有的(为什么?)所以使用TCP,而信息那一部分,往往是数据比较大的,都用tcp传的话,会占用更多的资源。所以我们聊天的时候,有时候会出现这样一条信息,“由于网络问题,该信息可能发送失败”。想想,如果是tcp传输,那么就只有成功和失败,没有什么可能的问题,注意传文件是两个客户端进行tcp连接的,不然怎么确保正确性呢,哎,其实还是很复杂的。(真的吗?求辟谣!)

  回到我们的程序中来吧,我的处理办法是在服务器里判断第一个单词是不是simple,如果是就读取第二个单词,为用户名,然后根据用户名从fd_C中查找,看fd_C对应fd_A的socket号码,然后根据这个fd号码进行转发,而不是进行群发。(如果要增加什么功能,如传文件的话那么,道理一样,判断第一个单词是不是file,如果是第二个单词是文件名什么的,具体就是这样做的。如果是做有界面的客户端,就可以进行选择要聊天的用户,然后在后台生成simple这个标识号了。就对用户友好一点。)

  好了,到了激动人心的时刻了,下面是代码讲解。

  client.c 基本不变

  server.c

  ...
 17 #include <mysql.h>//用于mysql连接...
 24
 25 struct user
 26 {...  29 };
 30
 31 int MAX(int a,int b)...  37
 38 void print_time(char * ch,time_t *now)
 39 {...  43 }
 44
 45
 46 int mysql_check_login(struct user su)
 47 {...  91     return 0;
 92 }
 93
 94 //根据用户名返回该用户名在fd_A中的位置
 95 //fd=-1,表示没有该用户 //fd>0 正常返回
 96 int fd_ctoa(char fd_C[][32],char *ch)
 97 {
 98     int i,j;
 99     int fd=-1;
100     for(i=0;i<BACKLOG;i++)
101     {
102         if(strcmp(fd_C[i],ch)==0)
103         {
104             fd=i;
105             break;
106         }
107     }
108     return fd;
109 }
110
111 int main(int argc,char *argv[])
112 {...
187     while(1)
188     {
189         FD_ZERO(&servfd);//清空所有server的fd
190         FD_ZERO(&recvfd);//清空所有client的fd
191         FD_SET(sockfd,&servfd);
192         //timeout.tv_sec=30;//可以减少判断的次数
193         switch(select(max_servfd+1,&servfd,NULL,NULL,&timeout))
194         {        ... ...242         }
243         //FD_COPY(recvfd,servfd);
244         for(i=0;i<MAX_CON_NO;i++)//最大队列进行判断,优化的话,可以使用链表
245         {...250         }
251
252         switch(select(max_recvfd+1,&recvfd,NULL,NULL,&timeout))
253         {
254             case -1:
255                 //select error
256                 break;
257             case 0:
258                 //timeout
259                 break;
260             default:
261                 for(i=0;i<conn_amount;i++)
262                 {
263                     if(FD_ISSET(fd_A[i],&recvfd))
264                     {
265                         /*receive datas from client*/
266                         if((recvSize=recv(fd_A[i],recvBuf,MAX_DATA_SIZE,0))==-1 || recvSize==0)
267                         {...273                         }
274                         else//客户端发送数据过来,然后这里进行转发
275                         {
276                             /*send datas to client*/
277                             /*下面是私聊代码,为了方便讲解所以写在这里*/
278                             sscanf(recvBuf,"%s%s",ch,username);
279                             if(strcmp(ch,"simple")==0)//判断第一个单词是不是simple私聊的标识符
280                             {
281                                 printf("私聊信息处理:  %s\n",recvBuf);
282                                 for(j=0;j<strlen(recvBuf);j++)//为了方便我规定聊天信息中以#符号后面为发送的聊天文本前面为标识符号(一切都是为了方便 ^v^)
283                                 {
284                                     if(recvBuf[j]=='#')
285                                     {
286                                         j++;
287                                         break;
288                                     }
289                                 }
290                                 if(j<strlen(recvBuf))
291                                 {
292                                     printf("%s对%s私聊说:%s\n",fd_C[i],username,&recvBuf[j]);//打印在服务器控制台方便调试
293                                     fd=fd_ctoa(fd_C,username);//根据用户名得到该用户名所对应的fd_A中的位置
294                                     printf("fd=%d\n",fd);//打印描述符号,用于调试
295                                     if(fd>=0)//表示找到对应的用户名
296                                     {
297                                         strcpy(sendBuf,fd_C[i]);
298                                         strcat(sendBuf," 对您私聊  ");
299                                         print_time(ch,&now);
300                                         strcat(sendBuf,ch);//加个时间戳
301                                         strcat(sendBuf,"\t\t");
302                                         strcat(sendBuf,&recvBuf[j]);
303                                         //sendSize=send(fd_A[fd],&recvBuf[j],strlen(recvBuf)-j,0);
304                                         sendSize=send(fd_A[fd],sendBuf,strlen(sendBuf),0);//发往指定的客户端
305                                     }
306                                     else
307                                     {
308                                         strcpy(ch,"私聊信息发送失败,可能是没有该用户");
309                                         sendSize=send(fd_A[i],ch,strlen(ch),0);
310
311                                     }
312                                 }
313                                 else
314                                 {
315                                     strcpy(ch,"私聊信息发送失败,可能是没有 # 符号");
316                                     sendSize=send(fd_A[i],ch,strlen(ch),0);
317
318                                 }
319                                 break;
320                             }
321                             else
322                             {
323                             }
324                             //其他else就是其他命令了,为了方便就不支持其他命令了
325                               ...352                         }
353                     }
354                 }
355                 break;
356         }//end-switch
357     }//end-while(1)
358     return 0;
359 }

  照例给个运行时的截图,提提神。

  好了,我们已经完成群聊和私聊的功能了,作为一个聊天程序实现这两个基本功能也就差不多啦。

  一点小小的补充:

//上一节忘了说mysql怎么设置开机启动了,指令如下,root用户执行
chkconfig mysqld on
service mysqld start

  另一个知识点的补充,也是今天才注意到的。以前我们每登陆一个客户端都会分配一个文件描述符fd,而服务器中对每个连接产生的fd号是从3开始,连一个就加一个。而现在分配的ID(fd)号是从4开始的不说,还每次增加2。这就奇怪了。

 1 [myuser@localhost client-server]$ ./server ser
 2 username:ser
 3 Success to establish a socket...
 4 Success to bind the socket...
 5 Success to accpet a connection request...
 6 >>>>>> 127.0.0.1:54880 join in! ID(fd):4
 7 加入的时间是:06:39:04
 8
 9 客户端发来的用户名是:user3,密码:123456
10 查询的sql:select * from clients where username="user3" and password="123456";
11 验证成功!
12 Success to accpet a connection request...
13 >>>>>> 127.0.0.1:54881 join in! ID(fd):6
14 加入的时间是:06:39:04
15
16 客户端发来的用户名是:user1,密码:123456
17 查询的sql:select * from clients where username="user1" and password="123456";
18 验证成功!
19 Success to accpet a connection request...
20 >>>>>> 127.0.0.1:54882 join in! ID(fd):8
21 加入的时间是:06:39:04
22
23 客户端发来的用户名是:user2,密码:123456
24 查询的sql:select * from clients where username="user2" and password="123456";
25 验证成功!
26 数据是:user2 06:39:04

  就是那几个大红色标出来的fd号,连接3个客户端居然是分配到4,6,8。而不是3,4,5

  还好我们的代码每次都增加不多,可以很快就知道为什么?因为有了数据库的连接。

  解释:文件描述符0,1,2这三个默认分配给stdin,stdout,stderr,然后接下来就按需分配了。3号是服务器用于接收客户端请求而创建的sockfd,在一开始就创建了。4号就是client1了,5号就是client1下连接数据库而创建的。由于我们的服务器对每个连接都要有一次访问数据库,所以对应单数的那些fd都是用在数据库连接上了。(什么是文件描述符?自己上网查咯)

  一些小总结,其实网络编程还是很有趣的,了解后就会发现很多看起来很叼的技术,其内部底层还是很简单的实现的。就我们常常听到的下面这些技术 防火墙,远程控制,远程SHELL,VPN,内网穿透等等看起来很厉害的技术,都基本上都是使用服务器,实现一对一的转发而已。只不过特定的功能还要靠特定的优化办法(如一些特定的IO操作,算法,安全性等)处理而已,也就是优化处理速度与安全性。如果是一般的使用,那我们其实都是可以实现的。所以别看一个小小的聊天程序的一个私聊功能,其实还是很多高级应用的基础(麻雀虽小,五脏俱全)。(由于本人技术问题,本博只提供思路,想法和一个小小的入门级程序。)

  参考资料

  关于标识符包头的详解: http://blog.csdn.net/jia162/article/details/1926576

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

Socket网络编程--聊天程序(8)相关推荐

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

    上一小节,已经讲到可以每个人多说话,而且还没有限制,简单的来说,我们已经完成了聊天的功能了,那么接下来我们要实现什么功能呢?一个聊天程序至少应该支持一对多的通讯吧,接下来就实现多个客户端往服务器发送数 ...

  2. 基于Udp的Socket网络编程聊天程序

    1.新建一个工程区Net 在工作区中添加两个工程 NetSrv 和 NetClient 为两个工程添加库文件 (Link中) ws2_32.lib 2.在工程NetSrv中添加Server.cpp文件 ...

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

    上一节简单如何通过Socket创建一个连接,然后进行通信.只是每个人只能说一句话.而且还是必须说完才会接收到信息,总之是很不方便的事情.所以这一小节我们将对上一次的程序进行修改,修改成每个人可以多说话 ...

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

    http://www.cnblogs.com/wunaozai/p/3893469.html 各位好呀!这一小节应该就是这个小小网盘程序的最后一小节了,这一节将实现最后的三个功能,即列出用户在服务器中 ...

  5. Socket网络编程(2)--服务端实现

    中秋了,首先祝大家中秋快乐,闲着无事在家整一个socket的聊天程序,有点仿QQ界面,就是瞎折腾,不知道最后是不是能将所有功能实现. 如果你对socket不了解,请看这篇文章:http://www.c ...

  6. MFC socket网络编程(流程示例)

    MFC socket网络编程(流程示例) 1.TCP流式套接字的编程步骤 在使用之前须链接库函数:工程->设置->Link->输入ws2_32.lib,OK! 服务器端程序: 1.加 ...

  7. Netty网络编程聊天项目

                                     Netty网络编程聊天项目 后端编写 导入依赖    <dependencies><dependency>&l ...

  8. BIO,Socket网络编程入门代码示例,NIO网络编程入门代码示例,AIO 网络编程

    BIO,Socket网络编程入门代码示例 1.BIO服务器端程序 package cn.itcast.bio;import java.io.InputStream; import java.io.Ou ...

  9. python网络编程讲解_详解Python Socket网络编程

    Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...

最新文章

  1. 计算机ROM的数据可以写入吗,计算机ROM存贮器中的内容只能读出,不能写入。()
  2. 画蛇添足:四条管脚的电位器
  3. mysql againts函数_MariaDB / MySQL TO_SECONDS和AGGREGATE函数
  4. java微信oppo,OPPO实现全球首次5G微信视频通话,国产手机满分操作
  5. 【BZOJ 2744 】[HEOI2012]朋友圈
  6. J2Pay –实施网关
  7. [SDOI2008]Cave 洞穴勘测
  8. 自适应简约数字动画404错误页面源码
  9. git 无法提交空目录
  10. Kernel panic - not syncing:Attempted to kill...
  11. Android Cloud to Device Messaging 服务介绍 – 如何使用云计算推送消息到手机(转载)...
  12. Redis详解(三)——Redis持久化
  13. 使用Jmeter对API进行性能测试
  14. 2021-11-13 变电站综合自动化 二次系统安全
  15. 把数字翻译成英文声明.C语言,如何把数字翻译成英文
  16. 硕士研究生阶段如何学习slam机器人?
  17. 西部世界:币本位是什么?
  18. WIN32API串口接收数据简单测试
  19. 计算机用户密码无法输入,电脑数字密码输入不了怎么办
  20. BootStrap前端框架网页模板

热门文章

  1. VTK:Utilities之Animation
  2. VTK:Utilities之FileOutputWindow
  3. VTK:IO之ReadOBJ
  4. C语言Catalan number卡特兰数(使用n个键可以搜索多少个二叉搜索树)的算法(附完整源码)
  5. OpenGL HDR曝光的实例
  6. C++Miller Rabin算法的实现(附完整源码)
  7. QT的QSslSocket类的使用
  8. C++STL常用算术生成算法
  9. C++ 类对象作为类成员
  10. c++设计模式编程基础