socket编程实践--贰

    上一篇文章介绍了基本的使用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<NUM;i++)
	{
		client_sock[i]=accept(sock,&addr[i],&addrlen);
		if(client_sock[i]==-1)
			display_error("accept");

		pthread_t tid;
		/*
		//这一步是十分关键的
		index=(int*)malloc(sizeof(int));
		
		if(index==NULL)
		{
			printf("内存分配失败\n");
			exit(1);
		}
		*index=i;
		if(pthread_create(&tid,NULL,run,index)!=0)
			display_error("thread");
		*/
			if(pthread_create(&tid,NULL,run,i)!=0)
			display_error("thread");
	}
	
	//主线程阻塞,不让其退出
	getchar();
	return 0;
}

    我这里就仅仅贴出了有坑的地方.大家可以看到我注释的位置,也就是说:我一开始就是想在创建一个多线程的时候,顺便传入一个参数 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;
	}
}
  • select的使用
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<NUM;i++)
		{
			FD_SET(client_sock[i],&read_fds);
			FD_SET(client_sock[i],&exception_fds);
			ret=select(client_sock[i]+1,&read_fds,NULL,&exception_fds,NULL);
			if(ret<0)
			{
				printf("select filed\n");
				return;
			}
			else if(FD_ISSET(client_sock[i],&read_fds))
			{
						
				if(readn(client_sock[i],&rece_msg,sizeof(rece_msg))!=sizeof(rece_msg))
					display_error("readn");
				//	printf("msgType==%d\n",rece_msg.msgType);

				if(rece_msg.msgType==1)
				{
					//printf("落子信息:%d,%d",rece_msg.x,rece_msg.y);
					//为了区别这里的客户端的信息,所以这里使用client_sock[index]可以区分对别
					if((winner=playChess(rece_msg.x,rece_msg.y,client_sock[i]))!=-1)
					{
						if(winner==1)
						{
							write_result(&win_result,&fail_result,0);
							closeSocket(client_sock,NUM);
							exit(0);
						}
						else if(winner==2)
						{
							write_result(&win_result,&fail_result,1);
							closeSocket(client_sock,NUM);
							exit(0);
						}
					}
				}
				else
				{
					if(writen(client_sock[NUM-1-i],&rece_msg,sizeof(rece_msg))!=sizeof(rece_msg))
					display_error("writen");
				}
			}
			else if(FD_ISSET(client_sock[i],&exception_fds))
			{
				printf("select has a exception\n");
			}
		}
	}
}

    我就在这里说一下select的好处。因为我这里是得去检查两个客户端是不是向我服务器发送消息了。假设先检查客户端1是不是向我发送消息,但是如果没有发送消息,read()是会阻塞的。但是此时客户端2已经向我发送消息了,但是我的服务器就已经被阻塞在那里的。这个样子的结果不是我想要的。当然,使用fork和多线程都是可以完成的,我之前也是这么做的。但是我觉得之后通信的时候,select也是一个不错的选择。

现在给大家一个链接,如果有兴趣可以下载玩玩。
链接:https://pan.baidu.com/s/1KLlevA0qnpS76XRR3PtbjA
提取码:5s3p

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值