说明

在Linux中通过流式套接字编程(TCP),实现一个并发服务器的访问回显,适合刚学完Linux套接字编程的朋友进行巩固训练
具体功能:

  • 服务器能够同时连接、处理多个客户端的信息
  • 客户端向服务器发送数据之后,服务器收到数据,然后反手发送给客户端
  • 服务器能够对客户端的退出做出反应,并在客户端退出连接的时候给出提示
  • 服务器能够识别每个客户端发送的信息,在显示的时候加上客户端的IP地址
  • 服务器中能够对已经退出的服务进程作回收处理
  • 客户端能够对服务器的退出作出反应,检测到服务器退出后客户端也退出

注意事项

  • 多进程并发服务器编程中,每次建立一个套接字连接,都会fork一个进程来处理
  • accept是自带阻塞的,所以fork返回父进程之后,父进程就会阻塞等待下一个已连接套接字
  • 客户端的关闭通过ctrl-c发出的信号(SIGINT)来终止客户端
  • 当客户端终止之后,服务器上对应的服务进程通过exit结束,此时由于服务器的主进程还阻塞在accept中,所以无法及时回收子服务进程,所以通过注册一个信号SIGCHLD处理函数,在信号处理的时候回收僵尸子进程。SIGCHLD是子进程结束的时候发送给父进程的信号,默认忽略。
  • 服务器进程如何检测客户端退出呢?通过recv()函数,当返回值为0的时候,表示客户端已经关闭套接字,即客户端退出。
  • 当服务线程主动关闭的时候,客户端也会通过recv()收到服务器关闭的信息,然后客户端主动退出
  • 关于套接字描述符,因为描述符也算是进程的资源,当套接字描述符的引用值为0的时候,才会关闭套接字,或者是进程退出的时候释放套接字描述符资源
  • 每次fork的时候,都会产生一个对于已经打开的套接字描述符的引用,所以要在进入子服务进程后关闭监听套接字描述符、在主服务进程中关闭已连接套接字描述符、在子服务进程退出的时候关闭已连接套接字描述符、在退出主服务器进程的时候关闭监听套接字描述符,这样才做到了有始有终(fork之后已连接套接字描述符的引用就有两份)

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h> #define SERVER_ADDR "172.17.44.154"
#define BUFSIZE 100 void sigchld_handler(int arg); int main(int argc, const char *argv[])
{
int socket_fd, new_fd;
struct sockaddr_in server_addr, cli_addr;
char buf[BUFSIZE];
int pid;
struct sigaction sig; /* 注册中断信号处理函数 */
sig.sa_handler = sigchld_handler;
sigaction(SIGCHLD, &sig, NULL); /* 创建套接字,并获取套接字描述符 */
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == socket_fd) {
perror("socket");
exit(-1);
} /* 绑定地址 */
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(5001);
inet_pton(AF_INET, SERVER_ADDR, (void*)&server_addr.sin_addr.s_addr); //地址转换
if (-1 == bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))) {
perror("bind");
exit(-1);
} /* 转换为被动连接套接字 */
if (-1 == listen(socket_fd, 5)) {
perror("listen");
exit(-1);
} #if 0 //单进程服务器
/* 获取已连接套接字 */
socklen_t len = 0;
new_fd = accept(socket_fd, (struct sockaddr*)&cli_addr, (socklen_t *)&len);
if (-1 == new_fd) {
perror("accept");
exit(-1);
}
printf("accept socket!\nclient ip :%s port:%d\n", inet_ntoa(cli_addr.sin_addr), cli_addr.sin_port); while (1) {
memset(buf, 0, BUFSIZE);
if (0 == recv(new_fd, buf, BUFSIZE, 0)) { //接受数据
printf("the client is closed\n");
break;
}
printf("read:%s\n", buf);
send(new_fd, buf, BUFSIZE, 0); //回应客户端
}
close(new_fd); #else //多进程并发服务器
while (1) { socklen_t len = 0;
new_fd = accept(socket_fd, (struct sockaddr*)&cli_addr, (socklen_t *)&len);
if (-1 == new_fd) {
if (errno == EINTR) continue; //accept可能会被信号中断
perror("accept");
exit(-1);
} /* 并发服务器:子进程中进行TCP通信 */
pid = fork();
if (pid == -1) {
perror("fork");
exit(0);
} else if (pid == 0) {
close(socket_fd); //关闭监听套接字 while (1) {
memset(buf, 0, BUFSIZE);
if (0 == recv(new_fd, buf, BUFSIZE, 0)) { //接受数据,只有当客户端主动关闭的时候,才退出线程,还要对关闭之后的子进程
printf("the client socket %s is closed\n", inet_ntoa((struct in_addr)cli_addr.sin_addr));
close(new_fd); //退出之前记得关闭 已连接套接字
exit(0); //通过信号处理函数进行回收 }
getsockname(new_fd, (struct sockaddr*)&cli_addr, (socklen_t *)&len); //获取连接套接字信息
printf("recv client IP:%s data:%s\n", inet_ntoa((struct in_addr)cli_addr.sin_addr), buf);
send(new_fd, buf, BUFSIZE, 0); //回应客户端
} } else {
close(new_fd); //父进程关闭 已连接套接字
} } #endif close(socket_fd);
return 0;
} void sigchld_handler(int arg)
{
int child_pid;
if (SIGCHLD == arg) {
if ((child_pid = waitpid(-1, NULL, WNOHANG)) == -1) {
perror("sigchld");
}
printf("a client %d is end\n", child_pid);
} }

client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#define SERVER_ADDR "172.17.44.154"
#define BUFSIZE 100 int main(int argc, const char *argv[])
{
int new_fd;
struct sockaddr_in server_addr;
char buf[BUFSIZE]; /* 创建套接字,并获取套接字描述符 */
new_fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == new_fd) {
perror("socket");
exit(-1);
} server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(5001);
inet_pton(AF_INET, SERVER_ADDR, (void*)&server_addr.sin_addr.s_addr);
if (-1 == connect(new_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))) {
perror("connect");
exit(-1);
} while (1) {
printf("input:");
fgets(buf, BUFSIZE, stdin); //获取数据 if (-1 == send(new_fd, buf, BUFSIZE, 0)) { //发送数据
perror("send");
close(new_fd);
exit(-1);
}
if (0 == recv(new_fd, buf, BUFSIZE, 0)) { //收到数据
printf("server closed\n");
break;
}
printf("recv:%s\n", buf);
}
close(new_fd); return 0;
}

运行截图

PS:这里是在同一主机下做实验的,所以各个客户端的IP地址都是一样的
正常运行的状态如下:

当有一个客户端退出时,服务器会显示信息,但是对其他客户端的服务正常进行:

当服务器主动关闭之后,所有客户端都会收到服务器关闭的信息,并且主动退出:

Linux 并发服务器编程(多进程)的更多相关文章

  1. Linux 高性能服务器编程——多进程编程

    问题聚焦:     进程是Linux操作系统环境的基础.     本篇讨论以下几个内容,同时也是面试经常被问到的一些问题:     1 复制进程映像的fork系统调用和替换进程映像的exec系列系统调 ...

  2. Linux 高性能服务器编程——高性能服务器程序框架

    问题聚焦:     核心章节.     服务器一般分为如下三个主要模块:I/O处理单元(四种I/O模型,两种高效事件处理模块),逻辑单元(两种高效并发模式,有效状态机)和存储单元(不讨论). 服务器模 ...

  3. Linux 高性能服务器编程——Linux网络编程基础API

    问题聚焦:     这节介绍的不仅是网络编程的几个API     更重要的是,探讨了Linux网络编程基础API与内核中TCP/IP协议族之间的关系.     这节主要介绍三个方面的内容:套接字(so ...

  4. 第15章 高并发服务器编程(2)_I/O多路复用

    3. I/O多路复用:select函数 3.1 I/O多路复用简介 (1)通信领域的时分多路复用 (2)I/O多路复用(I/O multiplexing) ①同一线程,通过“拨开关”方式,来同时处理多 ...

  5. linux高性能服务器编程

    <Linux高性能服务器编程>:当当网.亚马逊 目录: 第一章:tcp/ip协议族 第二章:ip协议族 第三章:tcp协议详解 第四章:tcp/ip通信案例:访问Internet 第五章: ...

  6. linux高性能服务器编程 (一) --Tcp/Ip协议族

    前言: 在学习swoole入门基础的过程中,遇到了很多知识瓶颈,比方说多进程.多线程.以及进程池和线程池等都有诸多的疑惑.之前也有学习相关知识,但只是单纯的知识面了解.而没有真正的学习他们的来龙去脉. ...

  7. Linux 高性能服务器编程——多线程编程

    问题聚焦:     在简单地介绍线程的基本知识之后,主要讨论三个方面的内容:    1 创建线程和结束线程:    2 读取和设置线程属性:    3 线程同步方式:POSIX信号量,互斥锁和条件变量 ...

  8. Linux 高性能服务器编程——I/O复用

    问题聚焦:     前篇提到了I/O处理单元的四种I/O模型.     本篇详细介绍实现这些I/O模型所用到的相关技术.     核心思想:I/O复用 使用情景: 客户端程序要同时处理多个socket ...

  9. Linux 高性能服务器编程——Linux服务器程序规范

    问题聚焦:     除了网络通信外,服务器程序通常还必须考虑许多其他细节问题,这些细节问题涉及面逛且零碎,而且基本上是模板式的,所以称之为服务器程序规范.     工欲善其事,必先利其器,这篇主要来探 ...

随机推荐

  1. springboot整合拦截器如何让其不拦截默认的访问路径

    1.注册自定义拦截器2.拦截器3.控制器4.其它说明:我想做控制拦截登陆,将所有的请求拦截下来判断如果当前的session里没有用户名则跳转到登陆页面.问题是目前可以拦截所有请求了,但第一次进入登陆页 ...

  2. 深入理解Java多线程——ThreadLocal

    目录 定义 API 场景分析 场景实验,观察Spring框架在多线程场景的执行情况 10000此请求,单线程 10000次请求,线程数加到100 对c的访问加锁 把c设为ThreadLocal 收集多 ...

  3. [刘阳Java]_TortoiseSVN基础应用_第1讲

    TortoiseSVN是一个免费的SVN客户端,非常好用.这里我们介绍一下TortoiseSVN基础应用. 下面的内容是转载博客园的某兄弟写的,个人觉得很不错.所以尊重转载的这篇文章,必须要给出这篇博 ...

  4. SLAM的数学基础(2):协方差和协方差矩阵

    之前我们知道,方差是一组数据的离散程度,它的公式为: 那么如果我们有几组数据,需要知道这几组数据的协同性呢? 举个例子,还是在小红,几次考试成绩如下: 入学考试:数学:80,语文:80 期中考试:数学 ...

  5. spring pom文件报错:提示no declaration can be found for element 'dubbo:service'.

    转自:http://blog.csdn.net/happylife_haha/article/details/52755425 pom文件报错:The matching wildcard is str ...

  6. 分享一个自己画div的技巧

    分享一个自己画div的技巧 笔者是小白,前端不是很懂.现在想总结下自己画div布局的小技巧和思路. 先对着设计图把div给好好框选出来 我个人觉得这一步是很重要的,要先分析大局,再细节处理.一定要先决 ...

  7. CreateWindow() -- 创建普通的窗口

    (1)函数原型 1 HWND CreateWindow( 2 LPCTSTR lpClassName, //pointer to register class name 3 LPCTSTR lpWin ...

  8. .net 5+ 知新:【2】 .Net Framework 、.Net 、 .NET Standard的概念与区别

    作为了解历史和眼睛或者过程,我们需要将 .Net Framwork ..Net. .Net Stander几个概念进行下理解. .net 代表跨平台框架,从.net 5开始就统一叫.net,废弃原来的 ...

  9. Spring Cloud Alibaba基础教程:Nacos+Dubbo

    Spring Cloud Alibaba为分布式应用程序开发提供了一站式解决方案. 它包含开发分布式应用程序所需的所有组件,使您可以轻松地使用Spring Cloud开发应用程序.Dubbo是Alib ...

  10. Webstorm 快速补全

    el-row>el-col*3>[:span='7'] 按Tab <el-row> <el-col> <div :span="">& ...