高并发服务器简单说就是:服务器可以同时容许多个客户端同时并发访问常用的有多进程并发服务器和多线程并发服务器。
两个进程的工作过程:
父进程循环accept,当父进程接收到连接请求之后,立即fork出一个新的子进程去处理通信,而父进程继续循环等待接收accept()(没有连接请求父进程则阻塞,但是不会影响到子进程通信)。而对于自己进程回收,父进程可以用一个单独的子进程去回收用于通信的子进程。子进程也可以自己fork出新的子进程与原进程分别处理读与写(发与收),以致于读写之间不受阻塞限制。
注意:子进程会继承父进程文件描述符,对于用不到的文件描述符listenfd需要关闭,并且父进程中在创建fork之后也需要关闭connectfd。防止文件描述符无意义的耗费过度。
使用多进程并发服务器时要考虑以下几点:
**多进程服务器过程分析**
1. Socket(); 创建 监听套接字 lfd
2. Bind() 绑定地址结构 Strcut scokaddr_in addr;
3. Listen();
4.父进程:循环等待客户端连接
while (1) {
cfd = Accpet(); 接收客户端连接请求。
pid = fork();
if (pid == 0){ 子进程 read(cfd) --- 小-》大 --- write(cfd)
close(lfd) 关闭用于建立连接的套接字 lfd ,子进程不需要监听连接
}
else if (pid > 0)
{
close(cfd); 父进程不需要传递数据,关闭用于与客户端通信的套接字 cfd
contiue;
}
}
5. 子进程:处理接收的数据
close(lfd)
read()
操作函数
write()
父进程:
close(cfd);
注册信号捕捉函数: SIGCHLD
在回调函数中, 完成子进程回收
while (waitpid());
示例代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//错误封装,上一节内容
#include "wrap.h"
#define SRV_PORT 9999
void catch_child(int signum)
{
while ((waitpid(0, NULL, WNOHANG)) > 0);
return ;
}
int main(int argc, char *argv[])
{
int lfd, cfd;
pid_t pid;
struct sockaddr_in srv_addr, clt_addr;
socklen_t clt_addr_len;
char buf[BUFSIZ];
int ret, i;
//将地址结构
//memset(&srv_addr, 0, sizeof(srv_addr));
bzero(&srv_addr, sizeof(srv_addr));
//定义服务器端地址结构
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = Socket(AF_INET, SOCK_STREAM, 0);
Bind(lfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
Listen(lfd, 128);
clt_addr_len = sizeof(clt_addr);
//父进程循环等待客户端连接
while (1)
{
cfd = Accept(lfd, (struct sockaddr *)&clt_addr, &clt_addr_len);
/*有客户端连接,创建子进程*/
pid = fork();
if (pid < 0)
{
perr_exit("fork error");
}
//子进程退出循环,去处理接收的数据
else if (pid == 0)
{
/*子进程不需要listenfd*/
close(lfd);
break;
}
/*父进程使用信号循环回收子进程*/
else if (pid > 0)
{
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGCHLD, &act, NULL);
if (ret != 0) {
perr_exit("sigaction error");
}
/*父进程不需要connectfd*/
close(cfd);
continue;
}
}
/*退出循环的子进程*/
if (pid == 0) {
for (;;) {
ret = Read(cfd, buf, sizeof(buf));
//如果没有数据,关闭连接
if (ret == 0)
{
close(cfd);
exit(1);
}
for (i = 0; i < ret; i++)
{
buf[i] = toupper(buf[i]);
}
write(cfd, buf, ret);
write(STDOUT_FILENO, buf, ret);
}
}
return 0;
}
代码中子进程回收用信号捕捉来实现。编译方式:gcc server.c wrap.h -o server -Wall -g
client代码为第2节socket里的客户端中运行结果:
多线程并发服务器和多进程类似,父线程不需要回收子线程,只需设置线程分离即可。多线程并发服务器的思路如下。
多线程并发服务器: server.c
1. Socket(); 创建 监听套接字 lfd
2. Bind() 绑定地址结构 Strcut scokaddr_in addr;
3. Listen();
4. 父线程:循环等待连接
while (1) {
cfd = Accept(lfd, );
pthread_create(&tid, NULL, tfn, (void *)cfd);
// pthead_join(tid, void **); 新线程---专用于回收子线程。
//设置线程分离
pthread_detach(tid);
}
5. 子线程:线程处理函数来处理接收的数据
void *tfn(void *arg)
{
// close(lfd) 子线程不能关闭lfd,主线程要使用lfd
read(cfd)
操作函数
write(cfd)
pthread_exit((void *)10);
}
#include
#include
#include
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
//定义一个结构体, 将地址结构跟cfd捆绑
typedef struct s_info {
struct sockaddr_in cliaddr;
int connfd;
}s_info;
//子线程处理接收数据的过程
void *do_work(void *arg)
{
int n,i;
struct s_info *ts = (struct s_info*)arg;
char buf[MAXLINE];
//#define INET_ADDRSTRLEN 16 可用"[+d"查看
char str[INET_ADDRSTRLEN];
while(1)
{
//读客户端
n = Read(ts->connfd, buf, MAXLINE);
//读到文件末尾,跳出循环,关闭cfd
if (n == 0) {
printf("the client %d closed...\n", ts->connfd);
break;
}
//打印客户端信息
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr,
str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port));
//小写-->大写
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
· //写出至屏幕
Write(STDOUT_FILENO, buf, n);
//回写给客户端
Write(ts->connfd, buf, n);
}
Close(ts->connfd);
return (void *)0;
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
pthread_t tid;
int i = 0;
s_info ti[256];
//创建一个socket, 得到lfd
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
//地址结构清零
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
//指定本地任意IP
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//指定端口号
servaddr.sin_port = htons(SERV_PORT);
//绑定
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
//设置同一时刻链接服务器上限数
Listen(listenfd, 128);
printf("Accepting client connect ...\n");
//父线程等待客户端连接,有连接则创建子线程
while (1) {
cliaddr_len = sizeof(cliaddr);
//阻塞监听客户端链接请求
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
/*有客户端连接,创建子线程处理*/
/*线程处理函数的参数需要连接的connfd,和accept返回的客户端的地址结构,将他们定义在结构体中*/
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
ptread_create(&tid, NULL, do_work, (void*)&ts[i]);
/*设置线程分离,不需要再回收函数*/
pthread_detach(tid);
}
return 0;
}
总结:
多进程占用资源大,但是一个进程挂了不会影响另一个。这与多线程刚好相反,多线程服务器不稳定。