《TCP/IP网络编程》二、构建网络服务

来源:互联网 发布:中金所杯 知乎 编辑:程序博客网 时间:2024/04/29 11:26

  • 多进程服务器端
      • 并发服务器端实现模型和方法
      • fork函数创建进程
      • 僵尸进程
      • wait和waitpid函数
      • 信号处理
      • sigaction函数进行信号处理
      • 基于多任务的并发服务器
      • 客户端分割TCP的IO程序
  • 进程间通信
      • 管道PIPE
  • IO复用
      • select函数
      • 设置文件描述符
      • 设置检查监控范围及超时
      • 调用select函数后查看结果
      • 应用
  • 多种IO函数
      • Linux中的sendrecv
      • readvwritev函数
  • 多播与广播
      • 多播
      • 路由和TTL
      • 广播

10.多进程服务器端

1.并发服务器端实现模型和方法:

多进程服务器:通过创建多个进程提供服务
多路复用服务器:通过捆绑并统一管理I/O对象提供服务
多线程服务器:通过生成与客户端等量的线程提供服务

2.fork函数创建进程

父进程:fork函数返回子进程ID
子进程:fork函数返回0
父子进程拥有完全独立的内存结构,只是共享同一代码而已。

3.僵尸进程

一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。

子进程应该向其父进程传递子进程的exit参数值或return语句的返回值,如果父进程未主动要求获取子进程的结束状态值,操作系统将一直保存,其进程号也会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。

4.wait和waitpid函数

调用wait函数时,如果没有已终止的子进程,程序将阻塞直到有子进程终止
调用waitpid函数时,程序不会阻塞

5.信号处理

子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。
信号注册函数:

#include<signal.h>void (*signal(int signo, void (*func)(int)))(int);//返回类型:参数为int,返回void函数指针

SIGALRM:已到通过调用alarm函数注册的时间
SIGINT:输入CTRL+C
SIGCHLD:子进程终止

6.sigaction函数进行信号处理

signal函数在UNIX系列的不同操作系统中可能存在区别,但sigaction函数完全相同。

#include<signal.h>int sigaction(int signo, const struct sigaction * act, struct sigaction * oldact;

sigaction结构体:

struct sigaction{    void (*sa_handler)(int);//保存信号处理函数的指针值    sigset_t sa_mask;//初始化为0    int sa_flags;//初始化为0}

7.基于多任务的并发服务器

通过fork函数只复制了文件描述符,并未复制套接字,复制后的两个文件描述符指向同一套接字。

只有这两个文件描述符都终止(销毁)后才能销毁套接字,所以要在父子进程中分别销毁自己不处理的文件描述符(父进程中close客户端socket,子进程close服务端socket)

8.客户端分割TCP的I/O程序

客户端父进程负责接收数据,额外创建子进程负责发送数据,实现简单,收发独立,可以提高频繁交换数据的程序性能
这里写图片描述

11.进程间通信

1.管道PIPE

#include <unistd.h>int pipe(int filedes[2]);

filedes[0] 通过管道接收数据时使用的文件描述符,即管道出口
filedes[1] 通过管道传输数据时使用的文件描述符,即管道入口

使用时,先调用pipe创建管道,再调用fork,子进程中将同时拥有前面创建的两个文件描述符,注意复制的并非管道,而是用于管道I/O的文件描述符。

数据进入管道后成为无主数据,先读的进程会把数据取走

通过管道进行进程间双向通信
这里写图片描述

12.I/O复用

1.select函数

#include <sys/select.h>    int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

maxfd:监视对象文件描述符数量
readfds:将所有关注“是否存在带读取数据”的文件描述符注册到fd_set型变量,并传递其地址值
writefds:将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值
exceptset:将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值
timeout:调用select函数后,为防止陷入无限阻塞状态,传递超时信息
返回值:超时为0,错误为-1;因发生关注的事件返回时,返回大于0的值,该值时发生事件的文件描述符数
这里写图片描述

2.设置文件描述符

存有0和1的位数组集中多个需要监视的文件描述符
这里写图片描述

3.设置检查(监控)范围及超时

监视范围与第一个参数maxfd有关,实际上为监控对象文件描述符的数量,由于每次新建文件描述符时,其值都会增1,故只需传递最大的文件描述符+1

超时与最后一个参数有关,如果不想设置超时,传递NULL

struct timeval{           long tv_sec;   /* 秒 */    long tv_usec;  /* 微秒 */   }

4.调用select函数后查看结果

fd_set变量中值仍为1的位置上的文件描述符发生了变化

5.应用

调用select函数后的除发生变化的文件描述符对应位外,剩下的所有位将初始化为0,因此需要将准备好的fd_set变量备份一份,这是通用方法。

调用select后,结构体timeval的成员tv_sec和tv_usec的值将被替换为超时前剩余时间,因此每次调用select前,都需要初始化timeval结构体变量。

13.多种I/O函数

1.Linux中的send&recv

前文为了强调Linux环境下文件I/O和套接字I/O相同,只用了read和write函数

#include <sys/socket.h>ssize_t send(int sockfd, const void * buf, size_t nbytes, int flags);ssize_t recv(int sockfd, void * buf, size_t nbytes, int flags);

最后一个参数是收发数据时的可选项,不同操作系统对下述可选项的支持也不同。

可选项 含义 send recv MSG_OOB 传输紧急消息(Out-of-band data) · · MSG_PEEK 验证输入缓冲中是否存在接收的数据 · MSG_DONTROUTE 数据传输不参照路由表,在本地网络中寻找目的地 · MSG_DONTWAIT 非阻塞I/O · · MSG_WAITALL 防止函数返回,直到接收全部请求的字节数 ·

2.readv&writev函数

对数据进行整合传输以及发送。通过writev函数可以将分散保存在多个缓冲中的数据一并发送,通过readv函数可以由多个缓冲分别接收。适当使用这两个函数可以减少I/O函数的调用次数。

14.多播与广播

1.多播

多播服务器端针对特定多播组,只需发送1次数据,该组内的所有所有客服端都能接收数据。
多播组数可在IP地址范围内任意增加。
加入特定组即可接收发往该多播组的数据

多播组是D类IP地址(224.0.0.0~239.255.255.255),多播是基于UDP完成的,向网络传递一个多播数据包时,路由器将复制该数据包并传递到多个主机。

2.路由和TTL

TTL即生存时间(Time to Live):指服务端发送的数据包最远能传递的距离,用整数表示,并且每经过1个路由器就减1,当为0时,该数据包无法再被传递,只能销毁。因此,这个值设置过大将影响网络流量;当然,设置过小也会无法传递到目标。

3.广播

广播在功能上和多播是一样的,都是同时可以向大量客户传递数据。但他们在网络范围上有区别,多播可以跨越不同的网络,只要加入了多播组就能接收数据。但广播只能向同一网络中的主机传输数据。

直接广播sender的IP地址只需指定网络地址,主机地址全部填255。这样处在这个网络地址里的所有主机就可以接收数据了。

本地广播sender的IP地址写255.255.255.255,这样本地网络所有主机就可以接收数据了。