目录
[1> 多进程:每有一个客户端连接创建一个进程进行通信](#1> 多进程:每有一个客户端连接创建一个进程进行通信)
[2> 多线程:每有一个客户端连接创建一个线程进行通信](#2> 多线程:每有一个客户端连接创建一个线程进行通信)
[3> IO多路复用](#3> IO多路复用)
[4> 总结](#4> 总结)
服务器模型
在网络通信中,通常一个服务器要连接多个客户端
为了处理多个客户端的请求,通常有多种表现形式:循环服务器、并发服务器
1》循环服务器
循环服务器:一个服务器在同一时间只能处理一个客户端的请求
cs
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
int main(int argc, char const *argv[])
{
if (argc != 2)
{
perror("argc err\n");
return -1;
}
char buf[128] = {0};
// 1.创建套接字(socket)---------------》有手机
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd < 0)
{
perror("socket err\n");
return -1;
}
printf("socketfd: %d\n", socketfd);
// 2.指定(自己)网络信息--------------------》有号码
// struct sockaddr_in
// {
// sa_family_t sin_family; /* address family: AF_INET */
// in_port_t sin_port; /* port in network byte order */
// struct in_addr sin_addr; /* internet address */
// };
// /* Internet address. */
// struct in_addr
// {
// uint32_t s_addr; /* address in network byte order */
// };
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 全局地址,自动获取本机地址
// saddr.sin_addr.s_addr = INADDR_ANY;
// 3.绑定套接字(bind)------------------》绑定手机(插卡)
if (bind(socketfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err\n");
return -1;
}
printf("bind ok\n");
// 4.监听套接字(listen)-----------------》待机
if (listen(socketfd, 6) < 0)
{
perror("listen err\n");
return -1;
}
printf("listen ok\n");
// 5.接收客户端连接请求(accept)--》接电话
while (1) // 循环等待客户端链接,实现循环服务器功能
{
int len = sizeof(caddr);
int acceptfd = accept(socketfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("accept err\n");
return -1;
}
printf("acceptfd: %d\n", acceptfd);
printf("port: %d ip: %s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr)); // 来电显示,显示客户端信息
// 6.接收、发送数据(recv send)---》通话
int ret;
/*设置套接字属性:超时重传*/
struct timeval tm = {2, 0};
setsockopt(acceptfd, SOL_SOCKET, SO_REUSEADDR, &tm, sizeof(tm));
while (1)
{
ret = recv(acceptfd, buf, sizeof(buf), 0);
if (ret < 0)
{
perror("recv error\n");
}
else if (ret == 0)
{
printf("client exit\n");
break;
}
else
{
printf("buf: %s\n", buf);
memset(buf, 0, sizeof(buf));
}
}
// 7.关闭套接字(close)-----------------》挂电话
close(acceptfd);
}
close(socketfd);
return 0;
}
2》并发服务器
并发服务器:一个服务器在同一时间内可以处理多个客户端的请求,有三种实现方法
1> 多进程:每有一个客户端连接创建一个进程进行通信
cs
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
void handler(int sig)
{
wait(NULL);//回收子进程资源
}
int main(int argc, char const *argv[])
{
pid_t pid;
char buf[128] = {0};
int ret, acceptfd;
// 1.创建套接字(socket)---------------》有手机
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd:%d\n", sockfd); // 3
// 2.指定网络信息---------------------------》有号码
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET; // IPV4
saddr.sin_port = htons(atoi(argv[1])); // 端口号
// saddr.sin_addr.s_addr = inet_addr("192.168.50.13"); // 虚拟机IP
// saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
saddr.sin_addr.s_addr = INADDR_ANY;
int len = sizeof(caddr);
// 3.绑定套接字(bind)------------------》绑定手机(插卡)
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err");
return -1;
}
printf("bind ok\n");
// 4.监听套接字(listen)-----------------》待机
if (listen(sockfd, 6) < 0)
{
perror("listen err");
return -1;
}
printf("listen ok\n");
// 5.接收客户端连接连接请求(accept)--》接电话
// tcp服务器一共有两类文件描述符,一类用于连接,一类用于通信
// socket函数返回值:用于连接的文件描述符
// accept函数返回值:用于通信的文件描述符
signal(SIGCHLD, handler);//设置信号处理方式
while (1)
{
acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("accept err");
return -1;
}
printf("port:%d ip:%s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));
printf("acceptfd:%d\n", acceptfd);
pid = fork();//创建子进程
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)//子进程用于通信
{
// 6.接收、发送数据(recv send)---》通话
while (1)
{
// read/write()
ret = recv(acceptfd, buf, sizeof(buf), 0);
if (ret < 0)
{
perror("recv err");
break;
}
else if (ret == 0)//客户端退出
{
printf("client exit\n");
break;
}
else
{
printf("buf:%s\n", buf);
memset(buf, 0, sizeof(buf));
}
}
// 7.关闭套接字(close)-----------------》挂电话
close(acceptfd);//关闭子进程通信描述符
exit(0);//退出子进程
}
else//父进程用于循环链接客户端
close(acceptfd);//关闭父进程通信文件描述符
}
close(sockfd);
return 0;
}
2> 多线程:每有一个客户端连接创建一个线程进行通信
cs
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
void *handler(void *arg)//从线程函数
{
int ret, acceptfd;
acceptfd = *((int *)arg);//强转为 int 类型
char buf[128] = {0};
// 6.接收、发送数据(recv send)---》通话
while (1)
{
// read/write()
ret = recv(acceptfd, buf, sizeof(buf), 0);
if (ret < 0)
{
perror("recv err");
return NULL;
}
else if (ret == 0)
{
printf("client exit\n");
break;
}
else
{
printf("buf:%s\n", buf);
memset(buf, 0, sizeof(buf));
}
// 7.关闭套接字(close)-----------------》挂电话
}
close(acceptfd);
pthread_exit(NULL);//退出线程
}
int main(int argc, char const *argv[])
{
int acceptfd;
pthread_t tid;
// 1.创建套接字(socket)---------------》有手机
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd:%d\n", sockfd); // 3
// 2.指定网络信息---------------------------》有号码
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET; // IPV4
saddr.sin_port = htons(atoi(argv[1])); // 端口号
// saddr.sin_addr.s_addr = inet_addr("192.168.50.13"); // 虚拟机IP
// saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
saddr.sin_addr.s_addr = INADDR_ANY;
int len = sizeof(caddr);
// 3.绑定套接字(bind)------------------》绑定手机(插卡)
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err");
return -1;
}
printf("bind ok\n");
// 4.监听套接字(listen)-----------------》待机
if (listen(sockfd, 6) < 0)
{
perror("listen err");
return -1;
}
printf("listen ok\n");
// 5.接收客户端连接连接请求(accept)--》接电话
// tcp服务器一共有两类文件描述符,一类用于连接,一类用于通信
// socket函数返回值:用于连接的文件描述符
// accept函数返回值:用于通信的文件描述符
while (1)
{
acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("accept err");
return -1;
}
printf("port:%d ip:%s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));
printf("acceptfd:%d\n", acceptfd);
pthread_create(&tid, NULL, handler, &acceptfd);//创建从线程
pthread_detach(tid);//不阻塞回收从线程资源
}
close(sockfd);
return 0;
}
3> IO多路复用
select poll epoll 见上篇博客 IO多路复用
4> 总结
多进程:
优点:服务器更稳定,父子进程资源独立,安全性高
缺点:需要开辟多个进程,资源消耗大,系统开销大
多线程:
优点:相对于多进程,资源开销小,多个线程共享同一个进程的资源
缺点:需要开辟多个线程,安全性差
IO多路复用:
优点:节省资源,系统开销小,性能高
缺点:代码复杂度高
今天的分享就到这里结束啦,如果有哪里写的不好的地方,请指正。
如果觉得不错并且对你有帮助的话点个关注支持一下吧!