博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Socket网络编程--聊天程序(6)
阅读量:6581 次
发布时间:2019-06-24

本文共 6205 字,大约阅读时间需要 20 分钟。

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

  其他的解决方法是,其实对于结构体还有几个相应的函数,是用于块传输的。结构体就是一个块。(函数如下readv,writev,readn,writen,为了程序的简单,就不引入其他函数了。)

  Ps:有几点要注意的,就是在传输的过程中要注意字节序和网络序问题,还有就是结构体字节对齐问题。由于我是在同一台PC上运行的,就没有出现什么大问题,只要保证两边的结构体定义相同即可。但是如果是在不同的机器上就可以会有些小问题。我这里就简单说一下,提醒一下。

1 struct test{2     int a;3     double b;4     char c[11];5 };

  就上面这个结构体,你能说这个结构体的大小吗?我在CentOS6.5的32位机器上gcc运行的结果是24.因为这里的默认对齐是4个字节。我们可以通过伪指令#pragma pack (n),C编译器将按照n个字节对齐。但n=1时,结构体的大小是23了。具体,什么结构体里面还有结构体的就不说了。另一个问题就是网络字符流的顺序问题,具体原因google或baidu。这个流循序问题会出现在什么情况下呢?我知道的就是在32位和64位的机器上有所区别。由于我没有64位机器可以测试,但是我知道,至少指针类型大小肯定是不一样的。因此就有可能自己对传输过来的那一块数据进行人为解析。

  我们要知道在网线中传的是二进制,所以我们服务器得到的那一块数据其实就是一块二进制数据。要对其解析就还有考虑字节序和网络序了。

  解析过程,我以前看到过一篇博客有讲到() 

   

  上面所说的种种麻烦,使用java就没有这么麻烦了。(是不是有点想用java写socket网络程序了?)

  好了,说了这么多。就进入正题吧。

  加入用户的聊天程序

  加入一个用户的结构体

1 struct user2 {3     char name[32];4     char pwd[32];5 };

  client.c 修改如下

14 ... 15 struct user 16 { 17     char name[32]; 18     char pwd[32]; 19 }; 20  21 int main(int argc,char *argv[]) 22 {    ...  31     struct user use; 32  33     if(argc != 4) 34     { 35         perror("use: ./client [hostname] [username] [password]"); 36         exit(-1); 37     } 38     strcpy(use.name,argv[2]); 39     strcpy(use.pwd,argv[3]); 40     printf("username:%s\n",use.name); 41     printf("password:%s\n",use.pwd); 42     host=gethostbyname(argv[1]);     ...  71     printf("Success to connect the socket...\n"); 72  73     //发送用户名和密码过去 74     if(send(sockfd,(char *)&use,sizeof(struct user),0)==-1) 75     { 76         perror("fail to send datas."); 77         exit(-1); 78     } 79     if((recvSize=recv(sockfd,recvBuf,MAX_BUF,0)==-1)) 80     { 81         perror("fail to receive datas."); 82         exit(-1); 83     } 84     //printf("Server:%s\n",recvBuf); 85     if(strcmp(recvBuf,"no")==0) 86     { 87         perror("密码或者用户名错误"); 88         exit(-1); 89     } 90  91     //send-recv 一些返回指没有判断,具体可以看server.c     ...126     return 0;127 }

  上面73-89行就是修改的主要代码,只是在连接的一开始就向服务器发送用户名和密码,然后服务器会给出一个回应,yes表示用户名密码正确,no表示错误。代码也不多。

  接下来是server.c的代码

   ... 22  23 struct user 24 { 25     char name[32]; 26     char pwd[32]; 27 }; 28     ... 35  36 int main(int argc,char *argv[]) 37 {     ...  42     fd_set servfd,recvfd;//用于select处理用的 43     int fd_A[BACKLOG+1];//保存客户端的socket描述符 44     char fd_C[BACKLOG+1][32];//用于保存客户端的用户名     ...  52     struct timeval timeout; 53     struct user use; 54      ...  99     FD_ZERO(&servfd);//清空所有server的fd100     FD_ZERO(&recvfd);//清空所有client的fd101     FD_SET(sockfd,&servfd);102     conn_amount=0;103     max_servfd=sockfd;104     max_recvfd=0;105     memset(fd_A,0,sizeof(fd_A));106     memset(fd_C,0,sizeof(fd_C));107 108     while(1)109     {110         FD_ZERO(&servfd);//清空所有server的fd111         FD_ZERO(&recvfd);//清空所有client的fd112         FD_SET(sockfd,&servfd);113         //timeout.tv_sec=30;//可以减少判断的次数114         switch(select(max_servfd+1,&servfd,NULL,NULL,&timeout))115         {116             case -1:117                 perror("select error");118                 break;119             case 0:120                 break;121             default:122                 //printf("has datas to offer accept\n");123                 if(FD_ISSET(sockfd,&servfd))//sockfd 有数据表示可以进行accept124                 {125                     /*accept a client's request*/126                     if((clientfd=accept(sockfd,(struct sockaddr  *)&clientSockaddr, &sinSize))==-1)127                     {128                         perror("fail to accept");129                         exit(1);130                     }131                     printf("Success to accpet a connection request...\n");132                     printf(">>>>>> %s:%d join in! ID(fd):%d \n",inet_ntoa(clientSockaddr.sin_addr),ntohs(clientSockaddr.sin_port),clientfd);133             /*这里是对每个连接成功的客户端都进行身份的验证,这里的第二个参数是一个结构体use,我们取地址,转换为char* 表示在use这个地址上取大小sizeof(struct user)个字节,因为sizeof返回的就是字节的个数*/134                     if((recvSize=recv(clientfd,(char *)&use,sizeof(struct user),0))==-1 || recvSize==0)135                     {136                         perror("fail to receive datas");137                     }138                     printf("客户端发来的用户名是:%s,密码:%s\n",use.name,use.pwd);139                     memset(recvBuf,0,sizeof(recvBuf));140                     if(strcmp(use.name,"admin")==0 || strcmp(use.pwd,"admin")==0)141                     {
/*这里是要用&&表示都是正确才能进入,不是我弄错了,是因为要多个用户来登陆,为了区别就写成这样了,不要在意这些细节。下一节就使用数据库了*/142 printf("验证成功!\n");143 strcpy(sendBuf,"yes");144 }145 else146 {147 printf("验证失败!\n");148 strcpy(sendBuf,"no");149 }150 if((sendSize=send(clientfd,sendBuf,MAX_DATA_SIZE,0))==-1)151 {152 perror("fail to receive datas");153 }154 //每加入一个客户端都向fd_set写入155 fd_A[conn_amount]=clientfd;156 strcpy(fd_C[conn_amount],use.name);157 conn_amount++;158 max_recvfd=MAX(max_recvfd,clientfd);159 }160 break;161 }162 //FD_COPY(recvfd,servfd);163 for(i=0;i

  总体就是增加一个fd_C[]数组保存各个客户端的用户名,用户名与保存sockefd号fd_A是对应的。

  先上一个运行结果图看一下吧。

   看了结果就知道了,可以传输用户名和密码了,也就是说可以传输结构体了。而且对于传输的数据都在服务器端加上一个来源自的用户名以用于区分数据是谁发送的。这个在聊天室中还是比较重要的。

   为了聊天的方便我们就加入个时间戳吧。用于区分时间和发送的循序。

  下面这个小程序是讲如何获取当前时间的

1 #include 
2 #include
3 4 int main() 5 { 6 time_t now; 7 struct tm *timenow; 8 time(&now); 9 timenow=localtime(&now);10 printf("%d %d %d %02d:%02d:%02d\n",timenow->tm_year+1900,timenow->tm_mon+1,timenow->tm_mday,timenow->tm_hour,timenow->tm_min,timenow->tm_sec);11 return 0;12 }

  好了,接下来的图是加入时间戳后的聊天界面

  呵呵!现在看来还是有模有样的呢!加了这个时间戳,篇幅差不多了。下一小结将在server.c的140行处加入数据库的连接和判断。

 

 

  参考资料:

  socket对结构体解析问题 

  解决结构体问题 

  本文地址: 

转载地址:http://hnnno.baihongyu.com/

你可能感兴趣的文章
作业一 统计软件简介与数据操作
查看>>
css布局
查看>>
HBase-java api 基本操作
查看>>
POJ2229 Sumsets
查看>>
在LINQ-TO-SQL中实现“级联删除”的方法
查看>>
lemur run PLSA
查看>>
如何在PHP7中安装mysql的扩展
查看>>
HTTP中的header头解析说明
查看>>
MVC3.0原理学习及总结
查看>>
删除windows中的库、家庭组、收藏夹
查看>>
ggplot2 geom相关设置—分布图
查看>>
war 宽度变窄
查看>>
21. Wireless tools (无线工具 5个)
查看>>
set p4 environment in windows
查看>>
java调用jacob组件实现word转pdf,HTML等出现的问题
查看>>
django 在python 3中提示 无法找到 MySQLDB
查看>>
相关系数
查看>>
pl/sql development 查询的数据复制到excel
查看>>
自定义指令的参数
查看>>
python实现进度条
查看>>