一. 密码加密
因为在linux下没有getch()函数,所以就实现了一个在linux下的getch()
实现的思路(参考了网上代码):
总体来说就是设置终端的属性
设置为原始模式,这种模式下输入就是无缓冲的,设置过去,输入完之后然后再更改回来
主要就是两个函数 tcgetattr()和tcsetattr()
- tcgetattr和tcsetattr说明
int tcgetattr(int fd, struct termios termptr);/ 获取终端属性*/
int tcsetattr(int fd, int opt, const struct termios termptr);/ 设置终端属性*/
实现代码:
#include <termios.h>
//linux下没有getch()
void getch(char *buf)
{
struct termios tm, n_tm;
char ch;
tcgetattr(0, &tm); //获取当前的终端属性设置,并保存到tm结构体中
n_tm = tm;
n_tm.c_lflag &= ~(ICANON | ECHO);
int i = 0;
while(1)
{
tcsetattr(0, TCSANOW, &n_tm); //设置上更改之后的设置
ch = getchar();
tcsetattr(0, TCSANOW, &tm); //接收字符完毕后将终端设置回原来的属性
if(ch=='\n')
{
buf[i]='\0';
break;
}
buf[i]=ch;
i++;
printf("*");
}
printf("\n");
}
二. 构建一个epoll框架
在写聊天室之前刚看过epoll的io多路复用,其实还是有点迷的,大概摸清楚了,但又好像不太懂,写聊天室时也是看了网上的epoll简单框架
代码如下:
# define MAXEPOLL 1000
epfd = epoll_create(MAXEPOLL);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = sock_fd;
if(epoll_ctl(epfd, EPOLL_CTL_ADD, sock_fd, &ev) < 0)
{
perror("epoll_ctl");
}
printf("服务器启动成功--**--\n");
while(1)
{
if((nfds = epoll_wait(epfd, events, MAXEPOLL, -1)) < 0)
{
perror("epoll_wait");
}
for (i = 0; i < nfds; i++)
{
if (events[i].data.fd == sock_fd)
//如果是主socket的事件的话,则表示
//有新连接进入了,进行新连接的处理。
{
if ((conn_fd = accept(sock_fd, (struct sockaddr*)&cli, &socklen)) < 0)
{
perror("accept");
}
printf("connect success!\n套接字编号:%d\n", conn_fd);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev) < 0)
{
perror("epoll_ctl");
}
continue;
}
else if(events[i].events & EPOLLERR)
{
epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,0);
close(events[i].data.fd);
continue;
} else if (events[i].events & EPOLLHUP) {
epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,0);
close(events[i].data.fd);
continue;
} else if (events[i].events & EPOLLIN) {
//表示对应的文件描述符可以读
//如果是已经连接的用户,并且收到数据,
//那么进行读入
memset(&recv_pack, 0, sizeof(PACK));
if (recv(events[i].data.fd, &recv_pack, sizeof(PACK), MSG_WAITALL) < 0)
{
close(events[i].data.fd);
perror("recv");
continue;
}
printf("\n\e[1;34m*************PACK*************\e[0m\n");
printf("\e[1;34m*\e[0m type : %d\n",recv_pack.type);
printf("\e[1;34m*\e[0m send_fd : %d\n", recv_pack.data.send_fd);
printf("\e[1;34m*\e[0m send_account : %d\n",recv_pack.data.send_id);
printf("\e[1;34m*\e[0m recv_fd : %d\n",recv_pack.data.recv_fd);
printf("\e[1;34m*\e[0m recv_account : %d\n",recv_pack.data.recv_id);
printf("\e[1;34m*\e[0m content_buff : %s\n",recv_pack.data.content_buff);
// printf("\e[1;34m*\e[0m mess_buff : %s\n",recv_pack.data.mess_buff);
printf("\e[1;34m*\e[0m recv_user : %s\n",recv_pack.data.recv_user);
printf("\e[1;34m*\e[0m send_name : %s\n",recv_pack.data.send_user);
printf("\e[1;34m*******************************\e[0m\n");
recv_pack.data.recv_fd = events[i].data.fd;
pack = (PACK*)malloc(sizeof(PACK));
memcpy(pack, &recv_pack, sizeof(PACK));
pthread_create(&pid, NULL, solve, (void*)pack); //开线程解决事件
}
}
}
这篇博客不错,推荐一下
epoll原理详解及epoll反应堆模型
三. MYSQL数据库的使用
也是在写聊天室之前才接触到这个mysql数据库,很多东西都还不了解,参考了大三学长的两篇博客,写的非常详细,非常受用
mysql数据库的简单用法
C语言操作MYSQL数据库
没有在聊天室用到的主键外键,后来自己也去了解了一些,会发现有主键和外键在操作数据库方面更方便
四. 服务器和客户端通信
这个在暑假前的任务网络编程那里看到过,服务端和客户端的交互
大体C/S模型:
先从服务器端说起:
服务器端先初始化socket,然后与端口绑定(bind
),对端口进行监听(listen
),调用accept
阻塞,等待客户端连接。
在这时如果有个客户端初始化一个socket,然后连接服务器(connect
),如果连接成功,这时客户端与服务器端的连接就建立了。
客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
五. 每个用户/群的账号生成
当时在写注册的时候一直在想怎么能生成不重复的用户账号呢,后来想到了随机数rand(),但这个有可能会生成重复的数,就又了解到了srand()
随机数的生成
注册生成个人账号代码如下:
# include <time.h>
int reg(PACK *pack, MYSQL n_mysql)
{
MYSQL mysql = n_mysql;
char handle[100];
PACK *recv_pack = pack;
int account;
time_t t = time(NULL);
srand(t);
account = rand()%1000000;
sprintf(handle, "insert into user values(%d,\"%s\",\"%s\",%d,%d)", account, recv_pack->data.send_user, recv_pack->data.content_buff, 0, recv_pack->data.recv_fd);
recv_pack->data.send_id = account;
time_t now;
now = time(NULL);
mysql_query(&mysql, handle);
memset(handle, 0, sizeof(handle));
sprintf(handle, "insert into password values(%d,%d)",account, now);
mysql_query(&mysql, handle);
return 0;
}
创建群生成群号代码如下:
# include <time.h>
int create_group(PACK *pack, MYSQL n_mysql)
{
MYSQL mysql = n_mysql;
char handle[100];
PACK *recv_pack = pack;
int account;
time_t t = time(NULL);
srand(t);
account = rand()%100000;
sprintf(handle, "insert into groups values(%d,\"%s\",1)", account, recv_pack->data.recv_user);
recv_pack->data.recv_id = account;
mysql_query(&mysql, handle);
memset(handle, 0, sizeof(handle));
sprintf(handle, "insert into group_mess values(%d,\"%s\",%d,\"%s\",1)",recv_pack->data.recv_id, recv_pack->data.recv_user, recv_pack->data.send_id, recv_pack->data.send_user);
mysql_query(&mysql, handle);
return 0;
}
六. 收包
我是用结构体每次定长发送,就将recv()最后的参数由0设置成了等待所有数据的MSG_WAITALL
,设置成这个的原因就是让包收完再返回。
但是这里的MSG_WAITALL
也只是尽量读全,在有中断的情况下recv 还是可能会被打断,造成没有读完指定的buffer的长度。
七. 申请好友/群 时的交互
申请好友时,要判断是否在线
在线,直接发送添加申请
不在线,保存到离线消息,上线再发送对
对方同意,则操作数据添加好友信息
对方不同意,则没有任何操作【这里还是有bug,因为对方同没同意,添加方最后并不知道,只有后来看好友列表时才会知道对方有没有同意好友请求】--后续会尝试解决这个问题
申请加群时,当时就没有申请什么的,就申请直接就能进了【参考qq直接进群功能 】,当然这是很大的不足,后续会继续改进,实现申请给群主或管理,登群主管理同意后再进群
八. 传输文件
这个当时在网上看了好多博客,还是有点迷,太难了
后来参考了学长的思路:发送者先循环将文件发送给服务端保存到文件中,接收者方循环接收服务端发送的文件,保存到新的文件中
发送方要写好文件在本机的绝对路径
我是先将绝对路径下文件名解析,发送到服务端时就会保存真实的文件名(这样就可以发送不同类型的文件【.c,.txt,.jpg等等】)
再服务端向接收者发送文件,就更方便了
但发送的文件大小还是不能太大