前言:学习编程一定要敲,接着测试,然后查资料,最后总结!!!
一、线程的概念
在同一个进程中,可以运行多个线程,运行于同一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享全局变量和对象,启动一个线程所消耗的资源比启动一个进程所消耗的资源要少。
和多进程相比,多线程是一种比较节省资源的多任务操作方式。启动一个新的进程必须分配给它独立的地址空间,每个进程都有自己的堆栈段和数据段,系统开销比较高,进行数据的传递只能通过进行间通信的方式进行。
二、多线程基础知识
注意用命令查看线程时,进程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);
}
自行编译后测试。