Linux环境下多线程并发服务器

摘要:本文主要记录linux下多线程并发服务器开发,在次之前,我想先梳理一下线程相关的知识,闲啰嗦的道友可以直接略过,后面会有源代码链接和测试结果。

一、线程和进程

  • 1、线程是进程的一条执行路径,称为轻量级进程。

  • 2、所有线程都是在同一进程空间运行,共享该进程中的全部系统资源,例如虚拟地址空间,文件描述符和信号处理等等。

    3、虽然说线程在同一进程空间运行,但是每个线程有各自的调用栈、寄存器环境、线程本地存储等。

    4、进程中的多个线程并行执行不同的任务,达到并发的作用。
    5、不同程序间交换数据,对于多进程,需要在用户空间和内核空间进行频换切换,开销大。对于多线程来说,使用全局变量,只需要在用户空间切换,开销小,效率高。

二、主线程和子线程

  • 1、对于c和c++,我的理解是main函数就是主线程,因为程序运行是从main函数开始的,然后再main函数中创建的新进程就是子进程,每个子进程都有自己的线程id,多个子进程之间是平级的,同时运行,没有先后顺序。

  • 2、主线程和子线程的关系:主线程退出,所有子线程就会退出,这时整个进程结束或者僵死。

    3、为了避免僵死状态,主线程和子线程可会合和相分离两种关系。

       (1)、可会合:主线程等待子线程结束之后,在做下一步操作,这样整个程序就会有条不紊地结束,释放掉所有占用的资源。
       
       (2)、相分离:子线程的结束和主线程之间没有关系,子线程结束后自动释放掉资源,主线程无需等待,相比可会合,高效稳定得多。
    

三、多线程可能用到的知识

  • 1、临界资源:多个线程共享的资源,比如一个变量。 2、临界区:临界资源的范围。

  • 3、互斥锁:多个线程共享一个临界资源,难免会发生竞争,为了避免发生竞争,就需要用到互斥锁。

    4、死锁:用到互斥锁的话,可能会产生死锁,当A线程正在使用资源1的同时想要使用资源2,但是资源2被线程B持有,如果此时线程B也想使用资源1,A和B就会打架,实力不相上下,谁也不想把资源让给对方,此时就产生死锁了。

    5、总结一下产生死锁的条件就是:互斥、占有且等待、循环等待、不可抢占。

四、多线程编程相关函数

1、线程属性设置函数,可参考博客:https://www.cnblogs.com/jacklikedogs/p/4030048.html。在项目中我们一般主要设置线程堆栈大小、线程分离状态两个属性,其他可以使用默认值。

typedef struct
           {
                   int                           detachstate;     线程的分离状态,非分离	状态只有当pthread_join() 函数返回时,创建的线程才算终,才能释放自己占用的系统资源
                   int                          schedpolicy;   线程的调度策略
                   struct sched_param      schedparam;   线程的调度参数
                   int                          inheritsched;    线程的继承性
                   int                          scope;          线程的作用域
                   size_t                      guardsize; 线程栈末尾的警戒缓冲区大小
                   int                          stackaddr_set;
                   void *                     stackaddr;      线程栈的位置
                   size_t                      stacksize;       线程栈的大小
            }pthread_attr_t;

  

2、创建进程函数

 int pthread_create(
                    pthread_t *restrict tidp,   //新线程ID。
                    const pthread_attr_t *restrict attr,  //线程属性,默认为NULL
                    void *(*start_rtn)(void *), //子进程执行的程序,函数指针
                    void *restrict arg //子进程函数的参数,这个参数类型为任意类型,所以子进程需要多个参数时,我们需要打包成一个结构体,传                   
   //入结构体
                     ); 

3、销毁结构体函数

int pthread_attr_destroy(pthread_attr_t *attr); //结构体使用结束后应该销毁
成功返回0

4、会合函数

int pthread_join(pthread_t thread, void **retval);//主线程要等待非分离的子线程结束才能完全释放资源
成功返回0

5、初始化锁函数

 Int  pthread_mutex_init(pthread_mutex_t  *restrict_mutex,//锁的一种属性结构体
                          const pthread_mutextattr_t *restrict attr//NULL为默认值普通锁
                         )

6、锁操作函数

int pthread_mutex_lock(pthread_mutex_t *mutex);//获取阻塞锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);//获取非组赛锁
int pthread_mutex_unlock(pthreadd_mutex_t *mutex)//释放锁

五、代码实现

注:服务器和客户端相互配合使用,这里只贴出服务器server的主程序main程序,main程序中调用的一些用户程序包含在其他的c文件,我将它提交的git服务器进行管理,有需要的朋友点击以下链接查看。

服务器端完整代码链接:点击查看

客户端完整代码链接:点击查看

server_main.c:

#include "server.h"
int main(int argc,char **argv)
{

	int		server_port;
	int		sockfd 	= -1;
	int		clienfd	= -1;
	int		re_val 	= -1;

	pthread_t 	pthread_id;

	struct sockaddr_in   clien_addr;
	socklen_t            clienaddr_len;

	//pthread_args_t       pthread_args ;
	pthread_args_t       *pthread_args ;

	//参数解析
	re_val = argument_parse(argc, argv, &server_port);
	if(re_val < 0)
	{
		printf("参数解析错误!\n");
		exit(0);
	}

	//套接字初始化
	re_val = socket_init(server_port,&sockfd);
	if(re_val < 0)
	{
		printf("套接字初始化错误!\n");
	}

	//服务器开始工作
	while(1)
	{	
		//等待客户端连接上来
		printf("serve is waitting to connect new client...\n");
		mylog(__FUNCTION__,__LINE__,INFO,"serve is waitting to connect new client...:%s\n",strerror(errno));

		if((clienfd = accept(sockfd,(struct sockaddr *)&clien_addr,&clienaddr_len)) <0 )
		{

			printf("serve accept failed: %s\n",strerror(errno));
			mylog(__FUNCTION__,__LINE__,ERROR,"server accept faild:%s\n",strerror(errno));
			return -1;
		
		}
		printf("Accept new client[%s:%d] scuccessfully.\n",inet_ntoa(clien_addr.sin_addr),ntohs(clien_addr.sin_port));
		mylog(__FUNCTION__,__LINE__,INFO,"Accept new client[%s:%d] scuccessfully.\n",\
			inet_ntoa(clien_addr.sin_addr),ntohs(clien_addr.sin_port));


		//线程开始	
		pthread_args = (pthread_args_t *)malloc(sizeof(pthread_args_t));
		//pthread_args.clienfd = clienfd;
		pthread_args->clienfd = clienfd;	
                                       
 		thread_start(&pthread_id, pthread_work, pthread_args);

	}
	return 0;
}


七、测试结果

流程:通过客户端采集数据,这里采集的是室内环境下的温度,然后通过TLV(用户自定义协议)发送给服务器中的数据库中保存。 以下显示图片均是服务器端接收到的数据,数据格式是:设备号28-041731f7c0ff + 温度 + 时间。

第一次测试结果:在这里插入图片描述
显示一些特殊符号<、[ 等乱码,经过检查代码发现下面一个问题:close(sockfd);我以为客户端连接上建立了新的文件描述符,这个描述符就可以关闭,其实不然,关闭就会出现乱码,所以后面的程序中直接将它删除,数据正常。

		//线程开始	
		//close(sockfd);
		pthread_args = (pthread_args_t *)malloc(sizeof(pthread_args_t));
		//pthread_args.clienfd = clienfd;
		pthread_args->clienfd = clienfd;	
                                       
 		thread_start(&pthread_id, pthread_work, pthread_args);


第二次测试结果:
在这里插入图片描述
client[4]代表第一个客户端已连接
client[5]代表第二个客户端已连接

client[4]单独连接的适合一切正常,但是当client[5]连接上来的时候,client[4]就被顶下去了。和小伙伴们一起分析:

*定义子线程参数结构体*/
typedef struct
{
	int		clienfd;//客户端文件描述符
	int		count;//线程数
	pthread_mutex_t	lock;//线程锁

}pthread_args_t;

pthread_args_t pthread_args;//这是传给子线程的参数结构体
pthread_create //在这里调用

问题就在于结构体定义的方式,因为子线程和主线程在同一个进程空间运行,子进程之间共享资源,我在主函数栈中静态分配一个结构体空间,每来一个客户端都会对这个结构体中clienfd的值进行改变,所以后面连接的都是最后一个连接上来的客户端,经过和小伙伴们分析总结出以下解决方案:

1、 传单个值
2 、malloc动态分配内存,然后传地址

		//线程开始	
		pthread_args = (pthread_args_t *)malloc(sizeof(pthread_args_t));

3、 静态分配内存,可以在栈上分配一个足够大的,可以容纳所有参数,然后传地址;

int main()
{
pthread_args_t pthread_args[10000];
create_x(...., &pthread_args[i]);
}

第三次测试结果:

在这里插入图片描述结果显示,服务器有序的稳定的向客户端4和客户端5交叉接收数据。。。。

注:在这里感谢以下团队小伙伴的帮助,和一群优秀的小伙伴一起学习事倍功半!代码程序肯定有很多地方不够完美,请朋友们积极指出,谢谢~~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值