设计一个猜数游戏(C/S)
设计一个猜数游戏(c语言)
环境:
Linux系统:
Linux chen-virtual-machine 5.15.0-47-generic #51-Ubuntu SMP Thu Aug 11 07:51:15 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
编译器:
gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0
Copyright © 2021 Free Software Foundation, Inc.
g++ (Ubuntu 11.2.0-19ubuntu1) 11.2.0
Copyright © 2021 Free Software Foundation, Inc.
内容:
1、框架介绍
猜数游戏即计算机事先准备好一个目标数字,然后与我们键盘输入的数字相比较,返回结果为“大了”或者“小了”,直至“猜对了”为止。
服务器先利用随机数生成一个在指定区间范围的目标数字,客户端(用户)向服务器输入其所猜测的数字,服务器根据具体情况向客户端(用户)返回其猜测的数偏大还是偏小,并记录客户端(用户)猜测的次数,如此反复,直到客户端(用户)猜对为止。服务器再将用户的id和猜对数字所用的次数输出到指定文件并维护形成一个排行榜(看看那个用户猜对数字所需的次数最少),最终实现客户端、服务器交互类型的猜数游戏。可以作为闲暇时间的智益游戏,其排行榜也可以实现好友之间的排名对战。
主要系统调用:
主要运用了socket(套接字)来解决网络中进程的通信,实现了本地进程间通信。套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。
Socket编程 总流程:服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
程序文件组成:
2. 演示说明
编译运行:
服务器运行示例:
客户端运行示例:
3. 结果总结
程序实现流程:
客户端与服务器建立套接口(socket),利用 send() 和 recv() 函数进行通信
步骤一:客户端和服务端建立联系
步骤二:客户端将确定开始游戏的信号以及用户名发送给服务端
步骤三:服务端利用 rand() 和 time() 函数生成指定范围内的随机数,作为需要猜测的数字,并告诉客户端可以开始猜测
步骤四:客户端向服务端发送所猜测的数字
步骤五:服务端接收客户端所猜测的数字,并与步骤三生成的随机数对比,比较它们的数值大小,并将结果发送给客户端进程。
步骤六:客户端根据服务端给出的大小情况,确定下次猜测的数字,并发送给服务端
重复步骤四~六,直至猜中数字
步骤七:服务端先将ranking_list.txt(记录猜数排行榜的文件)读出,将本次用户的用户名以及猜对的次数增加进排行榜,并根据猜对的次数降序生成新的排行榜,再次写入ranking_list.txt中
步骤八:客户端界面也可以输出排行榜进行查看,以及实现可以提前退出游戏
程序优点 :利用SOCKET套接口技术实现了客户端与服务端之间的相互通讯,实现了文件的读出和写入的功能,实现了C编写的客户端、服务器交互类应用
程序缺点 :对于用户数据的保存仅是利用一个文件来保存,这样安全系数低,且管理效率低。游戏输出界面过于简单。
改进方向 :可以利用MySQL等数据库语言来实现利用数据库来管理用户信息。并且可以利用JavaScript等前端技术来设计好看的游戏界面。
4. 编程总结
Socket套接口技术通讯的相关内容:
int socket(int af, int type, int protocol); // 创建套接字
uint32_t htonl(uint32_t hostlong); // 将主机数转换成无符号长整型的网络字节顺序
u_short htons(u_short hostshort); // 将整型变量从主机字节顺序转变成网络字节顺序
int listen(int sockfd, int backlog); // 让一个套接字处于监听到来的连接请求的状态
int accept(int sockfd, struct spckaddr *addr, skcklen_t *sddlen); //在一个套接口接受的一个连接
int connect (int sockfd,struct sockaddr *serv_addr, int addrlen); //用于建立与指定socket的连接
int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);
//调用用于s指定的已连接的数据报或流套接字上接收输入数据
int send( SOCKET s, const char FAR *buf, int len, int flags );
//调用用于钥纪纪数s指定的已连接的数据报或流套接字上发送输出数据
C语言文件操作函数:
FILE *fp;
FILE *fopen(const char *filename, const char *mode);
"r" 打开一个用于读取的文件。该文件必须存在。
"w" 创建一个用于写入的空文件。如果文件名称与已存在的文件相同,
则会删除已有文件的内容,文件被视为一个新的空文件。
"a" 追加到一个文件。写操作向文件末尾追加数据。如果文件不存在,则创建文件。
"r+" 打开一个用于更新的文件,可读取也可写入。该文件必须存在。
"w+" 创建一个用于读写的空文件。
"a+" 打开一个用于读取和追加的文件。
int fclose(FILE *stream);
int fscanf(FILE *stream, const char *format, ...);
int fprintf(FILE *stream, const char *format, ...)
刚开始进行服务器和客户端通讯的时候,send() 和 recv() 函数两者的 buf 大小设置的不一致,一开始认为由于发送的数据量每次都很小,所以不满足一致性也可以,结果导致了send() 与 recv() 的发送接收不一致。
当客户端连续两次 send() 发送buf,服务器连续两次 recv() 接收buf时,会存在问题,可以通过在两次 send() 之间增加 sleep() 函数来解决。
Makefile 若需要生成多个可执行文件。由于Makefile只能有一个目标,所以可以构造一个没有规则的终极目标 all,并以多个可执行文件作为依赖。
make clean 可以将之前产生的可执行档及其他档案删除, 有时发现重新编译出来的档案没有更新, 可以先执行 make clean。
在函数体内一般不能用 sizeof 获取数组的长度,因为我们调用函数时,传入的是指向数组首部的指针,用 sizeof 实际上获取的是指针的长度。获取字符串数组的长度可以使用头文件 <string.h> 里的 strlen 函数。
要想读出文本文件后再重新写入,可以指定 fopen 的 mode 为 ‘w’,这样可以删除已有文件的内容,文件被视为一个新的空文件。
5. 附录源程序代码
Makefile:
all: s c
s: server.c
gcc server.c -o s
c: client.c
gcc client.c -o c
clean:
rm s c
服务器:
//tcp-server
#include <sys/types.h>
#include <sys/socket.h> // 包含套接字函数库
#include <stdio.h>
#include <netinet/in.h> // 包含AF_INET相关结构
#include <arpa/inet.h> // 包含AF_INET相关操作的函数
#include <unistd.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>
#define PORT 3339
//用户信息
typedef struct{
char username[20];
int mark;
}user;
//sort 对于用户的mark值进行排序
void Sort(user stu[], int N){
for(int i = 0; i < N-1 ; i++){
for(int j = 0; j < N-i-1 ;j++){ //冒泡排序
if(stu[j].mark > stu[j+1].mark){ //比较mark的大小
user temp = stu[j+1]; //mark值大的交换所在的结构体元素
stu[j+1] = stu[j];
stu[j] = temp;
}
}
}
}
//check
int check_char(char* s1, char * s2){
int lop = 1;
for(int i=0;i<strlen(s1);i++){
if(s1[i] > s2[i]){
lop = 0; // smaller
break;
}
else if(s1[i] < s2[i]){
lop = 2; // bigger
break;
}
}
return lop;
}
// 比较两个int数值的大小
int check_int(int a, int b){
if(a > b) return 0;
if(a < b) return 2;
return 1;
};
//change 将char类型转化为int类型
int char2int(char* s){
int ans=0;
for(int i=0;i<strlen(s);i++){
int temp = s[i]-'0';
ans = ans*10 + temp;
}
return ans;
}
int main()
{
//------------------------------message-------------------------------------//
char sendbuf[256]="---Please guess a positive integer up to 100---";
char sendbuf1[64]="Your result is CORRECT. Congratulations!!!";
char sendbuf2[64]="Your guess is too SMALL. Please try again";
char sendbuf3[64]="Your number of guesses is too LARGE. Please try again";
char usrname[128];
char buf[512];
//-------------------------------------------------------------------------//
//-----------------------------socket--------------------------------------//
int s_fd, c_fd; // 服务器和客户套接字标识符
int s_len, c_len; // 服务器和客户消息长度
struct sockaddr_in s_addr; // 服务器套接字地址
struct sockaddr_in c_addr; // 客户套接字地址
s_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
s_addr.sin_family = AF_INET; // 定义服务器套接字地址中的地址域为IPv4
s_addr.sin_addr.s_addr=htonl(INADDR_ANY); // 定义套接字地址
s_addr.sin_port = htons(PORT); // 定义服务器套接字端口
s_len = sizeof(s_addr);
bind(s_fd, (struct sockaddr *) &s_addr, s_len); // 绑定套接字与设置的端口号
listen(s_fd, 10); //监听,最大连接请求设为10
printf("请稍候,等待客户端发送数据\n");
c_len = sizeof(c_addr); //接收客户端连接请求
c_fd = accept(s_fd,(struct sockaddr *) &c_addr,(socklen_t *__restrict) &c_len);
//------------------------------------------------------------------------//
//-----------------------------begin srand---------------------------------//
// rand + time
/*---------------------------get user name--------------------------------//
while(1){
if(recv(c_fd,buf,256,0)>0){
for(int i=0;i < sizeof(buf); i++){
usrname[i] = buf[i];
}
printf("usrname = %s\n",usrname);
break;
}
}
//------------------------------------------------------------------------*/
//-------------------------保存用户名以及获取随机数--------------------------//
int answer;
while(1){
if(recv(c_fd,buf,512,0)>0){
strcpy(usrname,buf);
printf("usrname is %s\n",usrname);
srand( time (NULL) ); //得到随机数
answer = rand() % 100; //100以内随机数
/*
int i=0;
int length=0;
int num = answer;
while(1){
int temp = num % 10;
num /= 10;
length++;
sendbuf[i++] = temp+'0';
if(num == 0) break;
}
*/
printf("Randomly generated guess numbers is %d\n",answer);
send(c_fd,sendbuf,sizeof(sendbuf),0);
break;
}
}
//-------------------------------------------------------------------------//
//----------------------------GAME RUN-------------------------------------//
int num=0; // num记录猜数的轮次
while (1){
if(recv(c_fd,buf,512,0)>0) //接收消息recv(c_fd,buf,256,0)>0
{
//read(c_fd,buf,256,0)
//buf[sizeof(buf)+1]='\0';
num++;
printf("第%d次猜数\n",num);
printf("The number the user guessed is %s\n",buf); //输出到终端
// send(c_fd,sendbuf,sizeof(sendbuf),0);
// int res = check(sendbuf,buf);
int guess = char2int(buf);
// printf("%d %d\n",answer,guess);
int res = check_int(answer,guess);
//根据res的不同结果输出不同的最后语句
if(res == 0){
send(c_fd,sendbuf2,sizeof(sendbuf2),0); //回复消息
}
else if(res == 1){
send(c_fd,sendbuf1,sizeof(sendbuf1),0); //right
break;
}
else{
send(c_fd,sendbuf3,sizeof(sendbuf3),0); //回复消息
}
}
}
//--------------------------------------------------------------------------//
//--------------------------输出 RANKING LIST------------------------------//
puts("");
puts("------RANKING LIST------");
int nn;
user a[21];
FILE * f;
FILE * f1;
f = fopen("ranking_list.txt","r"); // 读入文件
fscanf(f,"%d",&nn);
// printf("%d\n",num);
for(int i=0;i<nn;i++)
fscanf(f,"%s %d",a[i].username, &a[i].mark);
strcpy(a[nn].username,usrname); // 新数据
a[nn].mark = num; // 新数据
Sort(a,nn+1); // 排序
for(int i=0;i<nn+1;i++)
printf("%s %d\n",a[i].username,a[i].mark); // 输出到终端
fclose(f);
f1 = fopen("ranking_list.txt","w"); // 将排序结果重新写入文件
nn++;
fprintf(f1,"%d\n",nn);
for(int i=0;i<nn;i++){
fprintf(f1,"%s %d\n",a[i].username,a[i].mark);
}
fclose(f1);
//-------------------------------------------------------------------------//
close(c_fd); // 关闭连接
}
客户端:
/*cp-client*/
#include <sys/types.h>
#include <sys/socket.h> // 包含套接字函数库
#include <stdio.h>
#include <netinet/in.h> // 包含AF_INET相关结构
#include <arpa/inet.h> // 包含AF_INET相关操作的函数
#include <unistd.h>
#include <string.h>
#define PORT 3339
#define MAX 1000
//用户信息
typedef struct{
char username[20];
int mark;
}user;
//读入并输出排行榜
void read_list(){
puts("------RANKING LIST------");
FILE * f;
f = fopen("ranking_list.txt","r");
int mark;
int nn;
user a[21];
fscanf(f,"%d",&nn);
for(int i=0;i<nn;i++){
fscanf(f,"%s %d",a[i].username, &a[i].mark);
}
for(int i=0;i<nn;i++){
printf("%s %d\n",a[i].username, a[i].mark);
}
fclose(f);
}
//游戏开始界面
void print_hello(){
puts(" ********************* ");
puts(" ******** WELCOME ******** ");
puts(" *************************** ");
puts(" ******* Guessing Game ******* ");
puts(" ******************************* ");
puts(" ************ press ************ ");
puts(" ******* 1-Game Begin ******** ");
puts(" ****** 0-Quit Game ******* ");
puts(" ***** 2-Ranking List **** ");
puts(" ********************* ");
printf("\nPlease enter ");
}
//将char类型转化为int类型
int char2int(char* s){
int ans=0;
for(int i=0;i<strlen(s);i++){
int temp = s[i]-'0';
ans = ans*10 + temp;
}
return ans;
}
//判断两个字符串是否相等
int check(char* s1, char * s2){
int lop = 1;
for(int i=0;i<strlen(s1);i++){
if(s1[i] != s2[i]){
lop = 0;
break;
}
}
return lop;
}
int main() {
//-------------------------------socket----------------------------//
int sockfd; // 客户套接字标识符
int len; // 客户消息长度
struct sockaddr_in addr; // 客户套接字地址
int newsockfd;
char buf[512]; //要发送的消息
char buf_ans[512]="Your result is CORRECT. Congratulations!!!";
int len2;
char rebuf[512];
sockfd = socket(AF_INET,SOCK_STREAM, 0); // 创建套接字
addr.sin_family = AF_INET; // 客户端套接字地址中的域
addr.sin_addr.s_addr=htonl(INADDR_ANY);
addr.sin_port = htons(PORT); // 客户端套接字端口
len = sizeof(addr);
newsockfd = connect(sockfd, (struct sockaddr *) &addr, len); //发送连接服务器的请求
if (newsockfd == -1) {
perror("连接失败");
return 1;
}
len2=sizeof(buf);
//--------------------------------------------------------------------//
//-------------------------------ready--------------------------------//
int sign=0;
int dp[MAX+1]; //dp数组用来记录每次猜数的结果
int num=0;
int answer;
//--------------------------------------------------------------------//
//---------------------------GAME BEGIN-------------------------------//
while(1){
if(sign == 0){ // 利用sign标记来实现游戏开始界面的不同
print_hello(); // 游戏开始界面
sign=1;
int res;
scanf("%d",&res);
if(res == 0){ // res=0 直接退出游戏
close(sockfd);
puts("ByeBye~~~");
return 0;
}
if(res == 2){ // res=2 输出排行榜
read_list();
puts("Continue(1) Quit(0)");
int sign1;
scanf("%d",&sign1);
if(sign1 == 0){
close(sockfd);
puts("ByeBye~~~");
return 0;
}
}
continue;
}
else if(sign == 1){ // 获取参与游戏的用户名
printf("Please enter your user name: ");
scanf("%s",buf);
send(sockfd,buf,len2,0);
if(recv(sockfd,rebuf,512,0)>0) //接收新消息
{
printf("\n%s\n",rebuf); //输出到终端
}
sign = 2;
continue;
}
else printf("Please enter the number you guessed: ");
scanf("%s",buf);
// printf("buf=%s\n",buf);
int temp = char2int(buf); // char -> int
/*
printf("temp=%d\n",temp);
dp[num] = temp;
printf("dp=%d\n",dp[num]);
num++;
*/
dp[num++] = temp; //dp数组记录每次猜数的结果
send(sockfd,buf,len2,0); //发送消息
if(recv(sockfd,rebuf,512,0)>0) //接收新消息
{
//rebuf[sizeof(rebuf)+1]='\0';
printf("\n%s\n",rebuf); //输出到终端
if(check(rebuf,buf_ans)==1){ //游戏结束
answer = char2int(buf);
break;
}
}
/*
if(num==5){
sleep(1);
printf("You've tried five times. Come on!!!");
scanf("%s",buf);
send(sockfd,buf,len2,0);
recv(sockfd,rebuf,512,0);
}
*/
}
//--------------GAME OVER-----------------------------//
// 输出猜数过程以及结果
puts("\n\n————RECORD————");
puts("轮次 猜数 差值");
for (int i=0; i<num; i++)
printf(" %2d : %4d %+4d\n", i+1, dp[i], dp[i]-answer);
printf("最终用%d次猜中了数字\n",num);
puts("----GAME OVER----");
puts("");
//---------------------------------------------------//
//-----------read ranking list----------------------//
puts("Need to see the rankings? YES(1) NO(0)");
int lop;
scanf("%d",&lop);
if(lop==1){
read_list(); //读入排行榜,并在终端输出显示
/*
puts("------RANKING LIST------");
FILE * f;
f = fopen("ranking_list.txt","r");
int mark;
int nn;
user a[21];
fscanf(f,"%d",&nn);
for(int i=0;i<nn;i++){
fscanf(f,"%s %d",a[i].username, &a[i].mark);
}
for(int i=0;i<nn;i++){
printf("%s %d\n",a[i].username, a[i].mark);
}
fclose(f);
*/
}
//--------------------------------------------------//
puts("ByeBye~~~");
close(sockfd); // 关闭连接
return 0;
}