上一篇文章介绍了基本的使用TCP协议的socket编程的问题,以及应该要注意的问题。这里有链接,有兴趣的小伙胖可以去看看。https://blog.csdn.net/suliangkuanjiayou/article/details/88633100
这篇文章我将会讲一下我使用socket做出来的五子棋项目的遇到的一些问题,以及解决方法,还是形成思路。讲述部分仅仅只会贴上需要的代码,然后,在文章之后我会放上我的百度云盘链接,大家有需要的话可以下载看看!
项目实现的功能:
使用socket两个客户端之间不仅仅可以下棋,还可以通信。基本上实现的原理就是客户端都是把信息发送给服务端,服务端转发这条消息给另一个客户端,让客户端觉得是它们之间之间通信。
用到的知识:
在客户端使用了fork,因为这样子才能保证客户端在接收消息的同时又可以发送消息。
在服务端也是得去检查客户端有没有向自己发送消息,而自己也得去将消息发给别的客户端。为了不让服务端阻塞,但是使用fork其实对cpu资源消耗还是比较大的,并且没有到达我想要的资源共享的目的。因为fork的进程之间的地址都是不一样的,都有自己的被分配的内存。但是我的程序里里面有一个必须要共享的数组,所以,放弃使用了fork,使用多线程时我也遇到了一个大坑,因为当时还不是特别熟悉多线程,所以放弃使用了多线程。所以,最后使用的是select来实现这个。
对消息体的封装,虽然这个不是什么知识,但是这个因为是我觉得这个项目收获最大的一个。发送消息,不再是以简简单单的字符数组的形式发送,而是形成一个个结构体将信息发送出去。也像我上一篇文章上面说的,其实等到之后可以根据这个思想做一个多人聊天的程序,大家的消息,使用链表将所有的消息结构体给串起来。(之后有时间得去实现以下)
使用多线程遇到的大坑
int main(int agc,char *agv[])
{
if(agc!=2||atoi(agv[1])<=0)
{
printf("use ./program hostname port");
exit(1);
}
int i=0;
int sock_fd;
int *index;
socklen_t addrlen;
if((sock=make_server_socket(atoi(agv[1])))==-1)
display_error("socket");
if(listen(sock,NUM)!=0)
display_error("listen");
for(i=0;i
我这里就仅仅贴出了有坑的地方.大家可以看到我注释的位置,也就是说:我一开始就是想在创建一个多线程的时候,顺便传入一个参数 i 到线程要执行的函数里面。其实咋一看也没有什么问题,但是问题在于线程执行的时候,可能你的第一遍for循环结束了,也就是i==0,把他传入run()函数里面,但是可能你的for循环开始第二遍了,你的线程都还没有启动i,那么此时i ==1了。因为这个线程执行,是由内核决定了,不是说你想要它执行他就会执行的。
解决方法大家也看到了,就是使用一个指针变量,而且一定的malloc,这个样子,就算你的线程还没有来的及去执行,但是传入的参数始终是之前malloc分配的内存的位置的值。就可以解决这个问题了。
#ifndef MESSAGE_H_
#define MESSAGE_H_
#define MSG_PUTSTEP 0x01 //落子消息
#define MSG_DRAW 0x02 //请求和棋消息
#define MSG_AGREE_DRAW 0x03 //同意和棋
#define MSG_DENY_DRAW 0x04 //拒绝和棋
#define MSG_EXTERN 0x05 //聊天消息
typedef struct Message
{
//消息发送者编号
int num;
//消息长度
int len;
// 消息ID
unsigned int msgType;
// 落子信息
int x;
int y;
int color;
// 其他消息内容
char byMsg[128];
}MSGSTRUCT;
#endif
它的好处我也刚刚说了,就利于之后消息的传输。还有一个链表的思想我还得说一遍,提醒自己今后的程序中做到这一点。使用链表将许多结构体串起来,之后就遍历链表得到所有消息。想做成这个样子就一定得去封装和解封装,代码如下:
//封装一个消息体
void packInfo(MSGSTRUCT *msg,char info[],int num)
{
int msgType=-1;
char *byMsg;
msgType=atoi(strtok(info,";"));
msg->num=num;
switch(msgType)
{
case 1:
msg->len=1;
msg->msgType=1;
msg->x=atoi(strtok(NULL,","));
msg->y=atoi(strtok(NULL,","));
break;
case 2:
msg->len=1;
msg->msgType=2;
break;
case 3:
msg->len=1;
msg->msgType=3;
break;
case 4:
msg->len=1;
msg->msgType=4;
break;
case 5:
msg->len=2;
msg->msgType=5;
byMsg=strtok(NULL,";");
if(byMsg!=NULL)
{
strcpy(&(msg->byMsg),byMsg);
}
break;
default:
printf("input message type id %d iderror\n",msg->msgType);
exit(1);
}
}
//解封这个结构体消息
void decompre(MSGSTRUCT msg)
{
switch(msg.msgType)
{
case 2:
printf("对方向您提出和棋请求,您是否同意?\n");
break;
case 3:
printf("对方同意了你的的和棋请求\n");
break;
case 4:
printf("对方拒绝了您的和棋请求,所以您得继续完成本次博弈\n");
break;
case 5:
printf("%s",msg.byMsg);
break;
case 6:
printf("%s",msg.byMsg);
handler();
exit(1);
default:
printf("输入信息格式有错\n");
break;
}
}
void run(fd_set read_fds,fd_set exception_fds)
{
MSGSTRUCT rece_msg,send_msg;
MSGSTRUCT win_result,fail_result;
char send_info[BUFSIZ];
int i=0;
int ret=0;
int winner=-1;
while(1)
{
//clear data
memset(&rece_msg,0,sizeof(MSGSTRUCT));
memset(&send_msg,0,sizeof(MSGSTRUCT));
memset(send_info,0,BUFSIZ);
//reset discritor
for(i=0;i
我就在这里说一下select的好处。因为我这里是得去检查两个客户端是不是向我服务器发送消息了。假设先检查客户端1是不是向我发送消息,但是如果没有发送消息,read()是会阻塞的。但是此时客户端2已经向我发送消息了,但是我的服务器就已经被阻塞在那里的。这个样子的结果不是我想要的。当然,使用fork和多线程都是可以完成的,我之前也是这么做的。但是我觉得之后通信的时候,select也是一个不错的选择。
现在给大家一个链接,如果有兴趣可以下载玩玩。
链接:https://pan.baidu.com/s/1KLlevA0qnpS76XRR3PtbjA
提取码:5s3p