一、服务器模型
- 在网络程序里面,通常都是一个服务器处理多 个客户机。
- 为了处理多个客户机的请求, 服务器端的程序有不同的处理方式。
1、循环服务器模型
socket();
bind();
liste();
while(1)
{
accept();
while(1)
{
recv
ret==0;
break;
}
close(acceptfd);
}
close(sockfd);
2、并发服务器模型
同一时刻相应多个客户端(tcp)。多进程模型/多线程模型/IO多路复用(select、poll、epoll)
socket();
bind();
listen()
while(1)
{
accept();
if(fork()==0)
{
while(1)
{
recv
ret==0;
break;
}
close(acceptfd);
exit();
}
else
{}
}
注意:收到客户端消息后,打印下是来自哪个客户端的数据(来电显示)
使用SIGCHLD来处理子进程结束的信号,信号函数中回收进程资源。
3、多进程特点总结
- fork之前的代码被复制,但是不会重新执行一遍;fork之后的代码被复制,并且再被执行一遍。
- fork之后两个进程相互独立,子进程拷贝了父进程的所有代码,但内存空间独立
- fork之前打开文件,fork之后拿到的是同一个文件描述符,操作的是同一个文件指针
/*服务器创建代码 */
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
void handler(int arg)
{
waitpid(-1, NULL, WNOHANG);
}
int main(int argc, char const *argv[])
{
if (argc < 2)
{
printf("plase input <ip><port>\n");
return -1;
}
//1.创建套接字,用于链接
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd:%d\n", sockfd);
//2.绑定 ip+port 填充结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET; //协议族ipv4
saddr.sin_port = htons(atoi(argv[1])); //端口号,htons将无符号短整数hostshort从主机字节顺序到网络字节顺序。
saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); //ip地址,转化为16进制表示
socklen_t len = sizeof(saddr); //结构体大小
//bind绑定ip和端口
if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
{
perror("bind err");
return -1;
}
printf("bind success\n");
//3.启动监听,把主动套接子变为被动套接字
if (listen(sockfd, 6) < 0)
{
perror("listen err");
return -1;
}
printf("listen success\n");
//4.阻塞等待客户端的链接请求
int acceptfd;
char buf[64];
int ret;
while (1)
{
acceptfd = accept(sockfd, (struct sockaddr *)&saddr, &len);
//获取客户端的ip和端口,(struct sockaddr *)&saddr:用来存放返回的ip,和端口
if (acceptfd < 0)
{
perror("accept err");
return -1;
}
printf("client ip:%s ,port:%d\n", inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
printf("connect success\n");
signal(SIGCHLD, handler);
pid_t pid = fork(); //创建子进程
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
while (1)
{
ret = recv(acceptfd, buf, sizeof(buf), 0);
if (strncmp(buf, "quit", 4) == 0) //接收到quit退出
{
break;
}
if (ret < 0)
{
perror("recv err.");
return -1;
}
else if (ret == 0) //客户端退出
{
printf("client exit\n");
break;
}
else
{
printf("buf:%s\n", buf);
}
}
close(acceptfd);//关闭子进程的文件描述副
close(sockfd);//关闭子进程的套接字文件描述符,不影响主进程套接字
exit(0);
}
close(acceptfd);//关闭主进程打开的文件描述符,
//为下次循环开辟的文件描述符空位置,否则只能连续开辟文件描述符到1024个
}
close(sockfd);
return 0;
}
4、多线程模型
每来一个客户端连接,开一个子线程来专门处理客户端的数据,实现简单,占用资源较少,属于使用比较广泛的模型:
socket();
bind();
listen();
while(1)
{
accept();
pthread_creat();
pthread_detach();
}
1)多线程服务器
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
void *mythread(void *arg)
{
int acceptfd= *((int *)arg);
int ret;
char buf[128];
while (1)
{
ret=recv(acceptfd,buf,sizeof(buf),0);
if (ret < 0)
{
perror("recv err.");
return -1;
}else if (ret ==0)
{
printf("%d client exit\n",acceptfd);
close(acceptfd);
break;
}else
{
printf("buf:%s\n",buf);
}
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("please input %s <port>\n", argv[0]);
return -1;
}
// 1.创建流式套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 链接
if (sockfd < 0)
{
perror("socket err.");
return -1;
}
printf("sockfd:%d\n", sockfd); // 3
// 填充ipv4的通信结构体
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1])); //"8888" int a= atoi("8888")//a=8888
// saddr.sin_addr.s_addr = inet_addr(argv[1]);
// 设置服务器自动获取自己主机的ip
// saddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 0x00000000 "0.0.0.0"
// saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
socklen_t len = sizeof(caddr);
// 2.绑定套接字 ip和端口(自己)
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err.");
return -1;
}
printf("bind ok.\n");
// 3.监听
if (listen(sockfd, 5) < 0)
{
perror("listen err.");
return -1;
}
printf("listen ok.\n");
while (1)
{
int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0) // 4
{
perror("accept err.");
return -1;
}
printf("acceptfd=%d\n", acceptfd);
printf("ip=%s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
pthread_t tid;
pthread_create(&tid,NULL,mythread,&acceptfd);
pthread_detach(tid);//游离态
}
close(sockfd);
return 0;
}
5、IO多路复用模型
借助select、poll、epoll机制,将新连接的客户端描述符增加到描述符表中,只需要一个线程即可处理所有的客户端连接,在嵌入式开发中应用广泛,不过代码写起了稍显繁琐。