Socket编程实践(6) --TCPNotes服务器

僵尸进程过程

1)通过忽略SIGCHLD信号,避免僵尸进程

    在server端代码中加入

    signal(SIGCHLD, SIG_IGN);

 

2)通过wait/waitpid方法。解决僵尸进程

signal(SIGCHLD,onSignalCatch);

void onSignalCatch(int signalNumber)
{
    wait(NULL);
}

3) 假设多个客户端同一时候关闭, 问题描写叙述如以下两幅图所看到的:


/** client端实现的測试代码**/
int main()
{
    int sockfd[50];
    for (int i = 0; i < 50; ++i)
    {
        if ((sockfd[i] = socket(AF_INET, SOCK_STREAM, 0)) == -1)
            err_exit("socket error");

        struct sockaddr_in serverAddr;
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(8001);
        serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
        if (connect(sockfd[i], (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)
            err_exit("connect error");
    }
    sleep(20);
}

在客户执行过程中按下Ctrl+C,则能够看到在server端启动50个子进程,而且全部的客户端全部一起断开的情况下,产生的僵尸进程数是惊人的(此时也证明了SIGCHLD信号是不可靠的)!


解决方法-将server端信号捕捉函数改造例如以下:

void sigHandler(int signo)
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
        ;
}

waitpid返回值解释:

  on  success,  returns the process ID of the child whose state has changed(返回已经结束执行

的子进程的PID); if WNOHANG was specified and one or more child(ren) specified by pid exist, 

but have not yet changed state, then 0 is returned(假设此时尚有好多被pid參数标识的子进程存在

且没有结束的迹象, 返回0).  On error, -1 is returned.

 

地址查询API

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);	//获取本地addr结构
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);	//获取对方addr结构

int gethostname(char *name, size_t len);
int sethostname(const char *name, size_t len);

#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);

#include <sys/socket.h>       /* for AF_INET */
struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
struct hostent *gethostent(void);
//hostent结构体
struct hostent
{
    char  *h_name;            /* official name of host */
    char **h_aliases;         /* alias list */
    int    h_addrtype;        /* host address type */
    int    h_length;          /* length of address */
    char **h_addr_list;       /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
/**获取本机IP列表**/
int gethostip(char *ip)
{
    struct hostent *hp = gethostent();
    if (hp == NULL)
        return -1;

    strcpy(ip, inet_ntoa(*(struct in_addr*)hp->h_addr));
    return 0;
}

int main()
{
    char host[128] = {0};
    if (gethostname(host, sizeof(host)) == -1)
        err_exit("gethostname error");

    cout << "host-name: " << host << endl;
    struct hostent *hp = gethostbyname(host);
    if (hp == NULL)
        err_exit("gethostbyname error");

    cout << "ip list: " << endl;
    for (int i = 0; hp->h_addr_list[i] != NULL; ++i)
    {
        cout << '	'
             << inet_ntoa(*(struct in_addr*)hp->h_addr_list[i]) << endl;
    }

    char ip[33] = {0};
    gethostip(ip);
    cout << "local-ip: " << ip << endl;
}

TCP协议的11种状态 


1.例如以下图(客户端与服务器都在本机:两方(server的子进程,与client)链接已经建立(ESTABLISHED),等待通信)

 

2.最先close的一端,会进入TIME_WAIT状态; 而被动关闭的一端能够进入CLOSE_WAIT状态 (下图,server端首先关闭)

 

3.TIME_WAIT 时间是2MSL(报文的最长存活周期的2倍) 

  原因:(ACK y+1)假设发送失败能够重发, 因此假设server端不设置地址反复利用的话, 服务器在短时间内就无法重新启动;

    服务器端处于closed状态,不等于客户端也处于closed状态。

(下图, client先close, client出现TIME_WAIT状态)

 

4.TCP/IP协议的第1种状态:图上仅仅包括10种状态,另一种CLOSING状态

产生CLOSING状态的原因:

Server端与Client端同一时候关闭(同一时候调用close,此时两端同一时候给对端发送FIN包),将产生closing状态,最后两方都进入TIME_WAIT状态(例如以下图)。


SIGPIPE信号

往一个已经接收FIN的套接中写是同意的。接收到FIN仅仅代表对方不再发送数据;可是在收到RST段之后,假设还继续写,调用write就会产生SIGPIPE信号,对于这个信号的处理我们通常忽略就可以。

    signal(SIGPIPE, SIG_IGN); 

/** 測试: 在Client发送每条信息都发送两次
当Server端关闭之后Server端会发送一个FIN分节给Client端,
第一次消息发送之后, Server端会发送一个RST分节给Client端, 
第二次消息发送(调用write)时, 会产生SIGPIPE信号;
注意: Client端測试代码使用的是下节将要介绍的Socket库
**/
void sigHandler(int signo)
{
    if (SIGPIPE == signo)
    {
        cout << "receive SIGPIPE = " << SIGPIPE << endl;
        exit(EXIT_FAILURE);
    }
}
int main()
{
    signal(SIGPIPE, sigHandler);
    TCPClient client(8001, "127.0.0.1");
    try
    {
        std::string msg;
        while (getline(cin, msg))
        {
            client.send(msg);
            client.send(msg);   //第二次发送
            msg.clear();
            client.receive(msg);
            client.receive(msg);
            cout << msg << endl;
            msg.clear();
        }
    }
    catch (const SocketException &e)
    {
        cerr << e.what() << endl;
    }
}

close与shutdown的差别

#include <unistd.h>
int close(int fd);

#include <sys/socket.h>
int shutdown(int sockfd, int how);

shutdown的how參数

SHUT_RD

关闭读端

SHUT_WR

关闭写端

SHUT_RDWR

读写均关闭

1.close终止了数据传送的两个方向;

  而shutdown能够有选择的终止某个方向的数据传送或者终止数据传送的两个方向。

2.shutdown how=SHUT_WR(关闭写端)能够保证对等方接收到一个EOF字符(FIN段),而无论是否有其它进程已经打开了套接字(shutdown并没採用引用计数)。

  而close须要等待套接字引用计数减为0时才发送FIN段。也就是说直到全部的进程都关闭了该套接字。

 

演示样例分析:

   客户端向服务器依照顺序发送:FIN E D C B A, 假设FIN是当client尚未接收到ABCDE之前就调用close发送的, 那么client端将永远接收不到ABCDE了, 而通过shutdown函数, 则能够有选择的仅仅关闭client的发送端而不关闭接收端, 则client端还能够接收到ABCDE的信息;


/**測试: 实现与上面相似的代码(使用close/shutdown)两种方式实现 **/

完整源码请參照:

http://download.csdn.net/detail/hanqing280441589/8486517

注意: 最好读者须要有select的基础, 没有select基础的读者能够參考我的博客<Socket编程实践(8)>相关章节

版权声明:本文博客原创文章,博客,未经同意,不得转载。

原文地址:https://www.cnblogs.com/gcczhongduan/p/4733613.html