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文件:
得到类似这样的结果表示,代码没有问题,可以得到想要的结果
至此就实现了基本功能,接下来再实现其它附加的功能