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

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

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

 struct test{
int a;
double b;
char c[];
};

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

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

  解析过程,我以前看到过一篇博客有讲到(http://blog.csdn.net/hslinux/article/details/6214594)

  

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

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

  加入用户的聊天程序

  加入一个用户的结构体

 struct user
{
char name[];
char pwd[];
};

  client.c 修改如下

 ...
struct user
{
char name[];
char pwd[];
}; int main(int argc,char *argv[])
{
    ...
struct user use; if(argc != )
{
perror("use: ./client [hostname] [username] [password]");
exit(-);
}
strcpy(use.name,argv[]);
strcpy(use.pwd,argv[]);
printf("username:%s\n",use.name);
printf("password:%s\n",use.pwd);
host=gethostbyname(argv[]);
    ...
71 printf("Success to connect the socket...\n"); //发送用户名和密码过去
if(send(sockfd,(char *)&use,sizeof(struct user),)==-)
{
perror("fail to send datas.");
exit(-);
}
if((recvSize=recv(sockfd,recvBuf,MAX_BUF,)==-))
{
perror("fail to receive datas.");
exit(-);
}
//printf("Server:%s\n",recvBuf);
if(strcmp(recvBuf,"no")==)
{
perror("密码或者用户名错误");
exit(-);
} //send-recv 一些返回指没有判断,具体可以看server.c
    ...
return ;
}

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

  接下来是server.c的代码

   ...

 struct user
{
char name[];
char pwd[];
};     ...
int main(int argc,char *argv[])
{
     ...
42 fd_set servfd,recvfd;//用于select处理用的
int fd_A[BACKLOG+];//保存客户端的socket描述符
char fd_C[BACKLOG+][];//用于保存客户端的用户名
     ...
52 struct timeval timeout;
struct user use;      ...
FD_ZERO(&servfd);//清空所有server的fd
FD_ZERO(&recvfd);//清空所有client的fd
FD_SET(sockfd,&servfd);
conn_amount=;
max_servfd=sockfd;
max_recvfd=;
memset(fd_A,,sizeof(fd_A));
memset(fd_C,,sizeof(fd_C)); while()
{
FD_ZERO(&servfd);//清空所有server的fd
FD_ZERO(&recvfd);//清空所有client的fd
FD_SET(sockfd,&servfd);
//timeout.tv_sec=30;//可以减少判断的次数
switch(select(max_servfd+,&servfd,NULL,NULL,&timeout))
{
case -:
perror("select error");
break;
case :
break;
default:
//printf("has datas to offer accept\n");
if(FD_ISSET(sockfd,&servfd))//sockfd 有数据表示可以进行accept
{
/*accept a client's request*/
if((clientfd=accept(sockfd,(struct sockaddr *)&clientSockaddr, &sinSize))==-)
{
perror("fail to accept");
exit();
}
printf("Success to accpet a connection request...\n");
printf(">>>>>> %s:%d join in! ID(fd):%d \n",inet_ntoa(clientSockaddr.sin_addr),ntohs(clientSockaddr.sin_port),clientfd);
            /*这里是对每个连接成功的客户端都进行身份的验证,这里的第二个参数是一个结构体use,我们取地址,转换为char* 表示在use这个地址上取大小sizeof(struct user)个字节,因为sizeof返回的就是字节的个数*/
if((recvSize=recv(clientfd,(char *)&use,sizeof(struct user),))==- || recvSize==)
{
perror("fail to receive datas");
}
printf("客户端发来的用户名是:%s,密码:%s\n",use.name,use.pwd);
memset(recvBuf,,sizeof(recvBuf));
if(strcmp(use.name,"admin")== || strcmp(use.pwd,"admin")==)
{/*这里是要用&&表示都是正确才能进入,不是我弄错了,是因为要多个用户来登陆,为了区别就写成这样了,不要在意这些细节。下一节就使用数据库了*/
printf("验证成功!\n");
strcpy(sendBuf,"yes");
}
else
{
printf("验证失败!\n");
strcpy(sendBuf,"no");
}
if((sendSize=send(clientfd,sendBuf,MAX_DATA_SIZE,))==-)
{
perror("fail to receive datas");
}
//每加入一个客户端都向fd_set写入
fd_A[conn_amount]=clientfd;
strcpy(fd_C[conn_amount],use.name);
conn_amount++;
max_recvfd=MAX(max_recvfd,clientfd);
}
break;
}
//FD_COPY(recvfd,servfd);
for(i=;i<MAX_CON_NO;i++)//最大队列进行判断,优化的话,可以使用链表
{
if(fd_A[i]!=)
{
FD_SET(fd_A[i],&recvfd);
}
} switch(select(max_recvfd+,&recvfd,NULL,NULL,&timeout))
{
case -:
//select error
break;
case :
//timeout
break;
default:
for(i=;i<conn_amount;i++)
{
if(FD_ISSET(fd_A[i],&recvfd))
{
/*receive datas from client*/
if((recvSize=recv(fd_A[i],recvBuf,MAX_DATA_SIZE,))==- || recvSize==)
{
                  ...
}
else//客户端发送数据过来,然后这里进行转发
{
/*send datas to client 为每个发送到客户端的信息加上一个客户的用户名作为提示,表示这一条信息是谁发出的*/
strcpy(sendBuf,fd_C[i]);
strcat(sendBuf,":");
strcat(sendBuf,recvBuf);
printf("数据是:%s\n",sendBuf);
for(j=;j<MAX_CON_NO;j++)
{
if(fd_A[j]!=&&i!=j)
{
                      ...
}
}
//可以判断recvBuf是否为bye来判断是否可以close
memset(recvBuf,,MAX_DATA_SIZE);
}
}
}
break;
}
}
return ;
}

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

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

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

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

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

 #include <stdio.h>
#include <time.h> int main()
{
time_t now;
struct tm *timenow;
time(&now);
timenow=localtime(&now);
printf("%d %d %d %02d:%02d:%02d\n",timenow->tm_year+,timenow->tm_mon+,timenow->tm_mday,timenow->tm_hour,timenow->tm_min,timenow->tm_sec);
return ;
}

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

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

  参考资料:

  socket对结构体解析问题 http://blog.csdn.net/hslinux/article/details/6214594

  解决结构体问题 http://blog.csdn.net/quwu_bjut/article/details/3459051

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

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

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

    这一节应该是聊天程序的最后一节了,现在回顾我们的聊天程序,看起来还有很多功能没有实现,但是不管怎么说,都还是不错的.这一节我们将讲多服务器问题(高大上的说法就是负载问题了.)至于聊天程序的文件发送(也 ...

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

    很早的一段时间,看了APUE和UNPv1了解了网络编程,但是但是只是看而已,没有具体的实践,趁现在没有什么事做,就来实践了解一下网络编程.写博客保存下来,方便以后用到的时候可以查到. 此次的聊天程序是 ...

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

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

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

    接上一小节,本来是计划这一节用来讲数据库的增删改查,但是在实现的过程中,出现了一点小问题,也不是技术的问题,就是在字符界面上比较不好操作.比如要注册一个帐号,就需要弄个字符界面提示,然后输入数字表示选 ...

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

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

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

    上一小节讲到可以实现多客户端与服务器进行通讯,对于每一个客户端的连接请求,服务器都要分配一个进程进行处理.对于多用户连接时,服务器会受不了的,而且还很消耗资源.据说有个select函数可以用,好像还很 ...

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

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

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

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

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

    各位好呀!这一小节应该就是这个小小网盘程序的最后一小节了,这一节将实现最后的三个功能,即列出用户在服务器中的文件列表,还有删除用户在服务器中的文件,最后的可以共享文件给好友. 列出用户在服务器中的文件 ...

随机推荐

  1. 080 HBase的属性

    一:基本属性 1.查看属性 2.解释属性 NAME:列簇名 BLOOMFILTER:布隆过滤器,用于对storefile的过滤 共有三种类型: ROW:行健过滤 ROWCOL:行列过滤 NONE:无 ...

  2. Java中的Lambda表达式

    Lambda来源于希腊字母入,发音为  /'læmdə/对高数有所了解的人都知道λ用于声明一个数学逻辑系统,表示根据XX的输入参数,会返回某个Y结果.这正是编程语言中函数(方法)的意思.因此Lambd ...

  3. CentOS root用户修改密码

    1.root用户修改密码: #passwd -------------------------------- 参考资料: 1.Centos修改root密码:http://blog.163.com/wz ...

  4. scanf清除缓存区

    为什么需要清除scanf缓存区呢?看一个例子: int main() { int a,b; scanf("%d",&a); scanf("%d",&am ...

  5. pandas学习(数据分组与分组运算、离散化处理、数据合并)

    pandas学习(数据分组与分组运算.离散化处理.数据合并) 目录 数据分组与分组运算 离散化处理 数据合并 数据分组与分组运算 GroupBy技术:实现数据的分组,和分组运算,作用类似于数据透视表 ...

  6. MyBatis持久层框架学习之01 MyBatis的起源和发展

    一.MyBatis的简介  MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.    MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集. MyB ...

  7. Unity3D 入门 游戏开发 Unity3D portal game development

    Unity3D 入门 游戏开发 Unity3D portal game development 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:313134555@qq.com ...

  8. BZOJ.3638.CF172 k-Maximum Subsequence Sum(模拟费用流 线段树)

    题目链接 各种zz错误..简直了 /* 19604kb 36292ms 题意:选$k$段不相交的区间,使其权值和最大. 朴素线段树:线段树上每个点维护O(k)个信息,区间合并时O(k^2),总O(mk ...

  9. Python3练习题系列(01)

    2018-06-13 题目: 根据用户回答做出相应的判断,完成一个“回答-判断”的小游戏 Python3知识点: if, else, elif 实例代码: print("You enter ...

  10. 潭州课堂25班:Ph201805201 爬虫基础 第三课 urllib (课堂笔记)

    Python网络请求urllib和urllib3详解   urllib是Python中请求url连接的官方标准库,在Python2中主要为urllib和urllib2,在Python3中整合成了url ...