UDP+线程池处理问题!

Mrzhen009 2015-08-17 11:09:00
加精
本人新手,最近要求基于UDP,在Linux系统下做一个类似服务器的东东
需求:
C语言,UDP,支持多并发(每一路每隔20ms有一包数据)

实现:
以下是自己的实现:之前看了一些帖子,帖子中大家对于高并发UDP都推荐使用一个socket,这里我也使用了,而且大家多数推荐使用线程池,于是我在网上看了看,稍作修改用了一个较为简单的,不知道自己的设计是否合理,因为确实没有经验。
首先,while(1)中,recvfrome,接到数据后将接收buffer与用户处理函数放进线程池,工作线程调用用户处理函数对buffer中的数据进行转换处理,然后在这个线程中通过sendto将转换完成的数据通过全局socket发送回源端口。
程序的重点是数据转换,这一部分功能是别人封装为一个库给我的,测试中发现并发两路就崩掉了,于是在转换函数那里加了锁,程序不会在这里崩了(意思就是说还有别的地方崩掉了~~~~(>_<)~~~~),但是这样加锁,结合20ms一包数据,不知道是否会影响质量。

这样设计不知道合不合理,不知道有没有必要贴代码。
...全文
2374 29 打赏 收藏 转发到动态 举报
写回复
用AI写文章
29 条回复
切换为时间正序
请发表友善的回复…
发表回复
screwzm 2015-09-06
  • 打赏
  • 举报
回复
看看unix网络编程,上面有你需要的所有消息
Mrzhen009 2015-09-04
  • 打赏
  • 举报
回复
引用 29 楼 u014149003 的回复:
在做FTP和email 客户端的时候用的也是线程池,线程池不是类似生产者-消费者么 只要锁的力度和区域设计好,一切都会变得很简单~
嗯,是的,不过现在遇到的问题是要在线程池中进行数据处理,而数据处理用到了别人封装的一个静态库,现在看来这个静态库不是线程安全的,如果锁住了这个静态库提供的函数,那就跟顺序执行没什么两样了,得不到并发执行线程池也就没什么意义了。
lyzlwb2010 2015-08-29
  • 打赏
  • 举报
回复
在做FTP和email 客户端的时候用的也是线程池,线程池不是类似生产者-消费者么 只要锁的力度和区域设计好,一切都会变得很简单~
Mrzhen009 2015-08-25
  • 打赏
  • 举报
回复
引用 16 楼 dooX8086 的回复:
相关楼主还要注意 惊群现象 (pthread_cond_signal 惊群) process 函数中的 iRet = Enc(gtSlot[iSessID].pHandle, &sBuf[0], tSndBuf.ucText);// 这个gtSolt 那里来的? 看样子就个公共变量,还有 iSessID .... 等公共变量,如果外部没有保证这些变量 这个 函数不是线程安全的 对于 process 函数 process(...); // 如果它是放在 这行mutex_unlock() 之前,那么这个 多线程的设计10分不好(比单线程还慢得多) pthread_mutex_unlock() <<========= // 较好的设计它应该放在 mutex_unlock() 这后
高人,不知道能不能跟您继续学习讨论一下呢?
qq_30773211 2015-08-22
  • 打赏
  • 举报
回复
这样吗 我怎么知道不得
aierda 2015-08-22
  • 打赏
  • 举报
回复
关注下,不错
cattpon 2015-08-22
  • 打赏
  • 举报
回复
这个规划有点怪怪的
Mrzhen009 2015-08-22
  • 打赏
  • 举报
回复
引用 24 楼 qq_30773211 的回复:
这样吗 我怎么知道不得
没明白,什么意思?
Mrzhen009 2015-08-22
  • 打赏
  • 举报
回复
引用 20 楼 cattpon 的回复:
这个规划有点怪怪的
那里比较怪啊?
laoer_2002 2015-08-22
  • 打赏
  • 举报
回复
学习
lzh3ng 2015-08-22
  • 打赏
  • 举报
回复
Mrzhen009 2015-08-20
  • 打赏
  • 举报
回复
引用 16 楼 dooX8086 的回复:
相关楼主还要注意 惊群现象 (pthread_cond_signal 惊群) process 函数中的 iRet = Enc(gtSlot[iSessID].pHandle, &sBuf[0], tSndBuf.ucText);// 这个gtSolt 那里来的? 看样子就个公共变量,还有 iSessID .... 等公共变量,如果外部没有保证这些变量 这个 函数不是线程安全的 对于 process 函数 process(...); // 如果它是放在 这行mutex_unlock() 之前,那么这个 多线程的设计10分不好(比单线程还慢得多) pthread_mutex_unlock() <<========= // 较好的设计它应该放在 mutex_unlock() 这后
以下是线程池中每个线程的执行函数:

void *thread_routine(void *arg)
{
	T_Worker *worker = NULL;
	
	DEBUG_PRINT("starting thread 0x%x\n",pthread_self());

	while(1)
	{
		pthread_mutex_lock(&(gpPool->queue_lock));

		while(gpPool->cur_queue_size == 0 && !gpPool->shutdown)
		{
			DEBUG_PRINT("thread 0x%x is waiting\n",pthread_self());
			pthread_cond_wait(&(gpPool->queue_ready),&(gpPool->queue_lock));
		}
		
		if(gpPool->shutdown)
		{
			pthread_mutex_unlock(&(gpPool->queue_lock));
			DEBUG_PRINT("thread 0x%x will exit\n",pthread_self());
			pthread_exit(NULL);
		}
		DEBUG_PRINT("thread 0x%x is starting to work\n",pthread_self());

		assert(gpPool->cur_queue_size !=0);
		assert(gpPool->queue_head != NULL);

		gpPool->cur_queue_size--;
		worker = gpPool->queue_head;
		gpPool->queue_head = worker->next;

		pthread_mutex_unlock(&(gpPool->queue_lock));

		(*(worker->process))(worker->arg);//此处执行pool_add_work()添加进队列的回调函数
		free(worker);
		worker = NULL;
	}	
	pthread_exit(NULL);
}
gtSlot[N]是全局数组,N就是程序支持的最大的并发个数,每一路并发(就是在程序里做转换)依托一个处理句柄,这个全局数组就是保存对应的N个句柄,每一路发来的包中包含一个iID,在0~N中,由它直接定位哪一个句柄。 通过实验发现,如果EncDec两个处理函数不加锁,转换出来的数据误差较大,但是加了锁会不会大大降低了效率?这两个函数是库里的,我看不到实现,本以为依托于一个处理句柄应该可重入的。我也知道这个问题问您有点不太合适,因为怎么实现的都不知道。
Mrzhen009 2015-08-20
  • 打赏
  • 举报
回复
引用 16 楼 dooX8086 的回复:
相关楼主还要注意 惊群现象 (pthread_cond_signal 惊群) process 函数中的 iRet = Enc(gtSlot[iSessID].pHandle, &sBuf[0], tSndBuf.ucText);// 这个gtSolt 那里来的? 看样子就个公共变量,还有 iSessID .... 等公共变量,如果外部没有保证这些变量 这个 函数不是线程安全的 对于 process 函数 process(...); // 如果它是放在 这行mutex_unlock() 之前,那么这个 多线程的设计10分不好(比单线程还慢得多) pthread_mutex_unlock() <<========= // 较好的设计它应该放在 mutex_unlock() 这后
感谢高人继续指导哈 gtSlot是个全局结构数组,这里的iSessID写错了,其实是上边的iID,一路并发对应一个iID,这个转换函数要基于每一路的pHandle。从这些全局变量的作用来看,我要怎么做才是线程安全的? process在线程中调用,在链表取数据时加解锁,解锁后调用process这个回调,晚上回去我贴下代码,这样设计是否会好一点?
dooX8086 2015-08-20
  • 打赏
  • 举报
回复
相关楼主还要注意 惊群现象 (pthread_cond_signal 惊群) process 函数中的 iRet = Enc(gtSlot[iSessID].pHandle, &sBuf[0], tSndBuf.ucText);// 这个gtSolt 那里来的? 看样子就个公共变量,还有 iSessID .... 等公共变量,如果外部没有保证这些变量 这个 函数不是线程安全的 对于 process 函数 process(...); // 如果它是放在 这行mutex_unlock() 之前,那么这个 多线程的设计10分不好(比单线程还慢得多) pthread_mutex_unlock() <<========= // 较好的设计它应该放在 mutex_unlock() 这后
707wk 2015-08-20
  • 打赏
  • 举报
回复
引用 14 楼 Mrzhen009 的回复:
[quote=引用 13 楼 zxh707wk 的回复:] 直接上iocp吧
这个不太了解,搜了下是win下的模型吧,现在要求使用Linux。这个是不是与epool差不多?[/quote]还是继续用epoll吧
Mrzhen009 2015-08-19
  • 打赏
  • 举报
回复
初始化的时候gpPool->queue_head = NULL
Mrzhen009 2015-08-19
  • 打赏
  • 举报
回复
引用 13 楼 zxh707wk 的回复:
直接上iocp吧
这个不太了解,搜了下是win下的模型吧,现在要求使用Linux。这个是不是与epool差不多?
707wk 2015-08-19
  • 打赏
  • 举报
回复
直接上iocp吧
xian_wwq 2015-08-19
  • 打赏
  • 举报
回复
assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。 崩溃的原因在于断言为假了,建议检查gpPool的初始化及其他操作
Mrzhen009 2015-08-19
  • 打赏
  • 举报
回复
引用 8 楼 dooX8086 的回复:
这一段是向线程池添加任务的函数,现在总是在这里崩溃 int pool_add(void* (*process)(void *arg), void* arg) { T_Worker *newworker = (T_Worker *)malloc(sizeof(T_Worker)); T_Worker *member = gpPool->queue_head; // 如果同时入这就有问题 ( 因为你的 queue 是不带头结点的单链表) =====>>>>>> newworker->process = process; memcpy(newworker->arg,(unsigned char *)arg,512); newworker->next = NULL; pthread_mutex_lock(&(gpPool->queue_lock)); // 应该放到这下面 ....... ////如果程序的重点是数据转换 ... 而转换函数又不是线程安全,(不能多路同进入)。 //// 这个多线程,跑起来还不如单线程快和高效
高人,我还是不太明白T_Worker *member = gpPool->queue_head;这一句 不加锁出错的原因,能不能指点一下? 下面是process回调函数,不知道有没有可重入的风险:

void *process(void *arg)
{
	//arg为recvfrom得到的buffer,包含一个头和要转换的数据
	PT_RcvBuf ptRcvBuf = (PT_RcvBuf)arg;

	T_OutBuf tSndBuf;
	short sBuf[PCM_LEN_PER_PACKET];
	unsigned char ucBuf[EVRC_LOAD_LEN_PER_PACKET];
	int iRet,iID;
	int sendbytes = 0;

	memset(&tSndBuf, 0, sizeof(T_OutBuf));
	memset(&sBuf[0], 0, sizeof(sBuf));
	memset(&ucBuf[0], 0, sizeof(ucBuf));

	iID = (int)(ptRcvBuf->tOutBuf.tHead.ucID);
		
	switch ( (int)(ptRcvBuf->tOutBuf.tHead.ucDirection) )
	{
		case ENCODE:
			memcpy(&sBuf[0],ptRcvBuf->tOutBuf.ucText,ENCODE_LEN);
			//编解码函数基于每一路的pHandle指针做处理,调试过程中这里有过报错,因为在lib库中,无法
			//查看,所以有锁,这里略去没写
			iRet = Enc(gtSlot[iSessID].pHandle, &sBuf[0], tSndBuf.ucText);//编码
			if (iRet != 0)
			{
				printf("Enc Error!\n");
				return ;
			}
			break;

		case DECODE:
			memcpy(&ucBuf[0],ptRcvBuf->tOutBuf.ucText,DECODE_LEN);
			//编解码函数基于每一路的pHandle指针做处理
			iRet = Dec(gtSlot[iID].pHandle, &ucBuf[0], tSndBuf.ucText);//解码
			if (iRet != 0)
			{
				printf("Dec Error!\n");
				return ;
			}
			break;

		default:
			printf("Unknown convert type: %d !\n",(int)(ptRcvBuf->tOutBuf.tHead.ucDirection));
			return ;
	}

	memcpy( &(tSndBuf.tHead), &(ptRcvBuf->tOutBuf.tHead), sizeof(T_Head) );

	//通过recvfrom把client的sockaddr结构保存下来,并在此把处理后的数据发送回这个IP及PORT
	sendbytes = sendto(giSocketServer,&tSndBuf,sizeof(T_OutBuf),0,(struct sockaddr *)&(ptRcvBuf->tSocketClientAddr),giAddrLen);
	if (sendbytes !=sizeof(T_OutBuf))
	{
		perror("sendto");
	}
}
加载更多回复(9)

69,371

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧