一:多进程版
多进程版服务器优缺点:
1.缺点:
- 连接来的时候才创建子进程,此时,性能受损。
- 多进程服务器每个进程特别“吃”资源,进而导致该服务器只能服务有限个客户。
- 多进程服务器随着进程增多,cpu压力增大,会影响性能。
2.优点
- 能处理多个用户
- 代码简单,编写周期短
- 稳定性强(一个进程挂了,不会影响其他进程)
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#define SIZE 128
void service(int new_sock, char* ip, int port)
{
char buf[SIZE];
while(1)
{
buf[0] = 0;//清空buf
//先从new_sock上读取内容
ssize_t s = read(new_sock, buf, sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("[%s %d] say# %s\n", ip, port, buf);
//再将原内容返回给客户端
write(new_sock, buf, strlen(buf));
}
else if(s == 0)//表示对方已经断开连接
{
printf("client [%s %d] quit!\n", ip, port);
break;
}
else
{
break;
}
}
}
int StartUp(char* ip, int port)
{
//创建套接字文件描述符
//第一个参数为ip地址类型,第二个(tcp->SOCK_STREAM, udp->SOCK_DGRAM),最后一个参数设0
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock < 0)
{
perror("socket");
exit(2);
}
//设置本地网络信息
struct sockaddr_in local;
local.sin_family = AF_INET;//地址类型
local.sin_addr.s_addr = inet_addr(ip);//设置ip地址
//local.sin_addr.s_addr = htonl(INADDR_ANY);//设置为INADDR_ANY(其实就是0)表示可以在所有的ip地址(服务器可能有多个网卡)上监听
local.sin_port = htons(port);
//将设置好的信息于listen_sock绑定
if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
perror("bind");
exit(3);
}
//将listen_sock设置为监听状态
//第二个参数为等待队列长度,不能为0,也不能太长(5-10)
if(listen(listen_sock, 5) < 0)
{
perror("listen");
exit(4);
}
return listen_sock;
}
//用命令行参数将启动服务器的主机网络信息传进来
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("Usage: %s [ip] [port]\n", argv[0]);
exit(1);
}
int listen_sock = StartUp(argv[1], atoi(argv[2]));
struct sockaddr_in peer;//用来保存客户端的网络信息
char ipBuf[24];
int p;
while(1)
{
ipBuf[0] = 0;//清空buf
int new_sock;//真正用来通信的套接字
socklen_t len = sizeof(peer);
if((new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len)) < 0)
{
perror("accept");
continue;//连接失败重新连接新的客户端
}
inet_ntop(AF_INET, (const void*)&peer.sin_addr, ipBuf, sizeof(ipBuf));//将整数类型ip转化为字符串风格
p = ntohs(peer.sin_port);//将网络字节序的port转化为主机字节序
printf("Get a new connect: [%s %d]\n", ipBuf, p);
//多进程版本--子进程提供服务, 父进程一直接收请求
//因为父进程一直不会退出,而子进程提供完服务之后就可能会退出
//就会成为僵尸进程,造成内存泄露
pid_t id = fork();
if(id == 0)//child
{
close(listen_sock);
//这里可以在子进程中再创建子进程,子进程退出,
//孙子进程成为孤儿进程,孙子进程提供服务,退出后由
//1号进程回收,不会造成内存泄露
if(fork() == 0)
{
service(new_sock, ipBuf, p);
close(new_sock);//因为子进程也有自己的文件描述符表,关闭不会对父进程造成影响,所以这里一定要关闭
exit(0);
}
}
else if(id > 0)//father
{
//父进程只在这里重复的接收连接,不用new_sock去通信
close(new_sock);
waitpid(id, NULL, 0);
}
else
{
perror("fork");
continue;
}
}
return 0;
}
二:多线程版
多线程服务器优缺点:
1.缺点
- 多进程的缺点多线程都有,但是由于线程是比进程更细的执行流,所以多线程的每个线程”吃“的资源较少,调度周期更短。
- 多线程稳定性差,有可能会因为线程安全的问题而导致整个服务器挂掉。
2.优点
- 能处理多个用户
- 代码简单,编写周期短
#include <pthread.h>
#include <malloc.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#define SIZE 128
typedef struct
{
int sock;
char ip[24];
int port;
}net_info_t;
void service(int new_sock, char* ip, int port)
{
char buf[SIZE];
while(1)
{
buf[0] = 0;//清空buf
//先从new_sock上读取内容
ssize_t s = read(new_sock, buf, sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("[%s %d] say# %s\n", ip, port, buf);
//再将原内容返回给客户端
write(new_sock, buf, strlen(buf));
}
else if(s == 0)//表示对方已经断开连接
{
printf("client [%s %d] quit!\n", ip, port);
break;
}
else
{
break;
}
}
}
void* thread_serevice(void* arg)
{
net_info_t* info = (net_info_t*)arg;
service(info->sock, info->ip, info->port);
//服务完之后一定要关闭文件描述符
close(info->sock);
//因为info是在堆上动态申请的,所以一定要释放info
free(info);
}
int StartUp(char* ip, int port)
{
//创建套接字文件描述符
//第一个参数为ip地址类型,第二个(tcp->SOCK_STREAM, udp->SOCK_DGRAM),最后一个参数设0
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock < 0)
{
perror("socket");
exit(2);
}
//设置本地网络信息
struct sockaddr_in local;
local.sin_family = AF_INET;//地址类型
local.sin_addr.s_addr = inet_addr(ip);//设置ip地址
//local.sin_addr.s_addr = htonl(INADDR_ANY);//设置为INADDR_ANY(其实就是0)表示可以在所有的ip地址(服务器可能有多个网卡)上监听
local.sin_port = htons(port);
//将设置好的信息于listen_sock绑定
if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
perror("bind");
exit(3);
}
//将listen_sock设置为监听状态
//第二个参数为等待队列长度,不能为0,也不能太长(5-10)
if(listen(listen_sock, 5) < 0)
{
perror("listen");
exit(4);
}
return listen_sock;
}
//用命令行参数将启动服务器的主机网络信息传进来
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("Usage: %s [ip] [port]\n", argv[0]);
exit(1);
}
int listen_sock = StartUp(argv[1], atoi(argv[2]));
struct sockaddr_in peer;//用来保存客户端的网络信息
char ipBuf[24];
int p;
while(1)
{
ipBuf[0] = 0;//清空buf
int new_sock;//真正用来通信的套接字
socklen_t len = sizeof(peer);
if((new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len)) < 0)
{
perror("accept");
continue;//连接失败重新连接新的客户端
}
inet_ntop(AF_INET, (const void*)&peer.sin_addr, ipBuf, sizeof(ipBuf));//将整数类型ip转化为字符串风格
p = ntohs(peer.sin_port);//将网络字节序的port转化为主机字节序
printf("Get a new connect: [%s %d]\n", ipBuf, p);
net_info_t *info = (net_info_t*)malloc(sizeof(net_info_t));
if(info == NULL)
{
perror("malloc");
close(new_sock);//一定要关闭文件描述符
continue;
}
strcpy(info->ip, ipBuf);
info->port = p;
info->sock = new_sock;
pthread_t tid;
pthread_create(&tid, NULL, thread_serevice, info);
//在主线程中将新线程分离就不用等
pthread_detach(tid);
}
return 0;
}
总结:多线程与多进程的服务器只能应用于中小型项目。