以下用到的TCP协议的程序代码见:网络编程------TCP协议网络程序
以下用到的三子棋的游戏规则的相关代码见:三子棋
在网络编程------TCP协议网络程序一文中根据TCP协议分别实现了单进程,多进程,多线程版本的服务器端程序。在多进程和多线程环境中服务器可以同时接收来自多个客户端的连接请求并与之互发消息进行通信。在本文中将继续根据TCP协议来实现与客户端的通信。
本文中的二者通信是基于之前写过的三子棋小游戏。客户端和服务器端分别扮演两个游戏玩家,来实现对战操作。服务器端和客户端在正式进入游戏前的操作与在网络编程------TCP协议网络程序一文中的操作相同。
服务器端:先打开网卡文件,绑定套接字,将文件描述符设置为监听模式,接收来自客户端的连接请求。客户端:打开文件描述符,向服务器端发出连接请求。关于二者这部分前期的工作不在重复。
待二者连接建立成功之后,便开始通信。因为二者要下棋对战,所以每次都要将各自的下棋坐标位置发给对方,然后各自将棋盘打印出来。一人一次走一步,一个人发完消息,另一个人接收完之后在发送自己的坐标选择给对方。这就实现了二者之间通过网络来对战的目的。
所以该程序代码是三子棋项目和TCP协议网络协议代码的结合。具体如何实现可参照上述两篇博客。
代码如下:
头文件comm.h:
#pragma once #include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<stdlib.h> #include<netinet/in.h> #include<arpa/inet.h> #include<unistd.h> #include<string.h> #include<sys/wait.h> #include<time.h> #define ROW 3 #define COL 3 //坐标信息 typedef struct { int x; int y; }location; //客户端发送的进入游戏或退出游戏的信息 typedef struct Res { int res;//1表示开始游戏,0表示退出游戏 }Response; void menu();//打印菜单 void print(char arr[ROW][COL], int x, int y);//打印棋盘 void Init(char arr[ROW][COL], int x, int y);//初始化数组 //移动函数 void move(char arr[ROW][COL],int x,int y,location* loc,char flag); int iswin(char arr[ROW][COL], int x, int y);//判断是否有人赢或产生平局 int who_win(int ret);//判断是谁赢
服务器端和客户端使用的一些方法封装函数文件comm.c:
#include "comm.h" void Init(char arr[ROW][COL], int x, int y)//数组的初始化函数定义 { memset(arr, ' ', x*y*sizeof(arr[0][0])); } void menu()//菜单函数定义 { printf("*****************************\n"); printf("****1.开始游戏 0.退出游戏****\n"); printf("*****************************\n"); } void print(char arr[ROW][COL], int x, int y)//打印棋盘函数定义 { int i = 0; for (i = 0; i < x; i++) { printf(" %c | %c | %c \n", arr[i][0], arr[i][1], arr[i][2]); if (i < x - 1) { printf("---- ---- ----\n"); } } } int who_win(int ret)//判断是谁赢 { if (ret == 'Y') { printf("玩家1赢\n"); return 1; } else if (ret == '*') { printf("玩家2赢\n"); return 1; } else if (ret == 0) { printf("平局\n"); return 1; } return 0; } int iswin(char arr[ROW][COL], int x, int y) { int i = 0; int j = 0; int count = 0; if ((arr[0][0] == arr[1][1]) && (arr[1][1] == arr[2][2]))//正对角赢 { return arr[0][0]; } if ((arr[0][2] == arr[1][1]) && (arr[1][1] == arr[2][0]))//玩家斜对角赢 { return arr[0][2]; } for (i = 0; i < x; i++) { if ((arr[i][0] == arr[i][1]) && (arr[i][1] == arr[i][2])) { return arr[i][0];//横向赢 } } for (i = 0; i < y; i++) { if ((arr[0][i] == arr[1][i]) && (arr[1][i] == arr[2][i])) { return arr[0][i];//竖向赢 } } for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { if (arr[i][j] == ' ') { count++;//统计棋盘上空格的个数,如果没有空格表示棋盘满了还没分出胜负,则平局 } } } if (count == 0) { return 0; } } //移动函数 void move(char arr[ROW][COL],int x,int y,location* loc,char flag) { int count = 0; while(!count) { scanf("%d%d",&(loc->x),&(loc->y));//服务器走 if (loc->x >= 1 && loc->x <= x && loc->y >= 1 && loc->y <= y) { if (arr[loc->x - 1][loc->y - 1] == ' ') { arr[loc->x - 1][loc->y - 1] = flag; count++; } else { printf("该位置已满!请重新选择:"); } } else { printf("位置错误!请重新选择:"); } } }
服务器端代码:
#include "comm.h" int server_sock(char* ip,int port) { int sock = socket(AF_INET,SOCK_STREAM,0); if(sock < 0) { printf("socker error\n"); exit(1); } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_addr.s_addr = inet_addr(ip); server.sin_port = htons(port); socklen_t len = sizeof(server); if(bind(sock,(struct sockaddr*)&server,len) < 0) { printf("bind error\n"); exit(2); } if(listen(sock,10) < 0) { printf("listen error\n"); exit(3); } return sock;//得到监听套接字 } void server_work(int client_sock,char* ip,int port) { int ret = 0; char arr[ROW][COL] = { 0 }; Init(arr, ROW, COL);//初始化数组 print(arr,ROW,COL); while (1) { printf("please wait...\n"); location client_loc; read(client_sock,&client_loc,sizeof(client_loc));//客户端先走 arr[client_loc.x - 1][client_loc.y - 1] ='*'; print(arr,ROW,COL);//打印棋盘 ret = iswin(arr,ROW,COL);//判断是否有人胜利 int r = who_win(ret); if(r == 1) { //有人赢 break; } location loc; printf("玩家1走,玩家1请选择:"); move(arr,ROW,COL,&loc,'Y');//客户端移动 print(arr, ROW, COL);//打印棋盘 write(client_sock,&loc,sizeof(loc)); ret = iswin(arr,ROW,COL);//判断是否有人胜利 r = who_win(ret); if(r == 1) { //有人赢 break; } } } void process_work(int client_sock,char* ip_buf,int port) { pid_t pid = fork(); if(pid < 0) { printf("fork error\n"); return; } else if(pid == 0)//child { pid_t pid1 = fork(); if(pid1 < 0) { printf("fork error\n"); return; } else if(pid1 == 0)//grandchild { while(1) { Response res; read(client_sock,&res,sizeof(res)); if(res.res == 0) { break; } printf("开始游戏\n"); server_work(client_sock,ip_buf,port); } printf("client quit\n"); exit(0); } exit(0); } else//father { waitpid(pid,NULL,0); } } int main(int argc,char* argv[]) { if(argc != 3) { printf("Usage:%s [ip][port]\n",argv[0]); return 1; } int listen_sock = server_sock(argv[1],atoi(argv[2]));//得到监听套接字 printf("bind and listen success,wait accept...\n"); struct sockaddr_in client; while(1) { socklen_t len = sizeof(client); int client_sock = accept(listen_sock,(struct sockaddr*)&client,&len); if(client_sock < 0) { printf("accept error\n"); continue; } char ip_buf[24]; ip_buf[0] = 0; inet_ntop(AF_INET,&client.sin_addr,ip_buf,sizeof(ip_buf)); int port = ntohs(client.sin_port); printf("connect success, ip:[%s],port:[%d]\n",ip_buf,port); process_work(client_sock,ip_buf,port); } return 0; }
客户端代码:
#include"comm.h" void client_work(int client_sock) { int ret = 0; char arr[ROW][COL] = { 0 }; Init(arr, ROW, COL);//初始化数组 print(arr, ROW, COL);//首先打印九宫格 while (1) { location loc; printf("玩家2走,玩家2请选择:"); move(arr,ROW,COL,&loc,'*'); write(client_sock,&loc,sizeof(loc)); print(arr, ROW, COL); ret = iswin(arr, ROW, COL); int r = who_win(ret);//判断是谁赢 if(r == 1) { //有人赢 break; } printf("wait ...\n"); location client_loc; ssize_t s = read(client_sock,&client_loc,sizeof(client_loc)); arr[client_loc.x - 1][client_loc.y -1] ='Y'; print(arr,ROW,COL); ret = iswin(arr,ROW,COL); r = who_win(ret);//判断是谁赢 if(r == 1) { //有人赢 break; } } } int main(int argc,char* argv[]) { if(argc != 3) { printf("Usage:%s [ip][port]\n",argv[0]); return 1; } int sock = socket(AF_INET,SOCK_STREAM,0); if(sock < 0) { printf("socket error\n"); return 2; } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_addr.s_addr = inet_addr(argv[1]); server.sin_port = htons(atoi(argv[2])); socklen_t len = sizeof(server); if(connect(sock,(struct sockaddr*)&server,len) < 0) { printf("connect failed\n"); return 3; } printf("connect success\n"); int input = 0; Response res; do { menu(); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: printf("开始游戏\n"); res.res = input; write(sock,&res,sizeof(res)); client_work(sock); printf("\n"); break; case 0: res.res = input; write(sock,&res,sizeof(res)); printf("退出游戏\n"); break; default: printf("选择错误,请重新选择!\n"); printf("\n"); break; } } while (input); close(sock); return 0; }
测试代码(由本地环回进行测试):
服务器先运行:
客户端再运行:
客户端进行选择:
客户端界面:
服务器端界面:
二者进入游戏,客户端(玩家2)先走:
服务器端(玩家1)后走:
当客户端(玩家2)退出游戏即断开连接后,服务器端还一直在运行,等待接收来自其它客户断的连接请求。
说明:
上述代码中实现了两个玩家来通过网络来发送坐标位置进行通信。改进了原本的只有一个玩家与电脑进行通信的缺点。
服务器端既要接收来自客户端的连接请求,又要扮演玩家的作用与客户端进行对战。而在实际中,应当是两个客户端同时向服务器端发送连接请求,由服务器端处理将二者连接,实现两个客户端之间的对战。因此,这也是上述代码不完善之处。
在实际中,服务器端应一直在后台运行(守护进程)接收来自多个客户端的连接请求,连接成功之后,将多个客户端的套接字信息管理组织起来。将已连接的客户端信息反馈给各客户,由客户选择与谁进行对战,双方均同意之后,由服务器将这两个客户端连接起来,进行通信。或者由服务器端随机为某个客户匹配对手玩家。如果连接到服务器端的只有一个玩家,则在后台由电脑随机生成坐标与该客户对战。
有关上述处理将会在后续进行改进。