Linux:简单的并发服务器实现
我前两天实现了一个简单的服务器和一个对应的客户端,也简单的解决了一些错误检查和常用的函数的封装,但是那个服务器的一次只能连接一个客户端,鸡肋,太鸡肋了,今天我来实现可以连接多个客户端的服务器实例:多进程并发服务器。
使用多进程并发服务器时要考虑以下几点:
- 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
- 系统内创建进程个数(与内存大小相关)
- 进程创建过多是否降低整体服务性能(进程调度)
其实也不难,就是将以往的知识应用就是:循环创建多个子进程,循环回收子进程,信号捕捉函数。主要就增加这三个知识。父进程专注于揽业务(连接客户端),子进程专注于处理客户端的数据,信号捕捉函数专门来回收子进程。说到这里,大体的思路应该是有了吧。上菜:
客户端是没有变化的。。。还是贴上来,占篇幅吧!
client.c:
#include
<string.h>#include
"my_error.h"#include
<sys/types.h>#include
<sys/socket.h>#include
<stdio.h>#include
<stdlib.h>#include
<unistd.h>#include
<sys/stat.h>#include
<arpa/inet.h>#include
<netinet/in.h>#define
ADDR_POST 8888int main(void)
{
int c_fd;
int len;
char buf[BUFSIZ];
struct
sockaddr_in clie_addr;socklen_t addr_len;
if ((c_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket error");
exit(1);
}
if (-1 == (inet_pton(AF_INET, "127.0.0.1", &clie_addr.sin_addr.s_addr)))
{
perror("inet_pton error");
exit(1);
}
clie_addr.sin_family = AF_INET;
clie_addr.sin_port = htons(ADDR_POST);
if (-1 == (connect(c_fd, (struct
sockaddr*)&clie_addr, sizeof(clie_addr)))){
perror("connect error");
exit(1);
}
while (1)
{
fgets(buf, sizeof(buf), stdin);
write(c_fd, buf, strlen(buf));
len = read(c_fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
}
close(c_fd);
return 0;
}
然后是服务器的代码:变得挺多的,所以注释也有,并且比较详细。
#include<stdio.h>
#include
<ctype.h>#include
<netinet/in.h>#include
<sys/wait.h>#include<string.h>
#include
<signal.h>#include<unistd.h>
#include<stdlib.h>
#include
<arpa/inet.h>#include
"my_error.h"#define
SERV_PORT 8888#define
MY_IP
"127.0.0.1"void func_wait(int
signal){
while (waitpid(0, NULL, WNOHANG))//循环不阻塞的回收子进程
{
;
}
}
int main(void)
{
int lfd, cfd;
pid_t pid;
struct
sockaddr_in serv_addr, clie_addr;socklen_t clie_addr_len;
char buf[BUFSIZ];
struct
sigaction act;lfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//inet_pton(AF_INET, MY_IP, &serv_addr.sin_addr.s_addr);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
Bind(lfd, (struct
sockaddr*)&serv_addr, sizeof(serv_addr));Listen(lfd, 32);
while (1)//父进程专门揽业务
{
clie_addr_len = sizeof(serv_addr);
cfd = Accept(lfd, (struct
sockaddr*)&clie_addr, &clie_addr_len);pid = fork();
if (pid > 0)
{
Close(cfd);//父进程出去揽业务,那么他就不需要子进程的需要的与客户端建立连接之后产生的新的socket文件秒数符了
/*
act.sa_flags = 0;
act.sa_handler = func_wait;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGCHLD);
if ((sigaction(SIGCHLD, &act, NULL)) < 0)//注册一个信号捕捉函数,回收子进程只能由父进程完成
{
perror("sigaction error");
exit(1);
}
*/
signal(SIGCHLD, func_wait);//注册一个信号捕捉函数,回收子进程只能由父进程完成
}
else
if (pid < 0)//出错{
perror("fork error");
exit(1);
}
else
if (0 == pid){
Close(lfd);//子进程不再需要刚开始建立的那个socket文件描述符了,这个描述符是用来和客户端建立连接的,连接已经建立,那么子进程也不需要了
break;
}
}
if (pid == 0)
{
while (1)//子进程循环读取/处理/发送客户端发来的数据。直到read返回0(表示读到末尾,即客户端已被关闭);
{
int len = Read(cfd, buf, sizeof(buf));
if (len > 0)
{
for (int i = 0; i != len; i++)
{
buf[i] = toupper(buf[i]);
}
}
else
if (len == 0)//读到结尾,客户端关闭了{
Close(cfd);
return 0;
}
else
{
perror("read error");
exit(1);
}
Write(cfd, buf, len);//写数据回去
}
}
return 0;
}
另外还有一个my_error.h,虽然上一篇博文也有,但是为了不麻烦,贴在这儿吧:
#pragma
once#include
<string.h>#include
<sys/types.h>#include
<sys/socket.h>#include
<stdio.h>#include
<stdlib.h>#include
<unistd.h>#include
<sys/stat.h>#include
<arpa/inet.h>#include
<netinet/in.h>#include
<errno.h>void perr_exit(const
char *s){
perror(s);
exit(1);
}
int Accept(int
fd, struct
sockaddr *sa, socklen_t *salenptr){
int n;
again: //accrpt是慢速系统调用,在阻塞期间可能会被信号杀死
if ((n = accept(fd, sa, salenptr)) < 0) {
if ((errno == ECONNABORTED) || (errno == EINTR))//进一步判断返回值,EINTR代表函数被信号中断;ECONNABORTED代表连接中断,这两种情况不算是异常,所以重启
goto again;
else
perr_exit("accept error");
}
return n;
}
int Bind(int
fd, const
struct
sockaddr *sa, socklen_t
salen){
int n;
if ((n = bind(fd, sa, salen)) < 0)
perr_exit("bind error");
return n;
}
int Connect(int
fd, const
struct
sockaddr *sa, socklen_t
salen){
int n;
if ((n = connect(fd, sa, salen)) < 0)
perr_exit("connect error");
return n;
}
int Listen(int
fd, int
backlog){
int n;
if ((n = listen(fd, backlog)) < 0)
perr_exit("listen error");
return n;
}
int Socket(int
family, int
type, int
protocol){
int n;
if ((n = socket(family, type, protocol)) < 0)
perr_exit("socket error");
return n;
}
ssize_t Read(int
fd, void *ptr, size_t
nbytes){
ssize_t n;
again: //read也是慢速系统调用。
if ((n = read(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
ssize_t Write(int
fd, const
void *ptr, size_t
nbytes){
ssize_t n;
again:
if ((n = write(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
int Close(int
fd){
int n;
if ((n = close(fd)) == -1)
perr_exit("close error");
return n;
}
/*
应用场景:以太网帧一次最多发送1500字节的数据,若是我们要读取4096字节的数据,但是4096字节的数 据需要四次才能完全发送过来,如果只调用一次read,那就只能读到1500就返回不读了,所以我们需要让 系统调用多次,必须读够那么多数据。所以这次调用,n要等于4096
//参数三:应该读取到的字节数*/
ssize_t Readn(int
fd, void *vptr, size_t
n){
size_t nleft; //unsigned int 剩余未读取的字节数
ssize_t nread; //int 实际读取到的字节数
char *ptr;
ptr = (char*)vptr;
nleft = n; //n 未读取到的字节数
while (nleft > 0) {
if ((nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)// EINTR(表"被信号中断")
nread = 0; // 读到了0个字节
else
return -1;//其他错误
}
else
if (nread == 0)// 文件读取完break;
nleft -= nread;
ptr += nread;
}
return
n - nleft;//返回实际读取到的字节数}
ssize_t Writen(int
fd, const
void *vptr, size_t
n){
size_t nleft; //剩余未写的字节数
ssize_t nwritten; //实际写的字节数
const
char *ptr;ptr = (char*)vptr;
nleft = n; //未写的字节数
while (nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return
n;}
static
ssize_t my_read(const
int
fd, char *ptr){
static
int read_cnt;static
char *read_ptr;static
char read_buf[100];//读一次可以传出100字节,但是不是一次性传出100字节,实际上 是一次传一个字符出去if (read_cnt <= 0) {
again:
if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return -1;
}
else
if (read_cnt == 0)return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
/*
应用场景:fgets只能从普通文件或者标准输入输出设备读去数据,他不能从socket中读取数据,所以用readline代替fgets来读取一行
*/
ssize_t Readline(int
fd, void *vptr, size_t
maxlen){
ssize_t n, rc;
char c, *ptr;
ptr = (char*)vptr;
for (n = 1; n < maxlen; n++) {
if ((rc = my_read(fd, &c)) == 1) {
*ptr++ = c;
if (c == '\n')
break;
}
else
if (rc == 0) {*ptr = 0;
return n - 1;
}
else
return -1;
}
*ptr = 0;
return n;//返回读到的字节数
}
这是结果:

可惜,这东西貌似只能在我的电脑上运行,在我室友的电脑上就不行,郁闷,不知道是哪儿出问题了。不过没事,以后肯定能知道。不过这个就算是能够通用,它也不能用到实际中,因为他的开销很大。当然,应用到那种只需要几十个几百个客户端的情况下还是可以的。比如智能家居。
Linux:简单的并发服务器实现的更多相关文章
- Go语言之进阶篇简单版并发服务器
1.简单版并发服务器 示例1: package main import ( "fmt" "net" "strings" ) //处理用户请求 ...
- (二)通过fork编写一个简单的并发服务器
概述 那么最简单的服务端并发处理客户端请求就是,父进程用监听套接字监听,当有连接过来时那么监听套接字就变成了已连接套接字(源和目的的IP和端口都包含了),这时候就可以和客户端通信,但此时其他客户端无法 ...
- linux 简单搭建git服务器
如果使用git的人数较少,可以使用下面的步骤快速部署一个git服务器环境. 1. 生成 SSH 公钥 每个需要使用git服务器的工程师,自己需要生成一个ssh公钥进入自己的~/.ssh目录,看有没有用 ...
- Linux select TCP并发服务器与客户端编程
介绍:运行在ubuntu linux系统,需要先打开一个终端运行服务端代码,这时,可以打开多个终端同时运行多个客户端代码(注意客户端数目要小于MAX_FD);在客户端输入数据后回车,可以看见服务器收到 ...
- 简单的并发服务器(多个线程各自accept)
基于之前讲述的简单循环服务器,做一个多个线程各自accept的服务器demo 由于多个线程各自accept,容易造成数据错误,需要在accept前后枷锁 先看下客户端 客户端创建socket,初始化服 ...
- [GO]简单的并发服务器
package main import ( "net" "fmt" "strings" ) func HandleConn(conn net ...
- Linux网络编程服务器模型选择之并发服务器(上)
与循环服务器的串行处理不同,并发服务器对服务请求并发处理.循环服务器只能够一个一个的处理客户端的请求,显然效率很低.并发服务器通过建立多个子进程来实现对请求的并发处理.并发服务器的一个难点是如何确定子 ...
- Linux网络编程服务器模型选择之并发服务器(下)
前面两篇文章(参见)分别介绍了循环服务器和简单的并发服务器网络模型,我们已经知道循环服务器模型效率较低,同一时刻只能为一个客户端提供服务,而且对于TCP模型来说,还存在单客户端长久独占与服务器的连接, ...
- socket 中午吃的啥 socket 并发服务器 fork
http://www.cnblogs.com/thinksasa/archive/2013/02/26/2934206.html zh.wikipedia.org/wiki/網路插座 在作業系統中,通 ...
随机推荐
- Django的视图层简介
Django的视图层 视图函数 所谓视图函数,其实就是我们Django项目中各个应用中的views.py文件中定义的每个用来处理URL路由映射到这里的逻辑函数.一个视图函数简称视图,它是个简单的Pyt ...
- 00009 - cat、tail、head、tee、wc、sort文件操作和过滤
绝大多数命令行工作是针对文件的.我们会在本节中讨论如何观察及过滤文件内容,使用一条命令从文件中提取所需信息,以及对文件的内容进行排序. cat.tail.head.tee:文件打印命令这些命令的语法基 ...
- 基于HAProxy+Keepalived高可用负载均衡web服务的搭建
一 原理简介 1.HAProxyHAProxy提供高可用性.负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费.快速并且可靠的一种解决方案.HAProxy特别适用于那些负载特大的web ...
- 自适应页面设计: Viewport控制, media query和相对单位
viewport,视口,就是对用户的可见部分, 大小因设备而不同.H5引入. * 没有它: 整体缩放 ( 老网页是固定的大小,浏览器在手机上只是简单地缩放整个页面,所以用户体验很差) * 有了它: 浏 ...
- elasticsearch无故关闭,Log无报错
可以看到图中的关闭log之前没有任务报错,这也让博主非常抓狂,这看着就像是人为关闭的,于是博主在群里问是不是有人动过该服务,确认没人关闭后,百度无果,社区上也没找到有关信息,最后灵光一闪,猜测是不是因 ...
- 使用原子类或synchronized(没用Lock)解决阐述多线程所遇到线程安全问题和解决方案
例子题目: 创建10个线程,每个线程执行10000次加1,输出总和 正常结果100000 但是如果出现线程不安全会低于100000 import java.util.concurrent.Count ...
- 将16进制的颜色转为rgb颜色
在前端面试过程中,常常会遇到这样一种类型的题目: 使用js将16进制的颜色值转为rgb颜色! 反而在项目中,不怎么遇到这种问题,也很少有这种需求的项目. 但毕竟面试中常常遇到,我自己在之前的面试的时候 ...
- super的使用方法与使用范围
如果你了解,用this是调用一个类里面的变量或者对象方法.那么super你可以理解为调用多态或者继承类中的构造方法和对象方法.在super调用构造方法时,只能调用带参的构造方法,这也是唯一调用其他类里 ...
- centos7安装LNMP与Laravel遇到的一些小问题
安装LNMP 第一次安装 yum update CentOS7下 Nginx1.13.5 + PHP7.1.10 + MySQL5.7.19 源码编译安装 安装mySQL时,mysqld: error ...
- Oracle 日志归档 自动清理
exp emis/emis@orcl file=d:\backup\oracle\oracle%date:~0,10%.dmp owner=emis log=d:\backup\oracle\orac ...