1、简述:
采用了cs结构
服务器端四步走:socket,bind,listen,accept
客户端两步走:socket,connect
简单模型:客户端a消息发送到服务器;服务器找到客户端b的socket,在把消息发送给客户端b;
设计数据库操作存储的一些操作比较简单这里不提
2、看代码:
(1)、服务器端代码
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sqlite3.h>
#define PORT 9999
struct User
{
char name[20]; // 用户名
int socket; // 和用户通信的socket
};
// 协议
struct Msg
{
char information[1024]; //发送消息
char msg[1024]; // (姓名)消息内容
int id;
char sex[20];
int age;
int password;
char toname[20];
char fromname[20];
int cmd; // 消息类型
char online_name[20]; //在线姓名;
};
struct User user[2000] = {0};
//打开数据库函数,客户端用户登录数据传递过来,服务器端打开数据库比较查询是否匹配此登录用户信息;
int Database_enter(int client_socket,struct Msg *msg)
{
sqlite3 *database;
int ret = sqlite3_open( "information.db", &database );
if(ret != SQLITE_OK)
{
perror("sqlite3_open");
return -1;
}
printf("数据库打开成功\n");
// 3、char ***resultp: char *resultp[] = {"id", "name", "sex", "age", "1", "zhang", "M", "12","2".....};
// 4、nrow: 多少行数据
// 5、ncolumn: 多少列数据
char ** resultp;
char *errmsg = NULL;
int nrow;
int ncolumn;
//进行什么操作;
char *sql = "select * from information";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg );
if(ret != SQLITE_OK)
{
printf("数据库操作失败 %s\n",errmsg);
return -1;
}
//查询之后,把客户端传递过来的数据和这张表里的数据想比较,先比较唯一的name;再比较password;
int i;
for(i = 0 ;i < (nrow+1)*ncolumn;i++)
{
if (strncmp (resultp[i],msg->msg,strlen(msg->msg)) == 0)
//if (strncmp (resultp[i],msg->msg,strlen(msg->msg)-1) == 0)
{
if(atoi(resultp[i+1]) == msg->password) //强制转换;
{
printf("服务器端已经通过登录验证\n");
msg->cmd = 1002;
int k;
for(k = 0 ;k < 2000; k++)
{
if(user[k].socket == 0)
{
strcpy(user[k].name, msg->msg);
user[k].socket = client_socket;
printf("名字:%s\n",user[k].name);
break;
}
}
break;
}
else
{
printf("服务器端发现你的密码不正确哦!\n");
msg->cmd = -1;
break;
}
}
}
if(i == (nrow+1)*ncolumn)
{
msg->cmd = -2;
printf("服务器端没找到该用户\n");
}
sqlite3_free_table(resultp); //之前的char**resultp (数组)等于malloc了一个空间来储存数据库的数据;所以需要释放;
sqlite3_close(database);
printf("%d,%d\n",msg->cmd,sizeof(msg));
}
//私聊数据库函数
int Database_chat2(int client_socket,struct Msg *msg)
{
sqlite3 *database;
int ret = sqlite3_open( "information.db", &database );
if(ret != SQLITE_OK)
{
perror("sqlite3_open");
return -1;
}
printf("数据库打开成功\n");
// 3、char ***resultp: char *resultp[] = {"id", "name", "sex", "age", "1", "zhang", "M", "12","2".....};
// 4、nrow: 多少行数据
// 5、ncolumn: 多少列数据
char ** resultp = NULL;
char *errmsg = NULL;
int nrow;
int ncolumn;
//进行什么操作;
char *sql = "select * from information";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg );
if(ret != SQLITE_OK)
{
printf("数据库操作失败 %s\n",errmsg);
return -1;
}
//查询之后,把客户端传递过来的数据和这张表里的数据想比较,先比较唯一的name;再比较password;
int i;
for(i = 0 ;i < (nrow+1)*ncolumn;i++)
{
if (strncmp (resultp[i],msg->toname,strlen(msg->toname)) == 0)
// if (strncmp (resultp[i],msg->toname,strlen(msg->toname)-1) == 0)
{
printf("222222\n");
msg->cmd = 11;
return 11;
}
}
if(i == (nrow+1)*ncolumn)
{
msg->cmd = -5;
return -5;
}
sqlite3_free_table(resultp); //之前的char**resultp (数组)等于malloc了一个空间来储存数据库的数据;所以需要释放;
sqlite3_close(database);
}
//数据库写入注册数据函数
int Database_insert( struct Msg *msg)
{
sqlite3 *database;
//打开数据库,数据库指针指向你要打开的数据库,studtet.db是你创建的数据库文件
int ret = sqlite3_open( "information.db", &database );
if(ret != SQLITE_OK)
{
perror("sqlite3_open");
return -1;
}
printf("数据库打开成功\n");
//下边一大段是创建一个表
char *errmsg = NULL;
//sql语句
char *sql = "create table if not exists information (name TEXT, PASSWORD INTEGER, primary key(name))";
//执行sql语句的函数;
ret = sqlite3_exec(database ,sql, NULL ,NULL ,&errmsg);
if(ret != SQLITE_OK)
{
printf("数据库创建操作失败 %s\n",errmsg);
return -1;
}
//往表里边插入数据;
//insert into student values(1, 'Zhang', 'M', 18);
char buf[100];
sprintf(buf," insert into information values('%s',%d)",msg->msg ,msg->password );
//设置主键之后,如果插入的id已经存在那么就会返回错误,一班来说返回的错误都是插入重复这里有点取巧;
ret = sqlite3_exec(database ,buf, NULL ,NULL ,&errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库插入操作失败:%s\n", errmsg);
return -2;
}
printf("数据库已经成功插入了你的注册信息\n");
sqlite3_close(database);
return 1;
}
// 初始化套接字,返回监听套接字
int init_socket()
{
//1、创建socket
int listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (listen_socket == -1)
{
perror ("socket");
return -1;
}
// 2、命名套接字,绑定本地的ip地址和端口
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; // 设置地址族
addr.sin_port = htons(PORT); // 设置本地端口
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 使用本地的任意IP地址
int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1)
{
perror ("bind");
return -1;
}
// 3、监听本地套接字
ret = listen(listen_socket, 5);
if (ret == -1)
{
perror ("listen");
return -1;
}
printf ("等待客户端连接.......\n");
return listen_socket;
}
// 处理客户端连接,返回与连接上的客户端通信的套接字
int MyAccept(int listen_socket)
{
// 4、接收连接
// 监听套接字不能用来与客户端进行通信,它的职责是监听客户端的连接
// accpet 处理客户端的连接,如果成功接收,会返回一个新的套接字,用来与客户端进行通信
// accept的第三个参数 是一个传入传出参数
struct sockaddr_in client_addr; // 用来保存客户端的ip和端口信息
int len = sizeof(client_addr);
int client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &len);
if (client_socket == -1)
{
perror ("accept");
}
printf ("成功接收一个客户端: %s\n", inet_ntoa(client_addr.sin_addr));
return client_socket;
}
//注册函数
void reg(int client_socket, struct Msg *msg)
{
printf ("%s 正在进行注册\n", msg->msg);
//下边的代码就是应该把注册的消息写进数据库;
int ret = Database_insert( msg );
if(ret == -2)
{
printf("该用户id已经被注册 \n");
msg->cmd = -2;
}
else
{
msg->cmd = 1001;
}
//msg->cmd = 1001;
/* {
// 将用户套接字进行保存,保存在数组里
int i = 0;
for (i = 0; i < 20; i++)
{
if (user[i].socket == 0)
{
strcpy (user[i].name , msg->msg);
user[i].socket = client_socket;
break;
}
}
if (i == 2000)
{
msg->cmd = -1;
}
else
{
msg->cmd = 1001;
}
}
*/
write (client_socket, msg, sizeof(struct Msg));
}
//登录验证函数;
void enter(int client_socket, struct Msg *msg)
{
Database_enter(client_socket,msg );
write(client_socket, msg ,sizeof(struct Msg));
printf("11111111\n");
}
// 群发
void chat(int client_socket, struct Msg *msg)
{
printf ("%s 发一次群消息\n", msg->msg);
// 将用户进行保存
int i = 0;
for (i = 0; i < 2000; i++)
{
if (user[i].socket != 0)
{
write (user[i].socket, msg, sizeof(struct Msg));
}
}
}
// 私聊
void chat2(int client_socket, struct Msg *msg)
{
printf ("%s 要 给 %s 发一条消息\n", msg->fromname, msg->toname);
//判断数据库是够有此用户
int ret = Database_chat2(client_socket,msg);
if(ret == -5)
{
printf("此用户不存在\n");
write(client_socket,msg,sizeof(struct Msg));
}
//用户才在于数据库,查找是否在线?
else if(ret == 11)
{
printf("数据库存在此用户,正在判断该用户是否在线\n");
//判断用户是否在线;
int i = 0;
for (i = 0; i < 2000; i++)
{
if (user[i].socket != 0 && strncmp(user[i].name, msg->toname,strlen(msg->toname))==0)
// if (user[i].socket != 0 && strncmp(user[i].name, msg->toname,strlen(msg->toname)-1)==0)
{
printf("11111111\n");
msg->cmd = 10;
write(client_socket,msg,sizeof(struct Msg));
printf("服务器判断出该用户在线,正在给该用户发送消息....\n");
msg->cmd = 3;
write (user[i].socket, msg, sizeof(struct Msg));
printf("1111111111\n");
break;
}
}
if(i == 2000)
{
printf("服务器显示该用户不在线\n");
msg->cmd = -6;
write(client_socket,msg,sizeof(struct Msg));
}
}
}
//显示在线好友列表;
void display(int client_socket,struct Msg *msg)
{
//服务器端把user[]里边的在线遍历出来发送给客户端;
int i;
msg->cmd = 4;
for(i = 0;i < 2000;i++)
{
if(user[i].socket != 0)
{
//遍历一次写给客户端一次;
strncpy(msg->online_name ,user[i].name,strlen(user[i].name));
write(client_socket, msg ,sizeof(struct Msg));
printf("44444444\n");
}
}
}
//注销用户信息
int logout(int client_socket, struct Msg *msg)
{
sqlite3 * database;
// 打开数据库
int ret = sqlite3_open("information.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return -1;
}
printf ("打开数据库成功\n");
char *errmsg = NULL;
char buf[100];
sprintf (buf, "delete from information where name = '%s'", msg->msg);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return -1;
}
printf("注销成功\n");
write(client_socket,msg, sizeof(struct Msg));
printf("msg.cmd %d\n",msg->cmd);
sqlite3_close(database);
}
// 把 负责处理客户端通信的函数改成线程的工作函数
void* hanld_client(void* v)
{
int client_socket = (int)v;
struct Msg msg;
while(1)
{
// 从客户端读一个结构体数据
int ret = read(client_socket, &msg, sizeof(msg));
//printf("2:%d,%d\n",msg.cmd,sizeof(msg));
if (ret == -1)
{
perror ("read");
break;
}
// 代表客户端退出
if (ret == 0)
{
printf ("客户端退出\n");
break;
}
switch (msg.cmd)
{
case 1 : // 客户但进行注册
reg(client_socket, &msg);
break;
case 2 : // 客户端进行群聊天
chat(client_socket, &msg);
break;
case 3 : // 客户端进行私聊
chat2(client_socket, &msg);
break;
case 4: //客户进行登录;
enter(client_socket,&msg);
break;
case 5: //显示在线好友列表;
display(client_socket,&msg);
break;
case 6: //注销用户;
logout(client_socket,&msg);
break;
}
}
close (client_socket);
}
int main()
{
// 初始化套接字
int listen_socket = init_socket();
while (1)
{
// 获取与客户端连接的套接字
int client_socket = MyAccept(listen_socket);
// 创建一个线程去处理客户端的请求,主线程依然负责监听
pthread_t id;
pthread_create(&id, NULL, hanld_client, (void *)client_socket);
pthread_detach(id); // 线程分离
}
close (listen_socket);
return 0;
}
(2)、客户端代码
这里写代码片#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sqlite3.h>
#define PORT 9999
void enter( int );
char myName[20];
char Sex[20];
struct Msg
{
char information[1024]; //发送消息;
char msg[1024]; // (姓名)消息内容
int id;
char sex[20];
int age;
int password;
char toname[20]; //消息发给谁
char fromname[20]; //消息从谁的那边发出来
int cmd; // 消息类型
char online_name[20]; //在线姓名;
};
// 主界面;
void interface()
{
printf ("\t\t 1、注册\n");
printf ("\t\t 2、登录\n");
}
void interface3()
{
//printf ("\t\t 1、注册\n");
printf ("\t\t 2、登录\n");
}
//登录后的界面;
void interface2()
{
printf ("\t\t 1、发送文件\n");
printf ("\t\t 2、群聊\n");
printf ("\t\t 3、私聊\n");
printf ("\t\t 4、注销当前用户\n");
printf ("\t\t 5、显示好友列表\n");
printf ("\t\t 6、查看本地聊天记录\n");
}
//读写分离的读线程的回调函数;
void * readMsg(void *v)
{
int socketfd = (int)v;
struct Msg msg;
while (1)
{
read(socketfd, &msg, sizeof(msg));
//printf("test msg.cmd %d,%d\n",msg.cmd,sizeof(msg));
switch (msg.cmd)
{
case -5: //私聊用户不存在;
printf("你想私聊的用户不存在\n");
break;
case 10: //用户存在并且在线;
printf("用户存在并且在线\n");
break;
case -6: //用户不在线;
printf("用户存在但是不在线\n");
break;
case 2: // 群聊
printf ("收到一条消息: %s\n", msg.information);
break;
case 3: // 私聊
printf ("%s 给你发一条消息:%s\n", msg.fromname, msg.information);
break;
case 4: // 显示在线好友列表;
printf("在线的好友有:%s\n",msg.online_name);
printf("3333333\n");
break;
case 6: //用户注销;
printf("注销成功!\n");
return;
break;
}
}
}
//注册函数
int reg(int socketfd)
{
struct Msg msg;
//msg.cmd = 1;
while(1)
{
msg.cmd = 1;
//memset(&msg,0,sizeof(msg));//清零
printf ("请输入用户名:\n");
scanf("%s",&(msg.msg));
printf ("请输入用户的密码 :\n");
scanf("%d",&(msg.password));
write (socketfd, &msg, sizeof(msg));
//来自服务器端的回复:到底有没有注册成功,成功了返回一个标号,要是失败了,那么为什么失败,返回另外一个标号,最后失败的需要全部重新注册
read (socketfd, &msg, sizeof(msg));
if (msg.cmd == 1001)
{
printf ("你已注册成功,欢迎加入聊天室\n");
/* int a;
while (1)
{
printf("\n");
interface3();
printf("\n");
printf("\n");
sleep(1);
printf("\t\t*_*输入功能:\n");
scanf("%d",&a);
printf("\n");
while(getchar()!= '\n');
switch(a)
{
case 2: //登录;
enter(socketfd);
break;
}
system("clear");
}
*/
break;
}
//用户已经被注册过了,重新注册
if (msg.cmd == -2)
{
printf ("该用户名已经被注册,请重新进行注册\n");
memset(&msg,0,sizeof(msg));//清零
continue;
}
//聊天室的登录人数上限已经到达注册失败;
else if(msg.cmd == -1)
{
printf ("聊天室的上限已经到达,请重新进行注册\n");
continue;
}
}
sleep(1);
}
//群聊函数,
void chat(int socketfd)
{
struct Msg msg;
msg.cmd = 2;
printf ("请输入要发送的内容: \n");
//fgets(msg.information, 1024, stdin);
scanf("%s",msg.information);
write (socketfd, &msg, sizeof(msg));
}
//私聊;
void chat2(int socketfd)
{
struct Msg msg;
msg.cmd = 3;
printf ("请输入要发送的对象名称: \n");
//fgets(msg.toname, 20, stdin);
scanf("%s",msg.toname);
printf ("请输入要发送的内容: \n");
//fgets(msg.information, 1024, stdin);
scanf("%s",msg.information);
printf("输入自己的名字 : \n");
//fgets(msg.fromname, 20, stdin);
scanf("%s",msg.fromname);
write (socketfd, &msg, sizeof(msg));
//调用存储聊天记录的函数;
int ret = insert_msg( &msg );
if(ret == 1)
{
printf("成功保存本地记录\n");
}
}
//注销用户;
void logout(int socketfd)
{
char ch[2];
printf("是否注销此用户: y/n \n");
scanf("%c",ch);
if(ch[0] == 'y')
{
struct Msg msg;
msg.cmd = 6;
printf("输入您的用户名:\n");
scanf("%s",msg.msg);
write(socketfd, &msg,sizeof(struct Msg));
}
else
{
return ;
}
}
//显示在线好友列表;
void display(int socketfd)
{
struct Msg msg;
printf("我想知道在线的好友列表\n");
msg.cmd = 5;
write(socketfd, &msg, sizeof(struct Msg));
}
//登录函数;
void enter(int socketfd)
{
struct Msg msg;
msg.cmd = 4;
printf("请输入用户名 :\n");
scanf("%s",&(msg.msg));
printf("请输入用户密码:\n");
scanf("%d",&(msg.password));
write (socketfd, &msg, sizeof(msg)); //消息写过去,等待回复,例如什么用户不存在,密码错误;
if(msg.cmd == 5)
{
}
read(socketfd ,&msg ,sizeof(msg)); //读取来自服务器的返回的消息,进行客户端屏幕打印;
//printf("%d,%d\n",msg.cmd,sizeof(msg));
if(msg.cmd == 1002)
{
printf("恭喜你登录成功\n");
//登录完成之后开始读写分离;
sleep(1);
pthread_t id;
pthread_create(&id, NULL, readMsg, (void *)socketfd);
pthread_detach(id); // 线程分离
int a;
while (1)
{
interface2();
printf("\n");
printf("\n");
sleep(1);
printf("\t\t*_*输入功能:\n");
scanf("%d",&a);
printf("\n");
while(getchar()!= '\n');
switch(a)
{
case 1: //发送文件
send_file(socketfd);
break;
case 2: // 群聊天
chat(socketfd);
break;
case 3: // 私聊
chat2(socketfd);
break;
case 4 : //注销用户;
logout(socketfd);
return;
case 5: //显示在线好友列表;
display(socketfd);
sleep(1);
break;
case 6: //查看本地聊天记录;
look_msg(socketfd);
sleep(1);
break;
}
system("clear");
}
}
else if(msg.cmd == -1)
{
printf("密码不正确\n");
usleep(5*100000);
}
else if(msg.cmd == -2)
{
printf("该用户不存在\n");
sleep(0.5);
}
}
//聊天记录存到本地;
int insert_msg(struct Msg* msg)
{
sqlite3 *database;
int ret = sqlite3_open( "msg.db", &database );
if(ret != SQLITE_OK)
{
perror("sqlite3_open");
return -1;
}
printf("数据库打开成功\n");
//下边一大段是创建一个表
char *errmsg = NULL;
char *sql = "create table if not exists msg (fromname TEXT, information TEXT,toname TEXT)"; //sql语句
ret = sqlite3_exec(database ,sql, NULL ,NULL ,&errmsg);//执行sql语句的函数;
if(ret != SQLITE_OK)
{
printf("数据库创建操作失败 %s\n",errmsg);
return -1;
}
//往表插入数据;
//insert into student values(1, 'Zhang', 'M', 18);
char buf[100];
printf("%s\n",msg->fromname);
sprintf(buf," insert into msg values('%s','%s' ,'%s')",msg->fromname ,msg->information ,msg->toname );
//设置主键之后,如果插入的id已经存在那么就会返回错误,一班来说返回的错误都是插入重复这里有点取巧;
ret = sqlite3_exec(database ,buf, NULL ,NULL ,&errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库插入操作失败:%s\n", errmsg);
return -1;
}
printf("聊天记录成功保存到本地数据库*_*\n");
sqlite3_close(database);
return 1;
}
//查看本地聊天记录;
int look_msg(int socketfd)
{
//本地创建一张数据库表,存放本地的消息;
//查看不就简单了,直接打开本地的数据库就好了;
sqlite3 *database;
int ret = sqlite3_open( "msg.db", &database );
if(ret != SQLITE_OK)
{
perror("sqlite3_open");
return -1;
}
printf("数据库打开成功\n");
char ** resultp;
char *errmsg = NULL;
int nrow;
int ncolumn;
//进行什么操作;
char *sql = "select * from msg";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg );
if(ret != SQLITE_OK)
{
printf("数据库操作失败 %s\n",errmsg);
return -1;
}
int i;
printf("nrow = %d,ncolumn = %d",nrow, ncolumn);
for(i = 0; i < (nrow +1)* ncolumn;i++)
{
if(i % ncolumn == 0)
{
printf("\n");
}
printf("%-18s" ,resultp[i]);
}
printf("\n");
sqlite3_free_table(resultp); //之前的char**resultp (数组)等于malloc了一个空间来储存数据库的数据;所以需要释放;
sqlite3_close(database);
}
//传送文件;
send_file(int socketfd)
{
}
// 客户端向服务器发送数据处理函数;
void ask_server(int socketfd)
{
int a ;
while (1)
{
//char ch[2];
int a;
while (1)
{
printf("\n");
interface();
printf("\n");
printf("\n");
sleep(1);
printf("\t\t*_*输入功能:\n");
scanf("%d",&a);
printf("\n");
while(getchar()!= '\n');
switch(a)
{
case 1: //注册;
reg(socketfd);
printf("注册注册注册\n");
break;
case 2: //登录;
enter(socketfd);
break;
}
system("clear");
}
}
}
int main()
{
// 创建与服务器通信的套接字
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd == -1)
{
perror ("socket");
return -1;
}
// 连接服务器
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; // 设置地址族
addr.sin_port = htons(PORT); // 设置本地端口
inet_aton("127.0.0.1",&(addr.sin_addr));
// 连接服务器,如果成功,返回0,如果失败,返回-1
// 成功的情况下,可以通过socketfd与服务器进行通信
int ret = connect(socketfd, (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1)
{
perror ("connect");
return -1;
}
printf ("成功连上服务器\n");
//开始进行操作;
ask_server(socketfd);
// 关闭套接字
close(socketfd);
return 0;
}