#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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 5
#define MAX_BUFFER_SIZE 1024
void processcli_57(int connectfd_57,struct sockaddr_in client_57);
void *fun(void *arg);
void savedata_r(char* recvbuf,int len,char* cli_data);
struct ARG{
int connfd;
struct sockaddr_in client;
};
struct ST_DATA{
int index;
};
pthread_key_t key;
pthread_once_t once=PTHREAD_ONCE_INIT;
static void destructor(void *ptr)
{
free(ptr);
}
static void createkey_once(void)
{
pthread_key_create(&key,destructor);
}
int main(void)
{
int listenfd_57, connectfd_57;
struct sockaddr_in server_57;
struct sockaddr_in client_57;
socklen_t sin_size;
pthread_t tid;
struct ARG *arg;
if((listenfd_57=socket(AF_INET, SOCK_STREAM, 0))==-1)
{
perror("Create socket failed");
exit(-1);
}
int opt =SO_REUSEADDR;
setsockopt(listenfd_57, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server_57, sizeof(server_57));
server_57.sin_family = AF_INET;
server_57.sin_port = htons(PORT);
server_57.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listenfd_57, (struct sockaddr *)&server_57, sizeof(struct sockaddr))==-1)
{
perror("Bind error");
exit(-1);
}
if (listen(listenfd_57, BACKLOG) == -1)
{
perror("listen error");
exit(-1);
}
sin_size = sizeof(struct sockaddr_in);
while(1)
{
if ((connectfd_57 = accept(listenfd_57, (struct sockaddr *)&client_57, &sin_size)) == -1)
{
perror("accept error");
exit(-1);
}
arg=(struct ARG*)malloc(sizeof(struct ARG));
arg->connfd=connectfd_57;
memcpy((void *)&arg->client,&client_57,sizeof (client_57));
if(pthread_create(&tid,NULL,fun,(void*)arg))
{
perror("Pthread_create() error");
exit(1);
}
}
close(listenfd_57);
}
void processcli_57(int connectfd_57,struct sockaddr_in client_57){
char cli_data[MAX_BUFFER_SIZE];
char buffer_57[MAX_BUFFER_SIZE],cli_name[MAX_BUFFER_SIZE],sendbuf[MAX_BUFFER_SIZE];
int bytes_received = recv(connectfd_57, cli_name, MAX_BUFFER_SIZE, 0);
printf("Get a new connection from %s.\n",inet_ntoa(client_57.sin_addr));
if (bytes_received == 0) {
close(connectfd_57);
printf("Disconnected");
return;
}
cli_name[bytes_received]='\0';
printf("client name is %s\n",cli_name);
while (bytes_received = recv(connectfd_57, buffer_57, MAX_BUFFER_SIZE,0))
{
buffer_57[bytes_received] = '\0';
printf("Received client (%s) message: %s", cli_name, buffer_57);
savedata_r(buffer_57,bytes_received,cli_data);
int i=0;
for(i=0; i < bytes_received ; i++)
sendbuf[i] = buffer_57[bytes_received-i-1];
sendbuf[i] ='\0';
send(connectfd_57, sendbuf, strlen(sendbuf), 0);
}
close(connectfd_57);
printf("Client(%s) disconnected.\nUser's data:%s\n",cli_name,cli_data);
}
void *fun(void* arg)
{
struct ARG *info;
info=(struct ARG*)arg;
processcli_57(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,createkey_once);
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';
}
/*client.c*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define PORT 1234
#define MAXDATASIZE 1024
int main(int argc, char *argv[])
{
int fd, numbytes;
char buf_57[MAXDATASIZE];
//struct hostent * he;
struct sockaddr_in server_57,local_57;
if (argc != 2)
{
printf("Usage: %s <IP address>\n", argv[0]);
exit(-1);
}
/*if ((he = gethostbyname(argv[1])) == NULL)
{
perror("gethostbyname error.");
exit(1);
}*/
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Create socket failed.");
exit(1);
}
bzero(&server_57, sizeof(server_57));
server_57.sin_family = AF_INET;
server_57.sin_port = htons(PORT);
//server.sin_addr.s_addr = inet_addr("10.21.48.52");
//server.sin_addr = *((struct in_addr *) he->h_addr);
if (inet_pton(AF_INET, argv[1], &server_57.sin_addr) <= 0) {
perror("Invalid IP address.");
exit(1);
}
if (connect(fd, (struct sockaddr *)&server_57, sizeof(struct sockaddr)) == -1)
{
perror("connect failed.");
exit(1);
}
socklen_t len = sizeof(local_57);
if (getsockname(fd, (struct sockaddr *)&local_57, &len) == -1) {
perror("getsockname error.");
exit(1);
}
printf("Local address: %s:%d\n", inet_ntoa(local_57.sin_addr), ntohs(local_57.sin_port));
len = sizeof(server_57);
if (getpeername(fd, (struct sockaddr *)&server_57, &len) == -1) {
perror("getpeername error.");
exit(1);
}
printf("Server address: %s:%d\n", inet_ntoa(server_57.sin_addr), ntohs(server_57.sin_port));
int n=1;
while(1){
if(n==1)
{
printf("please input your name:\n");
scanf("%s",buf_57);
int c;
while ((c = getchar()) != '\n' && c != EOF) {}
send(fd, buf_57, MAXDATASIZE, 0);
n++;
}
printf("Enter message to send to server,enter 'quit' to exit: ");
fgets(buf_57, MAXDATASIZE, stdin);
if (strcmp(buf_57, "quit\n") == 0) {
printf("disconnected\n");
close(fd);
exit(0);
}
if (send(fd, buf_57, strlen(buf_57), 0) == -1) {
perror("send error.");
exit(1);
}
if ((numbytes = recv(fd, buf_57, MAXDATASIZE, 0)) == -1) {
perror("recv error.");
exit(1);
}
buf_57[numbytes] = '\0';
printf("Server Message(reversed): %s\n", buf_57);
n=5;
}
close(fd);
}
/*server*/
int main()
{
int sockfd,connfd;//wenjian miaoshu fu
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen=sizeof(client);
sockfd=socket(AF_INET,SOCK_STREAM,0);//error back -1
int opt=SO_REUSEADDR;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bzero(&server,sizeof(server));
server.sin_port=htons(PORT);
server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
bind(sockfd,(struct sockaddr*)&server,sizeof(server));
listen(sockfd,BACKLOG);
connfd=accept(sockfd,(struct sockaddr *)&client,&addrlen);
printf("you got a connection from client's ip is %s,port is %d\n",inet_ntoa(client.sin_addr), ntohs(client.sin_port));
//inet_ntoa 函数来将网络字节序的 IP 地址转换为点分十进制表示的字符串。对于端口号,使用 ntohs 函数来将网络字节序的端口号转换为主机字节序的短整型值。
send(connfd,"welcome\n",8,0);
close(connfd);
close(sockfd);
}
/*client*/
int main(int argc,char *argv[])
{
int sockfd;
char buf[MAXDATASIZE];
int len;
struct sockaddr_in server;
if(argc !=2)
{
printf("Usage:%s<IP address>\n",argv[0]);
exit(-1);
}
sockfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr=inet_addr(argv[1]);
//inet_addr("192.168.1.1");
//argv 是命令行参数的数组,argv[1] 是一个字符串,表示服务器的 IP 地址。因此,需要使用 inet_addr 函数
connect(sockfd,(struct sockaddr *)&server,sizeof(server));
len=recv(sockfd,buf,MAXDATASIZE,0);
buf[len]='\0';
printf("server:%s\n",buf);
close(sockfd);
}
//套接字类型
SOCK_STREAM 流式套接字
SOCK_DGRAM 数据报套接字
SOCK_RAW 原始套接字
//套接字地质结构
IPv4:sockaddr_in
IPv6:sockaddr_in6
struct in_addr{
In_addr_t s_addr;//32位IPv4地址
}
struct sockaddr_in{
unit8_t sin_len;
sa_family_t sin_family;
unit16_t sin_port;
struct in_addr sin_addr;//32位IP地址
char sin_zero;
}
//通用套接字地址结构
struct sockaddr{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[4];
}
//许多套接字函数都使用这个通用套接字地质结构,因此需要转换,例如bind
bind(sockfd.(struct sockaddr*)&server,sizeof(server));
//网络字节顺序采用大端字节
htons();
htonl();
ntohs();
ntohl();
//s是16位短整数、l是32位长整数
inet_aton();//把点分十进制的ip地址字符串转化成网络字节顺序的二进制,成功返回长整型数,失败为0
inet_addr();//与上相同
inet_ntoa();//网络字节ip地址转化成点分十进制ip地址字符串,成功时返回指针,失败null
//这三个只能用于ipv4
//字节操纵函数
void bzero(void *dest,size_t nbytes);
//对指定地址dest中指定长度nbytes设置为0
void bcopy(const void *src,void *dest,size_t nbytes);
//指定数目的字节从srccopy到dest
int bcmp(const void *src,void *dest,size_t nbytes);
//比较两个区域前面的指定字节数,相同0,不相同非0
void *memset(void *dest,int c,size_t len);
//把前len个字节设置成c的内容
void *memcop();
//类似与bcopy,但是不能处理src与dest相重叠的情况
int memcmp(cont void *ptr1,const void *ptr2,size_t nbytes);
比较两个字符串,相同0,否则非0
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
//激发三次握手,成功返回0,失败-1
如果客户端再75s内没收到SYN的响应(客户端在着75s内会尝试多次),则返回ETIMEDOUT
如果对客户端SYN的响应是RST,表明服务器在指定端口没有进程等待连接,返回ECONNREFUSED
如果客户发出的SYN最终目的地不可达,返回EHOSTUNREACH或ENETUNREACH
socklen_t len = sizeof(local_57);
if (getsockname(fd, (struct sockaddr *)&local_57, &len) == -1) {
perror("getsockname error.");
exit(1);
}
printf("Local address: %s:%d\n", inet_ntoa(local_57.sin_addr), ntohs(local_57.sin_port));
len = sizeof(server_57);
if (getpeername(fd, (struct sockaddr *)&server_57, &len) == -1) {
perror("getpeername error.");
exit(1);
}
printf("Server address: %s:%d\n", inet_ntoa(server_57.sin_addr), ntohs(server_57.sin_port));
struct hostent *gethostbyname(const char *hostname);
//ipv4\ipv6都可使用
//域名、点分十进制都可接收,若接收点分十进制,则拷贝到结果中,返回指向hostent结构
struct hostent{
char *h_name;
char **h__aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
}
1.服务器主机崩溃
客户端发送一个数据后,阻塞于recv,等待接收数据。客户端会坚持重传,试图接收ACK。重传最多12次,放弃前等待9分钟。可设置recv的超时时间改变长短。
2,服务器主机崩溃后重启
如果服务器主机不主动发送消息告知自己崩溃,那么主机重启后只能回复客户端RST
3.服务器运行时主动关闭主机
由init进程给所有进程发信号SIGTERM,然后给还在运行的进程发SIGKILI。客户端发送数据,服务器可以接收,但是只会回复客户端RST
/*server*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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 5
#define MAX_BUFFER_SIZE 1024
int main()
{
int sockfd;//wenjian miaoshu fu
int num;
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen=sizeof(client);
char buf[MAX_BUFFER_SIZE];
sockfd=socket(AF_INET,SOCK_DGRAM,0);//error back -1
bzero(&server,sizeof(server));
server.sin_port=htons(PORT);
server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
bind(sockfd,(struct sockaddr*)&server,sizeof(server));
while(1)
{
num=recvfrom(sockfd,buf,MAX_BUFFER_SIZE,0,(struct sockaddr *)&client,&addrlen);
buf[num]='\0';
printf("you got a connection from client's ip is %s,port is %d\nclient's info: %s",inet_ntoa(client.sin_addr), ntohs(client.sin_port),buf);
sendto(sockfd,"welcome\n",8,0,(struct sockaddr *)&client,addrlen);
}
close(sockfd);
}
/*client*/
#define PORT 1234
#define MAXDATASIZE 1024
int main(int argc,char *argv[])
{
int sockfd,num;
char buf[MAXDATASIZE];
struct hostent *he;//假如要用到gethostbyname时
struct sockaddr_in server,peer;
socklen_t addrlen=sizeof(server);
if(argc !=2)
{
printf("Usage:%s<IP address>\n",argv[0]);
exit(-1);
}
sockfd=socket(AF_INET,SOCK_DGRAM,0);
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr=inet_addr(argv[1]);
sendto(sockfd,"hello\n",6,0,(struct sockaddr *)&server,sizeof(server));
while(1)
{
num=recvfrom(sockfd,buf,MAXDATASIZE,0,(struct sockaddr *)&peer,&addrlen)
if(addrlen!=sizeof(server)||memcmp((const void *)&server,(const void *)&peer,addrlen)!=0)
{
printf("receive info from other server\n");
continue;
}
buf[num]='\0';
printf("server info:%s",buf);
break;
}
close(sockfd);
}
1.数据报丢失
那么recvfrom会一直被阻塞,解决的唯一方法就是给客户端的recvfrom设置一个超时时间
2.验证收到的响应
客户端只想知道来自服务器的响应时
if(len!sizeof(server)||memcmp((const void *)&server,(const void *)&peer,len)!=0)
比较recvfrom返回的地址长度len和服务器的地址长度,然后比较recvfrom返回的服务器地址是否一致。
另一个解决办法是,由客户端在dns中查找服务器主机名验证主机。
3.服务器未运行
服务器会以端口不可达的ICMP消息回应,但是这个消息不会传给客户端,客户端会永远阻塞于recvfrom。详见p48
详见p49
int main()
{
pid_t pid;
if((pid=fork())<0)
{
perror("fork error");
}
else if (pid == 0)//child
{
printf("child getpid()=%d\n",getpid());
}
else if(pid>0)
{
printf("parent getpid()=%d\n",getpid());
}
}
//fork后的东西都会执行两次,因为fork后生成了两个进程,一个进程为父进程,fork返回pid;另一个进程为父进程的子进程,fork会返回0,但该子进程也是有pid的。
例如
fork();
fork();
printf("hello\n");
该段代码会生成4个hello。因为第一个fork执行后,后面的fork、printf会执行两次,一次在父进程、一次在子进程。父进程中fork也开始执行,再生成一个父进程、一个子进程。同理子进程中也如此生成。我们称这四个进程为“父父进程”,“父子进程”,“子父进程”,“子子进程”。
其效果如下
int main()
{
pid_t pid;
if(pid = fork()>0)
{
if(pid=fork()>0)
{
printf("parent parent pid=%d\n",getpid());
}
else
{
printf("parent child pid=%d\n",getpid());
}
}
else
{
if(pid=fork()>0)
{
printf("child parent pid =%d\n",getpid());
}
else {
printf("child child pid=%d\n",getpid());
}
}
}
结果:
parent parent pid=54649
按 <RETURN> 来关闭窗口...
child parent pid =54650
parent child pid=54651
child child pid=54652
在进程编程中,wait()
函数用于等待一个子进程的终止,并获取子进程的终止状态。这个函数是 POSIX 标准中定义的,它使得父进程能够暂停执行,直到一个子进程结束。
pid_t wait(int *status);
int *status`:一个指针,指向一个整数变量,用于存储子进程的终止状态。如果子进程正常终止,`status` 中的值是 `WEXITSTATUS(status)`,其中 `status` 是子进程退出时的状态值(通常是退出代码)。如果子进程是由于信号而终止的,`status` 中的值是 `WTERMSIG(status)`,其中 `status` 是导致子进程终止的信号的编号。如果子进程是非正常终止,`status` 中的值是 `WUNTRACED
wait()
函数的作用包括:
- 阻塞父进程的执行,直到一个子进程终止。
- 获取子进程的终止状态,包括退出代码或信号编号。
- 如果有多个子进程结束,
wait()
函数只返回其中一个子进程的信息。
waitpid()
是一个在 POSIX 兼容操作系统中使用的系统调用,它用于等待一个指定的进程 ID (PID) 的子进程结束,并获取子进程的终止状态。与 wait()
函数不同,waitpid()
允许你指定要等待的子进程,以及是否等待所有子进程结束
-
pid_t waitpid(pid_t pid, int *status, int options); `waitpid()` 函数的作用包括: - 等待指定的子进程结束。 - 获取子进程的终止状态。 - 可以选择是否等待所有子进程结束。 - 可以选择是否返回正在等待的子进程的状态。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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 5
#define MAX_BUFFER_SIZE 1024
void process_cli(int connfd,struct sockaddr_in client);
int main()
{
pid_t pid;
int sockfd,connfd;//wenjian miaoshu fu
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen=sizeof(client);
sockfd=socket(AF_INET,SOCK_STREAM,0);//error back -1
int opt=SO_REUSEADDR;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bzero(&server,sizeof(server));
server.sin_port=htons(PORT);
server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
bind(sockfd,(struct sockaddr*)&server,sizeof(server));
listen(sockfd,BACKLOG);
while(1)
{
connfd=accept(sockfd,(struct sockaddr *)&client,&addrlen);
if((pid=fork())>0)
{
close(connfd);
continue;
}
else if(pid=fork()==0)
{
close(sockfd);
process_cli(connfd,client);
exit(0);
}
else {
printf("fork() error\n");
exit(0);
}
}
}
void process_cli(int connfd,struct sockaddr_in client)
{
int num;
char recvbuf[MAX_BUFFER_SIZE],sendbuf[MAX_BUFFER_SIZE],cli_name[MAX_BUFFER_SIZE];
printf("get connection form %s.\n",inet_ntoa(client.sin_addr));
num = recv(connfd,cli_name,MAX_BUFFER_SIZE,0);
if(num==0)
{
close(connfd);
printf("%s disconnected.\n",cli_name);
return;
}
cli_name[num-1]='\0';
printf("client's name:%s\n",cli_name);
while(num =recv(connfd,recvbuf,MAX_BUFFER_SIZE,0))
{
recvbuf[num]='\0';
printf("%s : %s",cli_name,recvbuf);
send(connfd,"successful received\n",sizeof("successful received\n"),0);
}
close(connfd);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define PORT 1234
#define MAXDATASIZE 1024
/*client*/
int main(int argc,char *argv[])
{
int sockfd;
char buf[MAXDATASIZE];
int num;
struct sockaddr_in server;
if(argc !=2)
{
printf("Usage:%s<IP address>\n",argv[0]);
exit(-1);
}
sockfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr=inet_addr(argv[1]);
//inet_addr("192.168.1.1");
//argv 是命令行参数的数组,argv[1] 是一个字符串,表示服务器的 IP 地址。因此,需要使用 inet_addr 函数
connect(sockfd,(struct sockaddr *)&server,sizeof(server));
printf("connected to server\n");
printf("input name:");
fgets(buf,MAXDATASIZE,stdin);
send(sockfd,buf,strlen(buf),0);
while(1)
{
fgets(buf,MAXDATASIZE,stdin);
send(sockfd,buf,strlen(buf),0);
num=recv(sockfd,buf,MAXDATASIZE,0);
buf[num]='\0';
printf("from server:\n%s",buf);
}
close(sockfd);
}
1.fork是昂贵的
2.fork父子进程、兄弟进程间通信需要通信IPC机制,给通信带来困难
3.多进程一定程度上仍不能有效利用系统资源
4.系统中进程个数有限制
int pthread_create(pthread_t *tid,const pthread_attr_t * attr,void *(*func)(void *),void *arg)
//成功0,失败-1
//还有返回错误EAGAIN超过线程数目限制 ENOMEN没有足够内存 EINVAL无效attr值
//pthread_t *tid:这是一个指向 pthread_t 类型变量的指针,该变量用于存储新创建线程的标识符。pthread_t 是一个无符号整数类型,用于唯一标识线程。
//const pthread_attr_t *attr:这是一个指向 pthread_attr_t 类型变量的指针。pthread_attr_t 是一个结构体类型,用于指定新创建线程的属性。如果你不需要设置特定的属性,你可以传递一个指向默认属性的指针,即 NULL。
//void *(*func)(void *):这是线程执行函数,也称为线程函数。它是一个函数指针,其类型为 void* (*)(void*)。线程函数接受一个 void* 类型的参数,并返回一个 void* 类型的值。线程函数是线程执行的核心,是线程创建的目的。
//void *arg:这是传递给线程函数的参数。它是一个 void* 类型的指针,你可以通过它向线程函数传递数据
int pthread_join(pthread_t tid,void **status);
//用于等待一个指定的线程结束,并获取该线程的终止状态。在多线程程序中,主线程有时需要等待其他线程完成其工作,这时就可以使用 pthread_join() 函数。
被等待线程必须是当前进程的成员,不能是分离的线程和守护线程
pthread_join() 函数的返回值:
如果线程正常结束,返回 0。
如果线程已退出但尚未加入,返回 EINVAL。
如果线程尚未结束,返回 EBUSY。
如果出现其他错误,返回相应的错误码。
pthread_join() 函数的作用包括:
等待指定的线程结束。
获取线程的终止状态。
在线程结束之前,阻止调用 pthread_join() 的线程的执行。
linux中提供一种基本的进程同步机制-互斥锁,用于保护现成代码中共享数据的完整性。
只有一个线程能加锁,其他线程只能等待解锁后才能加锁
pthread_mutex_lock();
pthread_mutex_unlock()
见书p70
以线程特定数据TSD取代静态变量,他是线程私有的全局数据
int pthread_key_create(ptread_key_t *key,void(*destructor)(void *value));
进程中分配一个键字,关键字是进程内部唯一的,用来标识线程专用数据项
int pthread_setspecific(pthread_key_t key, const void *value);
该函数为TSD关键字绑定(关联)一个与本线程相关的值
void *pthread_getspecific(pthread_key_t key);
该函数获得与调用线程相关的关键字所绑定的值
int pthread_key_delete(pthread_key_t key);
该函数删除进程内的TSD表示的关键字。该函数既不检查TSD是否有绑定值,也不调用该关
键字的析构函数。