这是一个最近写的小项目,想要总结一下,如有不合理地方,请多指教!
一、项目名称
基于TCP的在线词典
二、实现功能
1.新用户使用时需要注册,用户名不可以和已有用户重复。
2.登录时需要校验用户名及密码。
3.输入要查询的单词后返回查询结果,可循环查询,在输入#后退出查询界面。
4.查询历史记录时需要返回历史查询成功的所有单词及查询时的日期时间。
5. 服务器支持并发,支持多用户同时在线使用。
三、初步构思
1.用户注册需要保存用户名和密码,查询单词后保存记录,所以需要在数据库中创建两个表分别保存。
2.定义消息结构体,包含传递信息的类型(注册、登录、查询、查询历史记录),用户名,密码,传递信息(密码和传递信息可用同一个数组)。
3.可以不用把存放单词的文件导入数据库,写代码时可以顺便复习标准IO方面的一些知识点。
4.使用多路IO复用实现TCP并发。
四、流程图
客户端
服务器
五、代码实现
server.c
#include "dictionary.h"
int main(int argc,const char *argv[]){
/*your code*/
if(3 != argc){
printf("usage: <%s> <ip> <port>\n", argv[0]);
return -1;
}
//打开数据库
sqlite3 *my_db;
if(SQLITE_OK != sqlite3_open(DATABASE, &my_db)){
printf("sqlite3 error: %s\n", sqlite3_errmsg(my_db));
return -1;
}
//创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
ERRLOG("socket error");
//填充服务器网络信息结构体
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddrlen = sizeof(serveraddr);
//设置客户端网络信息结构体
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t clientaddrlen = sizeof(clientaddr);
//绑定套接字和服务器网络信息结构体
if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddrlen))
ERRLOG("bind error");
//设置套接字为被动监听状态
if(-1 == listen(sockfd, 5))
ERRLOG("listen error");
//使用IO多路复用实现并发
int max_fd = 0;
fd_set readfds;
fd_set readfds_temp;
FD_ZERO(&readfds);
FD_ZERO(&readfds_temp);
FD_SET(sockfd, &readfds);
max_fd = max_fd > sockfd ? max_fd : sockfd;
int ret;
int acceptfd;
int nbtyes;
msg_t msg;
memset(&msg, 0, sizeof(msg));
while(1){
readfds_temp = readfds;
if(-1 == (ret = select(max_fd+1, &readfds_temp, NULL, NULL, NULL)))
ERRLOG("select error");
for(int i = 3; i < max_fd+1 && ret != 0; i++){
if(FD_ISSET(i, &readfds_temp)){
if(i == sockfd){
if(-1 == (acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddrlen)))
ERRLOG("accept error");
printf("客户端[%d]号[%s:%d]成功连接...\n", acceptfd, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
FD_SET(acceptfd, &readfds);
max_fd = max_fd > acceptfd ? max_fd : acceptfd;
}else{
if(-1 == (nbtyes = recv(i, &msg, sizeof(msg), 0))){
ERRLOG("recv error");
}else if(0 == nbtyes){
printf("客户端[%d]号[%s:%d]断开连接...\n", i, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
FD_CLR(i, &readfds);
close(i);
continue;
}
// printf("type = %d\n", msg.type);
// printf("name = %s\n", msg.name);
switch(msg.type){
case R:
DO_register(i, &msg, my_db);
break;
case L:
DO_login(i, &msg, my_db);
break;
case Q:
DO_query(i, &msg, my_db);
break;
case H:
DO_history(i, &msg, my_db);
break;
}
}
ret--;
}
}
}
return 0;
}
client.c
#include "dictionary.h"
int main(int argc,const char *argv[]){
/*your code*/
if(3 != argc){
printf("usage: <%s> <ip> <port>\n", argv[0]);
return -1;
}
//创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
ERRLOG("socket error");
//填充服务器网络信息结构体
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddrlen = sizeof(serveraddr);
//与服务器进行连接
if(-1 == connect(sockfd, (struct sockaddr *)&serveraddr, serveraddrlen))
ERRLOG("connetc error");
//收发信息
int choose;
char buf[128] = {0};
msg_t msg;
memset(&msg, 0, sizeof(msg));
while(1){
print_menu1();
scanf("%d", &choose);
switch(choose){
case 1:
do_register(sockfd, &msg);
break;
case 2:
if(1 == do_login(sockfd, &msg))
goto NEXT;
break;
case 3:
close(sockfd);
return 0;
}
}
NEXT:
while(1){
print_menu2();
scanf("%d", &choose);
switch(choose){
case 1:
do_query(sockfd, &msg);
break;
case 2:
do_history(sockfd, &msg);
break;
case 3:
close(sockfd);
return 0;
}
}
return 0;
}
dictionary.c
#include "dictionary.h"
//服务器中的函数
void DO_register(int i, msg_t *msg, sqlite3 *my_db){
char sqlbuf[600] = {0};
int ret;
//组装sql语句
sprintf(sqlbuf, "INSERT INTO user VALUES('%s', '%s')", msg->name, msg->data);
//执行sql语句
if(SQLITE_OK != (ret = sqlite3_exec(my_db, sqlbuf, NULL, NULL, NULL))){
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, sqlite3_errmsg(my_db));
if(19 == ret){
strcpy(msg->data, "user name already exist!!!");
if(-1 == send(i, msg, sizeof(msg_t), 0)){
perror("send error");
return;
}
}
return;
}
strcpy(msg->data, "OK");
if(-1 == send(i, msg, sizeof(msg_t), 0)){
perror("send error");
return;
}
return;
}
void DO_login(int i, msg_t *msg, sqlite3 *my_db){
char sqlbuf[600] = {0};
int ret;
//组装sql语句
sprintf(sqlbuf, "SELECT * FROM user WHERE name='%s' AND passwd='%s'", msg->name, msg->data);
//执行sql语句
if(SQLITE_OK != (ret = sqlite3_exec(my_db, sqlbuf, NULL, NULL, NULL))){
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, sqlite3_errmsg(my_db));
strcpy(msg->data, "name or passwd is wrong!!!");
if(-1 == send(i, msg, sizeof(msg_t), 0)){
perror("send error");
return;
}
return;
}
strcpy(msg->data, "OK");
if(-1 == send(i, msg, sizeof(msg_t), 0)){
perror("send error");
return;
}
return;
}
void DO_query(int i, msg_t *msg, sqlite3 *my_db){
char sqlbuf[600] = {0};
int ret;
char word[256] = {0};
strcpy(word, msg->data);
int len = strlen(msg->data);
char buf[300] = {0};
int flag = 0;
FILE *fp;
if(NULL == (fp = fopen("./dict.txt", "r"))){
strcpy(msg->data, "dict can not open");
if(-1 == send(i, msg, sizeof(msg_t), 0)){
perror("send error");
return;
}
}
while(fgets(buf, sizeof(buf), fp) != NULL){
if((0 == strncmp(buf, word, len)) && (buf[len] == ' ')){
char *p = buf+len;
while(*p == ' '){
p++;
}
sprintf(msg->data, "%s", p);
if(-1 == send(i, msg, sizeof(msg_t), 0)){
perror("send error");
return;
}
flag = 1;
break;
}
}
if(0 == flag){
strcpy(msg->data, "word not found\n");
if(-1 == send(i, msg, sizeof(msg_t), 0)){
perror("send error");
return;
}
}
//保存查询记录
time_t ts;
struct tm *tm;
char date[32] = {0};
if((time_t)-1 == (ts = time(NULL))){
perror("time error");
return;
}
if(NULL == (tm = localtime(&ts))){
perror("localtime error");
return;
}
sprintf(date, "%d-%02d-%02d %02d:%02d:%02d", \
tm->tm_year + 1900,tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
// printf("%s", date);
//组装sql语句
sprintf(sqlbuf, "INSERT INTO record VALUES('%s', '%s', '%s')", msg->name, date, word);
//执行sql语句
if(SQLITE_OK != (ret = sqlite3_exec(my_db, sqlbuf, NULL, NULL, NULL))){
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, sqlite3_errmsg(my_db));
return;
}
return;
}
void DO_history(int i, msg_t *msg, sqlite3 *my_db){
char sqlbuf[600] = {0};
int ret;
//组装sql语句
sprintf(sqlbuf, "SELECT * FROM record WHERE name='%s'", msg->name);
//执行sql语句
if(SQLITE_OK != (ret = sqlite3_exec(my_db, sqlbuf, callback, (void *)&i, NULL))){
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, sqlite3_errmsg(my_db));
return;
}
strcpy(msg->data, "*****OVER*****");
if(-1 == send(i, msg, sizeof(msg_t), 0)){
perror("send error");
return;
}
return;
}
int callback(void *arg, int ncolumn, char **f_value, char **f_name){
int i = *(int *)arg;
msg_t msg;
sprintf(msg.data, "%s : %s", f_value[1], f_value[2]);
if(-1 == send(i, &msg, sizeof(msg_t), 0)){
perror("send error");
return -1;
}
return 0;
}
//客户端中的函数
void print_menu1(){
printf("----------------------------------\n");
printf("| 1: register 2: login 3: quit |\n");
printf("----------------------------------\n");
printf("please choose: ");
return;
}
void print_menu2(){
printf("---------------------------------\n");
printf("| 1: query 2: history 3: quit |\n");
printf("---------------------------------\n");
printf("please choose: ");
return;
}
void do_register(int sockfd, msg_t *msg){
msg->type = R;
printf("input your name: ");
scanf("%s", msg->name);
printf("input you passwd: ");
scanf("%s", msg->data);
if(-1 == send(sockfd, msg, sizeof(msg_t), 0)){
perror("send error");
return;
}
if(-1 == recv(sockfd, msg, sizeof(msg_t), 0)){
perror("recv error");
return;
}
printf("regiser: %s\n", msg->data);
return;
}
int do_login(int sockfd, msg_t *msg){
msg->type = L;
printf("input your name: ");
scanf("%s", msg->name);
printf("input you passwd: ");
scanf("%s", msg->data);
if(-1 == send(sockfd, msg, sizeof(msg_t), 0))
ERRLOG("send error");
if(-1 == recv(sockfd, msg, sizeof(msg_t), 0))
ERRLOG("recv error");
printf("login: %s\n", msg->data);
if(0 == strcmp(msg->data, "OK")){
return 1;
}else{
return -1;
}
}
void do_query(int sockfd, msg_t *msg){
msg->type = Q;
while(1){
printf("input word: ");
scanf("%s", msg->data);
if(0 == strcmp(msg->data, "#"))
break;
if(-1 == send(sockfd, msg, sizeof(msg_t), 0)){
perror("send error");
return;
}
if(-1 == recv(sockfd, msg, sizeof(msg_t), 0)){
perror("recv error");
return;
}
printf(">>>>%s", msg->data);
}
return;
}
void do_history(int sockfd, msg_t *msg){
msg->type = H;
if(-1 == send(sockfd, msg, sizeof(msg_t), 0)){
perror("send error");
return;
}
while(1){
if(-1 == recv(sockfd, msg, sizeof(msg_t), 0)){
perror("recv error");
return;
}
if(0 == strcmp(msg->data, "*****OVER*****"))
return;
printf("%s\n", msg->data);
}
return;
}
dictionary.h
#ifndef __DICTIONARY_H__
#define __DICTIONARY_H__
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/select.h>
#include <unistd.h>
#include <sqlite3.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#define ERRLOG(msg) do{\
printf("%s:%s:%d\n", __FILE__, __func__, __LINE__);\
perror(msg);\
return -1;\
}while(0)
#define R 1 // 注册
#define L 2 // 登录
#define Q 3 // 查询
#define H 4 // 历史记录查询
typedef struct {
int type;
char name[32];
char data[256];// 密码或者查询的单词
} msg_t;
#define DATABASE "dictionary.db"
//客户端中的函数
void DO_register(int i, msg_t *msg, sqlite3 *my_db);
void DO_login(int i, msg_t *msg, sqlite3 *my_db);
void DO_query(int i, msg_t *msg, sqlite3 *my_db);
void DO_history(int i, msg_t *msg, sqlite3 *my_db);
int callback(void *arg, int ncolumn, char **f_value, char **f_name);
//客户端中的函数
void print_menu1();
void print_menu2();
void do_register(int sockfd, msg_t *msg);
int do_login(int sockfd, msg_t *msg);
void do_query(int sockfd, msg_t *msg);
void do_history(int sockfd, msg_t *msg);
#endif
六、最终效果
并发: