Socket网络编程--聊天程序(6)
这一小节将增加一个用户的结构体,用于保存用户的用户名和密码,然后发给服务器,然后在服务器进行判断验证。这里就有一个问题,以前讲的就是发送字符串是使用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)的更多相关文章
- Socket网络编程--聊天程序(9)
这一节应该是聊天程序的最后一节了,现在回顾我们的聊天程序,看起来还有很多功能没有实现,但是不管怎么说,都还是不错的.这一节我们将讲多服务器问题(高大上的说法就是负载问题了.)至于聊天程序的文件发送(也 ...
- Socket网络编程--聊天程序(1)
很早的一段时间,看了APUE和UNPv1了解了网络编程,但是但是只是看而已,没有具体的实践,趁现在没有什么事做,就来实践了解一下网络编程.写博客保存下来,方便以后用到的时候可以查到. 此次的聊天程序是 ...
- Socket网络编程--聊天程序(8)
上一节已经完成了对用户的身份验证了,既然有了验证,那么接下来就能对不同的客户端进行区分了,所以这一节讲实现私聊功能.就是通过服务器对客户端的数据进行转发到特定的用户上, 实现私聊功能的聊天程序 实现的 ...
- Socket网络编程--聊天程序(7)
接上一小节,本来是计划这一节用来讲数据库的增删改查,但是在实现的过程中,出现了一点小问题,也不是技术的问题,就是在字符界面上比较不好操作.比如要注册一个帐号,就需要弄个字符界面提示,然后输入数字表示选 ...
- Socket网络编程--聊天程序(3)
上一小节,已经讲到可以每个人多说话,而且还没有限制,简单的来说,我们已经完成了聊天的功能了,那么接下来我们要实现什么功能呢?一个聊天程序至少应该支持一对多的通讯吧,接下来就实现多个客户端往服务器发送数 ...
- Socket网络编程--聊天程序(4)
上一小节讲到可以实现多客户端与服务器进行通讯,对于每一个客户端的连接请求,服务器都要分配一个进程进行处理.对于多用户连接时,服务器会受不了的,而且还很消耗资源.据说有个select函数可以用,好像还很 ...
- Socket网络编程--聊天程序(5)
上一小节我们讲了使用select来避免使用多进程的资源浪费问题.上次只是实现了从多个客户端发送数据给服务器端,接下来就要实现从服务器端发送数据给各个客户端. 使用select多路转换处理聊天程序2 c ...
- Socket网络编程--聊天程序(2)
上一节简单如何通过Socket创建一个连接,然后进行通信.只是每个人只能说一句话.而且还是必须说完才会接收到信息,总之是很不方便的事情.所以这一小节我们将对上一次的程序进行修改,修改成每个人可以多说话 ...
- Socket网络编程--小小网盘程序(5)
各位好呀!这一小节应该就是这个小小网盘程序的最后一小节了,这一节将实现最后的三个功能,即列出用户在服务器中的文件列表,还有删除用户在服务器中的文件,最后的可以共享文件给好友. 列出用户在服务器中的文件 ...
随机推荐
- 093实战 Nginx日志切割,以及脚本上传nginx的切割日志
一:日志切割步骤 命令都在root下进行 1.创建目录 mkdir -p /etc/opt/modules/bin ## 创建文件夹 2.上传cut 3.观察目录 4.修改的cut文件 5.检测 需要 ...
- ubuntu16.04下vim的安装与配置
一.安装vim 使用命令 $ sudo apt-get install vim 来安装vim,安装后的vim需要进行一些配置,不然使用起来会有些不方便,比如不会自动缩进. 二.配置vim 使用命令 ...
- Python2 - 基础2 - 数据类型和模块
一.数据类型 标准数据类型(5): Numbers(数字) String(字符串) List(列表) Tuple(元组) Dictionary(字典) 其中数字类型有4种: int(有符号整型) 在3 ...
- PopupWindow下拉列表
效果图 步骤: 1.画出编辑框的布局.popupWindow的布局.popupWindow中listview每行的布局 2.new一个PopupWindow对象,设置其属性 3.定义一个BaseAda ...
- poj2230 Watchcow【欧拉回路】【输出路径】(遍历所有边的两个方向)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4392 题目大意: 一个图,要将每条边恰好遍历两遍,而且要以不同的方向,还要回到原点. dfs解法 ...
- P2031 脑力达人之分割字串
P2031 脑力达人之分割字串字符串dp,f[i]表示主串到第i个字符,最多能分割成多少子串.f[i]=max(f[i],f[k]+1);k是能匹配到的前一位. #include<iostrea ...
- 三篇文章带你极速入门php(一)之语法
本文适合阅读用户 有其他语言基础的童鞋 看完w3cschool语法教程来回顾一下的童鞋(传送门,想全面看一下php语法推荐这里) 毫无基础然而天资聪慧颇有慧根(不要左顾右看说的就是你,老夫这里有一本& ...
- LOJ.6281.数列分块入门5(分块 区间开方)
题目链接 int内的数(也不非得是int)最多开方4.5次就变成1了,所以还不是1就暴力,是1就直接跳过. #include <cmath> #include <cstdio> ...
- 最小生成树之克鲁斯卡尔(kruskal)算法
#include <iostream> #include <string> using namespace std; typedef struct MGraph{ string ...
- STM32 逐次逼近寄存器型(SAR)模拟数字转换器(ADC)
是采样速率低于5Msps (每秒百万次采样)的中等至高分辨率应用的常见结构. SAR ADC的分辨率一般为8位至16位,具有低功耗.小尺寸等特点. 这些特点使该类型ADC具有很宽的应用范围,例如便携/ ...