编写linux c socket 遇到的各种坑

最近在使用linux c 编写一个tcp 的客户端和服务端,期间遇到了各种坑,现在将其记录下来,以备日后爬坑。

1、第一个是关于局部变量,局部变量在c语言中的特性是:自动存储期/块作用域/无链接。

我在编写socket服务端的时候,使用了thread,在获得一个新链接的时候,创建一个线程来执行socket的发送和接受。

代码如下:


void SendAndRecv(int *sockFd)
{
    if (*sockFd == -1)
    {
        printf("send and recv error:%d\n", errno);
        return;
    }

    printf("send and recv *sockFd is:%d\n", *sockFd);

    char buf[BUF_SIZE];
    int recvOffset = 0; //buffer recv address
    printf("accept success\n");
    // getchar();
    int recvSize;
    while (1)
    {
        send(*sockFd, "HELLO SERVER", 13, 0);

        send(*sockFd, PACK_END, 1, 0);

        // printf("Offset is %d\n",recvOffset);

        if (recvOffset >= BUF_SIZE)//out off buf
        {
            recvOffset = 0;
            continue;
        }

        recvSize = recv(*sockFd, &buf[recvOffset], BUF_SIZE - recvOffset, 0);

        if (recvSize <= 0)
        {
            printf("recv size is %d,socket [%d] is end!\n", recvSize,*sockFd);
            break;
        }

        //change to for round,beacuse i did not konw when the pack end come in
        int i = recvOffset + recvSize - 1;
        for (; i >= recvOffset; i--)
        {
            if (buf[i] == PACK_END[0]) //find pack end
            {
                break;
            }
        }

        if (i == recvOffset - 1) //not find pack end
        {
            recvOffset += recvSize;
            continue;
        }

        // buf[i] = '\0';
        // printf("recv msg:%s\n", buf);

        //find package end
        if (i < recvOffset + recvSize - 1) //0xFF not the end
        {
            memcpy(buf, &buf[i + 1], recvOffset + recvSize - 1 - i);
            recvOffset = recvOffset + recvSize - 1 - i;
            // continue;
        }
        else
        {
            //0xFF is the end
            recvOffset = 0;
        }

        memset(&buf[recvOffset], 0, BUF_SIZE - recvOffset);
    }

    shutdown(*sockFd, SHUT_RDWR);
    close(*sockFd);
}

void Accept(int *sockFd)
{

    if (*sockFd == -1)
    {
        printf("listen error,*sockFd is -1\n");
        return;
    }

    printf("*sockFd is :%d\n", *sockFd);

    struct sockaddr_in clientAddr;

    int sinSize = sizeof(struct sockaddr_in);

    while (1)
    {

        int newFd = accept(*sockFd, (struct sockaddr *)&clientAddr, &sinSize);

        if (newFd == -1)
        {
            printf("accept error:%d\n", errno);
            return;
        }

        //print client message
        printf("client addr:%s,client port:%d\n",
               inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));

        //run send and recv
        int retThread;
        pthread_t threadSendAndRecv;

        if (threadNumber >= 5) //复用thread
            threadNumber = 0;

        retThread = pthread_create(&(threadRun[threadNumber++]), NULL, (void *)&SendAndRecv, (void *)&newFd);
        if (retThread == -1)
        {
            printf("Accept error:%d\n", errno);
            return;
        }
    }

    //run next accept
    // Accept(sockFd);
    //when I am running Accept method again,
    //why the stack avaiable do not miss?
    //beasuse the last Accept is not game over?
    //I think this is the right answer.
}

在监听到新的连接时,将使用pthread创建一个新的线程,来处理发送接收的任务,使用void*指针来传递newFd(socket的编码)。在运行的过程中发现,一个while循环结束之后,之前循环的newFd并没有被回收,这与c语言的局部变量删除机制有冲突。(冲突的地方在于,理论上,一个while循环结束之后,之前的newFd将被回收,SendAndRecv中的*sockFd将因为找不到地址而出错)

那么问题出在哪里呢?

猜测1:一个函数中所有定义的变量都会在函数结束的时候统一回收。

猜测2:使用pthread函数后,编译器会执行特殊处理,让newFd存在的时间加长。

下面是验证的代码:

#include <stdio.h>
#include <stdlib.h>

int main()
{

    int *p;
    for (int i = 0; i < 10; i++) //i的存在期应该是for循环内
    {
        p = &i;
        printf("addr:%d,num:%d\n", &i, *p);
    }

    printf("*p,addr:%d,number:%d\n", p, *p);

    return 0;
}

结果如下:


addr:-6940,num:0
addr:-6940,num:1
addr:-6940,num:2
addr:-6940,num:3
addr:-6940,num:4
addr:-6940,num:5
addr:-6940,num:6
addr:-6940,num:7
addr:-6940,num:8
addr:-6940,num:9
*p,addr:-6940,number:10

从上面的执行结果可以看出来,i并没有在for循环结束之后回收,会存在于函数main()的整个生存期。

所以猜测1可能是对的。

继续验证上面的猜测,代码如下:

#include <stdio.h>
#include <stdlib.h>

int *p;

void print()
{

    for (int i = 0; i < 10; i++) //i的存在期应该是for循环内
    {
        p = &i;
        printf("addr:%d,num:%d\n", &i, *p);
    }

    printf("*p,addr:%d,number:%d\n", p, *p);
}

int main()
{

    print();

    printf("*p,addr:%d,number:%d\n", p, *p);
    
    print();
    printf("*p,addr:%d,number:%d\n", p, *p);
    
    return 0;
}

运行结果如下:

addr:1708207980,num:0
addr:1708207980,num:1
addr:1708207980,num:2
addr:1708207980,num:3
addr:1708207980,num:4
addr:1708207980,num:5
addr:1708207980,num:6
addr:1708207980,num:7
addr:1708207980,num:8
addr:1708207980,num:9
*p,addr:1708207980,number:10
*p,addr:1708207980,number:10
addr:1708207980,num:0
addr:1708207980,num:1
addr:1708207980,num:2
addr:1708207980,num:3
addr:1708207980,num:4
addr:1708207980,num:5
addr:1708207980,num:6
addr:1708207980,num:7
addr:1708207980,num:8
addr:1708207980,num:9
*p,addr:1708207980,number:10
*p,addr:1708207980,number:10

从上图中看出来,运行的print()函数分配的i的地址是一致的。

猜测如下:编译器优化了代码,判断了整个代码的流程。也可能是c语言的机制,在程序运行的时候,分配好了程序的堆栈,第一个函数print()结束后,回收了i的内存空间,在第二次执行的时候,再度分配。

下面验证,究竟是那种方式!

代码如下:(其实就是增加一个变量)

void print()
{

    for (int i = 0; i < 10; i++) //i的存在期应该是for循环内
    {
        // p = &i;
        printf("addr:%d,num:%d\n", &i, i);
    }

    // printf("*p,addr:%d,number:%d\n", p, *p);
}

int main()
{

    print();

    // printf("*p,addr:%d,number:%d\n", p, *p);

    int c=2000;
    printf("c:addr:%d,num:%d",&c,c);
    
    print();
    // printf("*p,addr:%d,number:%d\n", p, *p);
    
    return 0;
}

结果如下:

addr:642349804,num:0
addr:642349804,num:1
addr:642349804,num:2
addr:642349804,num:3
addr:642349804,num:4
addr:642349804,num:5
addr:642349804,num:6
addr:642349804,num:7
addr:642349804,num:8
addr:642349804,num:9
c:addr:642349836,num:2000addr:642349804,num:0
addr:642349804,num:1
addr:642349804,num:2
addr:642349804,num:3
addr:642349804,num:4
addr:642349804,num:5
addr:642349804,num:6
addr:642349804,num:7
addr:642349804,num:8
addr:642349804,num:9

上面的代码去掉了全局指针变量*p,发现print中i的地址和main函数中c的地址一致,那说明函数执行完成后,会局部变量,下一次使用的时候继续分配。

下面是将*p添加上去,得到的结果。

#include <stdio.h>
#include <stdlib.h>

int *p;

void print()
{

    for (int i = 0; i < 10; i++) //i的存在期应该是for循环内
    {
        p = &i;
        printf("addr:%d,num:%d\n", &i, i);
    }

    printf("*p,addr:%d,number:%d\n", p, *p);
}

int main()
{

    print();

    // printf("*p,addr:%d,number:%d\n", p, *p);

    int c=2000;
    printf("c:addr:%d,num:%d",&c,c);
    
    print();
    // printf("*p,addr:%d,number:%d\n", p, *p);
    
    return 0;
}

结果:

addr:286303564,num:0
addr:286303564,num:1
addr:286303564,num:2
addr:286303564,num:3
addr:286303564,num:4
addr:286303564,num:5
addr:286303564,num:6
addr:286303564,num:7
addr:286303564,num:8
addr:286303564,num:9
*p,addr:286303564,number:10
c:addr:286303596,num:2000addr:286303564,num:0
addr:286303564,num:1
addr:286303564,num:2
addr:286303564,num:3
addr:286303564,num:4
addr:286303564,num:5
addr:286303564,num:6
addr:286303564,num:7
addr:286303564,num:8
addr:286303564,num:9
*p,addr:286303564,number:10

加上*p和去掉*p的执行结果一致!所以上面的猜测中第二种是正确的,即函数执行完成后,所有非静态局部变量都会被回收,与是否是指针无关系,并且在下一次需要分配内存的时候,继续从回收的位置处分配,并且内存中的数据不会被设置为0,和回收前的数据一致!!!

这便是变量需要初始化的原因。

所以socketServer.c代码中传递newFw的方案有问题,会导致同一时间只有一个socketclient 可以发送数据。

修改方案:将0xFF作为结尾的方案取消,以空字符'\0'作为结尾。将socket的分段和粘连同时考虑进去,便有了下面的代码:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>   //线程
#include <arpa/inet.h> //格式转换

#define PORT 8000
#define BACKLISTENER 10
#define BUF_SIZE 0x2000

#define MESSAGE "HELLO SERVER"

const char PACK_END[1] = {0xFF};

// pthread_t threadAccept;
//can not use const int BACKLISTENER,
//because in the complier time, c lanage can not know what the const variable is.

static pthread_t threadRun[BACKLISTENER]; //use in this file,can not use in other files
static int sockNum[BACKLISTENER];

static int indexNum = 0;

void SendAndRecv(int *sockFd)
{
    if (*sockFd == -1)
    {
        printf("send and recv error:%d\n", errno);
        return;
    }

    printf("send and recv *sockFd is:%d\n", *sockFd);

    char sendChars[14];
    memcpy(sendChars,MESSAGE,sizeof(MESSAGE));
    memcpy(&sendChars[13],PACK_END,1);

    char buf[BUF_SIZE];
    int recvOffset = 0; //buffer recv address

    int recvSize;
    // int sendSize;

    int packStart = 0;
    while (1)
    {
        send(*sockFd,sendChars,sizeof(sendChars),0);

        if (recvOffset >= BUF_SIZE) //out off buf
        {
            recvOffset = 0;
            continue;
        }

        recvSize = recv(*sockFd, &buf[recvOffset], BUF_SIZE - recvOffset, 0);

        if (recvSize <= 0)
        {
            printf("recv size is %d,socket [%d] is end!\n", recvSize, *sockFd);
            break;
        }

        //change to for round,beacuse i did not konw when the pack end come in

        for (int i = recvOffset; i < recvOffset + recvSize; i++)
        {
            if (buf[i] == PACK_END[0])
            {
                buf[i] = '\0';
                printf("start:%d,end:%d package:%s\n", packStart, i, &buf[packStart]);
                packStart = i + 1; //next package start
            }
            else if (buf[i] == '\0')
            {
                buf[i] = ' ';
            }
        }

        if (packStart == recvOffset + recvSize)
        {
            recvOffset = 0;
        }
        else
        {
            memcpy(buf, &buf[packStart], recvOffset + recvSize - packStart);
            recvOffset = recvOffset + recvSize - packStart;
        }

        packStart = 0;

        memset(&buf[recvOffset], 0, BUF_SIZE - recvOffset);
    }

    close(*sockFd);
}

void Accept(int *sockFd)
{

    if (*sockFd == -1)
    {
        printf("listen error,*sockFd is -1\n");
        return;
    }

    printf("*sockFd is :%d\n", *sockFd);

    struct sockaddr_in clientAddr;

    int sinSize = sizeof(struct sockaddr_in);

    while (1)
    {

        sockNum[indexNum] = accept(*sockFd, (struct sockaddr *)&clientAddr, &sinSize);

        if (sockNum[indexNum] == -1)
        {
            printf("accept error:%d\n", errno);
            return;
        }

        //print client message
        printf("client addr:%s,client port:%d\n",
               inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));

        //run send and recv
        int retThread;

        if (indexNum >= BACKLISTENER) //复用thread
            indexNum = 0;

        retThread = pthread_create(&(threadRun[indexNum]), NULL, (void *)&SendAndRecv, (void *)&sockNum[indexNum]);
        if (retThread == -1)
        {
            printf("Accept error:%d\n", errno);
            return;
        }

        indexNum++;
    }
}

int main()
{
    // printf("HELLO MY JOB");
    int sockFd, newFd; //SOCKET句柄,连接后句柄
    struct sockaddr_in serverAddr;
    struct sockaddr_in clientAddr;
    int sinSize;

    sockFd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockFd == -1)
    {
        printf("socket() failed:%d\n", errno);
        return -1;
    }

    serverAddr.sin_family = AF_INET;                //传输协议
    serverAddr.sin_port = htons(PORT);              //端口号
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); //监听所有IP
    memset(&(serverAddr.sin_zero), 0, sizeof(serverAddr.sin_zero));

    if (bind(sockFd, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr)) < 0)
    {
        printf("bind error:%d\n", errno);
        return -1;
    }

    listen(sockFd, BACKLISTENER);

    int retThread;        //返回值
    pthread_t threadBody; //thread对象

    retThread = pthread_create(&threadBody, NULL, (void *)&Accept, (void *)&sockFd); //创建一个thread
    if (retThread != 0)
    {
        printf("创建thread对象失败\n");
        return -1;
    }

    printf("Enter any key to exit!\n");
    getchar();

    // shutdown(sockFd, SHUT_RDWR);

    close(sockFd);

    printf("socket listen is end!\n");

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值