###七、通信部分
客户端与服务端采用TCP/IP协议进行通信,客户端连接函数代码如下:
void Game::gconnect()
{
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2, 2);
struct sockaddr_in servaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9999);
inet_pton(AF_INET, "192.168.0.10", &servaddr.sin_addr);
while (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) < 0) {
SetOutputposition(38, 24, hout);
printf("connect error");
Sleep(1000);
printf(" press any key to retry");
_getch();
for (int i = 0; i != 40; i++)
cout << "\b \b";
}
SetOutputposition(53, 24, hout);
printf("connected!");
Sleep(1000);
for (int i = 0; i != 11; i++)
cout << "\b \b";
}
服务端MAIN代码:
int main(int argc, char **argv)
{
while (!connecttodb()) {
printf("connect to database error,press any key to retry...\n");
system("pause>>nul");
}
printf("connect to database succeed!\n");
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2, 2);
if (WSAStartup(sockVersion, &wsaData) != 0)
{
exit(0);
}
int listenfd, connfd=-1;
struct sockaddr_in servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
for (;;) {
if ((connfd = accept(listenfd, (SA *)NULL, NULL)) < 0) {
if (errno == EINTR)
continue;
else
printf("accept error");
}
else{
printf("connected\n");
str_ser(connfd);
printf("disconnected!\n");
}
}
exit(0);
第一段为数据库连接代码,下一章内容详细讲。通信部分首先进行初始化,然后创建一个套接字,将套接字地址结构置零,设置端口与IP地址,客户端申请与服务端连接,连接失败则询问用户,服务端在连接成功后进入str_ser函数与客户端进行数据交换。
连接建立成功后,客户端进行登陆操作:
void Game::login()
{
while (1) {
char sendline[20];
SetOutputposition(38, 24, hout);
cout << "Username: ";
sendline[0] = 'u';
cin >> (sendline + 1);
send(sockfd, sendline, strlen(sendline), 0);
SetOutputposition(38, 25, hout);
cout << "Password: ";
for (int i = 0; i != 18;)
{
char temp = _getch();
if (isalnum(temp))
{
sendline[i] = temp;
cout << '*';
i++;
}
else if (temp == '\b'&&i > 0) {
i--;
sendline[i] = '\0';
cout << "\b \b";
}
else if (temp == '\r') {
if (i == 0)
continue;
sendline[i] = '\0';
break;
}
}
send(sockfd, sendline, strlen(sendline), 0);
char recvmsg[4];
int n = recv(sockfd, recvmsg, 4, 0);
recvmsg[n] = 0;
SetOutputposition(38, 26, hout);
if (recvmsg[0] == 'y')
break;
cout << "User informaton error,please try again!";
SetOutputposition(78, 24, hout);
for (int i = 0; i != 40; i++)
cout << "\b \b";
SetOutputposition(78, 25, hout);
for (int i = 0; i != 40; i++)
cout << "\b \b";
}
}
在整个的通信过程中,客户端通过在发送的数据头部加上标识,如用户名头部加上’u’,使服务端能够识别不同的数据并进行相应的处理。登陆过程中,首先输入用户名并发送给服务端,服务端接受数据并从数据库中提取相应的数据与之后接受到的密码进行对比,若查找用户名失败或密码不正确则发送n,反之发送y,客户端接受到y则继续运行,否则询问用户再次输入。
服务端处理接受数据的代码如下:
void str_ser(int connfd) {
char user[20];
char codes[20];
char body[200] = "\0" ;
char food[4];
char direction[3];
char score[3];
char record[3];
RETCODE retcode;
int n;
char recvline[MAXLINE];
for (;;) {
again:
if ((n = recv(connfd, recvline, MAXLINE, 0)) > 0) {
recvline[n] = 0;
switch (recvline[0])//判断发送数据的头字母,区分不同的数据
{
case user_name:
strcpy(user, recvline);
if (readdb(user)) {
SQLLEN columnLen = 0;
retcode = SQLBindCol(hstmt1, 1, SQL_C_CHAR, user, 200, &columnLen);
retcode = SQLBindCol(hstmt1, 2, SQL_C_CHAR, codes, 200, &columnLen);
retcode = SQLBindCol(hstmt1, 3, SQL_C_CHAR, body, 200, &columnLen);
retcode = SQLBindCol(hstmt1, 4, SQL_C_CHAR, food, 200, &columnLen);
retcode = SQLBindCol(hstmt1, 5, SQL_C_CHAR, direction, 200, &columnLen);
retcode = SQLBindCol(hstmt1, 6, SQL_C_CHAR, score, 200, &columnLen);
retcode = SQLBindCol(hstmt1, 7, SQL_C_CHAR, record, 200, &columnLen);
retcode = SQLFetch(hstmt1);//从数据库获取数据
retcode = SQLCloseCursor(hstmt1);//执行select语句会分配一个cursor,下次执行必须先关闭该cursor
if ((n = recv(connfd, recvline, MAXLINE, 0)) > 0)
{
recvline[n] = 0;
char rpassword[20];
strcpy(rpassword, recvline);
if (!strcmp(codes, rpassword)) {
send(connfd, "y", strlen("y"), 0);//验证成功,发送y确认
break;
}
}
}
send(connfd, "n", strlen("n"), 0);//失败发送n
break;
case snake_body:
strcpy(body, recvline);
send(connfd, "LWB", 3, 0);
break;
case game_food:
strcpy(food, recvline);
send(connfd, "LWB", 3, 0);
break;
case snake_direction:
strcpy(direction, recvline);
send(connfd, "LWB", 3, 0);
break;
case game_score:
strcpy(score, recvline);
send(connfd, "LWB", 3, 0);
break;
case game_record:
strcpy(record, recvline);
send(connfd, "LWB", 3, 0);
break;
case ask_for_body:
send(connfd, body, strlen(body), 0);
break;
case ask_for_dir:
send(connfd, direction, strlen(direction), 0);
break;
case ask_for_food:
send(connfd, food, strlen(food), 0);
break;
case ask_for_score:
send(connfd, score, strlen(score), 0);
break;
case ask_for_record:
send(connfd, record, strlen(record), 0);
break;
default:
break;
}
}
if (n < 0 && errno == EINTR)
goto again;
if (n <= 0)//客户端连接断开
{
if (strlen(body))
writedb(user);
break;
}
readdb根据user名称从数据库读取其相应的数据,并根据接受到的不同数据保存到相应的缓存中,在客户端退出断开连接的时候用writerdb函数将缓存数据保存到数据库。根据main函数可以看出,登陆成功之后调用loaddata函数从服务器请求数据,并将请求到的数据分别载入到game、snake、和food类中:
void Game::loaddata()
{
snake.recvdata(sockfd);
food.recvdata(sockfd);
char recvline[3];
send(sockfd, "S", 1, 0);
if ((n = recv(sockfd, recvline, 3, 0)) > 0)
recvline[n] = 0;
score = recvline[1];
send(sockfd, "R", 1, 0);
if ((n = recv(sockfd, recvline, 3, 0)) > 0)
recvline[n] = 0;
record = recvline[1];
}
bool Snake::recvdata(int &sock)
{
char recvline[200];
send(sock, "B", 1, 0);
if ((n = recv(sock, recvline, 200, 0)) > 0)
{
recvline[n] = 0;
body.clear();
for (int i = 1; i < n; i++, i++)
{
Point temp;
temp.xx() = recvline[i];
temp.yy() = recvline[i + 1];
body.push_back(temp);
}
}
else
return false;
send(sock, "D", 1, 0);
if ((n = recv(sock, recvline, 200, 0)) > 0) {
recvline[n] = 0;
direction = recvline[1];
}
else
return false;
return true;
}
通过发送S告诉服务端请求score数据,同理,发送B请求snake的body数据等等,将收到的数据对已有的类进行初始化,在之后的游戏运行过程中,新建一个线程并不断调用一下senddata函数将游戏实时数据不断发往服务端,并在游戏退出或断开连接时让服务端将数据保存到数据库。
bool senddata()
{
string temp("s");
temp.push_back(score);
if ((n = send(sockfd, temp.c_str(), strlen(temp.c_str()), 0)) < 0 || !chekans(sockfd))
return false;
temp.assign("r");
temp.push_back(record);
if ((n = send(sockfd, temp.c_str(), strlen(temp.c_str()), 0)) < 0 || !chekans(sockfd))
return false;
if(!snake.senddata(sockfd)||!food.sendfood(sockfd))
return false;
return true;
}