目标:
修改上一篇的select模式下的server,让它使用多线程来处理客户端请求(多进程的模式已经在上篇中加了注释)。
思路:
(1)服务器
我们已经在之前的客户端模型多个并发用户的过程中使用过多线程的技术了(其中还涉及到多线程利用条件变量进行线程同步),在这里我们可以很轻松的在上篇文章代码中加入线程部分代码。
//for thread
int *lptr;
pthread_t pid;
//for thread
for(i=0;i<=maxi;i++)
{
if( (sockfd=client[i]) <0)
continue;
if(FD_ISSET(sockfd,&rset))
{
//thread client
lptr=(int*)malloc(sizeof(int));
*lptr=sockfd;
int errerr=pthread_create(&pid,NULL,threadPerClient,lptr);
if(errerr!=0)
printf("err %s",strerror(errno));
FD_CLR(sockfd,&allset); //短连接后关闭socket,服务器发不过来
client[i]=-1;
printf("can read : %d,%d,%d\n",i,sockfd,nready);
if(--nready<=0)
break;
//thread client
}
}
但是!!!!真的那么轻松就可以加入吗? 为什么我不直接在pthread_create中传入sockfd,而是要再new一个整型数然后赋值给它再传递过去呢?
答案很明显,如果直接使用sockfd的指针,那么在下次sockfd被client[i]赋值时,sockfd的值变了!而这时线程的实际操作的正是这个sockfd(不只因为线程共享变量,同时还因为传递的是指针)!所以我们复制一个新值,让线程自己处理对应的fd。
这样就完了?很明显不会那么简单,让我们看看线程处理函数就知道。
void* threadPerClient(void *arg)
{
int connfd=*((int*)arg);
free(arg); //防止内存泄露
pthread_detach(pthread_self());
printf("client from %d(socket num)\n",connfd);
str_echo(connfd);
close( connfd );
return NULL;
}
看到注释了吗?“防止内存泄露“!!这是很容易发生的事,在刚才的代码中new一个新对象,那么我们就必须在使用后释放。除此以外我们还注意到这里调用了pthread_detach函数,因为线程分为joinable和unjoinable两种。
如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。
unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。或者将线程置为 joinable,然后适时调用pthread_join.
(其实就类似于进程退出时父亲进程的wait)
(2)客户端
无需修改
代码:
1 #include<sys/types.h>
2 #include<sys/socket.h>
3 #include<strings.h>
4 #include<arpa/inet.h>
5 #include<unistd.h>
6 #include<stdlib.h>
7 #include<stdio.h>
8 #include<string.h>
9 #include<errno.h>
10 #include<signal.h>
11 #include<sys/wait.h>
12 #include<sys/time.h>
13 #include<pthread.h>
14
15 #define LISTEN_PORT 84
16
17 //服务器处理
18 void str_echo(int sockfd) // 服务器收到客户端的消息后的响应
19 {
20 ssize_t n;
21 char line[512];
22
23 printf("ready to read\n");
24
25 while( (n=read(sockfd,line,512))>0 ) //阻塞IO版本
26 {
27 line[n]='\0';
28 printf("Client Diary: %s\n",line);
29
30 char msgBack[512];
31 snprintf(msgBack,sizeof(msgBack),"recv: %s\n",line);
32 write(sockfd,msgBack,strlen(msgBack));
33 bzero(&line,sizeof(line));
34 }
35 printf("end read\n");
36 }
37
38 //子进程结束的信号处理
39 float timeuse;
40 void sig_child(int signo) //父进程对子进程结束的信号处理
41 {
42 pid_t pid;
43 int stat;
44
45 struct timeval tpstart,tpend;
46 //float timeuse;
47 gettimeofday(&tpstart,NULL);
48
49 while( (pid=waitpid(-1,&stat,WNOHANG))>0)
50 printf("child %d terminated\n",pid);
51
52 gettimeofday(&tpend,NULL);
53
54 timeuse+=1000000*(tpend.tv_sec-tpstart.tv_sec)+tpend.tv_usec-tpstart.tv_usec;
55 //timeuse/=1000000;
56 printf("Use Time:%f\n",timeuse/1000000);
57 return;
58 }
59
60
61
62 //使用线程代替进程来处理客户请求
63 void* threadPerClient(void *arg)
64 {
65 int connfd=*((int*)arg);
66 free(arg); //防止内存泄露
67 pthread_detach(pthread_self());
68
69 printf("client from %d(socket num)\n",connfd);
70 str_echo(connfd);
71
72 close( connfd );
73 return NULL;
74 }
75
76
77 int main(int argc, char **argv)
78 {
79 int listenfd, connfd;
80 pid_t childpid;
81 socklen_t chilen;
82
83 struct sockaddr_in chiaddr,servaddr;
84
85 //for select
86 int i,maxi,maxfd,sockfd;
87 int nready,client[FD_SETSIZE];
88 ssize_t n;
89 fd_set rset,allset;
90 //for select
91
92 listenfd=socket(AF_INET,SOCK_STREAM,0);
93 if(listenfd==-1)
94 {
95 printf("socket established error: %s\n",(char*)strerror(errno));
96 }
97
98 bzero(&servaddr,sizeof(servaddr));
99 servaddr.sin_family=AF_INET;
100 servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
101 servaddr.sin_port=htons(LISTEN_PORT);
102
103 int bindc=bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
104 if(bindc==-1)
105 {
106 printf("bind error: %s\n",strerror(errno));
107 }
108
109 listen(listenfd,SOMAXCONN); //limit是SOMAXCONN
110
111 //initial "select" elements
112 maxfd=listenfd; //新增listenfd,所以更新当前的最大fd
113 maxi=-1;
114 for(i=0;i<FD_SETSIZE;i++)
115 client[i] = -1;
116 FD_ZERO(&allset);
117 FD_SET(listenfd,&allset);
118 //end initial
119
120 int cliconn=0;
121 signal(SIGCHLD,sig_child);
122 for(;;)
123 {
124 rset=allset;
125 nready=select(maxfd+1,&rset,NULL,NULL,NULL); //一开始select监听的是监听口
126 //如果有timeout设置,那么每次select之前都要再重新设置一下timeout的值
127 //因为select会修改timeout的值。
128 if(FD_ISSET(listenfd,&rset))
129 {
130 chilen=sizeof(chiaddr);
131
132 connfd=accept(listenfd,(struct sockaddr*)&chiaddr,&chilen);
133 //阻塞在accept,直到三次握手成功了才返回
134 if(connfd==-1)
135 printf("accept client error: %s\n",strerror(errno));
136 else
137 printf("client connected:%d\n",++cliconn);
138
139 for(i=0;i<FD_SETSIZE;i++)
140 {
141 if (client[i]<0)
142 {
143 client[i]=connfd; //找一个最小的插进入
144 break;
145 }
146 }
147 if(i==FD_SETSIZE)
148 {
149 printf("too many clients\n");
150 exit(0);
151 }
152 FD_SET(connfd,&allset); //新加入的描述符,还没判断是否可以或者写,所以后面使用rset而不是allset
153
154
155
156 if(connfd>maxfd)
157 maxfd=connfd;
158 if(i>maxi)
159 maxi=i;
160 if(--nready<=0)
161 continue;
162 }
163
164 //for thread
165 int *lptr;
166 pthread_t pid;
167 //for thread
168
169 for(i=0;i<=maxi;i++)
170 {
171 if( (sockfd=client[i]) <0)
172 continue;
173 if(FD_ISSET(sockfd,&rset))
174 {
175 //thread client
176 lptr=(int*)malloc(sizeof(int));
177 *lptr=sockfd;
178 //pthread_t pid;
179
180 int errerr=pthread_create(&pid,NULL,threadPerClient,lptr);
181 if(errerr!=0)
182 printf("err %s",strerror(errno));
183
184 FD_CLR(sockfd,&allset); //短连接后关闭socket,服务器发不过来
185 client[i]=-1;
186
187 printf("can read : %d,%d,%d\n",i,sockfd,nready);
188 if(--nready<=0)
189 break;
190 //thread client
191
192 }
193 }
194
195 }
196 }