网络通信中,服务器端会面对需要处理多个客户端的情况,由于客户端的请求不定时,可能同时请求;总体来说,服务器端一般采用循环服务器模型和并发服务器模型来处理。
循环服务器模型:服务器端依次处理每个客户端,直到当前客户端所有请求处理完成,再处理下一个客户端。
并发服务器模型:在服务器端采用多任务机制(多进程或者多线程),分别为每一个客户端创造一个任务来处理。
循环服务器模型 | 并发服务器模型 | |
优点 | 简单 | 服务器并发处理能力强,客户端不用等待时间过长 |
缺点 | 单一处理,容易造成其他客户端等待时间过长 | 需要避免僵死模式 |
TCP循环服务器模型:(工作流程)/(特点分析)
- 服务器端从连接请求队列中提取请求,建立连接并返回新的已经连接套接字;
- 服务器端通过已经连接的套接字循环接受数据,处理并发送给客户端直到客户端关闭;
- 服务器关闭已连接套接字,返回步骤(1)处理下一个客户端;
- -------------------------------------------------------------------------------------------------------------------
- 服务器端采用循环嵌套来实现:外层循环依次提取客户端的请求队列并建立TCP连接,内层循环接受处理连接客户端的所有数据,直到客户端关闭;
- 如果当前客户端没有处理结束,其他客户端必须一直等待;
流程:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
if(fork() == 0)
{
while(1)
{
recv(...);
process(...);
send(...);
}
close(...);
exit(...);
}
close(...);
}
代码实现:
TCP循环服务器:
#include <stdio.h>
#include "net.h"
#include <string.h>
//编写一子函数,监听socket对象
//参数1:IP地址 char * ip
//参数2:端口号 int port
//返回值:失败返回-1,成功返回socket对象
int tcp_server(char * ip,int port);
int tcp_server(char * ip,int port)
{
//1.创建Tcp套接字
//2.绑定自己的IP地址和端口号
//3.设置监听是否有人连接
//创建并打开套接字
int sockFd = socket(AF_INET,SOCK_STREAM,0);
if(sockFd < 0)
{
perror("socket error");
return -1;
}
//绑定服务器地址信息
struct sockaddr_in servAddr = {0};
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(port);
servAddr.sin_addr.s_addr = inet_addr(ip);
int ret = bind(sockFd,(struct sockaddr *)&servAddr,sizeof(servAddr));
if(ret < 0)
{
perror("bind error");
return -1;
}
printf("bind OK\n");
//创建监听队列
ret = listen(sockFd,5);
if(ret < 0)
{
perror("listen error");
close(sockFd);
return -1;
}
printf("listen OK\n");
return sockFd;
}
//编写一子函数,实现客户端和服务器端的通信
//参数:连接好的socket对象
//返回值:通信结束返回-1,继续返回0
int tcp_com(int sockFd);
int tcp_com(int sockFd)
{
char buf[N]={'\0'};
char cmd[N]={'\0'};
char filename[N]={'\0'};
//接受客户端发过来的消息
recv(sockFd,buf,sizeof(buf),0); //第一次接受"hello"
printf("buf=%s\n",buf);
if(strncmp(buf,"quit",4)==0)
{
printf("通信结束");
return -1;
}
parseStr(buf,cmd,filename);
printf("cmd=%s\n",cmd);
printf("filename=%s\n",filename);
if(strncmp(cmd,"put",3)==0)
{
//接受文件(下载文件 download)
download(sockFd,filename);
}else if(strncmp(cmd,"get",3)==0)
{
//发送文件(上传文件 upload)
}else
{
printf("指令有误\n");
}
return 0;
}
int main(int argc,char * argv[])
{
if(argc!=3)
{
printf("input appname ip port\n");
return -1;
}
//1.监听socket对象
int tcp_socket=tcp_server(argv[1],atoi(argv[2]));
//2.接受连接
struct sockaddr_in client;
int len=sizeof(client);
while(1)
{
int newfd=accept(tcp_socket,(struct sockaddr *)&client,&len);
if(newfd<0)
{
perror("accept error");
return -1;
}
printf("accept ok\n");
printf("client ip=%s port=%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
while(1)
{
//3.接受/发送消息
int n=tcp_com(newfd);
//客户端和服务器端进行通信
if(n<0){
//4.关闭套接字
close(newfd);
break;
}
}
}
close(tcp_socket);
}
头文件:
#ifndef _NET_H
#define _NET_H
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#define N 20
int parseStr(char *buf,char * cmd,char * filename);
int download(int sockfd,char * filename);
#endif
对应客户端:
#ifndef _NET_H
#define _NET_H
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define N 20
int parseStr(char *buf,char * cmd,char * filename);
int upload(int sockfd,char * filename);
#endif
#include <stdio.h>
#include "net.h"
#include <string.h>
//编写一子函数,连接服务器
//参数1:IP地址ecv(sockFd,buf,sizeof(buf),0);
//参数2:端口号
//返回值:连接成功返回连接好的socket对象,失败返回-1
int tcp_connect(char * ip,int port);
int tcp_connect(char * ip,int port)
{
//1.创建Tcp套接字
//2.设置对方的IP地址和端口号
//3.请求连接
//创建并打开套接字
int sockFd = socket(AF_INET,SOCK_STREAM,0);
if(sockFd < 0)
{
perror("socket error");
return -1;
}
printf("socket OK\n");
//连接服务器
struct sockaddr_in servAddr = {0};
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(port);
servAddr.sin_addr.s_addr = inet_addr(ip);
int ret = connect(sockFd,(struct sockaddr *)&servAddr,sizeof(servAddr));
if(ret < 0)
{
close(sockFd);
return -1;
}
printf("connect OK\n");
return sockFd;
}
//编写一子函数,实现客户端和服务端的通信
//参数:连接好的socket对象
//返回值:通信结束返回-1,继续返回0
int tcp_com(int sockFd);
int tcp_com(int sockFd)
{
printf("请输入指令");
char buf[N]={'\0'};
char cmd[N]={'\0'};
char filename[N]={'\0'};
fgets(buf,sizeof(buf),stdin);
send(sockFd,buf,strlen(buf),0);
parseStr(buf,cmd,filename);
printf("cmd==%s\n",cmd);
printf("filename=%s\n",filename);
if(strncmp(cmd,"put",3)==0)
{
//上传文件upload()
upload(sockFd,filename);
}else if(strncmp(cmd,"get",3)==0)
{
//下载文件download()
}else if(strncmp(cmd,"quit",4)==0)
{
printf("通信结束\n");
return -1;
}else
{
printf("指令有误\n");
}
return 0;
}
int main(int argc,char * argv[])
{
if(argc!=3)
{
printf("input appname ip port\n");
return -1;
}
//1.请求连接服务器 //atoi()将字符串转换成整数
// atof()将字符串转换成float
int tcp_socket=tcp_connect(argv[1],atoi(argv[2]));
//2.发送/接受消息
while(1){
//客户端和服务端之间的通信
int n=tcp_com(tcp_socket);
//3.关闭套接字
if(n<0){
close(tcp_socket);
break;
}
}
//
//
}
TCP并发服务器模型:(工作流程)/(特点分析)
- 服务器端父进程从连接请求队列中提取请求,建立连接并返回新的已创建套接字;
- 服务器端父进程创建子进程为客户端服务,客户端关闭连接时,子进程结束;
- 服务器端父进程关闭已连接套接字,返回步骤(1);
- --------------------------------------------------------------------------------------------------------
- 服务器端父进程一旦接收到客户端的连接请求,便建立好连接并创建新的子进程。这意味着每个客广端在服务器端有一个专门的子进程为其服务。
- 服务器端的多个子进程同时运行 (宏观上),处理多个容户端。
- 服务器端的父进程不具体处理每个客户端的数据请求。
1.多进程----流程:
socket(...)
bind(...)
listen(...)
while(1)
{
accept(...)
if(fork(...)==0)
{
process(...);
close(...);
exit(...);
}
close(...);
}
代码实现:
头文件:
#ifndef _NET_H
#define _NET_H
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <signal.h>
#define N 20
int parseStr(char *buf,char * cmd,char * filename);
int download(int sockfd,char * filename);
#endif
TCP多进程并发服务器:
#include <stdio.h>
#include "net.h"
#include <string.h>
//编写一子函数,监听socket对象
//参数1:IP地址 char * ip
//参数2:端口号 int port
//返回值:失败返回-1,成功返回socket对象
int tcp_server(char * ip,int port);
int tcp_server(char * ip,int port)
{
//1.创建Tcp套接字
//2.绑定自己的IP地址和端口号
//3.设置监听是否有人连接
//创建并打开套接字
int sockFd = socket(AF_INET,SOCK_STREAM,0);
if(sockFd < 0)
{
perror("socket error");
return -1;
}
//绑定服务器地址信息
struct sockaddr_in servAddr = {0};
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(port);
servAddr.sin_addr.s_addr = inet_addr(ip);
int ret = bind(sockFd,(struct sockaddr *)&servAddr,sizeof(servAddr));
if(ret < 0)
{
perror("bind error");
return -1;
}
printf("bind OK\n");
//创建监听队列
ret = listen(sockFd,5);
if(ret < 0)
{
perror("listen error");
close(sockFd);
return -1;
}
printf("listen OK\n");
return sockFd;
}
//编写一子函数,实现客户端和服务器端的通信
//参数:连接好的socket对象
//返回值:通信结束返回-1,继续返回0
int tcp_com(int sockFd);
int tcp_com(int sockFd)
{
char buf[N]={'\0'};
char cmd[N]={'\0'};
char filename[N]={'\0'};
//接受客户端发过来的消息
recv(sockFd,buf,sizeof(buf),0); //第一次接受"hello"
printf("buf=%s\n",buf);
if(strncmp(buf,"quit",4)==0)
{
printf("通信结束");
return -1;
}
parseStr(buf,cmd,filename);
printf("cmd=%s\n",cmd);
printf("filename=%s\n",filename);
if(strncmp(cmd,"put",3)==0)
{
//接受文件(下载文件 download)
download(sockFd,filename);
}else if(strncmp(cmd,"get",3)==0)
{
//发送文件(上传文件 upload)
}else
{
printf("指令有误\n");
}
return 0;
}
void handler(int sig)
{
printf("子进程发生改变\n");
while(waitpid(-1,NULL,WNOHANG)>0);
}
int main(int argc,char * argv[])
{
if(argc!=3)
{
printf("input appname ip port\n");
return -1;
}
signal(SIGCHLD,handler); //安装信号
//1.监听socket对象
int tcp_socket=tcp_server(argv[1],atoi(argv[2]));
//2.接受连接
struct sockaddr_in client;
int len=sizeof(client);
pid_t pid=0;
while(1)
{
int newfd=accept(tcp_socket,(struct sockaddr *)&client,&len);
if(newfd<0)
{
perror("accept error");
return -1;
}
printf("accept ok\n");
printf("client ip=%s port=%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
//创建子进程和客户端进行通信
pid=fork();
if(pid<0)
{
perror("fork error");
return -1;
}else if(pid==0) //子进程
{
while(1)
{
//3.接受/发送消息
int n=tcp_com(newfd);
//客户端和服务器端进行通信
if(n<0){
//4.关闭套接字
close(newfd);
break;
}
}
exit(0);//子进程提出
}
close(newfd);
}
close(tcp_socket);
}
2.多线程----流程 :
socket(...)
bind(...)
listen(...)
while(1)
{
accept(...)
if(pthread_create(...)== 0)
{
process(...);
close(...);
exit(...);
}
close(...);
}
代码实现:
头文件:
#ifndef _NET_H
#define _NET_H
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#define N 20
int parseStr(char *buf,char * cmd,char * filename);
int download(int sockfd,char * filename);
#endif
TCP多线程服务器端:
#include <stdio.h>
#include "net.h"
#include <string.h>
//编写一子函数,监听socket对象
//参数1:IP地址 char * ip
//参数2:端口号 int port
//返回值:失败返回-1,成功返回socket对象
int tcp_server(char * ip,int port);
int tcp_server(char * ip,int port)
{
//1.创建Tcp套接字
//2.绑定自己的IP地址和端口号
//3.设置监听是否有人连接
//创建并打开套接字
int sockFd = socket(AF_INET,SOCK_STREAM,0);
if(sockFd < 0)
{
perror("socket error");
return -1;
}
//绑定服务器地址信息
struct sockaddr_in servAddr = {0};
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(port);
servAddr.sin_addr.s_addr = inet_addr(ip);
int ret = bind(sockFd,(struct sockaddr *)&servAddr,sizeof(servAddr));
if(ret < 0)
{
perror("bind error");
return -1;
}
printf("bind OK\n");
//创建监听队列
ret = listen(sockFd,5);
if(ret < 0)
{
perror("listen error");
close(sockFd);
return -1;
}
printf("listen OK\n");
return sockFd;
}
//编写一子函数,实现客户端和服务器端的通信
//参数:连接好的socket对象
//返回值:通信结束返回-1,继续返回0
int tcp_com(int sockFd);
int tcp_com(int sockFd)
{
char buf[N]={'\0'};
char cmd[N]={'\0'};
char filename[N]={'\0'};
//接受客户端发过来的消息
recv(sockFd,buf,sizeof(buf),0); //第一次接受"hello"
printf("buf=%s\n",buf);
if(strncmp(buf,"quit",4)==0)
{
printf("通信结束");
return -1;
}
parseStr(buf,cmd,filename);
printf("cmd=%s\n",cmd);
printf("filename=%s\n",filename);
if(strncmp(cmd,"put",3)==0)
{
//接受文件(下载文件 download)
download(sockFd,filename);
}else if(strncmp(cmd,"get",3)==0)
{
//发送文件(上传文件 upload)
}else
{
printf("指令有误\n");
}
return 0;
}
//创建子线程
void * thread(void *arg)
{
int * pfd=(int *)arg; //处理主线程给子线程传过来的值
int newfd=*pfd;
//参数:需要连接好的socket对象
while(1)
{
//3.接受/发送消息
int n=tcp_com(newfd);
//客户端和服务器端进行通信
if(n<0){
//4.关闭套接字
close(newfd);
pthread_exit(0);//线程的退出
break;
}
}
}
int main(int argc,char * argv[])
{
if(argc!=3)
{
printf("input appname ip port\n");
return -1;
}
//1.监听socket对象
int tcp_socket=tcp_server(argv[1],atoi(argv[2]));
//2.接受连接
struct sockaddr_in client;
int len=sizeof(client);
pthread_t pid;
while(1)
{
int newfd=accept(tcp_socket,(struct sockaddr *)&client,&len);
if(newfd<0)
{
perror("accept error");
return -1;
}
printf("accept ok\n");
printf("client ip=%s port=%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
//创建子线程实现和客户端的通信
//第四个参数:主线程给子线程传值
pthread_create(&pid,NULL,thread,&newfd);
}
close(tcp_socket);
}
对应客户端:
头文件:
#ifndef _NET_H
#define _NET_H
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define N 20
int parseStr(char *buf,char * cmd,char * filename);
int upload(int sockfd,char * filename);
#endif
#include <stdio.h>
#include "net.h"
#include <string.h>
//编写一子函数,连接服务器
//参数1:IP地址ecv(sockFd,buf,sizeof(buf),0);
//参数2:端口号
//返回值:连接成功返回连接好的socket对象,失败返回-1
int tcp_connect(char * ip,int port);
int tcp_connect(char * ip,int port)
{
//1.创建Tcp套接字
//2.设置对方的IP地址和端口号
//3.请求连接
//创建并打开套接字
int sockFd = socket(AF_INET,SOCK_STREAM,0);
if(sockFd < 0)
{
perror("socket error");
return -1;
}
printf("socket OK\n");
//连接服务器
struct sockaddr_in servAddr = {0};
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(port);
servAddr.sin_addr.s_addr = inet_addr(ip);
int ret = connect(sockFd,(struct sockaddr *)&servAddr,sizeof(servAddr));
if(ret < 0)
{
close(sockFd);
return -1;
}
printf("connect OK\n");
return sockFd;
}
//编写一子函数,实现客户端和服务端的通信
//参数:连接好的socket对象
//返回值:通信结束返回-1,继续返回0
int tcp_com(int sockFd);
int tcp_com(int sockFd)
{
printf("请输入指令");
char buf[N]={'\0'};
char cmd[N]={'\0'};
char filename[N]={'\0'};
fgets(buf,sizeof(buf),stdin);
send(sockFd,buf,strlen(buf),0);
parseStr(buf,cmd,filename);
printf("cmd==%s\n",cmd);
printf("filename=%s\n",filename);
if(strncmp(cmd,"put",3)==0)
{
//上传文件upload()
upload(sockFd,filename);
}else if(strncmp(cmd,"get",3)==0)
{
//下载文件download()
}else if(strncmp(cmd,"quit",4)==0)
{
printf("通信结束\n");
return -1;
}else
{
printf("指令有误\n");
}
return 0;
}
int main(int argc,char * argv[])
{
if(argc!=3)
{
printf("input appname ip port\n");
return -1;
}
//1.请求连接服务器 //atoi()将字符串转换成整数
// atof()将字符串转换成float
int tcp_socket=tcp_connect(argv[1],atoi(argv[2]));
//2.发送/接受消息
while(1){
//客户端和服务端之间的通信
int n=tcp_com(tcp_socket);
//3.关闭套接字
if(n<0){
close(tcp_socket);
break;
}
}
//
//
}
(注:部分段落参考自《嵌入式-应用程序设计综合教程微课版》)