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() void echo_cli(int sock)
{
char sendbuf[] = {};
char recvbuf[] ={};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(sock, sendbuf, strlen(sendbuf));
read(sock, recvbuf, sizeof(recvbuf)); fputs(recvbuf, stdout);
memset(sendbuf, , sizeof(sendbuf));
memset(recvbuf, , sizeof(recvbuf));
} close(sock);
} int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < )
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
ERR_EXIT("connect"); echo_cli(sock); return ;
}
这里的代码不需要改变,主要是修改服务端,关于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() void echo_srv(int conn)
{
char recvbuf[];
while ()
{
memset(recvbuf, , sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
if (ret == )
{
printf("client close\n");
break;
}
else if (ret == -)
ERR_EXIT("read");
fputs(recvbuf, stdout);
write(conn, recvbuf, ret);
}
}int main(void)
{
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < )
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); int on = ;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < )
ERR_EXIT("setsockopt"); if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < )
ERR_EXIT("listen"); struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn; while ()
{
if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < )
ERR_EXIT("accept"); printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid = fork();
if (pid == -)
ERR_EXIT("fork"); if (pid == )
{//子进程
close(listenfd);//不需要处理监听
echo_srv(conn);
exit(EXIT_SUCCESS);
} else {
close(conn);//父进程不需要处理连接
}
} return ;
}
接下来要来进行服务端改造:
【注意】:由于是单进程,所以就没必要像创建进程的方式要关闭conn了,编程也简单了许多。
好了,下面编译运行一下:
从中可以发现程序正常运转,而且当客户端退出时,相应的线程也退出了,这是为什么呢?
另外当线程退出了之后,其实该线程是属于一个僵线程的状态,因为在主线程中并没有调用pthread_join()来等待新创建线程的退出,所以得避免僵线程的出现,修改代码如下:
关于这个程序还有一个细节需要探讨一下,如下:
那我可以这样写么?
此时线程入口函数也得发生改变:
编译运行看是否正常:
看似一切正常,当然肯定是有问题的,不然也不会换一种写法来进行说明了,有什么潜在风险呢?
这就是典型的Race Condition(也叫做资源竞争)问题。所以说conn只能值传递,而不能是传递指针,还是将代码还原,下面来讨论另外一个细节问题:
那如何解决呢?可以采用动态申请内存的方式:
再来编译运行:
从中可以发现,一切正常,这两个细节问题需要注意一下,好了,今天先学到这,下次继续~最后附上一张进程跟线程之间的对比图,画得比较草,可以对比着记忆:
linux网络编程之posix线程(一)的更多相关文章
- linux网络编程之posix线程(二)
继续接着上次的posix线程来学习: 回顾一下创建线程的函数: pthread_att_t属性变量是需要进行初始化才能够用的,一定初始化了属性变量,它就包含了线程的多种属性的值,那到底有哪些属性了,下 ...
- linux网络编程之posix条件变量
今天来学习posix的最后一个相关知识----条件变量,言归正传. 下面用一个图来进一步描述条件变量的作用: 为什么呢? 这实际上可以解决生产者与消费者问题,而且对于缓冲区是无界的是一种比较理解的解决 ...
- linux网络编程之posix信号量与互斥锁
继上次学习了posix线程之后,这次来讨论一下posix信号量与互斥锁相关的知识: 跟posix消息队列,共享内存的打开,关闭,删除操作一样,不过,上面的函数是对有名信号量进行操作,通过man帮助可以 ...
- linux网络编程之posix消息队列
在前面已经学习了System v相关的IPC,今天起学习posix相关的IPC,关于这两者的内容区别,简单回顾一下: 而今天先学习posix的消息队列,下面开始: 接下来则编写程序来创建一个posix ...
- linux网络编程之posix共享内存
今天继续研究posix IPC对象,这次主要是学习一下posix共享内存的使用方法,下面开始: 下面编写程序来创建一个共享内存: 编译运行: 那posix的共享内存存放在哪里呢?上节中学的posix的 ...
- linux网络编程之shutdown() 与 close()函数详解
linux网络编程之shutdown() 与 close()函数详解 参考TCPIP网络编程和UNP: shutdown函数不能关闭套接字,只能关闭输入和输出流,然后发送EOF,假设套接字为A,那么这 ...
- (十)Linux 网络编程之ioctl函数
1.介绍 Linux网络程序与内核交互的方法是通过ioctl来实现的,ioctl与网络协议栈进行交互,可得到网络接口的信息,网卡设备的映射属性和配置网络接口.并且还能够查看,修改,删除ARP高速缓存的 ...
- linux网络编程之socket编程(四)
经过两周的等待,终于可以回归我正常的学习之旅了,表哥来北京了在我这暂住,晚上回家了基本在和他聊天,周末带他在北京城到处乱转,几乎剥夺了我自由学习的时间了,不过,亲人之情还是很难得的,工作学习并不是生活 ...
- linux网络编程之socket编程(六)
经过一个国庆长假,又有一段时间没有写博文了,今天继续对linux网络编程进行学习,如今的北京又全面进入雾霾天气了,让我突然想到了一句名句:“真爱生活,珍惜生命”,好了,言归正传. 回顾一下我们之间实现 ...
随机推荐
- 【ARTS】01_41_左耳听风-201900819~201900825
ARTS: Algrothm: leetcode算法题目 Review: 阅读并且点评一篇英文技术文章 Tip/Techni: 学习一个技术技巧 Share: 分享一篇有观点和思考的技术文章 Algo ...
- jvm(5)---垃圾回收(回收算法和垃圾收集器)
1.垃圾回收算法 1.1 标记-清除算法 算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象.它是最基础的收集算法,效率也很高,但是会带来两个明显的问题 ...
- shrio学习笔记
Thymeleaf扩展坐标 <!--thyemleaf对shrio的扩展坐标--> <dependency> <groupId>com.github.thebora ...
- 如何用C++实现栈
栈的定义 栈(stack)又名堆栈,它是一种运算受限的线性表.限定仅在表尾进行插入和删除操作的线性表.这一端被称为栈顶,相对地,把另一端称为栈底.向一个栈插入新元素又称作进栈.入栈或压栈,它是把新元素 ...
- 关于 Visual Studio 的代码度量值
查看方式:Visual Studio -> Analyze -> Calculate code metrics feature 代码度量(Code Metrics)是用来测量专业标准的软件 ...
- [Oracle] - 关于星期(IW和WW)的算法
1. 查看数据库字符集(如果字符集不同,可能显示乱码) select DECODE(parameter, 'NLS_TERRITORY', 'TERRITORY', 'NLS_LANGUAGE', ' ...
- Spring之3:ListableBeanFactory
ListableBeanFactory在BeanFactory的位置见<Spring源码阅览——BeanFactory体系结构> ListableBeanFactory:同样扩展BeanF ...
- Python27之集合
集合说:“在我的世界里,你就是唯一” 一.集合的概念和使用 集合的概念和数学里数学里集合的概念是一致的,都是一组元素的集,且元素之间不能重复.元素必须是不可变的数据类型,例如元组也可以作为其中的一个元 ...
- maven 打包成 .jar 文件执行:没有主清单属性错误
报错原因是pom.xml配置文件中没有指定main入口信息,在pom.xml文件中添加如下代码: <build> <plugins> <plugin> <gr ...
- 『Python基础练习题』day05
# 请将列表中的每个元素通过 "_" 链接起来. users = ['毛利兰', '柯南', '怪盗基德'] # 请将元组 v1 = (11, 22, 33) 中的所有元素追加到列 ...