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)的。可结合态的线程能够被其他线程杀死和回收资源,但是这需要被其他线程显示的去回收。分离态则是线程执行完任务之后由操作系统来回收。所以在很多时候可以看到多线程并发服务器都将线程设置成分离态。