文章目录
- 前言
- 一、什么是并发服务器
- 二、服务器的实现
- 三、客户端的实现
- 四、代码测试结果
- 五、代码测试注意
- 总结
前言
本文是我在IO进线程、网络编程学习阶段的练习项目。项目基于linux平台,利用多进程实现并发服务器;利用sqlite3数据库、TCP传输控制协议实现了多个客户端同时访问服务器、页面菜单、用户注册、用户登录、单词词义查询等功能。
-
一、什么是并发服务器
为了提高服务器的并发处理能力,在这里又引入了并发服务器的模型。并发服务器解决了循环服务器的缺陷,即可以在同一时刻响应多个客户端的请求。其基本设计思想是在服务器端采用多任务机制(即多进程或多线程),分别为每一个客户端创建一个任务处理。此项目就是利用多进程机制实现的并发服务器。
-
二、服务器的实现
server_tcp.c
/*===============================================
* 文件名称:server_tcp.c
* 创 建 者:hclhy9191
* 创建日期:2022年08月03日
* 描 述:
================================================*/
#include "server_tcp.h"
#include "sqlite3_server.h"
#define PORT_NUMBER 6666
#define IP_NUMBER "0.0.0.0"
int server_init(void)
{
int sockfd = socket(AF_INET , SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//设置端口、IP可以重复使用
int opt = 1;
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0){
perror("setsockopt");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT_NUMBER);
addr.sin_addr.s_addr = inet_addr(IP_NUMBER);
if(0 > bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
if(0 > listen(sockfd, 5))
{
perror("listen");
return -1;
}
printf("server init success!\n");
return sockfd;
}
int wait_client(int sockfd)
{
struct sockaddr_in caddr;
socklen_t len = sizeof(caddr);
int connfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if(connfd < 0)
{
perror("accept");
return -1;
}
printf("server accept success!\n");
return connfd;
}
int data_communicate(int connfd, char *name)
{
char word[128] = {0};
int ret = read(connfd, word, sizeof(word));
if(ret < 0)
{
perror("read1");
return -1;
}
else if(ret == 0)
{
printf("clinet leaved!\n");
close(connfd);
exit(0);
}
printf("word_com:%s \n", word);
char translation[512] = {0};
if(0 > sqlite3_select(word, translation))
{
printf("select failure\n");
char err[32] = "没有为您找到相关单词\n";
write(connfd, err, strlen(err));
return -1;
}
else
{
write(connfd, translation, strlen(translation));
}
return 0;
}
int send_sigal_success(int connfd)
{
char buf[32] = "success";
write(connfd, buf, sizeof(buf));
return 0;
}
int send_sigal_err(int connfd)
{
char buf[32] = "error";
write(connfd, buf, sizeof(buf));
return 0;
}
server_tcp.h
/*===============================================
* 文件名称:server_tcp.h
* 创 建 者:hclhy9191
* 创建日期:2022年08月03日
* 描 述:
================================================*/
#ifndef SERVER_TCP_H_
#define SERVER_TCP_H_
//tcp_server
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "sqlite3_server.h"
//sqlite3
int server_init(void);
int wait_client(int sockfd);
int data_communicate(int connfd,char *name);
int send_sigal_success(int connfd);
int send_sigal_err(int connfd);
#endif
sqlite3_server.c
/*===============================================
* 文件名称:sqlite3_server.c
* 创 建 者:hclhy9191
* 创建日期:2022年08月04日
* 描 述:
================================================*/
#include "sqlite3_server.h"
int sqlite3_select(char *word, char *translation)
{
sqlite3 *db;
printf("word: %s\n",word);
if(SQLITE_OK != sqlite3_open("Dirctionary.db",&db))
{
printf("open: %s\n",sqlite3_errmsg(db));
return -1;
}
char sql[128] = {0};
sprintf(sql,"select translation from dict where word='%s';",word);
int nRow, nCloumn;
char **resultp;
if( sqlite3_get_table(db, sql, &resultp, &nRow, &nCloumn, NULL) != SQLITE_OK){
printf("select: %s\n", sqlite3_errmsg(db));
return -1;
}
if(NULL == resultp[nCloumn])
{
return -1;
}
strcpy(translation, resultp[nCloumn]);
return 0;
}
int sqlite3_usr_create(int connfd)
{
char name[32] = {0};
char passwd[32] = {0};
read(connfd, name, sizeof(name));
if(0 > dup_name(name))
{
send_sigal_err(connfd);
return -1;
}
else
{
send_sigal_success(connfd);
}
read(connfd, passwd, sizeof(passwd));
printf("name: %s passwd: %s\n",name, passwd);
sqlite3 *db;
if(SQLITE_OK != sqlite3_open("Dirctionary.db",&db))
{
printf("open: %s\n",sqlite3_errmsg(db));
return -1;
}
//建立用户目录
char sql[128] = {0};
char **resultp;
sprintf(sql, "create table if not exists usr(name text, passwd varchar(12) );");
if( sqlite3_get_table(db, sql, &resultp, NULL, NULL, NULL) != SQLITE_OK){
printf("create: %s\n", sqlite3_errmsg(db));
return -1;
}
//插入新建用户名和密码
sprintf(sql, "insert into usr values('%s', '%s');", name, passwd);
if( sqlite3_get_table(db, sql, &resultp, NULL, NULL, NULL) != SQLITE_OK)
{
printf("insert: %s\n", sqlite3_errmsg(db));
return -1;
}
printf("注册成功\n");
return 0;
}
int dup_name(char *name)
{
sqlite3 *db;
char **resultp;
if(SQLITE_OK != sqlite3_open("Dirctionary.db",&db))
{
printf("open: %s\n",sqlite3_errmsg(db));
return -1;
}
char sql[128] = {0};
sprintf(sql, "select name from usr where name='%s';",name);
int nRow, nCloumn;
if( sqlite3_get_table(db, sql, &resultp, &nRow, &nCloumn, NULL) != SQLITE_OK){
printf("select: %s\n", sqlite3_errmsg(db));
return -1;
}
if(NULL == resultp[nCloumn])
{
return 0;
}
else
{
printf("%s\n",resultp[nCloumn]);
printf("dup_name\n");
return -1;
}
}
int sqlite3_usr_enter(int connfd, char *name)
{
char passwd[32] = {0};
read(connfd, name, sizeof(name));
read(connfd, passwd, sizeof(passwd));
printf("name: %s passwd: %s\n",name, passwd);
sqlite3 *db;
char **resultp;
if(SQLITE_OK != sqlite3_open("Dirctionary.db",&db))
{
printf("open: %s\n",sqlite3_errmsg(db));
return -1;
}
char sql[128] = {0};
sprintf(sql, "select passwd from usr where name='%s';",name);
int nRow, nCloumn;
if( sqlite3_get_table(db, sql, &resultp, &nRow, &nCloumn, NULL) != SQLITE_OK){
printf("select: %s\n", sqlite3_errmsg(db));
return -1;
}
printf("passwd: %s\n", resultp[nCloumn]);
if(NULL == resultp[nCloumn])
{
printf("用户不存在\n");
return -1;
}
if( 0 == strcmp(passwd, resultp[nCloumn]))
{
printf("登陆成功\n");
return 0;
}
else
{
printf("用户名或密码错误\n");
return -1;
}
}
char my_list_enter(int connfd)
{
char list_enter;
int ret = read(connfd, &list_enter, 1);
if(ret < 0)
{
perror("read");
return -1;
}
else if(ret == 0)
{
printf("client leaved!\n");
exit(0);
}
printf("%c\n", list_enter);
switch(list_enter)
{
case REGISTER :
return '1';
break;
case ENTER :
return '2';
break;
case QUIT :
return '3';
break;
default :
printf("选项错误!\n");
return -1;
}
}
char my_list_select(int connfd)
{
char list_enter;
int ret = read(connfd, &list_enter, 1);
if(ret < 0)
{
perror("read");
return -1;
}
else if(ret == 0)
{
printf("client leaved!\n");
exit(0);
}
printf("%c\n", list_enter);
switch(list_enter)
{
case SELECT :
return '1';
break;
case FUNQUIT :
return '2';
break;
case HISTORY :
return '3';
break;
default :
printf("选项错误!\n");
return -1;
}
}
sqlite3_server.h
/*===============================================
* 文件名称:sqlite3_server.h
* 创 建 者:hclhy9191
* 创建日期:2022年08月04日
* 描 述:
================================================*/
#ifndef SQLITE3_SERVER_H_
#define SQLITE3_SERVER_H_
#include <sqlite3.h>
#include "server_tcp.h"
#define REGISTER '1'
#define SELECT '1'
#define ENTER '2'
#define FUNQUIT '2'
#define QUIT '3'
#define HISTORY '3'
int sqlite3_select(char *word, char *translation);
int sqlite3_usr_create(int connfd);
int sqlite3_usr_enter(int connfd, char *name);
char my_list_enter(int connfd);
char my_list_select(int connfd);
int sqlite3_history_in(char *name, char *word, char *translation);
int history(int connfd, char *name);
int dup_name(char *name);
#endif
main.c
/*===============================================
* 文件名称:main.c
* 创 建 者:hclhy9191
* 创建日期:2022年08月03日
* 描 述:
================================================*/
#include "server_tcp.h"
#include "sqlite3_server.h"
int main(int argc, char *argv[])
{
int sockfd = server_init();
while(1)
{
//主进程获取客户端连接套接字
int connfd = wait_client(sockfd);
//子进程与客户端交互
if(0 == fork())
{
int key = 0;
char name[32] = {0};
while(1)
{
memset(name, 0 , sizeof(name));
char list_enter;
while(1)
{ //初始菜单
list_enter = my_list_enter(connfd);
if(list_enter < 0)
{
send_sigal_err(connfd);
continue;
}
else
{
send_sigal_success(connfd);
}
switch(list_enter)
{ //注册
case REGISTER :
if(0 != sqlite3_usr_create(connfd))
{
send_sigal_err(connfd);
}
else
{
send_sigal_success(connfd);
}
break;
//登陆
case ENTER :
if ( 0 == sqlite3_usr_enter(connfd, name))
{
key = 1;
send_sigal_success(connfd);
}
else
{
send_sigal_err(connfd);
memset(name, 0, sizeof(name));
}
break;
//退出
case QUIT :
printf("client leaved!\n");
exit(0);
break;
}
if(key == 1)
{
break;
}
}
key = 0;
char list_select;
while(1)
{ //功能菜单
list_select = my_list_select(connfd);
switch(list_select)
{ //查询单词
case SELECT:
data_communicate(connfd, name);
break;
//退出登陆
case FUNQUIT:
key = 1;
break;
//查询历史记录
case HISTORY:
break;
}
if(key == 1)
{
break;
}
}
key = 0;
}
}
//close(connfd); //主进程关闭连接套接字
}
close(sockfd);
return 0;
}
-
三、客户端的实现
client_tcp.c
/*===============================================
* 文件名称:clinet_tcp.c
* 创 建 者:hclhy9191
* 创建日期:2022年08月03日
* 描 述:
================================================*/
#include "client_tcp.h"
#define PORT_NUMBER 6666
#define IP_NUMBER "192.168.12.149"
int client_init(void)
{
int sockfd = socket(AF_INET , SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
printf("client init success!\n");
return sockfd;
}
int connect_server(int sockfd)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT_NUMBER);
addr.sin_addr.s_addr = inet_addr(IP_NUMBER);
if(0 > connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)))
{
perror("connect");
return -1;
}
printf("client connect success!\n");
return 0;
}
int data_communicate(int sockfd, char *name)
{
//发送查询的单词
char buf[128] = {0};
printf("\033[1;36;40minput select word> \033[0m");
fflush(stdout);
//fgets(buf, sizeof(buf), stdin);
scanf("%s",buf);
getchar();
write(sockfd, buf, strlen(buf));
//接收服务器返回数据
char translation[512] = {0};
int ret = read(sockfd, translation, sizeof(translation));
if(ret < 0)
{
perror("read");
return -1;
}
if(0 != strcmp("没有为您找到相关单词\n",translation))
{
if(0 > sqlite3_history_in(name, buf, translation))
{
printf("history_in error!\n");
return -1;
}
}
printf("word:%s\t", buf);
printf("translation:%s\n",translation);
return 0;
}
int recv_sigal(int sockfd)
{
char buf[32] = {0};
read(sockfd, buf, sizeof(buf));
if (0 == strcmp("success",buf))
{
return 0;
}
else
{
return -1;
}
}
printf_vdio.c
#include "printf_vdio.h"
void my_printf_vdio()
{
printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t\t\t\t");
sleep(1);
int a,b;a=0;
while(a<=70)
{
system("clear");
b=1;
printf("\n\n\n\n\n\n\n\n\n\n\n\n\n");
while(b<=a)
{
printf(" ");
b++;
}
printf("┌════════┐...┌════════┐...┌.═════════┐ ╭═════╮ 欢迎使用\n");
b=1;
while(b<=a)
{ printf(" ");
b++;
}
printf("║ ║ ═ ║ ║ ═ ║ ║═ ║ ║\n");
b=1;
while(b<=a)
{ printf(" ");
b++;
}
printf("└⊙═⊙═⊙═⊙ ⊙═⊙═⊙═⊙ └⊙═⊙═⊙═⊙~~ ╰⊙═⊙╯\n");
usleep(100000);
a=a+1;
}
system("clear");
}
sqlite3_server.c
/*===============================================
* 文件名称:sqlite3_server.c
* 创 建 者:hclhy9191
* 创建日期:2022年08月04日
* 描 述:
================================================*/
#include "sqlite3_server.h"
char my_list_enter(int connfd)
{
printf("\033[1;34;40m*******************************************\n");
printf("****1.注册********2.登陆*********3.退出****\n");
printf("*******************************************\033[0m\n");
printf("\033[1;36;40minput list> \033[0m");
fflush(stdout);
char key;
scanf("%c",&key);
getchar();
printf("%c\n",key);
write(connfd, &key, 1);
system("clear");
return key;
}
char my_list_select(int connfd)
{
printf("\033[1;34;40m********************************************\n");
printf("***1.查询********2.退出*******3.历史记录****\n");
printf("********************************************\033[0m\n");
printf("\033[1;36;40minput list> \033[0m");
fflush(stdout);
char key;
scanf("%c",&key);
getchar();
printf("%c\n",key);
write(connfd, &key, 1);
system("clear");
return key;
}
int sqlite3_usr_create(int connfd)
{
printf("\033[1;34;40m*****************************************\n");
printf("**************注*********册**************\n");
printf("*****************************************\033[0m\n");
printf("\033[1;36;40minput name> \033[0m");
fflush(stdout);
char name[32] = {0};
scanf("%s",name);
getchar();
write(connfd, name, strlen(name));
if(0 > recv_sigal(connfd))
{
return -1;
}
printf("\033[1;36;40minput passwd> \033[0m");
fflush(stdout);
char passwd[32] = {0};
scanf("%s",passwd);
getchar();
write(connfd, passwd, strlen(passwd));
system("clear");
return 0;
}
int sqlite3_usr_enter(int connfd, char *name)
{
printf("\033[1;34;40m*****************************************\n");
printf("**************登*********陆**************\n");
printf("*****************************************\033[0m\n");
printf("\033[1;36;40minput name> \033[0m");
fflush(stdout);
scanf("%s",name);
getchar();
write(connfd, name, strlen(name));
printf("\033[1;36;40minput passwd> \033[0m");
fflush(stdout);
char passwd[32] = {0};
scanf("%s",passwd);
getchar();
write(connfd, passwd, strlen(passwd));
system("clear");
return 0;
}
int sqlite3_history_in(char *name, char *word, char *translation)
{
sqlite3 *db;
if(SQLITE_OK != sqlite3_open("Dirctionary.db",&db))
{
printf("open: %s\n",sqlite3_errmsg(db));
return -1;
}
//建立用户目录
char sql[128] = {0};
char **resultp;
sprintf(sql, "create table if not exists history(name text, word text, translation text);");
if( sqlite3_get_table(db, sql, &resultp, NULL, NULL, NULL) != SQLITE_OK){
printf("create: %s\n", sqlite3_errmsg(db));
return -1;
}
//插入新建用户名和密码
sprintf(sql, "insert into history values('%s', '%s', '%s');", name, word, translation);
if( sqlite3_get_table(db, sql, &resultp, NULL, NULL, NULL) != SQLITE_OK)
{
printf("insert: %s\n", sqlite3_errmsg(db));
return -1;
}
return 0;
}
int history(int connfd, char *name)
{
sqlite3 *db;
char **resultp;
if(SQLITE_OK != sqlite3_open("Dirctionary.db",&db))
{
printf("open: %s\n",sqlite3_errmsg(db));
return -1;
}
char sql[128] = {0};
sprintf(sql, "select word,translation from history where name='%s';",name);
int nRow, nCloumn;
if( sqlite3_get_table(db, sql, &resultp, &nRow, &nCloumn, NULL) != SQLITE_OK){
printf("select: %s\n", sqlite3_errmsg(db));
return -1;
}
printf("\033[5;33;40m********************************************\033[0m\n");
int k = 1;
for(int i = nCloumn; i < (nRow+1)*nCloumn; i++)
{
printf("%s\t",resultp[i]);
if(k % 2 == 0)
printf("\n");
k++;
}
printf("\033[5;33;40m********************************************\033[0m\n");
return 0;
}
main.c
/*===============================================
* 文件名称:main.c
* 创 建 者:hclhy9191
* 创建日期:2022年08月03日
* 描 述:
================================================*/
#include "client_tcp.h"
#include "sqlite3_server.h"
#include "printf_vdio.h"
int main(int argc, char *argv[])
{
int sockfd = client_init();
connect_server(sockfd);
while(1)
{
int count = 0;
char name[32] = {0};
my_printf_vdio();
while(1)
{
memset(name, 0, sizeof(name));
char key ; //初始菜单
key = my_list_enter(sockfd);
if(0 != recv_sigal(sockfd))
{
continue;
}
switch(key)
{ //注册
case REGISTER :
if(0 > sqlite3_usr_create(sockfd))
{
printf("\033[4;31;40m-------------用户名已存在--------------\033[0m\n");
}
if(0 > recv_sigal(sockfd))
{
printf("\033[4;31;40m---------------注册失败----------------\033[0m\n");
}
else
{
printf("\033[4;31;40m---------------注册成功----------------\033[0m\n");
}
break;
//登陆
case ENTER :
sqlite3_usr_enter(sockfd, name);
if(0 != recv_sigal(sockfd))
{
count = 2;
printf("\033[4;31;40m-------------密码或账户错误-------------\033[0m\n");
}
else
{
printf("\033[4;31;40m---------------登陆成功----------------\033[0m\n");
count = 1;
}
break;
//退出
case QUIT :
exit(0);
}
if(count == 1)
{
break;
}
else if(count == 2)
{
continue;
}
}
count = 0;
char list_select;
while(1)
{ //功能菜单
list_select = my_list_select(sockfd);
switch(list_select)
{ //查询单词
case SELECT:
data_communicate(sockfd, name);
break;
//退出登陆
case FUNQUIT:
count = 1;
break;
//查询历史记录
case HISTORY:
history(sockfd, name);
break;
}
if(count == 1)
{
break;
}
}
}
close(sockfd);
return 0;
}
client_tcp.h
/*===============================================
* 文件名称:client_tcp.h
* 创 建 者:hclhy9191
* 创建日期:2022年08月03日
* 描 述:
================================================*/
#ifndef CLIENT_TCP_H_
#define CLIENT_TCP_H_
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sqlite3.h>
int client_init(void);
int connect_server(int sockfd);
int data_communicate(int sockfd, char *name);
int recv_sigal(int sockfd);
extern int sqlite3_history_in(char *name, char *word, char *translation);
#endif
printf_vdio.h
/*===============================================
* 文件名称:printf_vido.h
* 创 建 者:hclhy9191
* 创建日期:2022年08月17日
* 描 述:
================================================*/
#ifndef PRINTF_VDIO_H
#define PRINTF_VDIO_H
#include<stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
void my_printf_vdio();
#endif
sqlite3_server.h
/*===============================================
* 文件名称:sqlite3_server.h
* 创 建 者:hclhy9191
* 创建日期:2022年08月04日
* 描 述:
================================================*/
#ifndef SQLITE3_SERVER_H_
#define SQLITE3_SERVER_H_
#include <sqlite3.h>
#include "client_tcp.h"
#define SELECT '1'
#define REGISTER '1'
#define ENTER '2'
#define FUNQUIT '2'
#define QUIT '3'
#define HISTORY '3'
int sqlite3_usr_create(int connfd);
int sqlite3_usr_enter(int connfd, char *name);
char my_list_enter(int connfd);
char my_list_select(int connfd);
int sqlite3_history_in(char *name, char *word, char *translation);
int history(int connfd, char *name);
#endif
-
四、代码测试结果
-
五、代码测试注意
1.保证客户端的ip为自己当前虚拟机的IP ---ifconfig 查询
客户端ip设置在client_tcp.h中
参考下图
2.确保服务器先运行,否则客户端连接失败
3.运行需要一个单词库和一个历史记录库(点赞私聊作者免费获取)
-
总结
运用TCP传输协议和多进程实现并发服务器,对我来说是个长足的进步。让我对网络传输、并发服务模型有了更加深入理解。同时查询单词的功能使用到了sqlite3数据库,也对数据库的有了一定的了解,更是一种知识的扩展。
推荐大家做为练习小项目!!!!
由于是自己初学写的练习小项目,其中细节还有些处理不当的地方。
例如大量客户端连接服务器,由于是多进程对cpu资源消耗很大,会导致客户端崩掉!!!想到也是很难受。通过查阅资料发现可以利用进程池技术和心跳包技术可以很好的解决这个问题!!
还有什么粘包问题!!!!
后续我会继续更新,感激浏览到这里读者们!!!祝大家技术快速飞跃!!!!