Linux&C++:网络编程(四)多线程

前言:学习编程一定要敲,接着测试,然后查资料,最后总结!!!

一、线程的概念

在同一个进程中,可以运行多个线程,运行于同一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享全局变量和对象,启动一个线程所消耗的资源比启动一个进程所消耗的资源要少。
和多进程相比,多线程是一种比较节省资源的多任务操作方式。启动一个新的进程必须分配给它独立的地址空间,每个进程都有自己的堆栈段和数据段,系统开销比较高,进行数据的传递只能通过进行间通信的方式进行。

二、多线程基础知识

在这里插入图片描述
注意用命令查看线程时,进程ID都是一个,但开启了N个线程相应的会有N个相同的进程ID。

二、多线程Demo示例

//MutiThreadingDemo.cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

//线程函数中不能使用exit(),如果用了会让整个进程退出
void *mainfunc1(void *arg);
void *mainfunc2(void *arg);

int variable = 0;//这个全局变量来验证线程的数据是共享的。
//一个进程间的多个线程,子进程可以运行完exit。但线程不行,一旦子线程exit,整个进程就退掉了,但可以使用线程退出函数(pthread_exit())来退出。
//线程间的数据空间是共享的,也就是进程的数据空间
//编译此程序方法:gcc MutiThreadingDemo.cpp -o  MutiThreadingDemo -lpthread
int main()
{
    pthread_t pthID1, pthID2; //pthread_t:long unsigned

    //创建一个子线程
    if (pthread_create(&pthID1, NULL, mainfunc1, NULL) != 0)
    {
        printf("Create thread failed.\n");
        return -1;
    }
    //创建一个子线程
    if (pthread_create(&pthID2, NULL, mainfunc2, NULL) != 0)
    {
        printf("Create thread failed.\n");
        return -1;
    }

    printf("threadID=%lu,threadID=%lu\n", pthID1, pthID2); //%lu表示输出无符号长整型整数 (long unsigned)

    printf("Waiting ChildThread out!\n");

    pthread_join(pthID2, NULL);
    printf("ChildThread2 out.\n");

    pthread_join(pthID1, NULL);
    printf("ChildThread1 out.\n");

    return 1;
}

void *mainfunc1(void *arg)//void * 必须有返回值才行。只有纯void才不写return
{
    for (int i = 0; i < 5; i++)
    {
        variable++;
        sleep(1);
        printf("ChildThread1 sleep 1s (%d)\n", variable);
    }
    //  return (void*)0;//不写这一步会报警告:no return statement in function returning non-void [-Wreturn-type]
    pthread_exit(0);
}

void *mainfunc2(void *arg)
{
    for (int i = 0; i < 5; i++)
    {
        variable++;
        sleep(1);
        printf("ChildThread2 sleep 1s (%d)\n", variable);
    }

    // exit(0);//执行到这儿会让进程退掉,故而程序直接全部退掉

    // return (void*)0;
     pthread_exit(0);
}

编译查看结果:

#涉及到pthread时需要连接静态库,-lpthread
g++ MutiThreadingDemo.cpp -o MutiThreadingDemo -lpthread
./MutiThreadingDemo

二、多线程涉及函数

1.线程创建函数pthread_create
函数声明:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

第一个参数 thread为为指向线程标识符的地址。
第二个参数 attr用于设置线程属性,一般为空,表示使用默认属性。
第三个参数 start_routine是线程运行函数的地址,填函数名就可以了。
第四个参数 arg是线程运行函数的参数。新创建的线程从start_routine函数的地址开始运行,该函数只有一个无类型指针参数arg。若要想向start_routine传递多个参数,可以将多个参数放在一个结构体中,然后把结构体的地址作为arg参数传入,但是要非常慎重,程序员一般不会这么做。

2.线程退出函数pthread_exit

函数声明:

void pthread_exit(void *retval);

参数retval填空,即0。

三、线程的终止

如果进程中的任一线程调用了exit,则整个进程会终止,所以,在线程的start_routine函数中,不能采用exit。
线程的终止有三种方式:
1)线程的start_routine函数代码结束,自然消亡。
2)线程的start_routine函数调用pthread_exit结束。
3)被主进程或其它线程中止。

四、pthread_create线程参数的传递

高并发的网络服务端的程序就需要把socket当做参数传递进去。
在这里插入图片描述
示例代码:
注意:使用线程参数时要注意转换问题

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

//线程中共享一个进程中的数据段
//GDB调试命令(加了-g): gcc -g MutiThreadParameter.cpp -o  MutiThreadParameter -lpthread
// gdb MutiThreadParameter
void * threadfunc(void *arg);
void * threadfunc2(void *arg);
void * threadfunc3(void *arg);
void * threadfunc4(void *arg);

long variable=1;

int main()
{
    int xx=10;
    void *ptr=(void*)(long)xx;
    int yy=(int)(long)ptr;
    pthread_t pID,pID2,pID3,pID4;

    if (pthread_create(&pID,NULL,threadfunc,NULL))
    {
        printf("create thread failed\n");
        return -1;
    }
    variable++;//利用传参的方式,可以将全局变量的值带入到每个线程中
    if (pthread_create(&pID2,NULL,threadfunc2,(void*)variable))//强转换要特别注意,要注意64位linux指针是8字节
    {
        printf("create thread failed\n");
        return -1;
    }
    variable++;
    if (pthread_create(&pID2,NULL,threadfunc3,(void*)variable))//强转换要特别注意,要注意64位linux指针是8字节
    {
        printf("create thread failed\n");
        return -1;
    }
    variable++;
    if (pthread_create(&pID2,NULL,threadfunc4,(void*)variable))//强转换要特别注意,要注意64位linux指针是8字节
    {
        printf("create thread failed\n");
        return -1;
    }

    pthread_join(pID,NULL);
    pthread_join(pID2,NULL);
    pthread_join(pID3,NULL);
    pthread_join(pID4,NULL);
    return 0;
}

void * threadfunc(void *arg)
{
    for (int i = 0; i < 5; i++)
    {
        sleep(1);
        printf("ChildThread sleep 1s (%ld) \n",variable);
    }

    // return (void*)0;
    pthread_exit(0);
}

void * threadfunc2(void *arg)
{
    printf("this thread is num%ld\n",(long)arg);
    for (int i = 0; i < 5; i++)
    {
        sleep(1);
        printf("ChildThread 2 sleep 1s (%ld) \n",(long)arg);
    }

    // return (void*)0;
    pthread_exit(0);
}

void * threadfunc3(void *arg)
{
    printf("this thread is num%ld\n",(long)arg);
    for (int i = 0; i < 5; i++)
    {
        sleep(1);
        printf("ChildThread 3 sleep 1s (%ld) \n",(long)arg);
    }

    // return (void*)0;
    pthread_exit(0);
}

void * threadfunc4(void *arg)
{
    printf("this thread is num%ld\n",(long)arg);
    for (int i = 0; i < 5; i++)
    {
        sleep(1);
        printf("ChildThread 4 sleep 1s (%ld) \n",(long)arg);
    }

    // return (void*)0;
    pthread_exit(0);
}

四、线程资源的回收

在这里插入图片描述
线程有joinable和unjoinable两种状态:

1. joinable状态
如果线程是joinable状态,当线程主函数终止时(自己退出或调用pthread_exit退出)不会释放线程所占用内存资源和其它资源,这种线程被称为“僵尸线程”。创建线程时默认是非分离的,或者称为可连接的(joinable)。
**注意:**joinable的线程才能在主函数中使用pthread_join函数,并有正确的返回值0,报错时函数返回值不为0。
线程资源的回收方法:
创建线程后,在创建线程的程序中调用pthread_join等待线程退出,一般不会采用这种方法,因为pthread_join会发生阻塞。

  pthread_join(pthid,NULL);

2. unjoinable状态
如果线程是unjoinable状态俺么线程退出时,系统自动回收线程资源。

设置线程为unjoinable状态的方法:

  • 创建线程前,调用pthread_attr_setdetachstate将线程设为detached
 pthread_attr_t attr;
 pthread_attr_init(&attr);
 pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);  // 设置线程的属性。
 pthread_create(&pthid,&attr,pth_main,(void*)((long)TcpServer.m_clientfd);
  • 主线程中调用pthread_detach
pthread_detach(pthid);
  • 线程主函数中调用pthread_detach
pthread_detach(pthread_self());

示例代码:

//MutiThreadRecycling.cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

//线程回收参考链接:http://freecplus.net/c893ece166b94df4b2e185fa6f1c920c.html
//https://www.bilibili.com/video/BV1zf4y1Q7Nj?p=4
//正常写代码设置了对线程设置了分离状态就不会再设置pthread_join。

//线程中共享一个进程中的数据段
//GDB调试命令(加了-g): gcc -g MutiThreadParameter.cpp -o  MutiThreadParameter -lpthread
// gdb MutiThreadParameter
void *threadfunc(void *arg);
void *threadfunc2(void *arg);


int main()
{
    //方法2
    // pthread_attr_t attr;
    // pthread_attr_init(&attr);
    // pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置线程的属性。//引用这部分代码后,主线程退出后,子进程会交给系统托管后立即退出并释放资源

    pthread_t pID, pID2;

    // if (pthread_create(&pID, &attr, threadfunc, NULL)) //第二个参数是用来线程回收的
    if (pthread_create(&pID, NULL, threadfunc, NULL)) //第二个参数是用来线程回收的
    {
        printf("create thread failed\n");
        return -1;
    }
     pthread_detach(pID);//方法3 和方法2功能一样  参考链接http://freecplus.net/c893ece166b94df4b2e185fa6f1c920c.html


    // if (pthread_create(&pID2, NULL, threadfunc2, (void *)variable)) //强转换要特别注意,要注意64位linux指针是8字节
    // {
    //     printf("create thread failed\n");
    //     return -1;
    // }

    int res = pthread_join(pID, NULL); //上面创建线程时使用了第二个参数设置线程回收年的状态,那这一步就join不到了
    //int res2 = pthread_join(pID2, NULL);

    printf("thread out res= %d\n", res);//设置了分离状态会打印出22
    //printf("thread2 out res= %d", res2);//正常打印出0
    // sleep(5);
    return 0;
}

void *threadfunc(void *arg)
{
    //方法4 在子线程中设置状态修改
     pthread_detach(pthread_self());//这个地方要注意主线程jion时,如果这行没跑到就会jion到,故而主线程还是会等待子线程结束
    for (int i = 0; i < 5; i++)
    {
        sleep(1);
        printf("ChildThread sleep 1s  \n" );
    }

    return (void *)0;
}

void *threadfunc2(void *arg)
{
    printf("this thread is num%ld\n", (long)arg);
    for (int i = 0; i < 5; i++)
    {
        sleep(1);
        printf("ChildThread 2 sleep 1s (%ld) \n", (long)arg);
    }

    return (void *)0;
}

编译运行,查看测试结果。

五、pthread_join获取线程返回值

//pthread_join的第二个参数就是线程pID的返回值;而函数pthread_join的返回值成功为0,失败不为0.
int ival;
pthread_join(pID,(void**)&ival);

示例代码如下:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void *threadFunc(void *arg);

int main()
{
    pthread_t pID;
    if (pthread_create(&pID, NULL, threadFunc, NULL) != 0)
    {
        printf("---CREATE THREAD FAILED---");
    }
    // usleep(150000);
    usleep(2000);//这一步sleep是为了避免子线程跑完了,而下面的cancel还没执行,导致认为cancel无效
    pthread_cancel(pID);//取消线程
    int ival;
    printf("-wait for the child thread to end\n");
    int result = pthread_join(pID,(void**)&ival);//子线程未设置为分离状态时(默认就是非分离的)可以获取到pthread_join的返回值,正常总是返回0,
                                                //pthread_join第二个参数线程函数的返回值,也就是pthread_create指定的线程函数的返回值。
                                                //如果线程被取消这个会返回-1,而不是指定的返回值
    printf("-child thread end (result=%d,ival=%d)\n",result,ival);
    return 0;
}

void * threadFunc(void * arg)
{
    // pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);//会让pthread_cancel失效,和pthread_cancel配合使用,如果不设置这行代码,默认就是PTHREAD_CANCEL_ENABLE,第二个参数是线程老的状态
    // pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);//默认就是PTHREAD_CANCEL_DEFERRED:表示立即取消
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);//PTHREAD_CANCEL_ASYNCHRONOUS表示延迟取消,直到一个取消点函数执行完后取消。
                                                            //取消点函数有很多,常用的printf,sleep都是取消点
    // for (int i = 0; i < 3; i++)
    // {
    //     sleep(1);
    //     printf("child thread sleep 1s\n");//加\n清除缓存问题
    // }
    int j=0;
    for (int anceltype = 0; anceltype < 50000; anceltype++)
    {
        j++;
    }
    printf("j=%d\n",j);
    // pthread_exit(0);
    // pthread_exit((void*)11);
    // return 0;
    return (void*)111;
    //上面的两种返回类型是相同的,都可以让pthread_join的第二个参数获取到返回值。
    //返回的结果是指针,而不是数字
}

六、线程的取消

在这里插入图片描述

- 函数pthread_setcancelstate():
通过此函数设置对pthread_cancel请求的响应方式。

pthread_setcancelstate(int state,int* oldstate);

第一个参数 打算设置的状态。
第一个参数 是否保留设置之前的状态。
用法:

//1.设置了这个状态后,pthread_cancel(pID);就会失效
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
//2.系统默认就是PTHREAD_CANCEL_ENABLE
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
//3.需要保留之前状态的话就按下面的方法使用第二个参数即可。
int oldstate;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,&oldstate);

代码示例:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void *threadFunc(void *arg);

int main()
{
    pthread_t pID;
    if (pthread_create(&pID, NULL, threadFunc, NULL) != 0)
    {
        printf("---CREATE THREAD FAILED---");
    }

    usleep(1500000);
    pthread_cancel(pID);
    int ival;
    
    printf("-wait for the child thread to end\n");
    int result = pthread_join(pID, (void **)&ival);
    printf("-child thread end (result=%d,ival=%d)\n", result, ival);
    return 0;
}

void *threadFunc(void *arg)
{
    int oldstate;
    // pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,&oldstate);//main函数中pthread_cancel会失效
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&oldstate);

    for (int i = 0; i < 3; i++)
    {
        sleep(1);
        printf("child thread sleep 1s\n"); //加\n清除缓存问题
        // pthread_cleanup_pop(1);
    }
    pthread_exit((void*)10);

- 函数pthread_setcanceltype():
通过此函数设置对线程取消函数pthread_cancel的时刻。

pthread_setcanceltype(int type,int * oldtype);

第一个参数 打算设置的取消类型。
第一个参数 是否保留设置之前的取消类型。
用法:

//1.立即取消,也是系统默认取消类型
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&type);
//2.延迟取消,直到某一个取消点运行完毕。
//对于取消点的问题,一般我们用到的函数都是取消点,比如printf、sleep等函数
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&type);
//3.需要保留之前状态的话就按下面的方法使用第二个参数即可。
int type;
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&type);//立即取消

代码示例:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void *threadFunc(void *arg);

int main()
{
    pthread_t pID;
    if (pthread_create(&pID, NULL, threadFunc, NULL) != 0)
    {
        printf("---CREATE THREAD FAILED---");
    }
    usleep(1500000);
    pthread_cancel(pID);
    int ival;
    
    printf("-wait for the child thread to end\n");
    int result = pthread_join(pID, (void **)&ival);
    printf("-child thread end (result=%d,ival=%d)\n", result, ival);
    return 0;
}

void *threadFunc(void *arg)
{
    int oldstate;
    // pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&oldstate);//立即取消
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,&oldstate);//延迟取消

    for (int i = 0; i < 3; i++)
    {
        sleep(3);
        printf("child thread sleep 1s\n"); //加\n清除缓存问题
    }
    pthread_exit((void*)10);
}

六、线程的清理

在这里插入图片描述
特别注意:线程注册函数时是进栈的过程,所以弹出时是出栈过程,所以清理函数是正序注册,倒序执行。
1. 涉及函数说明

- pthread_cleanup_push

void pthread_cleanup_push(void (*routine)(void *), void *arg);

第一个参数 就是线程清理函数名。
第一个参数 传递给线程清理函数的参数。

- pthread_cleanup_pop

void pthread_cleanup_pop(int execute);

参数 只有一个,一般设置为非0:通过pthread_cleanup_push注册的清理函数弹出并执行;设置为0:通过pthread_cleanup_push注册的清理函数只弹出不执行。

两个函数用法如下:

void *threadFunc(void *arg)
{
    long para=222;
    //注册清理函数
    //每个清理函数都要注册,一般每个线程里只需要注册一个
    //注意:后面pop时是按注册的反顺序进行pop的
    pthread_cleanup_push(cleanfunc1,(void*)para);//第二个参数是用来给第一个参数的函数传参的
    pthread_cleanup_push(cleanfunc1,NULL);

    for (int i = 0; i < 3; i++)
    {
        // pthread_cleanup_push(cleanfunc1,NULL);//push和下面的pop必须在同一个作用域中成对出现
        sleep(1);
        printf("child thread sleep 1s\n"); //加\n清除缓存问题
        // pthread_cleanup_pop(1);
    }

    //弹出线程清理函数
    //参数为0就弹出函数但不执行,为大于0的整数就弹出并执行
    //上面注册了几个,这个地方就相应的要pop几次,否则会报错
    pthread_cleanup_pop(1);
    pthread_cleanup_pop(0);//参数为0就弹出函数但不执行,故threadfunc2未执行

    pthread_exit((void*)10);
}

void cleanfunc1(void *arg)
{
    printf("call cleanfunc1 arg=%ld.\n",(long)arg);
}

六、线程与信号

在这里插入图片描述

一般捕获信号的处理函数都设置在主函数中:

signal(2, hdfunc);

- pthread_kill函数
主线程用来向子线程发送信号,一般配合捕获信号函数一起使用,并且如果捕获信号函数signal未设置处理函数那么会使整个进程直接中止。反之,整个进程不会出现中断(不会像进程信号捕获一样中断系统调用),仍然正常运行。
示例代码:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>

//在任何子线程中使用了信号处理函数,那所有线程的相应信号处理都会被改变。所以信号处理函数一般都只有一个

void *threadFunc(void *arg);

void hdfunc(int sig);

int main()
{
    
    signal(2, hdfunc);//这个捕获信号的代码放在主线程或子线程函数中都可以。
    pthread_t pID;
    if (pthread_create(&pID, NULL, threadFunc, NULL) != 0)
    {
        printf("---CREATE THREAD FAILED---\n");
    }

    // char str[50];
    // scanf("%s",str);//不会对子线程造成中断,而是会阻塞在子线程函数执行完后主线程的scanf的地方,而进程会直接造成中断。
    // printf("str=%s\n",str);

    pthread_kill(pID,2);//主线程向子线程发送信号用此函数,此时子线程会中断
                        //如果不设置线程信号处理函数,那么这里会将父子线程全部杀死。

    int ival;
    printf("-wait for the child thread to end\n");
    int result = pthread_join(pID, (void **)&ival); //子线程未设置为分离状态时(默认就是非分离的)可以获取到pthread_join的返回值,正常总是返回0,
                                                    //pthread_join第二个参数线程函数的返回值,也就是pthread_create指定的线程函数的返回值。
                                                    //如果线程被取消这个会返回-1,而不是指定的返回值
    printf("-child thread end (result=%d,ival=%d)\n", result, ival);

    return 0;
}

void *threadFunc(void *arg)
{
    for (int i = 0; i < 5; i++)
    {
        sleep(1);
        printf("Child thread sleep %ds\n",i);
    }
    pthread_exit(0);
}

void hdfunc(int sig)
{
    printf("sig=%d\n",sig);
    for (int i = 0; i < 3; i++)
    {
        sleep(1);
        printf("--Get signal:%d sleep %ds\n",sig,i);
    }
    // pthread_exit(0);//这里加这个会让子线程直接退出。不加的话,会让子线程接着跑
}

自行测试。

六、多线程网络服务端

下面示例未涉及日志的问题,后续补充。
Server.cpp:

/*
 * 程序名:server.cpp,此程序用于演示socket通信的服务端
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <signal.h>
#include <vector>
using namespace std;

int exitflag;

void *pthMain(void *arg); //和客户端通信线程函数
void mainexit(int sig);   //信号处理函数

int commonlistenfd;          //主线程监听socket
vector<int> v_pid;           //用来存放和客户端通信的所有socket
void pthmainexit(void *arg); //子线程清理函数

int main(int argc, char *argv[])
{
    signal(2, mainexit);  //Ctrl+c
    signal(15, mainexit); //kill 或 killall

    if (argc != 2)
    {
        printf("Using:./server port\nExample:./server 5005\n\n");
        return -1;
    }

    // 第1步:创建服务端的socket。
    int listenfd;
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        return -1;
    }
    commonlistenfd = listenfd;

    // 第2步:把服务端用于通信的地址和端口绑定到socket上。
    struct sockaddr_in servaddr; // 服务端地址信息的数据结构。
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;                // 协议族,在socket编程中只能是AF_INET。
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。
    //servaddr.sin_addr.s_addr = inet_addr("192.168.190.134"); // 指定ip地址。
    servaddr.sin_port = htons(atoi(argv[1])); // 指定通信端口。
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0)
    {
        perror("bind");
        close(listenfd);
        return -1;
    }

    // 第3步:把socket设置为监听模式。
    if (listen(listenfd, 5) != 0)
    {
        perror("listen");
        close(listenfd);
        return -1;
    }

    while (1)
    {
        
        // 第4步:接受客户端的连接。
        int clientfd;                             // 客户端的socket。
        int socklen = sizeof(struct sockaddr_in); // struct sockaddr_in的大小
        struct sockaddr_in clientaddr;            // 客户端的地址信息。
        clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, (socklen_t *)&socklen);
        if (exitflag == 1)//此块代码用来设置当没有客户端连接时,也能通过信号让程序正确退出。
        {
            break;
        }
        printf("客户端(%s)已连接。\n", inet_ntoa(clientaddr.sin_addr));

        //主线程负责监听,子进程负责和客户端通信
        pthread_t pID1;
        if (pthread_create(&pID1, NULL, pthMain, (void *)(long)clientfd) != 0)
        {
            printf("-----pthread_create failed!-----\n");
        }
        v_pid.push_back(pID1);
    }

    return 0;
}

void *pthMain(void *arg)
{
    int threadID = gettid();       //获取进程ID
    int tid = syscall(SYS_gettid); //获取线程ID
    int socketID = (int)(long)arg;

    pthread_cleanup_push(pthmainexit, arg);              //注册线程清理函数
    pthread_detach(pthread_self());                      //设置线程分离
    pthread_setcanceltype(PTHREAD_CANCEL_DISABLE, NULL); //设置cancel为立即取消

    char buffer[1024];
    while (1)
    {
        int iret;
        memset(buffer, 0, sizeof(buffer));
        if ((iret = recv(socketID, buffer, sizeof(buffer), 0)) <= 0) // 接收客户端的请求报文。
        {
            printf("iret=%d\n", iret);
            break;
        }
        printf("threadID=%d,%d, 接收:%s\n", threadID, tid, buffer);
        sleep(2);
        strcpy(buffer, "ok");
        if ((iret = send(socketID, buffer, strlen(buffer), 0)) <= 0) // 向客户端发送响应结果。
        {
            perror("send");
            break;
        }
        printf("threadID=%d,%d,发送:%s\n", threadID, tid, buffer);
    }

    pthread_cleanup_pop(1);
    pthread_exit(0);
}

//信号处理函数
void mainexit(int sig)
{
    printf("---server exit begin\n");
    exitflag = 1;
    //close listen socket
    close(commonlistenfd);

    for (int i = 0; i < v_pid.size(); i++)
    {
        printf("thread cancel id=%d", v_pid[i]);
        pthread_cancel(v_pid[i]);
    }

    printf("---server exit end\n");
    //exit(0);
}

//子线程清理函数
void pthmainexit(void *arg)
{
    printf("---thread cleanup begin---\n");
    //关闭与客户端的socket
    close((int)(long)arg);

    //在线程容器中去掉本线程的值
    for (int i = 0; i < v_pid.size(); i++)
    {
        if (v_pid[i] == pthread_self())
        {
            printf("v_pid erase threadid=%ld", pthread_self());
            v_pid.erase(v_pid.begin() + i);
        }
    }
    printf("---thread cleanup end---\n");
}

Client.cpp:

/*
 * 程序名:client.cpp,此程序用于演示socket的客户端
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    printf("getpid=%d",getpid());
    if (argc != 3)
    {
        printf("Using:./client ip port\nExample:./client 127.0.0.1 5005\n\n");
        return -1;
    }

    // 第1步:创建客户端的socket。
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        return -1;
    }

    // 第2步:向服务器发起连接请求。
    struct hostent *h;
    if ((h = gethostbyname(argv[1])) == 0) // 指定服务端的ip地址。
    {
        printf("gethostbyname failed.\n");
        close(sockfd);
        return -1;
    }
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通信端口。
    memcpy(&servaddr.sin_addr, h->h_addr, h->h_length);
    if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) // 向服务端发起连接清求。
    {
        perror("connect");
        close(sockfd);
        return -1;
    }

    char buffer[1024];

    // 第3步:与服务端通信,发送一个报文后等待回复,然后再发下一个报文。
    for (int ii = 0; ii < 3; ii++)
    {
        int iret;
        memset(buffer, 0, sizeof(buffer));
        // sprintf(buffer, "getpid=%d,这是第%d个超级女生,编号%03d。", getpid(), ii + 1, ii + 1);
        sprintf(buffer, "这是第%d个超级女生,编号%03d。", ii + 1, ii + 1);
        if ((iret = send(sockfd, buffer, strlen(buffer), 0)) <= 0) // 向服务端发送请求报文。
        {
            perror("send");
            break;
        }
        printf("发送:%s\n", buffer);

        memset(buffer, 0, sizeof(buffer));
        if ((iret = recv(sockfd, buffer, sizeof(buffer), 0)) <= 0) // 接收服务端的回应报文。
        {
            printf("iret=%d\n", iret);
            break;
        }
        printf("接收:%s\n", buffer);
    }

    // 第4步:关闭socket,释放资源。
    close(sockfd);
}

自行编译后测试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值