一:服务器模型一般分为两种
1:循环服务器:服务器同一时刻只能响应一个客户端的请求
2:并发服务器:服务器在同一时刻可以响应多个客户端的请求
二:并发服务器的三中实现方式
1:多进程并发服务器
是指TCP连接后,每一个客户机的请求并不由服务器直接处理,而是由服务器创建一个子进程来处理
2:多线程并发服务器
多线程服务器是对多进程服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样的服务处理程序可以较快的创建。据统计,创建线程要比创建进程要快100~1000倍。线程与进程不同的是:一个进程内的所有线程共享相同的全局内存,全局变量等信息。
是指TCP连接后,每一个客户机的请求并不由服务器直接处理,而是由服务器创建一个子线程来处理
3:多路复用I/O
I/O是为了解决线程/进程阻塞在哪个i/o调用中,常用select或者pool。
四:多进程服务器的过程及fork函数
1:fork调用后,父进程和子进程继续执行fork函数后的指令,具体是父进程还是子进程先执行是不确定的,这取决于系统内核所使用的调度算法;
2:在网络编程中,父进程调用fork函数之前所打开的所有套接字描述符在函数fork返回之后,都是共享的。如果父子进程同时对一个描述符操作,且没有任何形式的同步,那么他们的输出就会混合。
3:父子进程各自执行不同的程序段,这是非常典型的网络服务器。父进程等待客户的请求,当请求到达时,父进程调用fork函数,产生一个子进程,由子进程对该请求作处理。父进程则继续等待下一个客户端的请求。并且在这种情况下,在fork函数之后,父子进程需要关闭各自不使用的描述符,即父进程关闭不需要的已连接的描述符,子进程关闭不需要的监听描述符,这样做的原因有以下几点好处: 1:节省系统资源 2:防止父子进程同时对共享描述符进行操作
3:最重要的是,确保close函数能够正确的关闭套接字描述符
4:多进程服务器基本思路
1 建立连接——>2 服务器调用fork函数产生子进程——>3 父进程关闭连接套接字,子进程关闭监听套接字——>4 子进程处理客户请求,父进程等待另一个客户连接。
5:多进程服务器的缺点
1:fork函数是昂贵的。fork函数调用成功后,需要复制父进程的所有资源(内存映像,描述字等)
2:fork产生子进程后,父子进程,兄弟进程间的通信需要用到进程通信IPC机制,给通信带来了困难
3:多进程在一定程度上仍不能有效的利用系统资源
4:系统中进程个数是有限制的。
五:具体代码如下:
服务端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MYPORT 9877 //服务器端口
#define QUEUE 5 //等待队列大小
#define BUFFER_SIZE 1024 //缓冲区大小
int main()
{
//定义socket
int server_sockfd = socket(AF_INET,SOCK_STREAM,0);
if(server_sockfd<0)
{
printf("creat server_sockfd faliure\n");
return -1;
}
printf("server_sockfd=%d\n",server_sockfd);
//定义sockaddr_in,是作为bind函数的一个结构体参数,确定好源ip和源端口号
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(MYPORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//bind,成功返回0,出错返回-1
int ret=bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr));
if(ret<0)
{
perror("bind error!\n");
exit(1);
}
//lisen,成功返回0,出错返回-1
if(listen(server_sockfd,QUEUE)==-1)
{
perror("lisen error!\n");
exit(1);
}
int count=0;
while(1)
{
//客户端套接字
char buffer[BUFFER_SIZE];
char sendbuf[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
//接收连接 成功返回非负描述字,出错返回-1
int conn = accept(server_sockfd,(struct sockaddr*)&client_addr,&length);
if(conn<0)
{
if(errno==EINTR)
{
continue;
}
perror("connect error!\n");
exit(1);
}
int pid;
//调用fork()函数时 如果是父进程调用则返回进程号,如果是子进程调用则返回0
//这里父进程用来创建连接 子进程负责处理请求 所以当pid=0才执行
if((pid=fork())==0)
{
//子进程会复制父进程的资源 这里我们关闭连接只是把子进程复制的连接关闭并不会真正关闭连接
close(server_sockfd);
while(1)
{
//清空缓冲区
memset(buffer,0,sizeof(buffer));
memset(sendbuf,0,sizeof(sendbuf));
//接收客户端发送来的数据
int len = recv(conn,buffer,sizeof(buffer),0);
if(strcmp(buffer,"exit\n")==0||len==0)
{
break;
}
fputs(buffer,stdout);
fgets(sendbuf,sizeof(sendbuf),stdin);
send(conn,sendbuf,strlen(sendbuf),0);
if(strcmp(sendbuf,"exit\n")==0)
{
break;
}
}
close(conn);
exit(0);
}
close(conn);
count++;
}
close(server_sockfd);
return 0;
}
客户端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MYPORT 9877
#define BUFFER_SIZE 1024
int main()
{
//定义socket
int sock_cli = socket(AF_INET,SOCK_STREAM,0);
if(sock_cli<0)
{
printf("creat sock_cli faliure\n");
return -1;
}
//定义sockaddr_in
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(MYPORT);
servaddr.sin_addr.s_addr = inet_addr("192.168.1.104");
//连接服务器,成功返回0,出错返回-1
if(connect(sock_cli,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
{
error("connect error!\n");
exit(1);
}
char sendbuf[BUFFER_SIZE];
char recvbuf[BUFFER_SIZE];
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
{
send(sock_cli,sendbuf,strlen(sendbuf),0);
if(strcmp(sendbuf,"exit\n")==0)
{
break;
}
recv(sock_cli,recvbuf,sizeof(recvbuf),0);
fputs(recvbuf,stdout);
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock_cli);
return 0;
}