1. 多線程 & 多進程
線程是進程內的獨立執行實體和調度單元。又稱爲“輕量級”進程。創建線程比創建進程要快10~100倍(數據待考證),又因爲一個進程下的所有線程都共享一些內核資源,相比多進程併發服務器來說,多線程併發服務器在節省資源上比多進程併發服務器更有優勢。
線程共享資源與獨佔資源如圖:
多線程下併發服務的模型與多進程下基本一致,多線程併發服務器總是讓主線程處於阻塞監聽狀態。一旦有客戶端連接,那麼就創建一個新的線程來響應客戶端請求,主線程任然回到阻塞監聽狀態。
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#define SERV_PORT 8000
#define MAXLINE 1024
struct s_info{
struct sockaddr_in addr;
int connfd;
};
void *do_work(void *arg)
{
int n, i;
struct s_info *ts = (struct s_info*)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
pthread_detach(pthread_self());
while(1)
{
n = read(ts->connfd, buf, MAXLINE);
if (n == 0)
{
printf("peer closed. \n");
break;
}
printf("client ip: %s, port: %d \n",
inet_ntop(AF_INET, &(*ts).addr.sin_addr, str, sizeof(str)),
ntohs(ts->addr.sin_port));
for(i=0; i<n; ++i)
buf[i] = toupper(buf[i]);
write(ts->connfd, buf, n);
}
close(ts->connfd);
return NULL;
}
int main()
{
int fd, confd, clientaddrlen, revlen, i = 0;
struct sockaddr_in serveraddr, clientaddr;
char str[INET_ADDRSTRLEN];
struct s_info ts[383];
pthread_t tid;
//1. 創建一個socket
fd = socket(AF_INET, SOCK_STREAM, 0);
//2. 綁定一個端口
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERV_PORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(fd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
//3. 設置監聽
listen(fd, 20);
printf("Accepting Connecting...\n");
while(1)
{
//4.接受鏈接
clientaddrlen = sizeof(clientaddr);
confd = accept(fd, (struct sockaddr*)&clientaddr, &clientaddrlen);
ts[i].addr = clientaddr;
ts[i].connfd = confd;
pthread_create(&tid, NULL, do_work, (void *)&ts[i]);
i++;
if(i >= 382)
i = 0;
}
close(fd);
return 0;
}
2. 注意點
<1> 由於pthread_create創建的線程共享文件描述符(上面的圖中可以看到),所以在線程處理函數中我們並沒有看到關閉監聽套接字和連接套接字。這一點是和多進程模型的一點不同。
<2> 分離態的設置 我們知道在任何時間點,線程可以是可結合態(joinable)或者是分離態(detached)的。可結合態的線程能夠被其他線程殺死和回收資源,但是這需要被其他線程顯示的去回收。分離態則是線程執行完任務之後由操作系統來回收。所以在很多時候可以看到多線程併發服務器都將線程設置成分離態。