【在线词典】项目实现

15_Dictionary

在线词典

搭建客户端-服务器架构

准备必要的资源

整理原始数据

整理英汉双语对照表,将XLSX格式转换成CSV格式,准备好vocabulary_list.csv文件备用

注意:CSV格式的文件必须使用UTF-8的字符集;

建立mydatabase.db数据库,并创建dictionary表;

shell命令行终端输入sqlite3 mydatabase.db

c 复制代码
create table dictionary (
English text not null,
Chinese text not null);

sqlite3当中CSV格式的导入

c 复制代码
.mode csv
.import vocabulary_list.csv dictionary

查询数据

c 复制代码
select * from dictionary where English='main';

如果显示如下信息表示配置成功:

c 复制代码
main,"a.主要的,最重要的"

实现服务端代码:

udp.h

包含必要的头文件,定义必要的宏,定义函数指针

c 复制代码
#ifndef _UDP_H_
#define _UDP_H_

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <errno.h>
#include <arpa/inet.h>

#define ErrExit(msg) printf("[%s:%d]%s:%s", __FUNCTION__, __LINE__,msg, strerror(errno)), exit(EXIT_FAILURE)

#endif

udp.c

实现UDP通信,预留udp_main接口

c 复制代码
#include "udp.h"

extern void udp_main(const int fd, const struct sockaddr_in *addr);

int main(int argc, const char *argv[])
{
	/* 1.检查参数 */
	if(argc < 3) {
		printf("[%s][addr][port]\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	/* 2.创建数据报套接字 */
	int fd = socket(AF_INET, SOCK_DGRAM, 0);
	if(fd < 0)
		ErrExit("socket");

	/* 3.设置通信结构体 */
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) );
	if( inet_aton(argv[1], &addr.sin_addr) == 0) {
		printf("[%s:%d] Invalid address\n", __FUNCTION__, __LINE__);
		exit(EXIT_FAILURE);
	}

	/* 4.绑定通信结构体 */
	if( bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) )
		ErrExit("bind");

	/* 5.处理客户端数据 */
	udp_main(fd, &addr);
	
	/* 6.关闭套接字 */
	close(fd);
	return 0;
}

udp_main.c

预留的客户端数据处理接口

c 复制代码
#include "udp.h"

/* 其他必要的环境已封装好了,只需要实现与客户端的交互即可
 * 这里的fd是服务端的socket,
 * addr是服务端的地址*/
void udp_main(const int fd, const struct sockaddr_in *addr) {
	printf("udp main test.\n");
}

实现词典查询功能

在文件udp_main.c实现词典查询功能

c 复制代码
#include "udp.h"
#include <ctype.h>
#include <sqlite3.h>

#define DATABASE_NAME "mydatabase.db"

int callback(void *, int, char **, char **);

/* 其他必要的环境已封装好了,只需要实现与客户端的交互即可
 * 这里的fd是服务端的socket,
 * addr是服务端的地址*/
void udp_main(const int fd, const struct sockaddr_in *addr) {
	int ret = 0, rc;
	struct sockaddr_in client_addr;
	socklen_t addrlen = sizeof(client_addr);
	sqlite3 *db;
	char buf[BUFSIZ] = {};
	char *sql_query, *errmsg;

	/* 1.打开数据库 */
	if( (rc = sqlite3_open(DATABASE_NAME, &db) ) ) {
		printf("[%s:%d]无法打开数据库: %s\n", __FUNCTION__, __LINE__, sqlite3_errmsg(db) );
		exit(0);
	}
	/* 2.循环处理客户端数据 */
	while(1) {
		/* 2.1.接收客户端数据 */
		do {
			ret = recvfrom(fd, buf, BUFSIZ, 0, (struct sockaddr *)&client_addr, &addrlen);
		}while(ret < 0 && errno == EINTR);
		if(ret < 0)
			ErrExit("recvfrom");

		printf("[%s:%d]收到的数据: {%s}\n", __FUNCTION__, __LINE__, buf);
		/* 2.2.提取需要翻译的单词 */
		for(ret = 0; isalpha(buf[ret]) || buf[ret] == ' '; ret++);
		buf[ret] = '\0';
		/* 2.3.用SQL语句进行查询 */
		sql_query = sqlite3_mprintf("select * from dictionary where english like '%s'", buf);
		rc = sqlite3_exec(db, sql_query, callback, buf, &errmsg);
		if(rc != SQLITE_OK) {
			sprintf(buf, "查询失败:%s\n", errmsg);
			printf("[%s:%d]%s", __FUNCTION__, __LINE__, buf);
			sendto(fd, buf, strlen(buf) + 1, 0, (struct sockaddr *)&client_addr, addrlen);
			sqlite3_free(errmsg);
			continue;
		}
		sqlite3_free(sql_query);
		printf("[%s:%d]查询结果: {%s}\n", __FUNCTION__, __LINE__, buf);
		sendto(fd, buf, strlen(buf) + 1, 0, (struct sockaddr *)&client_addr, addrlen);
	}
	sqlite3_close(db);
	/* 3.关闭数据库,关闭fd,并且退出程序 */
	close(fd);
}

int callback(void *NotUsed, int argc, char **argv, char **ColName) {
	char *buf = NotUsed;
	if(argc == 2) {
		/* 把查询到的字符串复制给buf */
		strncpy(buf, argv[1], strlen(argv[1]) + 1 );
	} else
		buf[0] = '\0'; //如果失败就将字符串置空
	/* 将换行符替换为'\0' */
	buf[strlen(argv[1])] = '\n';
	buf[strlen(argv[1])+1] = '\0';
	return 0;
}

实现用户操作和服务端交互过程

接下来我们把文件组织成如下形式(执行tree命令可以看到):

备注:如果没安装:可以使用sudo apt-get install tree命令进行安装

设置环境变量

编辑.bashrc文件

执行sudo vim ~/.bashrc打开家目录下的.bashrc在文件末尾加上如下两句:

c 复制代码
export DICTIONARY_SERVER_HOST='127.0.0.1'
export DICTIONARY_SERVER_PORT='8888'

然后再执行source ~/.bashrc让.bashrc生效

最后执行env | grep DICTIONARY,如果能看到我们刚设置的命令表示环境变量生效了

connect函数是否可以用在UDP通信当中?

在UDP通信中,使用connect()函数发出"虚拟连接请求",以便建立虚拟连接。通过调用connect()函数,可以将UDP套接字绑定到目标IP地址和端口上,从而为UDP数据报提供一个默认的发送目的地。这样,在后续的send()函数调用中,就不需要再指定IP地址和端口。

但是,需要注意的是,在UDP通信中,由于不存在真正的连接,因此connect()函数并不会像TCP中那样进行三次握手。它只是在内核中存储了该套接字的目标地址,并在后续的send()或recv()函数调用中使用该目标地址。

下面是一个示例代码片段,展示如何在UDP通信中使用connect()函数:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define BUF_SIZE 1024

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(1);
    }

    struct sockaddr_in dest_addr;
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_addr.s_addr = inet_addr(argv[1]);
    dest_addr.sin_port = htons(atoi(argv[2]));

    if (connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) == -1) {
        perror("connect");
        exit(1);
    }

    char buf[BUF_SIZE];
    printf("Enter message: ");
    fgets(buf, BUF_SIZE, stdin);

    if (send(sockfd, buf, strlen(buf), 0) == -1) {
        perror("send");
        exit(1);
    }

    close(sockfd);
    return 0;
}

编写客户端代码

这里我们对client下的文件进行编辑:

udp.c

c 复制代码
#include "udp.h"

extern void udp_main(const int fd, const char *argv);

int main(int argc, const char *argv[])
{
	/* 获取环境变量DICTIONARY_SERVER_PORT */
	char *port = getenv("DICTIONARY_SERVER_PORT");
	if(port == NULL) {
		printf("没有发现环境变量[DICTIONARY_SERVER_PORT]\n");
		exit(EXIT_FAILURE);
	}

	/* 获取环境变量DICTIONARY_SERVER_HOST */
	char *host = getenv("DICTIONARY_SERVER_HOST");
	if(port == NULL) {
		printf("没有发现环境变量[DICTIONARY_SERVER_HOST]\n");
		exit(EXIT_FAILURE);
	}

	/* 检查参数, 其中第二个参数是需要翻译的单词 */
	if(argc < 2) {
		printf("[%s][word]\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	
	/* 打印环境变量的值 */
	printf("服务器的主机IP是%s, 端口号是%s\n", host, port);
	int fd = socket(AF_INET, SOCK_DGRAM, 0);
	if(fd < 0)
		ErrExit("socket");

	/* 设置通信结构体 */
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(port) );
	if( inet_aton( host, &addr.sin_addr) == 0) {
		printf("[%s:%d] Invalid address\n", __FUNCTION__, __LINE__);
		exit(EXIT_FAILURE);
	}

	/* 发起连接请求,注意UDP连接没有三次握手, 不存在连接失败, 只是确定接受端而已 */
	if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) )
		ErrExit("connect");

	/* 执行客户端处理程序 */
	udp_main(fd, argv[1]);
	
	/* 关闭套接字 */
	close(fd);

	return 0;
}

udp_main.c

c 复制代码
#include "udp.h"

/* 其他必要的环境已封装好了,只需要实现与客户端的交互即可
 * 这里的fd是服务端的socket,
 * addr是服务端的地址*/
extern void udp_main(const int fd, const char *argv) {
	char buf[BUFSIZ] = {};
	send(fd, argv, strlen(argv) + 1, 0);
	recv(fd, buf, BUFSIZ, 0);
	printf("buf=%s\n", buf);
}

编译

执行gcc *.c -o test -Wall -lsqlite3 -I ../head/, 得到test的可执行文件;

然后分别运行两边的test文件:

得到类似这样的结果表示,代码没有问题,可以得到想要的结果
至此就实现了基本功能,接下来再实现其它附加的功能

相关推荐
清梦20205 分钟前
经典问题---跳跃游戏II(贪心算法)
算法·游戏·贪心算法
滴水之功15 分钟前
VMware OpenWrt怎么桥接模式联网
linux·openwrt
枯无穷肉27 分钟前
stm32制作CAN适配器4--WinUsb的使用
stm32·单片机·嵌入式硬件
Dream_Snowar28 分钟前
速通Python 第四节——函数
开发语言·python·算法
ldinvicible34 分钟前
How to run Flutter on an Embedded Device
linux
Altair澳汰尔41 分钟前
数据分析和AI丨知识图谱,AI革命中数据集成和模型构建的关键推动者
人工智能·算法·机器学习·数据分析·知识图谱
不过四级不改名67743 分钟前
基于HAL库的stm32的can收发实验
stm32·单片机·嵌入式硬件
嵌入式科普1 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
嵌入式大圣1 小时前
单片机UDP数据透传
单片机·嵌入式硬件·udp
A懿轩A1 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列