并发服务器:一个服务器同一时间可以连接多个客户端
多进程:
优点:服务器更稳定,父子进程资源独立,安全性高
缺点:需要开辟多个进程,大量消耗资源,系统开销大
多线程:
优点:相对于多进程,资源开销小,多个线程共享同一个进程的资源
缺点:需要开辟多个线程,安全性较差
1. 多进程并发服务器
为什么要创建进程?------》通信
什么时间创建进程?------》accept之后fork
子进程:通信
父进程:等待下一个客户端连接
子进程结束,,,,-----》子进程回收资源 wait waitpid
SIGCHLD:信号:当子进程状态改变给父进程发送的信号(17)
思想:只要服务器接收到客户端的连接请求,就创建一个子进程来建立连接,处理该客户端的消息。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <signal.h>
void handler(int sig)
{
waitpid(-1, NULL, WNOHANG);
}
int main(int argc, char const *argv[])
{
int res;
// 1.创建流式套接字
int serverfd = socket(AF_INET, SOCK_STREAM, 0);
if (serverfd < 0)
{
perror("serverfd socket error");
;
return -1;
}
printf("server socket scuess\n");
// 2.指定本地的网络信息 struct sockaddr_in
struct sockaddr_in myaddr;
// 长度
socklen_t addrlen = sizeof(myaddr);
memset(&myaddr, 0, addrlen);
myaddr.sin_family = AF_INET; // ipv4族
myaddr.sin_addr.s_addr = INADDR_ANY; // 网络ip地址需要转成机器识别的类型;
myaddr.sin_port = htons(atoi(argv[1])); // 端口
// 3.绑定套接字 bind()
// 绑定自己的地址
res = bind(serverfd, (struct sockaddr *)&myaddr, addrlen);
if (res < 0)
{
perror("bind error");
return -1;
}
printf("bind scuess\n");
// 4.监听套接字 listen()
res = listen(serverfd, 5);
if (res < 0)
{
perror("listen error");
return -1;
}
printf("listen scuess\n");
// 5.链接客户端的请求 accept()
// 定义代表客户端的结构体变量
struct sockaddr_in acceptaddr;
int acceptfd;
while (1)
{
acceptfd = accept(serverfd, (struct sockaddr *)&acceptaddr, &addrlen);
if (acceptfd < 0)
{
perror("accept error");
return -1;
}
printf("acceptfd: %d \n", acceptfd);
printf("新的连接过来了\n");
printf("ip = %s, port = %d\n", inet_ntoa(acceptaddr.sin_addr),
ntohs(acceptaddr.sin_port));
// 创建子进程
pid_t pid = fork();
if (pid < 0)
{
perror("fork error");
return -1;
}
else if (pid == 0) // 子进程
{
// 6.接收/发送数据 recv()/send()
char buf[128] = "";
ssize_t num;
while (1)
{
// 每次接收前清空buf
memset(buf, 0, sizeof(buf));
num = recv(acceptfd, buf, sizeof(buf), 0); // num为接收到的字符个数
if (num < 0) // 接收失败
{
perror("recv error");
return -1;
}
else if (num == 0) // 客户端退出
{
perror("client exit");
break;
}
else // 正常接收到字符
{
printf("acceptfd: %d buf: %s \n", acceptfd, buf);
memset(buf, 0, sizeof(buf));
}
}
close(acceptfd);
exit(0);
}
else // 父进程
{
signal(SIGCHLD, handler); // 处理SIGCHLD信号,捕捉,回收子进程;
close(acceptfd);
}
}
close(serverfd);
close(acceptfd);
return 0;
}
2. 多线程并发服务器
为什么要创建线程?-------》通信
什么时间创建线程?-------》accept之后pthread_create
子线程:通信
主线程:循环等待下一个客户端连接
acceptfd,怎么给到子线程线程传参
思想:只要服务器接收到客户端的连接请求,就创建一个线程来处理该客户端的信息。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
void *handler_pthread(void *arg)
{
int acceptfd = *((int *)arg);
// 6.接收/发送数据 recv()/send()
char buf[128] = "";
ssize_t num;
while (1)
{
memset(buf, 0, sizeof(buf)); // 每次接收前清空buf
num = recv(acceptfd, buf, sizeof(buf), 0); // num为接收到的字符个数
if (num < 0) // 接收失败
{
perror("recv error");
return NULL;
}
else if (num == 0) // 客户端退出
{
perror("client exit");
break;
}
else // 正常接收到字符
{
printf("accept: %d buf: %s \n", acceptfd, buf);
memset(buf, 0, sizeof(buf));
}
}
close(acceptfd);
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_t tid;
int n;
int res;
// 1.创建流式套接字
int serverfd = socket(AF_INET, SOCK_STREAM, 0);
if (serverfd < 0)
{
perror("serverfd socket error");
;
return -1;
}
printf("server socket scuess\n");
// 2.指定本地的网络信息 struct sockaddr_in
struct sockaddr_in myaddr;
// 长度
socklen_t addrlen = sizeof(myaddr);
memset(&myaddr, 0, addrlen);
myaddr.sin_family = AF_INET; // ipv4族
myaddr.sin_addr.s_addr = INADDR_ANY;
myaddr.sin_port = htons(atoi(argv[1])); // 端口
// 3.绑定套接字 bind()
// 绑定自己的地址
res = bind(serverfd, (struct sockaddr *)&myaddr, addrlen);
if (res < 0)
{
perror("bind error");
return -1;
}
printf("bind scuess\n");
// 4.监听套接字 listen()
res = listen(serverfd, 5);
if (res < 0)
{
perror("listen error");
return -1;
}
printf("listen scuess\n");
// 5.链接客户端的请求 accept()
// 定义代表客户端的结构体变量
struct sockaddr_in acceptaddr;
int acceptfd;
while (1)
{
acceptfd = accept(serverfd, (struct sockaddr *)&acceptaddr, &addrlen);
if (acceptfd < 0)
{
perror("accept error");
return -1;
}
printf("acceptfd: %d \n", acceptfd);
printf("新的连接过来了\n");
printf("ip = %s, port = %d\n", inet_ntoa(acceptaddr.sin_addr),
ntohs(acceptaddr.sin_port));
// 创建线程
n = pthread_create(&tid, NULL, handler_pthread, &acceptfd);
if (n != 0)
{
perror("creat error");
return -1;
}
// 回收从线程
pthread_detach(&tid);
// 主线程继续等待新的连接
}
close(serverfd);
close(acceptfd);
return 0;
}