多线程socket练习中的问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Peter_tang6/article/details/82216053

以前写过多进程的socket通信,但是没有深究其中的一些细节问题,这次从多线程socket通信中学习到了很多东西,下面直接给出这次练习的程序代码:

//pthread_t  tid应该在循环里面,每次创建一个去执行线程
//close(fd)的问题,无法关闭fd,论shutdownclose的区别,shutdown禁用套接字,但是不减计数,close直接关闭套接字,描述符减1
//客户端一输入就会退出   //recv的buf大小设置问题导致
//结构体传参赋值问题
//recv: Connection reset by peer    //由于recv的buf大小设置问题导致
//打开多个客户端程序以后,前面的线程就会陆续失败,服务器接收不到数据了,原因是malloc分配的内存空间被覆盖了,一直用的都是新的fd

/*********************************************************************************
 *      Copyright:  (C) 2018
 *                  All rights reserved.
 *
 *       Filename:  server.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(08/29/2018)
 *         Author:  tangyanjun <519656780@qq.com>
 *      ChangeLog:  1, Release initial version on "08/29/2018 08:24:02 AM"
 *                 
 ********************************************************************************/
#include "../../common.h"

#define     PORT       5555
#define     BUFSIZE    100
#define     LISTEN     600

int         fd;


typedef struct confd {
    int   n_fd;
    char  n_addr[BUFSIZE];
} CONFD;



void *func(void *sockfd)
{
    pthread_detach(pthread_self());
    printf("%lu create success!\n", pthread_self());
    CONFD *C_fd;
    int sock;
    int   res;
    char  buf[BUFSIZE];

    #if 0
    //C_fd = (CONFD *)sockfd;

    printf("fd = %d, addr = %s\n", C_fd->n_fd, C_fd->n_addr);
    res = send(C_fd->n_fd, "welcome!", 9, 0);
    #else
    sock = *(int *)sockfd;
    res = send(sock, "welcome!", 9, 0);
    printf("fd = %d", sock);
    #endif
    if (res == -1)
    {
        error("send");
    }
    while(1)
    {
        memset(buf, '\0', BUFSIZE);
        res = recv(/*C_fd->n_fd*/sock, buf, BUFSIZE, 0);
        printf("recv:res=%d\n", res);
        if (res == 0)
        {
            //printf("%s leaved!\n", C_fd->n_addr);
            break;
        } 
        else if (res > 0)
        {
            //printf("%s say: %s\n", C_fd->n_addr, buf);
            printf("recv: %s\n", buf);
            printf("resvfrom client OK!\n");
        }
        else
        {
            perror("recv");
            break;
        }
    }
    printf("%lu exit! fd = %d\n", pthread_self(), sock);
    #if 0
    if (0 != close(sock) && 0 != close(*(int *)sockfd))
    {
        error("close");
    }
    else
    {
        printf("close OK!\n");
    }

    shutdown(sock, SHUT_RDWR);

    pthread_exit(NULL);
    #endif
    printf("terminating current client_connection...\n");
     close(sock);            //close a file descriptor.
     pthread_exit(NULL);   //terminate calling thread!
}

int main(int argc, char **argv)
{
    int         newfd;
    CONFD  *cfd;
    cfd = (CONFD*)malloc(sizeof(struct confd));
    struct sockaddr_in  addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    int reuse = 1;
    int res = 0;

    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        error("socket");
    }
    #if 1
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        error("setsockopt");
    }
    #endif
    if ((res = bind(fd, (struct sockaddr*)&addr, sizeof(addr))) == -1)
    {
        error("bind");
    }
    if (listen(fd, LISTEN) == -1)
    {
        error("listen");
    }
    printf("listen to waiting client.........\n");
    while(1)
    {
        pthread_t   pid;
        struct sockaddr_in from;
        socklen_t len = sizeof(from);
        if ((newfd = accept(fd, (struct sockaddr*)&from, &len)) == -1)
        {
            error("accept");
        }
        printf("%s connect!\n", inet_ntoa(from.sin_addr));

        cfd->n_fd = newfd;
        strcpy(cfd->n_addr, inet_ntoa(from.sin_addr));

        if (pthread_create(&pid, NULL, func, (void*)/*cfd*/&newfd) != 0)
        {
            error("pthread_create");
        }

    }

    close(fd);
    close(newfd);
    printf("Server shuts down\n");
    return 0;
}

其中遇到的问题在程序的最开头已经记录了,现在详细说明一下这些问题:
1. pthread_t tid应该在循环里面,每次创建一个去执行线程
2. close(fd)的问题,无法关闭fd,论shutdown和close的区别,shutdown禁用套接字,但是不减计数,close直接关闭套接字,描述符减1
3. 客户端一输入就会退出 //recv的buf大小设置问题导致
4. 打开多个客户端程序以后,前面的线程就会陆续失败,服务器接收不到数据了,原因是malloc分配的内存空间被覆盖了,一直用的都是新的fd
5. recv: Connection reset by peer //由于recv的buf大小设置问题导致
6. 结构体传参赋值问题

第一个问题,开始我在main函数中定义的线程变量,导致多线程起不来,这是个弱智问题…………………………….

第二个问题,我在程序中明明写了close(fd),而且打印返回都是成功的,但是当我关闭一个客户端以后,再连接一个新的客户端,新的描述符值却还是增加了1,这点让我很困惑,如果这样写,那么系统早晚会因为文件描述符不够而奔溃造成内存泄露,于是我想到了shutdown函数,这个函数用于禁用socket的相关连接,调用了它之后,在/proc/11423/fd中确实看不见关闭后的文件描述符了,其中11423是服务器的进程ID,但是调用下一个新的客户端连接的时候,其新的文件描述符依然是递增,这就让我很失望了,文件描述符一直释放不了呀,我猜想说不定等会就能用了,是不是还没有释放完。待我等待几分钟后,果然,描述符被释放了。接下来我查看当我关闭客户端以后的TCP连接状态:#netstat -an | grep 5555 –color,5555是我的端口号,结果如下:

tcp        0      0 0.0.0.0:5555                0.0.0.0:*                   LISTEN      
tcp        0      0 138.0.89.80:57726           138.0.89.80:5555            TIME_WAIT   

TIME_WAIT,不由得让我复习一下TCP连接与断开的情形了。

三次握手和四次挥手应该都会了,其中的半关闭状态和shutdown函数有关,这个函数可以选择关闭任意一个读端或者写端,也可全部关闭,因为TCP是全双工工作的,下面介绍下TCP传输过程中的状态。

这里写图片描述

这是一张不太清楚的状态转换图。

实线:主动方
虚线:被动方

实线:
CLOSED—–SYN_SENT—–ESTABLISHED :三次握手连接完成
ESTABLISHED———FIN_WAIT_1:主动方发送出FIN,等待对方应答
FIN_WAIT_1———-FIN_WAIT_2:收到对方的ACK以后的状态,主动发送方进入半关闭状态
FIN_WAIT_2———-TIME_WAIT:收到对方的FIN并且回发ACK应答之后。
TIME_WAIT(2MSL超时,大概1分钟):等待的目的是因为我不确定对方是否能收到我发送的ACK,等这么久是确保最后发送的ACK被对方收到
TIME_WAI———-CLOSING:关闭连接,等待1分钟之后的状态

虚线:
CLOSED——-LISTEN——SYN_RCVD——ESTABLISHED:三次握手连接完成
ESTABLISHED———CLOSE_WAIT:接收到FIN,并且回了ACK
CLOSE_WAIT——LAST_ACK——CLOSED:发送FIN,收到ACK

重点说一说TIMEWAIT,即2MSL等待状态,MSL是TCP的报文段最大生存时间,TCP主动关闭,发回最后一个ACK,TIME_WAIT状态停留时间必须为2MSL,TCP再次发送最后ACK防止丢失,还记得如果我们不把套接字选项设置成SO_REUSEADDR,那么我们在这个2MSL期间用不了服务器和客户端的IP地址和端口号,这也属于这个问题。因此对于close要等待一定时间才能关闭socket,这里也就解释清楚了。也说明本人对TCP/IP的学习还得加油呀。

第三个问题,由于我在recv中设定的接受字节使用的是strlen(buf),导致接收不到数据,因为strlen(buf)的值为0,然后当recv返回0的时候,对方以为你已经退出了,因此关闭连接。这也是个弱智问题………….

第四个问题,由于我想打印新连接的客户端的ip,因此给线程处理函数传送了一个结构体参数,但是我在main中给这个结构体分配一块内存,那么其实每次新连接描述符都会使用这一块内存,导致我以前的客户端描述符的输出被覆盖了,每次有用的描述符都是我新创建的客户端的,所以造成每次连接新的客户端以后,服务器就收不到旧的客户端发送的消息了,这个细节问题导致了bug的出现。

第五个问题,同第三个问题一样,对方认为你已经关闭退出了。

最后一个问题,是在给结构体赋值的时候,开始strcpy一个字符串给一个指针参数,导致程序段错误,后来改为数组就OK了,这也是个弱智问题……..

本次多线程socket花了一整天时间完善,可能还有其他缺陷,希望有人能提出来。

猜你喜欢

转载自blog.csdn.net/Peter_tang6/article/details/82216053