在多线程环境下,进程内的所有的线程共享进程 的数据空间,因此全局变量为所有线程共有。在程序设计中有时需要保存线程自己的全局变量,这种特殊的变量仅在某个线程内部 有效。在上一篇文章中提到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;
}