TCP套接字编程模型

来源:互联网 发布:java字符串转日期格式 编辑:程序博客网 时间:2024/05/04 18:27

        TCP套接字编程经常使用在客户/服务器编程模型(简称C/S模型)中,C/S模型根据复杂度分为简单的客户/服务器模型和复杂的客户/服务器模型。C/S简单客户/服务器模型是一对一关系,一个服务器端某一时间段内只对应处理一个客户端的请求,迭代服务器模型属于此模型。C/S复杂服务器模型是一对多关系,一个服务器端某一时间段内对应处理多个客户端的请求,并发服务器模型属于此模型。迭代服务器模型和并发服务器模型是socket编程中最常见使用的两种编程模型,图18-5画出两种模型服务端的处理流程。

      

18-5 迭代服务器&并发服务器图

1.   TCP套接字编程模型图

  图18-6是TCP套接字编程模型图,画出了客户端与服务端的编程模型和流程。此模型不仅适合迭代服务器,也适合并发服务器,两者实现流程类似,只不过并发服务器接收客户请求(accept)后会fork子进程,由子进程处理客户端的请求。

图18-6  TCP套接字编程模型图

2.  TCP编程流程说明

(1) 服务器端编程流程

TCP服务器端编程流程如下:

①  创建套接字;

②  绑定套接字;

③  设置套接字为监听模式,进入被动接受连接状态;

④  接受请求,建立连接;

⑤  读写数据;

⑥  终止连接。

(2) TCP客户端编程流程

TCP客户端编程流程如下:

①  创建套接字;

②  与远程服务器建立连接;

③  读写数据;

④  终止连接。

(3) TCP服务器三种异常情况

TCP服务器有三种异常情况,分别为服务器主机崩溃、服务器主机崩溃后重启、服务器主机关机。

在服务器主机崩溃的情况下,已有的网络连接上发不出任何东西。此时应用程序发出数据后,会阻塞于套接字的读取回应。由于服务器主机崩溃,此时客户TCP会持续重传数据分节,试图从服务器接收一个ACK,重传12次(源自Berkeley的实现)后,客户TCP最终选择放弃,返回给应用经常一个ETIMEDOUT错误;或者是因为中间路由器判定服务器主机不可达,则返回一个目的地不可达的ICMP消息响应,其错误代码为EHOSTUNREACH或ENETUNREACH。另外说明的是,通过设置套接字选项可以更改TCP持续重传等待的超时时间。

在服务器主机崩溃后重启的情况下,如果客户在主机崩溃重启前不主动发送数据,那么客户是不会知道服务器已崩溃。在服务器重启后,客户向服务器发送一个数据分节;由于服务器重启后丢失了以前的连接信息(尽管在服务端口上有进程监听,但连接套接字所在的端口无进程等待),因此导致服务器主机的TCP响应RST;当客户TCP收到RST,向客户返回错误ECONNRESET。如果客户对服务器的崩溃情况很关心,即使客户不主动发送数据也这样,这需要进行相关设置(如设置套接口选项SO_KEEPALIVE或某些客户/服务器心跳函数)。

当服务器主机关机的情况下,由于init进程给所有运行的进程发信号SIGTERM,这时服务器程序可以捕获该信号,并在信号处理程序中正常关闭网络连接。如果服务器程序忽略了SIGTERM信号,则init进程会等待一段固定的时间(通常是5s~20s),然后给所有还在运行的程序发信号SIGKILL。服务器将由信号SIGKILL终止,其终止时,所有打开的描述字被关闭,这导致向客户发送FIN分节,客户收到FIN分节后,能推断出服务器将终止服务。

3. 读写函数的封装

(1)网络数据读写说明

在网络程序中,向套接字文件描述符写时有以下两种可能:

①  write的返回值大于0,表示写了部分或者是全部的数据。

②  返回的值小于0,此时写出现了错误,需要根据错误类型来处理。如果错误号为EINTR,则为中断引起,可以忽略进行继续写操作;如果是其他错误号,则表示网络连接出现了问题(可能对方关闭了连接),则需报错退出。

像向套接字文件描述符写数据一样,读也有两种可能:

①    read的返回值大于0,表示读了部分或者是全部的数据。

②  返回的值小于0,此时读出现了错误,需要根据错误类型来处理。如果错误号为EINTR,则为中断引起,可以忽略进行继续读操作;如果是其他错误号,则表示网络连接出现了问题,则需报错退出。

 

(2)读写函数的封装

为了读写函数更加健壮,更加易用,需要对读写函数进行封装。tcpio.c源代码中readn函数对read函数进行了封装,writen函数对write进行了封装。

tcpio.c源代码如下:

#include <stdlib.h>

#include <stdio.h>

#include <errno.h>

#include <unistd.h>

int readn(int fd,void *buffer,int length)

{

    int bytes_left;

    int bytes_read;

    char *ptr;

    bytes_left=length;

    ptr=buffer ;

    while(bytes_left>0)

    {

        bytes_read=read(fd,ptr,bytes_left);

        if(bytes_read<0)

        {

            if(errno==EINTR)

                bytes_read=0;

            else

                return(-1);

        }

        else if(bytes_read==0)

            break;

        bytes_left-=bytes_read;

        ptr+=bytes_read;

    }

    return(length-bytes_left);

}

int writen(int fd,void *buffer,int length)

{

    int bytes_left;

    int written_bytes;

    char *ptr;

    ptr=buffer;

    bytes_left=length;

    while(bytes_left>0)

    {

        written_bytes=write(fd,ptr,bytes_left);

        if(written_bytes<0)

        {

            if(errno==EINTR) /* 错误由中断引起,可以继续写*/

                written_bytes=0;

            else /*其他错误,报错退出*/

                return(-1);

        }

        bytes_left-=written_bytes;

        ptr+=written_bytes; 

    }

    return(0);

}

 

摘录自《深入浅出Linux工具与编程》

原创粉丝点击