• linux网络编程之posix线程(一)


    今天继续学习posix IPC相关的东东,消息队列和共享内存已经学习过,接下来学习线程相关的知识,下面开始:

     

    【注意】:创建失败这时会返回错误码,而通常函数创建失败都会返回-1,然后错误码会保存在errno当中。

    下面用代码来实践一下:

    在处理线程创建失败检查时,下面来看一下检查错误的一些说明:

    所以下面来处理一下线程创建失败的错误:

    这是什么意思呢?

    而且每个线程都有自己的一个errono,避免多线程时有冲突。

    接下来做这样的一个操作,就是主线程打印A字符,然后创建的线程打印B字符,

    【注意】:新创建的线程不叫做子线程,因为并没有父子关系,但是可以把初始的线程叫主线程,如下:

    然后编写实验代码:

    编译运行:

    这是由于主线程已经结束了,而新创建的线程还没有被调度到,所以就没有打印出B,所以解决此问题的办法可能让主线程小睡一会:

    再次编译运行:

    可见新创建的线程被调度到了,实际上主线程跟新创建的线程是交替运行,下面修改下程序来说明下:

    再看下效果:

    从中可以发现每次运行的结果都不一样,这个取决于系统是如何调度线程的。

    另外有这样的一个问题,就是可能新创建的线程还没有执行完毕,主线程就已经执行完毕了,也就是主线程需要睡眠去等待新线程执行完,下面多次运行一下,看能否看到这种现象:

    其中在主线程中睡眠是一种解决方案,但是比较笨,有没有一个函数能够等待新创建的线程结束呢?实际上是有的,就好像进程一样,有waitpid来等待子进程的退出:

    下面来修改下代码:

    编译运行:

    确实是达到了等待新创建线程退出的目的,下面再来学习一个函数:

    下面来实践下:

    下面编译运行一下:

    当然线程的退出也可以是执行完了再退出,如下:

    编译运行:

    其中线程结束包含两种情况:

    ①、自杀:调用pthread_exit();在线程入口函数中调用return。

    ②、他杀:调用pthread_cancel()。

    如果在新创建的线程中调用此方法,如果主线程没有调用pthread_join的情况下,也能避免僵线程。

    下面用线程的方式来改造一下之前用进程的方式实现的回射客户/服务器程序,来进一步熟悉线程的使用:

    客户端echocli.c:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    
    void echo_cli(int sock)
    {
        char sendbuf[1024] = {0};
            char recvbuf[1024] ={0};
            while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
            {
                    write(sock, sendbuf, strlen(sendbuf));
                    read(sock, recvbuf, sizeof(recvbuf));
    
                    fputs(recvbuf, stdout);
                    memset(sendbuf, 0, sizeof(sendbuf));
                    memset(recvbuf, 0, sizeof(recvbuf));
            }
    
            close(sock);
    }
    
    int main(void)
    {
        int sock;
        if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
            ERR_EXIT("socket");
    
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
            ERR_EXIT("connect");
    
        echo_cli(sock);
    
        return 0;
    }

    这里的代码不需要改变,主要是修改服务端,关于socket编程可以复习一下之学习的,这里就不一一解释了,还是之前编写的。

    服务端echosrv.c:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <pthread.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    void echo_srv(int conn)
    {
        char recvbuf[1024];
            while (1)
            {
                    memset(recvbuf, 0, sizeof(recvbuf));
                    int ret = read(conn, recvbuf, sizeof(recvbuf));
            if (ret == 0)
            {
                printf("client close
    ");
                break;
            }
            else if (ret == -1)
                ERR_EXIT("read");
                    fputs(recvbuf, stdout);
                    write(conn, recvbuf, ret);
            }
    }int main(void)
    {
        int listenfd;
        if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
            ERR_EXIT("socket");
    
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
        int on = 1;
        if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
            ERR_EXIT("setsockopt");
    
        if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
            ERR_EXIT("bind");
        if (listen(listenfd, SOMAXCONN) < 0)
            ERR_EXIT("listen");
    
        struct sockaddr_in peeraddr;
        socklen_t peerlen = sizeof(peeraddr);
        int conn;
    
        while (1)
        {
            if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
                ERR_EXIT("accept");
    
            printf("ip=%s port=%d
    ", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
    
            pid = fork();
            if (pid == -1)
                ERR_EXIT("fork");
    
            if (pid == 0)
            {//子进程
                close(listenfd);//不需要处理监听
                echo_srv(conn);
                exit(EXIT_SUCCESS);
            } else {
                close(conn);//父进程不需要处理连接    
            }
        }
        
        return 0;
    }

    接下来要来进行服务端改造:

    【注意】:由于是单进程,所以就没必要像创建进程的方式要关闭conn了,编程也简单了许多。

    好了,下面编译运行一下:

    从中可以发现程序正常运转,而且当客户端退出时,相应的线程也退出了,这是为什么呢?

    另外当线程退出了之后,其实该线程是属于一个僵线程的状态,因为在主线程中并没有调用pthread_join()来等待新创建线程的退出,所以得避免僵线程的出现,修改代码如下:

    关于这个程序还有一个细节需要探讨一下,如下:

    那我可以这样写么?

    此时线程入口函数也得发生改变:

    编译运行看是否正常:

    看似一切正常,当然肯定是有问题的,不然也不会换一种写法来进行说明了,有什么潜在风险呢?

    这就是典型的Race Condition(也叫做资源竞争)问题。所以说conn只能值传递,而不能是传递指针,还是将代码还原,下面来讨论另外一个细节问题:

    那如何解决呢?可以采用动态申请内存的方式:

    再来编译运行:

    从中可以发现,一切正常,这两个细节问题需要注意一下,好了,今天先学到这,下次继续~最后附上一张进程跟线程之间的对比图,画得比较草,可以对比着记忆:

  • 相关阅读:
    Mysql(7) _常用函数
    Mysql(6)_ 帮助的使用
    Mysql(5)_ 基本数据类型-时间
    Mysql(4)_整型和浮点型
    1 HTTP请求头Header及其作用详解
    Java(35) _JDBC批量插入数据二
    Java(34)_ 用JDBC批量向数据库插入语句
    Java(33)_ JDBC指针移动
    Mysql(3)_ Mycat简介
    Mysql(2)_ binlog文件
  • 原文地址:https://www.cnblogs.com/webor2006/p/4235153.html
Copyright © 2020-2023  润新知