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

    我这里就仅仅贴出了有坑的地方.大家可以看到我注释的位置,也就是说:我一开始就是想在创建一个多线程的时候,顺便传入一个参数 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

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

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

你可能感兴趣的:(Linux学习,socket编程,五子棋)