浅谈并发服务器---多线程并发---4(线程安全)


           在多线程环境下,进程内的所有的线程共享进程 的数据空间,因此全局变量为所有线程共有。在程序设计中有时需要保存线程自己的全局变量,这种特殊的变量仅在某个线程内部 有效。在上一篇文章中提到savadate()函数是一个非线程安全函数,是因为使用了静态变量,在多线程环境里,应避免使用静态变量。在linux系统中提供了线程特定数据(TSD)来取代静态变量。它类似于全局变量,但是是各个线程私有的,它以线程为界限。TSD是定义线程私有数据的唯一方法。同一进程中的所有线程,它们的同一特定数据项都由一个进程内唯一的关键字key来标志。用这个关键字,线程可以存取线程私有数据。

            在线程特定数据中通常使用4个函数.

1) #include <pthread.h>

   int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *));

pthread_key_create()函数在进程内部分配一个标志TSD的关键字,参数key指向创建的关键字,该关键字对一个进程中的所有线程是惟一的。所以在创建key时,每个进程只能调用一次创建pthread_key_create(). 。在key创建之前,所有线程的关键字值是NULL。一旦关键字被建立,每个线程可以为该关键字绑定一个值,这个绑定的值对于线程是唯一的,每个线程独立维护。第二个参数为一个函数指针,如果指针不为空,则在线程退出时将以key所关联的数据为参数调用destr_function(),释放分配的缓冲区。

2)#incude <pthread.h>

int pthread_setspecific( pthread_key_t key, const void *value);

 此函数为TSD关键字绑定一个与本线程相关的值。参数key是TSD关键字,参数value是与本线程相关的值,value通常指向动态分配的内存空间。

3)#include <pthread.h>

void *pthread_getspecific(pthread_key_t    key);

pthread_getspecific()函数获取与调用线程相关的TSD关键字所绑定的值。参数key是TSD关键字。该函数正常执行后返回与调用线程相关的TSD关键字所绑定的值,否则返回NULL。

4)include <pthread.h>

int pthread_once(pthread_once_t  *once, void (*init) (void ))

pthread_once()函数使用once参数所指定的变量,保证每个进程只调用一次inti函数。通常once参数取常量PTHRAED_ONCE_INIT,它保证每个进程只调用一次init函数。


下面是使用线程安全函数的服务器端程序:(通过为每个线程设置一个私有数据data)

/*
 * =====================================================================================
 *
 *       Filename:  thread_server.c
 *
 *    	 Description:  多线程并发服务器的服务器端
 *
 *       Version:  1.0
 *       Created:  2014年07月21日 20时39分15秒
 *       Revision:  none
 *       Compiler:  gcc
 *	     CopyRight: open , free , share
 *       Author:  yexingkong(zhangbaoqing)
 *	     Email: abqyexingkong@gmail.com
 *       Company:  Xi'an University of post and Telecommunications
 *
 * =====================================================================================
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>



#define	PORT		1234		/*端口定义  */
#define	BACKLOG		20			/*监听队列大小  */
#define	MAXDATASIZE		1000	/*字符缓冲区大小  */





void process_cli(int connfd, struct sockaddr_in client);

void *function(void *arg);

void savedata_r(char *recvbuf, int len, char *cli_data);

struct ARG{
	int connfd;		//已链接套接字描述符
	struct sockaddr_in client;   //保存客户端地址信息
};

pthread_key_t	key;  //线程私有数据 
pthread_once_t	once=PTHREAD_ONCE_INIT;



static void destructor(void *ptr)
{
	free(ptr);   //当线程退出时,会注销与key值所关联的数据空间data
}

//创建TSD关键字
static  void creatkey_once(void )
{
	pthread_key_create(&key, destructor);  //分配一个标志TSD的关键字

}


struct ST_DATA{
	int index;
};


void process_cli(int connfd, struct sockaddr_in client)
{
	int num;
	char	recvbuf[MAXDATASIZE],sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];
	char cli_data[1000];

	printf("you got a connection from %s.\n",inet_ntoa(client.sin_addr));
	num = recv(connfd, cli_name, MAXDATASIZE,0);
	if (num == 0)
	{
		close(connfd);
		printf("Client disconnection\n");
		return;
	}

	cli_name[num - 1] = '\0';
	printf("Client's name is %s .",cli_name);
	while(num = recv(connfd, recvbuf, MAXDATASIZE, 0))
	{
		recvbuf[num] = '\0';
		printf("Received client( %s ) message: %s",cli_name, recvbuf);

		savedata_r(recvbuf, num, cli_data);//调用保存客户端数据函数

		int i;
		for (i = 0; i < num - 1; i++)
		{
			if ((recvbuf[i] >- 'a' && recvbuf[i] <= 'z') || (recvbuf[i] >= 'A' && recvbuf[i] <= 'Z'))
			{
				recvbuf[i] = recvbuf[i] + 3;//对客户端数据进行加密
				if ((recvbuf[i] > 'Z' && recvbuf[i] <= 'Z'+3) || (recvbuf[i]>'z'))
				{
					recvbuf[i] = recvbuf[i] - 26;
				}
			}
			sendbuf[i] = recvbuf[i];
		}
			sendbuf[num - 1] = '\0';
			send(connfd, sendbuf, strlen(sendbuf), 0);
	}	
	
	close(connfd);
}


void * function(void *arg)
{
	struct ARG *info;
	
	info = (struct ARG *)arg;
	process_cli(info->connfd, info->client);
	free(arg);
	pthread_exit(NULL);
}

//线线程安全函数
void savedata_r(char *recvbuf, int len, char *cli_data)
{
	struct ST_DATA *data;
	

	pthread_once(&once , creatkey_once);  //在第一个新线程中执行一次creatkey_once函数

	//如果当前线程没有分配空间给TSD,则分配空间并与TSD关键字绑定。
	if ((data  = (struct ST_DATA *)pthread_getspecific(key)) == NULL)
	{
		data = (struct ST_DATA *)malloc(sizeof(struct ST_DATA));
		pthread_setspecific(key, data); 
		data -> index = 0;
	}
	int i = 0;
	while(i < len - 1)
	{
		cli_data[data->index++] = recvbuf[i];
		i++;
	}
	cli_data[data->index] = '\0';
}



int main(int argc, char *argv[])
{

		int listenfd = 0, connfd = 0; //监听套接字描述符和已链接描述符定义
		pthread_t	tid;        
		struct ARG *arg;
		struct sockaddr_in server;   //tcp/ip协议族地址格式结构体变量
		struct sockaddr_in client;     //保存客户端地址信息
		socklen_t	len;

	//使用ipv4协议以及tcp类型
		if ((listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
		{ perror("Creating socket failed\n");
			exit(1);
		}

		//使地址重用,由于系统默认是只允许一个套接字绑定在一个特定的协议上,并且当该套接字关闭后,\
		//系统仍不允许在该地地址上绑定其他套接字。如果不用,有可能出现错误:Bind() error:Address aleady in use
		int opt = SO_REUSEADDR;
		setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

		bzero(&server, sizeof(server));      //将指定数目字节置0
		server.sin_family = AF_INET;
		server.sin_port = htons(PORT);
		server.sin_addr.s_addr = htonl(INADDR_ANY); //ip=0.0.0.0表示可链接任何ip地址

		//绑定端口
		if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1)
		{
			perror("Bind() error\n");
			exit(1);
		}

		//监听端口
		if (listen(listenfd, BACKLOG) == -1)
		{
			perror("listen() error\n");
			exit(1);
		}

		len = sizeof(client);
		while(1)
		{
			if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1)
			{
				perror("accept() error\n");
				exit(1);
			}
			arg = (struct ARG *)malloc(sizeof(struct ARG));
			arg -> connfd = connfd;
			memcpy((void *)&arg->client, &client, sizeof(client));

			if (pthread_create(&tid, NULL, function, (void *)arg))
			{
				perror("pthread_create() error\n");
				exit(1);
			}
		}

		close(listenfd);  //关闭监听套接字
	return EXIT_SUCCESS;
}


客户端源程序:

/*
 * =====================================================================================
 *
 *       Filename:  thread_client.c
 *
 *    	 Description:  多线程服务器的客户端程序
 *
 *       Version:  1.0
 *       Created:  2014年07月22日 09时39分18秒
 *       Revision:  none
 *       Compiler:  gcc
 *	     CopyRight: open , free , share
 *       Author:  yexingkong(zhangbaoqing)
 *	     Email: abqyexingkong@gmail.com
 *       Company:  Xi'an University of post and Telecommunications
 *
 * =====================================================================================
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>



#define	PORT	1234			/*端口定义 */
#define	MAXDATASIZE		500		/*字符缓冲区的大小 */



/* -------------------------------------------------------------------*/
/** 
 * @Synopsis=   客户端的数据处理
 * 
 * @Param= fp   文件指针
 * @Param= sockfd   链接描述符
 */
/* ----------------------------------------------------------------------------*/
void process(FILE *fp, int sockfd);

/* -------------------------------------------------------------------*/
/** 
 * @Synopsis=   从指定的文件中获取字符
 * 
 * @Param= sendline   字符指针,指向保存所获取的字符数组的首地址
 * @Param= len   所读取的字符最大数
 * @Param= fp   文件指针
 * 
 * @Returns= 返回NULL或sendline  
 */
/* ----------------------------------------------------------------------------*/
char * getMessage(char *sendline, int len, FILE *fp);


void process(FILE *fp, int sockfd)
{
	char sendline[MAXDATASIZE], recvline[MAXDATASIZE];
	int num;

	printf("Connected to server. \n");
	printf("Input your name: ");
	if  (fgets(sendline, MAXDATASIZE, fp) == NULL)
	{
		printf("\nExit.\n");
		return;
	}

	send(sockfd, sendline, strlen(sendline), 0);
	while(getMessage(sendline, MAXDATASIZE, fp) != NULL)
	{
		send(sockfd, sendline, strlen(sendline),0);
		if((num = recv(sockfd, recvline, MAXDATASIZE, 0)) == 0)
		{
			printf("Server terminated .\n");
			return;
		}
		recvline[num] = '\0';

		sendline[strlen(sendline)-1]='\0';
		if ((0 == strcmp(sendline,"bye")) || (0 == strcmp(sendline, "exit")) || (0 == strcmp(sendline, "quit")))
		{
			break;
		}
		printf("Server Message: %s\n", recvline);
	}
	printf("\nExit.\n");
}


char *getMessage(char *sendline, int len, FILE *fp)
{
	printf("Input string to server: ");
	return (fgets(sendline,MAXDATASIZE, fp));
}


int main(int argc, char *argv[])
{

	int sockfd;
	struct hostent *he;
	struct sockaddr_in server;

	if (argc != 2)
	{
		printf("Usage: %s <IP Address>l\n",argv[0]);
			exit(1);
	}

	if (gethostbyname(argv[1]) == NULL)
	{
		printf("gethostbynam() error\n");
		exit(1);
	}

	//创建本地套接字
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		printf("socket() error\n");
		exit(1);
	}

	bzero(&server, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(PORT);
	
	//链接请求
	if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1)
	{
		printf("connect() error\n");
		exit(1);
	}

	process(stdin, sockfd);
	close(sockfd);
	return EXIT_SUCCESS;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值