今天笔者带来的是server型号第一,这是最经常使用的模型的最基本的一个–TCP并发server,每个客户一个子进程。

首先简单介绍:TCP并发server,每个客户一个子进程,并发server调用fork派生一个子进程来处理每一个子进程,使得server能够同一时候为多个客户服务,每一个进程一个客户。

客户数目的唯一限制是操作系统对以其名义执行server的用户ID能够同一时候拥有多少子进程的限制。

详细到我们的需求,我们的client发送某个指令,服务端接收。假设符合服务端的要求。就将当时的时间发回给client。

需求非常easy,我们的着重点在server的模型。

Okay,来看代码:

这是服务端的主代码(serv.c):

#include "pub.h"

#define LISTENQ 1024

void sig_child(int signo);

//serv <port>
int main(int argc, char **argv)
{
int listenfd,connfd;
pid_t childpid;
int on = 1;
struct sockaddr_in servaddr, cliaddr;
socklen_t clilen;
char *ptr;
if(argc != 2)
{
fprintf(stderr,"usage: serv <port>\n");
exit(1);
} if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("serv socket error ");
exit(-1);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[1]));
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //设置可重用
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("serv bind error");
exit(-1);
} if(listen(listenfd, LISTENQ) < 0)
{
perror("serv listen error");
exit(-1);
} //信号处理函数
//这里主要是处理僵死进程
Signal(SIGCHLD, sig_child); //每一个子进程终止时就会产生SIGCHLD信号,默认是忽略 for( ; ; )
{
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0)
{
if(errno == EINTR)
continue;
else
{
perror("serv accept error");
exit(-1);
}
}
ptr = inet_ntoa(cliaddr.sin_addr);//cliaddr.sin_addr不用取地址
fprintf(stdout,"%s has connected\n", ptr); if((childpid = fork()) == 0)//child
{
//注意这里是另一个进程
close(listenfd); //子进程关闭listenfd do_child(connfd); ptr = inet_ntoa(cliaddr.sin_addr);//cliaddr.sin_addr不用取地址
fprintf(stdout,"%s has disconnected\n", ptr); close(connfd);
exit(0);
} close(connfd);//父进程关闭connfd } close(listenfd); exit(0);
} void sig_child(int signo)
{
pid_t pid;
int stat; while((pid = waitpid(-1, &stat, WNOHANG)) > 0) //不堵塞。返回child pid
{//防止同一时候有几个子进程killed,加while,知道处理全然部killed child
fprintf(stdout,"%d terminated\n",pid);
fflush(stdout);
} return;
}

看这小段连接的代码:

clilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0)
{
if(errno == EINTR)
continue;
else
{
perror("serv accept error");
exit(-1);
}
}
ptr = inet_ntoa(cliaddr.sin_addr);//cliaddr.sin_addr不用取地址
fprintf(stdout,"%s has connected\n", ptr);

注意我们这里的大循环。每次循环的開始都是accept,也就是从已连接的队列中返回当中一个。假设返回成功,就打印一下IP,显示是哪个IP来连接。

这里採用的是inet_ntoa()函数,这是个专门为IPV4准备的函数。

你也能够使用inet_pton()函数,这是IPV4和IPV6通用的函数。

再看以下的一小段:

if((childpid = fork()) == 0)//child
{
//注意这里是另一个进程
close(listenfd); //子进程关闭listenfd do_child(connfd); ptr = inet_ntoa(cliaddr.sin_addr);//cliaddr.sin_addr不用取地址
fprintf(stdout,"%s has disconnected\n", ptr); close(connfd);
exit(0);
}

这里就是真正的fork出一个子进程。

先close(listenfd),注意这里close并不会真正的关闭listenfd,这仅仅是降低了listenfd的一个引用次数,父进程另一个引用。

然后是do_child(connfd),这里是我们自己定义的来处理连接的函数。注意已经将connfd传递给这个函数。

稍后我们在讨论一下这个函数。

假设能从do_child()函数返回,就再打印一下,某一个IP 已经离开。

最后就是close(connfd),再exit(0)退出,这里的close(connfd),能够省略,子进程退出。就会关掉自身所打开的描写叙述符。

注意到我们这里另一信号处理函数,sig_child。例如以下所看到的:

void sig_child(int signo)
{
pid_t pid;
int stat; while((pid = waitpid(-1, &stat, WNOHANG)) > 0) //不堵塞。返回child pid
{//防止同一时候有几个子进程killed。加while,知道处理全然部killed child
fprintf(stdout,"%d terminated\n",pid);
fflush(stdout);
} return;
}

我们在之前已经注冊了这个信号处理函数,

//信号处理函数

Signal(SIGCHLD, sig_child); //每一个子进程终止时就会产生SIGCHLD信号,默认是忽略

这里我们的处理函数,就是打印一下离开的子进程的pid。就如同凝视所看到的。採用这样的形式:

while((pid = waitpid(-1, &stat, WNOHANG)) > 0) 能够防止同一时候有几个子进程killed,加while,知道处理全然部killed child

到了这里,server基本的模型已经展示出来了,如今我们来看基本的处理client连接的do_child()函数。

来看代码:

#include "pub.h"

void do_child(int sockfd)
{
time_t mytime;
char buff[MAXLINE];
int n; for( ; ; )
{
if((n = read(sockfd, buff, sizeof(buff))) <= 0)
{
if( n < 0 && errno == EINTR)
continue;
else if(n == 0){
//交给外面的主循环处理(打印某某离开)
break;
}else{
perror("child read error");
exit(-1);
}
}
else
{
//比較前几个字符串。是不是GETTIME,是则返回时间。否则返回 Gettime Command Error
if((strncmp(buff,"GETTIME", 7) == 0) ||
(strncmp(buff,"gettime", 7) == 0) )
{
mytime = time(NULL);
snprintf(buff, sizeof(buff), "%s", ctime(&mytime));
writen(sockfd, buff, strlen(buff));//这里最好用writen(自己定义)
}
else
{//不是的话,就返回 Gettime Command Error
snprintf(buff, sizeof(buff), "Gettime Command Error ");
writen(sockfd, buff, strlen(buff));
}
} } }

这里採用的处理非常easy,就如同我凝视中讲的,比較前几个字符串。是不是GETTIME或者gettime,是则返回时间,否则返回 Gettime Command Error

看这里:

mytime = time(NULL);
snprintf(buff, sizeof(buff), "%s", ctime(&mytime));
writen(sockfd, buff, strlen(buff));//这里最好用writen(自己定义)

通过time()和ctime()用字符串的形式打印出当前时间。

注意这里採用的是writen()函数。自己定义函数。

事实上就是write相当数目的buff,同一时候防止信号打断

来看writen函数的代码:

#include "pub.h"

int writen(int sockfd,const char *buff, int n)
{
int nleft = n;
int ncount = 0; const char *ptr = buff; while(nleft > 0)
{
if((ncount = write(sockfd, ptr, nleft)) <= 0)
{
if(errno == EINTR)
ncount = 0; //call again
else
return -1;
}
nleft -= ncount;
ptr += ncount;
} return n - nleft;
}

接下来,我们写了一个測试用的client(client.c):

来看代码:

#include "pub.h"

//client <ip> <port>
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
int n; if(argc != 3)
{
fprintf(stderr,"usage: <ip> <port> \n");
exit(1);
} if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("client socket error ");
exit(-1);
} memset(&servaddr, 0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1], &servaddr.sin_addr); if((connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0)
{
perror("client connect error");
exit(-1);
}
strcli(sockfd); exit(0);
}

这里就是简单的client代码,通过參数传进来要连接的ip与port,发送请求的函数是str_cli()。

来看str_cli函数:

#include "pub.h"

void strcli(int sockfd)
{
char sendbuff[MAXLINE], recvbuff[MAXLINE]; while(fgets(sendbuff, sizeof(sendbuff), stdin) != NULL)
{
writen(sockfd, sendbuff, strlen(sendbuff)); if(read(sockfd, recvbuff, sizeof(recvbuff)) == 0)
fprintf(stderr,"server has terminated\n");
fputs(recvbuff, stdout);
} }

这里事实上就是读取标准输入,发送给服务端。并read堵塞,等服务端发送回后。将服务端发送回的数据打印到标准输出上。

另外,另一个注冊信号处理函数Signal函数,之前讲过。但没有给出代码。

我这里直接将unix网络编程里的signal拿了过来。能够直接使用。

也来看一下,signal.c:

/* include signal */
#include "pub.h" Sigfunc *
signal(int signo, Sigfunc *func)
{
struct sigaction act, oact; act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
/* end signal */ Sigfunc *
Signal(int signo, Sigfunc *func) /* for our signal() function */
{
Sigfunc *sigfunc; if ( (sigfunc = signal(signo, func)) == SIG_ERR)
{
perror("signal error");
exit(1);
}
return(sigfunc);
}

至此,我们的第一种server模型就已经完毕了。全部的代码都已经晒了出来。并经过測试。你能够在你自己的linux上试试。看行不行。

另外,我们这里就是简单的client与服务端查询时间的处理请求。你也能够进行别的处理请求。比方能够两方通信。採用多线程,或者试试发送文件,都能够。

Just up to you.并且,你仅仅须要改动do_child()和str_cli()函数。其它的能够不改动,除非你有别的需求。

最后。还得讲一下这样的模型的问题。主要就是为每一个客户fork一个子进程比較消耗CPU时间,几百或几千的的客户是没有问题的,可是如今的每天的TCP连接都是上百万的。这里就会有问题。当然,假设系统负载较轻,这样的模型是不错好的选择。

好了。这就是我们的今天博客的全部内容,你懂了吗?假设我有出错的地方,欢迎大家指出。假设认为好。也能够点赞哦。

TCP并发server,每个客户一个子进程的更多相关文章

  1. TCP并发server模型(三)

    本篇博客讲述的是单client单线程模型,该模型相同由主进程统一accept,仅仅是将fork改为了pthread_create. 与进程相比,线程有非常多长处(速度快,占用资源少.数据能够共享). ...

  2. UNIX网络编程卷1 server程序设计范式1 并发server,为每一个客户请求fork一个进程

    本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.传统并发server调用 fork 派生一个子进程来处理每一个客户 2.传统并发serv ...

  3. tcp并发服务端

    TCP并发服务器:并发服务器的思想是每一个客户端的请求并不由服务器的主进程直接处理,而是服务器主进程创建一个子进程来处理. 创建TCP并发服务器的算法如下: socket(……): //创建一个TCP ...

  4. Linux TCP并发请求溺出 调优

    TCP并发请求溺出 调优:系统开启某个监听端口后,当多个TCP请求连接监听端后,会把多个请求交给backlog的默认监听队列由socket server一并处理,backlog有自己的队列长度默认12 ...

  5. 大并发server架构 &amp;&amp; 大型站点架构演变

    server的三条要求: 高性能:对于大量请求,及时高速的响应 高可用:7*24 不间断,出现问题自己主动转移.这叫fail over(故障转移) 伸缩性:使用跨机器的通信(TCP) 另外不论什么网络 ...

  6. TCP并发服务器简单示例

    并发服务器的思想是每一个客户的请求并不由服务器直接处理,而是由服务器创建一个子进程来处理 1. 服务器端 #include <stdio.h> #include <sys/types ...

  7. 一台服务器支持多少TCP并发链接

    误区一 1.文件句柄---文件描述符 每开一个链接,都要消耗一个文件套接字,当文件描述符用完,系统会返回can't  open so many files 这时你需要明白操作系统对可以打开的最大文件数 ...

  8. linux 网络编程 3---(io多路复用,tcp并发)

    1,io模型: 阻塞io.非阻塞io.io多路复用,信号驱动io. 阻塞Io与非阻塞io的转换,可用fcntl()函数 #include<unistd.h> #include<fcn ...

  9. 1高并发server:多路IO之select

     1 select A:select能监听的文件描写叙述符个数受限于FD_SETSIZE,一般为1024.单纯改变进程打开 的文件描写叙述符个数并不能改变select监听文件个数 B:解决1024 ...

随机推荐

  1. 李洪强iOS开发本人集成环信的经验总结_02_基本配置

     李洪强iOS开发本人集成环信的经验总结_02_基本配置 来到APPdelegate中做一些配置 01 - 导入头文件 02 - 在didFinishLaunchingWithOptions用法总结 ...

  2. [jobdu]从尾到头打印链表

    九度确实烂啊,用cin就超时,必须要scanf.唯一可说的就是pplast和递归打印.也可以用stack,其实和递归一样的空间复杂度. #include<stdio.h> using na ...

  3. Sublime Text修改显示图标

    选择喜欢的图片 首先你需要选择一个中意的图片做为新的图标,格式可以是png,jpg,gif的 转为ico格式 我们需要ico格式的图片,所以需要将上述的图片转换一下格式.同样,转ico格式的软件很多, ...

  4. Native Fullscreen JavaScript API (plus jQuery plugin)

    http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/ HTML5 <video> is gre ...

  5. iOS添加自定义字体方法

    1:获取字体文件 从各种渠道下载字体文件ttf, 网站或者从别的ipa里扣出来.(以fzltxh.ttf为例) 2:将fzltxh.ttf文件拷贝到工程中 3:在Info.plist中添加项: Fon ...

  6. 事件流处理框架NEsper for .NET z

    复合事件处理(Complex Event Processing)介绍提到了开源的Esper,NEsper 是一个事件流处理(Event Stream Processing,ESP)和复杂事件处理(Co ...

  7. 【转】Kinect使用

    文章转自http://blog.csdn.net/yangtrees/article/details/16106271 Kinect中深度值最大为4096mm (0x0fff) 微软建议在开发中使用1 ...

  8. SQL Server 堆表与栈表的对比(大表)

    环境准备 使用1个表,生成1000万行来进行性能对比(勉强也算比较大了),对比性能差别. 为了简化过程,不提供生成随机数据的过程.该表初始为非聚集索引(堆表),测试过程中会改为聚集索引(栈表). CR ...

  9. 我的iOS开发之路

    我终于开始写我的第一个cocos2d-iphone程序了.纪念一下 额,这是一个悲伤的故事.其实我从开始准备开发iOS已经好久了,从我装上Xcode开始到现在,应该已经有差不多一年的时间了把. 还记得 ...

  10. YUV转灰度

    转载自:http://blog.csdn.net/sxjk1987/article/details/7470545 标准的V4L2 API http://v4l.videotechnology.com ...