linuxsocket多线程编程
1. c++ socket 创建线程
线程的使用在socket应用还是非socket应用是都是相同的
没有什么区别
只需要在应用中需要使用线程的地方创建线程就可以了
一般socket服务器线程模型是指在服务器接收到新的链接时
会创建一个线程来与该链接进行数据交流
在accept成功返回后就立即创建一个线程,并将一些与之链接相关的私有数据传递给该线程后
服务器会继续等待新的链接
而之前的链接则交由新创建的线程来服务
一个简单的模型类型于这样
1、等待链接
2、链接成功,创建一个新线程,并将相关数据传递给该线程
3、再回到1
下面以linux下的一个简单的echo服务器(回显服务,回显任何接收到的数据)为例(socket编程的方法大致都是相通的,linux和windows都大相径庭,只是稍有不同而已,在线程模型上都是一样的)说明socket的多线程服务器模型
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#definePORThtons(8888)
typedefstructsockaddrSA;
typedefstructsockaddr_inSA_IN;
typedefstruct
{
intsockfd;
SA_INaddr;
}DATA;
voiderr_quit(constchar*msg)
{
perror(msg);
exit(-1);
}
voidecho(DATA*data)
{
charbuf[1024];
size_tn;
printf("newthread%s ",inet_ntoa(data->addr.sin_addr));
while((n=recv(data->sockfd,buf,sizeof(buf)-1,0))>0)
{
if(strncmp(buf,"exit",4)==0)
break;
send(data->sockfd,buf,n,0);
}
close(data->sockfd);
free(data);
pthread_exit(NULL);
}
intmain(intargc,char**argv)
{
intsockfd,newfd;
SA_INaddr,serv;
DATA*data;
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
err_quit("socket");
bzero(&serv,sizeof(SA_IN));
serv.sin_family=AF_INET;
serv.sin_port=PORT;
serv.sin_addr.s_addr=INADDR_ANY;
if(bind(sockfd,(SA*)&serv,sizeof(SA))==-1)
err_quit("bind");
if(listen(sockfd,20)==-1)
err_quit("listen");
while(1)
{
socklen_taddrlen;
pthread_tid;
bzero(&addr,sizeof(SA_IN));
newfd=accept(sockfd,(SA*)&addr,&addrlen);
if(newfd==-1)
{
perror("accept");
continue;
}
data=malloc(sizeof(DATA));
if(data==NULL)
{
close(newfd);
continue;
}
data->sockfd=newfd;
memcpy(&data->addr,&addr,sizeof(SA_IN));
pthread_create(&id,NULL,(void*)echo,data);//创建一个工作线程
}
return0;
}
由此可见,在socket中使用线程和其它应用中使用线程是没有任何区别的
2. 哪有linux c++ socket 线程池或者多线程 server的源码
io本身堵塞或非堵塞,看应用的吧。
至于异步,也是看应用情况。
3. 如何看懂《Linux多线程服务端编程
一:进程和线程
每个进程有自己独立的地址空间。“在同一个进程”还是“不在同一个进程”是系统功能划分的重要决策点。《Erlang程序设计》[ERL]把进程比喻为人:
每个人有自己的记忆(内存),人与人通过谈话(消息传递)来交流,谈话既可以是面谈(同一台服务器),也可以在电话里谈(不同的服务器,有网络通信)。面谈和电话谈的区别在于,面谈可以立即知道对方是否死了(crash,SIGCHLD),而电话谈只能通过周期性的心跳来判断对方是否还活着。
有了这些比喻,设计分布式系统时可以采取“角色扮演”,团队里的几个人各自扮演一个进程,人的角色由进程的代码决定(管登录的、管消息分发的、管买卖的等等)。每个人有自己的记忆,但不知道别人的记忆,要想知道别人的看法,只能通过交谈(暂不考虑共享内存这种IPC)。然后就可以思考:
·容错:万一有人突然死了
·扩容:新人中途加进来
·负载均衡:把甲的活儿挪给乙做
·退休:甲要修复bug,先别派新任务,等他做完手上的事情就把他重启
等等各种场景,十分便利。
线程的特点是共享地址空间,从而可以高效地共享数据。一台机器上的多个进程能高效地共享代码段(操作系统可以映射为同样的物理内存),但不能共享数据。如果多个进程大量共享内存,等于是把多进程程序当成多线程来写,掩耳盗铃。
“多线程”的价值,我认为是为了更好地发挥多核处理器(multi-cores)的效能。在单核时代,多线程没有多大价值(个人想法:如果要完成的任务是CPU密集型的,那多线程没有优势,甚至因为线程切换的开销,多线程反而更慢;如果要完成的任务既有CPU计算,又有磁盘或网络IO,则使用多线程的好处是,当某个线程因为IO而阻塞时,OS可以调度其他线程执行,虽然效率确实要比任务的顺序执行效率要高,然而,这种类型的任务,可以通过单线程的”non-blocking IO+IO multiplexing”的模型(事件驱动)来提高效率,采用多线程的方式,带来的可能仅仅是编程上的简单而已)。Alan Cox说过:”A computer is a state machine.Threads are for people who can’t program state machines.”(计算机是一台状态机。线程是给那些不能编写状态机程序的人准备的)如果只有一块CPU、一个执行单元,那么确实如Alan Cox所说,按状态机的思路去写程序是最高效的。
二:单线程服务器的常用编程模型
据我了解,在高性能的网络程序中,使用得最为广泛的恐怕要数”non-blocking IO + IO multiplexing”这种模型,即Reactor模式。
在”non-blocking IO + IO multiplexing”这种模型中,程序的基本结构是一个事件循环(event loop),以事件驱动(event-driven)和事件回调的方式实现业务逻辑:
[cpp] view plain
//代码仅为示意,没有完整考虑各种情况
while(!done)
{
int timeout_ms = max(1000, getNextTimedCallback());
int retval = poll(fds, nfds, timeout_ms);
if (retval<0){
处理错误,回调用户的error handler
}else{
处理到期的timers,回调用户的timer handler
if(retval>0){
处理IO事件,回调用户的IO event handler
}
}
}
这里select(2)/poll(2)有伸缩性方面的不足(描述符过多时,效率较低),Linux下可替换为epoll(4),其他操作系统也有对应的高性能替代品。
Reactor模型的优点很明显,编程不难,效率也不错。不仅可以用于读写socket,连接的建立(connect(2)/accept(2)),甚至DNS解析都可以用非阻塞方式进行,以提高并发度和吞吐量(throughput),对于IO密集的应用是个不错的选择。lighttpd就是这样,它内部的fdevent结构十分精妙,值得学习。
基于事件驱动的编程模型也有其本质的缺点,它要求事件回调函数必须是非阻塞的。对于涉及网络IO的请求响应式协议,它容易割裂业务逻辑,使其散布于多个回调函数之中,相对不容易理解和维护。
三:多线程服务器的常用编程模型
大概有这么几种:
a:每个请求创建一个线程,使用阻塞式IO操作。在Java 1.4引人NIO之前,这是Java网络编程的推荐做法。可惜伸缩性不佳(请求太多时,操作系统创建不了这许多线程)。
b:使用线程池,同样使用阻塞式IO操作。与第1种相比,这是提高性能的措施。
c:使用non-blocking IO + IO multiplexing。即Java NIO的方式。
d:Leader/Follower等高级模式。
在默认情况下,我会使用第3种,即non-blocking IO + one loop per thread模式来编写多线程C++网络服务程序。
1:one loop per thread
此种模型下,程序里的每个IO线程有一个event loop,用于处理读写和定时事件(无论周期性的还是单次的)。代码框架跟“单线程服务器的常用编程模型”一节中的一样。
libev的作者说:
One loop per thread is usually a good model. Doing this is almost never wrong, some times a better-performance model exists, but it is always a good start.
这种方式的好处是:
a:线程数目基本固定,可以在程序启动的时候设置,不会频繁创建与销毁。
b:可以很方便地在线程间调配负载。
c:IO事件发生的线程是固定的,同一个TCP连接不必考虑事件并发。
Event loop代表了线程的主循环,需要让哪个线程干活,就把timer或IO channel(如TCP连接)注册到哪个线程的loop里即可:对实时性有要求的connection可以单独用一个线程;数据量大的connection可以独占一个线程,并把数据处理任务分摊到另几个计算线程中(用线程池);其他次要的辅助性connections可以共享一个线程。
比如,在dbproxy中,一个线程用于专门处理客户端发来的管理命令;一个线程用于处理客户端发来的MySQL命令,而与后端数据库通信执行该命令时,是将该任务分配给所有事件线程处理的。
对于non-trivial(有一定规模)的服务端程序,一般会采用non-blocking IO + IO multiplexing,每个connection/acceptor都会注册到某个event loop上,程序里有多个event loop,每个线程至多有一个event loop。
多线程程序对event loop提出了更高的要求,那就是“线程安全”。要允许一个线程往别的线程的loop里塞东西,这个loop必须得是线程安全的。
在dbproxy中,线程向其他线程分发任务,是通过管道和队列实现的。比如主线程accept到连接后,将表示该连接的结构放入队列,并向管道中写入一个字节。计算线程在自己的event loop中注册管道的读事件,一旦有数据可读,就尝试从队列中取任务。
2:线程池
不过,对于没有IO而光有计算任务的线程,使用event loop有点浪费。可以使用一种补充方案,即用blocking queue实现的任务队列:
[cpp] view plain
typedef boost::function<void()>Functor;
BlockingQueue<Functor> taskQueue; //线程安全的全局阻塞队列
//计算线程
void workerThread()
{
while (running) //running变量是个全局标志
{
Functor task = taskQueue.take(); //this blocks
task(); //在产品代码中需要考虑异常处理
}
}
// 创建容量(并发数)为N的线程池
int N = num_of_computing_threads;
for (int i = 0; i < N; ++i)
{
create_thread(&workerThread); //启动线程
}
//向任务队列中追加任务
Foo foo; //Foo有calc()成员函数
boost::function<void()> task = boost::bind(&Foo::calc,&foo);
taskQueue.post(task);
除了任务队列,还可以用BlockingQueue<T>实现数据的生产者消费者队列,即T是数据类型而非函数对象,queue的消费者从中拿到数据进行处理。其实本质上是一样的。
3:总结
总结而言,我推荐的C++多线程服务端编程模式为:one (event) loop per thread + thread pool:
event loop用作IO multiplexing,配合non-blockingIO和定时器;
thread pool用来做计算,具体可以是任务队列或生产者消费者队列。
以这种方式写服务器程序,需要一个优质的基于Reactor模式的网络库来支撑,muo正是这样的网络库。比如dbproxy使用的是libevent。
程序里具体用几个loop、线程池的大小等参数需要根据应用来设定,基本的原则是“阻抗匹配”(解释见下),使得CPU和IO都能高效地运作。所谓阻抗匹配原则:
如果池中线程在执行任务时,密集计算所占的时间比重为 P (0 < P <= 1),而系统一共有 C 个 CPU,为了让这 C 个 CPU 跑满而又不过载,线程池大小的经验公式 T = C/P。(T 是个 hint,考虑到 P 值的估计不是很准确,T 的最佳值可以上下浮动 50%)
以后我再讲这个经验公式是怎么来的,先验证边界条件的正确性。
假设 C = 8,P = 1.0,线程池的任务完全是密集计算,那么T = 8。只要 8 个活动线程就能让 8 个 CPU 饱和,再多也没用,因为 CPU 资源已经耗光了。
假设 C = 8,P = 0.5,线程池的任务有一半是计算,有一半等在 IO 上,那么T = 16。考虑操作系统能灵活合理地调度 sleeping/writing/running 线程,那么大概 16 个“50%繁忙的线程”能让 8 个 CPU 忙个不停。启动更多的线程并不能提高吞吐量,反而因为增加上下文切换的开销而降低性能。
如果 P < 0.2,这个公式就不适用了,T 可以取一个固定值,比如 5*C。
另外,公式里的 C 不一定是 CPU 总数,可以是“分配给这项任务的 CPU 数目”,比如在 8 核机器上分出 4 个核来做一项任务,那么 C=4。
四:进程间通信只用TCP
Linux下进程间通信的方式有:匿名管道(pipe)、具名管道(FIFO)、POSIX消息队列、共享内存、信号(signals),以及Socket。同步原语有互斥器(mutex)、条件变量(condition variable)、读写锁(reader-writer lock)、文件锁(record locking)、信号量(semaphore)等等。
进程间通信我首选Sockets(主要指TCP,我没有用过UDP,也不考虑Unix domain协议)。其好处在于:
可以跨主机,具有伸缩性。反正都是多进程了,如果一台机器的处理能力不够,很自然地就能用多台机器来处理。把进程分散到同一局域网的多台机器上,程序改改host:port配置就能继续用;
TCP sockets和pipe都是操作文件描述符,用来收发字节流,都可以read/write/fcntl/select/poll等。不同的是,TCP是双向的,Linux的pipe是单向的,进程间双向通信还得开两个文件描述符,不方便;而且进程要有父子关系才能用pipe,这些都限制了pipe的使用;
TCP port由一个进程独占,且进程退出时操作系统会自动回收文件描述符。因此即使程序意外退出,也不会给系统留下垃圾,程序重启之后能比较容易地恢复,而不需要重启操作系统(用跨进程的mutex就有这个风险);而且,port是独占的,可以防止程序重复启动,后面那个进程抢不到port,自然就没法初始化了,避免造成意料之外的结果;
与其他IPC相比,TCP协议的一个天生的好处是“可记录、可重现”。tcpmp和Wireshark是解决两个进程间协议和状态争端的好帮手,也是性能(吞吐量、延迟)分析的利器。我们可以借此编写分布式程序的自动化回归测试。也可以用tcp之类的工具进行压力测试。TCP还能跨语言,服务端和客户端不必使用同一种语言。
分布式系统的软件设计和功能划分一般应该以“进程”为单位。从宏观上看,一个分布式系统是由运行在多台机器上的多个进程组成的,进程之间采用TCP长连接通信。
使用TCP长连接的好处有两点:一是容易定位分布式系统中的服务之间的依赖关系。只要在机器上运行netstat -tpna|grep <port>就能立刻列出用到某服务的客户端地址(Foreign Address列),然后在客户端的机器上用netstat或lsof命令找出是哪个进程发起的连接。TCP短连接和UDP则不具备这一特性。二是通过接收和发送队列的长度也较容易定位网络或程序故障。在正常运行的时候,netstat打印的Recv-Q和Send-Q都应该接近0,或者在0附近摆动。如果Recv-Q保持不变或持续增加,则通常意味着服务进程的处理速度变慢,可能发生了死锁或阻塞。如果Send-Q保持不变或持续增加,有可能是对方服务器太忙、来不及处理,也有可能是网络中间某个路由器或交换机故障造成丢包,甚至对方服务器掉线,这些因素都可能表现为数据发送不出去。通过持续监控Recv-Q和Send-Q就能及早预警性能或可用性故障。以下是服务端线程阻塞造成Recv-Q和客户端Send-Q激增的例子:
[cpp] view plain
$netstat -tn
Proto Recv-Q Send-Q Local Address Foreign
tcp 78393 0 10.0.0.10:2000 10.0.0.10:39748 #服务端连接
tcp 0 132608 10.0.0.10:39748 10.0.0.10:2000 #客户端连接
tcp 0 52 10.0.0.10:22 10.0.0.4:55572
五:多线程服务器的适用场合
如果要在一台多核机器上提供一种服务或执行一个任务,可用的模式有:
a:运行一个单线程的进程;
b:运行一个多线程的进程;
c:运行多个单线程的进程;
d:运行多个多线程的进程;
考虑这样的场景:如果使用速率为50MB/s的数据压缩库,进程创建销毁的开销是800微秒,线程创建销毁的开销是50微秒。如何执行压缩任务?
如果要偶尔压缩1GB的文本文件,预计运行时间是20s,那么起一个进程去做是合理的,因为进程启动和销毁的开销远远小于实际任务的耗时。
如果要经常压缩500kB的文本数据,预计运行时间是10ms,那么每次都起进程 似乎有点浪费了,可以每次单独起一个线程去做。
如果要频繁压缩10kB的文本数据,预计运行时间是200微秒,那么每次起线程似 乎也很浪费,不如直接在当前线程搞定。也可以用一个线程池,每次把压缩任务交给线程池,避免阻塞当前线程(特别要避免阻塞IO线程)。
由此可见,多线程并不是万灵丹(silver bullet)。
1:必须使用单线程的场合
据我所知,有两种场合必须使用单线程:
a:程序可能会fork(2);
实际编程中,应该保证只有单线程程序能进行fork(2)。多线程程序不是不能调用fork(2),而是这么做会遇到很多麻烦:
fork一般不能在多线程程序中调用,因为Linux的fork只克隆当前线程的thread of control,不可隆其他线程。fork之后,除了当前线程之外,其他线程都消失了。
这就造成一种危险的局面。其他线程可能正好处于临界区之内,持有了某个锁,而它突然死亡,再也没有机会去解锁了。此时如果子进程试图再对同一个mutex加锁,就会立即死锁。因此,fork之后,子进程就相当于处于signal handler之中(因为不知道调用fork时,父进程中的线程此时正在调用什么函数,这和信号发生时的场景一样),你不能调用线程安全的函数(除非它是可重入的),而只能调用异步信号安全的函数。比如,fork之后,子进程不能调用:
malloc,因为malloc在访问全局状态时几乎肯定会加锁;
任何可能分配或释放内存的函数,比如snprintf;
任何Pthreads函数;
printf系列函数,因为其他线程可能恰好持有stdout/stderr的锁;
除了man 7 signal中明确列出的信号安全函数之外的任何函数。
因此,多线程中调用fork,唯一安全的做法是fork之后,立即调用exec执行另一个程序,彻底隔断子进程与父进程的联系。
在多线程环境中调用fork,产生子进程后。子进程内部只存在一个线程,也就是父进程中调用fork的线程的副本。
使用fork创建子进程时,子进程通过继承整个地址空间的副本,也从父进程那里继承了所有互斥量、读写锁和条件变量的状态。如果父进程中的某个线程占有锁,则子进程同样占有这些锁。问题是子进程并不包含占有锁的线程的副本,所以子进程没有办法知道它占有了哪些锁,并且需要释放哪些锁。
尽管Pthread提供了pthread_atfork函数试图绕过这样的问题,但是这回使得代码变得混乱。因此《Programming With Posix Threads》一书的作者说:”Avoid using fork in threaded code except where the child process will immediately exec a new program.”。
b:限制程序的CPU占用率;
这个很容易理解,比如在一个8核的服务器上,一个单线程程序即便发生busy-wait,占满1个core,其CPU使用率也只有12.5%,在这种最坏的情况下,系统还是有87.5%的计算资源可供其他服务进程使用。
因此对于一些辅助性的程序,如果它必须和主要服务进程运行在同一台机器的话,那么做成单线程的能避免过分抢夺系统的计算资源。
4. 学嵌入式linux需要先学什么
韦东山:6000字长文告诉你如何学习嵌入式linux
链接:网页链接
第1章 单片机和Linux的区别
1.1 有哪些产品使用单片机或Linux
所有的电子产品,所用技术都可以认为要么是单片机,要么是Linux;GUI方面主要是QT/Android,它们都是运行于Linux之上的。
下面我们用类比和逻辑推导出嵌入式Linux系统的组成,没错,“推导”。
从上图可以知道:
① 组成:
嵌入式Linux系统
= bootloader + linux内核 + 根文件系统(里面含有APP)。
② bootloader:
它的目的是启动内核,去哪等读内核?读到哪里?去Flash等外设读内核,存到内存里去。所以需要有Flash里外设的驱动能力,为了调试方便还会有网络功能。
所以,可以认为 booloader = 裸机集合,它就是一个复杂的单片机程序。
③ Linux内核
Linux内核的最主要目的是去启动APP,APP保存在哪里?保存在“根文件系统”里。“根文件系统”又保存在哪里?在Flash、SD卡等设备里,甚至可能在网络上。所以Linux内核要有这些Flash、SD卡里设备的驱动能力。
不仅如此,Linux内核还有进程调度能力、内存管理等功能。
所以:Linux内核 = 驱动集合 + 进程调度 + 内存管理等。
2.3 要学习bootloader吗
Bootloader有很多种,常用的叫作u-boot。
在实际工作中,对于u-boot基本上是修修改改,甚至不改。但是u-boot本身是很复杂的,比如为了便于调试,它支持网络功能;有些内核是保存在FAT32分区里,于是它要能解析FAT32分区,读FAT32分区的文件。
花那么多精力去学习u-boot,但是工作中基本用不到,这对初学者很不友善。
所以,对于初学者,我建议:理解u-boot的作用、会使用u-boot的命令,这就可以了。
如果你的工作就是修改、完善bootloader,那么再去研究它吧。
2.4 要学习Linux内核、要学习驱动程序吗
之前我们说过Linux内核 = 驱动集合 + 进程调度 + 内存管理等,如果要学习Linux内核,从驱动程序入手是一个好办法。
但是人人都要学习Linux内核、人人都要学习Linux驱动吗?显然不是。
作为初学者,懂几个简单的驱动程序,有利于工作交流;理解中断、进程、线程的概念,无论是对驱动开发、应用程序开发,都是很有好处的。
所以对于初学者,建议前期只学习这几个驱动:LED、按键、中断。
① LED驱动程序:
这是最简单的驱动程序。
② 按键驱动程序:
它也比较简单,从它引入“中断”。
③ 中断:
从“中断”它可以引入:休眠-唤醒、进程/线程、POLL机制、异步通知等概念。这些概念无论是对驱动开发,还是对应用开发,都很重要。
所以,对于初学者,我建议必须学习这几个驱动:LED、按键、中断。
入门之后,如果你想从事内核开发、驱动开发,那么可以去钻研几个驱动程序(输入系统、I2C总线、SPI总线等),掌握若干个大型驱动程序后,你对内核的套路就有所了解了,再去研究其他部分(比如进程管理、文件系统)时你会发现套路是如此通用。
摄像头(VL42)、声卡ALSA驱动是Linux中比较复杂的2类驱动,它们是很难的,如果工作与此相关再去研究。
2.5,要学习Linux应用程序吗?先学一些基础技能
要学,即使以后你只想研究内核,一些基本的应用开发编写能力也是需要的:
① 基本设备的访问,比如LCD、输入设备
② 进程、线程、进程通信、线程同步与互斥
③ 休眠-唤醒、POLL机制、信号
④ 网络编程
①②③部分的知识,跟驱动有密切的关系,它们是相辅相承的。
掌握了基本驱动开发能力、基本应用开发能力之后,在工作中你就可以跟别人友好沟通了,不至于一脸懵逼。
2.6,应用程序是怎么启动的?要了解一下根文件系统
你辛辛苦苦写出了应用程序,怎么把它放到板子上,让它开机就自动启动?
你写的程序,它依赖于哪些库,这些库放到板子上哪个目录?
怎么做一个可升级的系统?即使升级中途断电了,也要保证程序至少还可以运行老的版本?
这些都需要我们了解一下根文件系统。
先了解一下init进程:它要读取配置文件,根据配置文件启动各个APP。
了解了init进程,你就了解了根文件系统的组成,就可以随心所欲裁剪系统,为你的项目制作出最精简的系统。
第3章 学习方法
3.1,先不要打破砂锅问到底
嵌入式涉及的东西太多太杂了,如果心里没有主线,碰到什么都要去研究个透彻,最终反而忘记自己要学什么了。
嵌入式涉及硬件知识、软件知识,软件里涉及汇编、ARM架构、C语言、Makefile、Shell;又分为bootloader、内核、驱动、基本的APP、GUI。
比如我们会用到Makefile,了解它的基本规则,会用我们提供的Makefile就可以。
不需要深入研究那些make函数,因为在工作中都有现成的Makefile给你使用,不需要自己去编写一套Makefile。何必花上好几天去深入研究它呢?
比如我们会用到bootloader,难道又要花上几个月来深入研究u-boot吗?工作中基本不需要改u-boot,会用那几个命令就可以。
甚至有些学员先去买本shell的书来学习shell命令,何必?我们在视频中用到什么命令,你不懂时再去网络一下这些命令就可以了。
不要脱离初学者的主线:应用基础、驱动基础。有了这2个基础后,你想深入研究某部分时,再去花时间吧。
3.2,思路要清晰,不怕抄代码
视频里的代码,请你一定要自己去写一次、写多次。为什么我现在写驱动那么熟?我2009年在华清远见上课时,
每次上课我都要给学生写一次那些驱动,十几次下来闭着眼睛都知道内核的套路了。
记不住那些函数?我也记不住,我都是去参考同类的驱动程序,这又不是闭卷考试。
但是要理清楚思路,你写这个程序要完成什么功能、怎么实现这些功能?这个要弄清楚。
有了思路后再写代码,不知道怎么写?没关系,看看视频,看看示例,然后关闭视频看看能否自己写出来。
3.3,对自己的方向很了解,我只能带你到这里了
我的专长是操作系统,是快速地带领大家掌握一些项目开发的基础知识。
如果你决定深入研究某方面时,我并不能带你多久。你要去看源码,去看这方面的专业书籍。
比如想深入钻研内核的内存管理时,它有页表映射(你需要阅读ARM架构的手册)、SLAB分配器、vmalloc/malloc实现、mmap实现、缺页中断、父进程子进程之间的页面管理等等,内容非常多。有时候连书籍都没有,你需要直接啃代码。
当你想从事某个行业时,就需要深入研究行业相关的知识。
比如CAN总线,它可以写成一本书:CAN协议、CAN报文、Socket CAN、车身网络拓扑结构,CAN应用报文,CAN网络管理报文,CAN诊断报文。
想做物联网网关,需要深入研究MQTT,MQTT协议相对简单,但是MQTT英文原版协议有130多页,中文版有近100页,是一本小书了。
每个行业都有自己的业务逻辑,在掌握基本的编程能力之一,你需要结合具体的业务去深入学习。
-☆ END ☆-
5. 刚介入linux c的socket编程没多久,想要写一个socket客户端,实现多线程处理发送和接收,哪位大侠帮帮忙啊
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8888
void *yourfunction(void *connect_fd)
{
int connfd = *((int *)connect_fd);
。。。。
} //你没说具体的应用,所以只能写这么多了。在这里面直接对connfd调用read和write函数就可以和客户端收发数据了。
//补充:是啊 返回给客户端什么信息啊?
int main(void)
{
int sockfd, n, connfd;
pthread_t tid;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket:");
exit(1);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
n = bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if (n == -1)
{
perror("bind:");
exit(1);
}
n = listen(sockfd, 20);
if (n == -1)
{
perror("listen:");
exit(1);
}
while (1)
{
connfd = accept(sockfd, (struct sockaddr *)&servaddr, NULL);
pthread_create(&tid, NULL, yourfunction, (void *)&connfd);
}
return 0;
}
6. socket同步异步多线程问题
1.是多线程,不算异步。
2.BeginAccept是异步,虽然你的程序中用myreset.WaitOne();进入了等待事件的过程。如果不等待事件,就可以继续运行下面的程序了。
我的经验是,这种情况就不用异步。
3.回调当然会消耗系统资源。大数据的话,我建议用线程循环做。
4.同步就会阻塞,异步主线程不阻塞,当需要监听后,还需要做一些其他处理的话,就用异步,如果一切就绪等待连接的,就用同步比较好。
5.委托给了.net框架,内部应该也是线程和阻塞。
路过,不足之处,请继续问。
7. linux下socket编程,只需要实现服务器端能接收多个用户端发来的消息这个功能就行,求代码,最好有相应解释
这些都是自己写的,由于我很粗心调试了好久,要是有任何问题我都可以帮你解决,包教会,而且可以进一步一起完善他的功能
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/time.h>
#define BUFF_SIZE 1024
#define PORT 8888
#define LSN_NUM 10
int main()
{
int sockfd = -1;
int clt_sockfd = -1;
int i = 0, fd;
fd_set inset, tmp_inset;
struct sockaddr_in *server_addr, *client_addr;
int addr_len = sizeof(struct sockaddr_in);
int len;
char buff[BUFF_SIZE];
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 1){
printf("Create socket error!\n");
exit(0);
}else{
printf("Create socket success! sockfd = %d\n", sockfd);
}
server_addr = malloc(addr_len);
client_addr = malloc(addr_len);
memset(server_addr, 0, sizeof(server_addr));
memset(client_addr, 0, sizeof(client_addr));
server_addr->sin_family = AF_INET;
server_addr->sin_port = htons(PORT); //此处要将主机字节序转为网络字节序
server_addr->sin_addr.s_addr = INADDR_ANY;
if(bind(sockfd, (struct sockaddr*)server_addr, sizeof(struct sockaddr_in)) < 0){
printf("Bind error!\n");
exit(0);
}else{
printf("Bind success!\n");
}
if(listen(sockfd, LSN_NUM) < 0){
printf("Listen error!\n");
exit(0);
}else{
printf("Listen success!\n");
}
printf("Waiting for connect ...\n");
i = 1;
FD_ZERO(&inset);
FD_SET(sockfd, &inset);
while(1){
printf("------ The %dth turning ------- \n", i++);
tmp_inset = inset;
memset(buff, 0, BUFF_SIZE);
//select
if(select(FD_SETSIZE, &tmp_inset, NULL, NULL, NULL) <= 0){
printf("Select failed!\n");
exit(0);
}else{
printf("Select success!\n");
}//end select
for(fd = 0; fd < FD_SETSIZE; fd++){
if(FD_ISSET(fd, &tmp_inset) > 0){
printf("crrent fd: %d\n", fd);
if(sockfd == fd){
if((clt_sockfd = accept(sockfd, (struct sockaddr*)client_addr, &addr_len)) < 0){
printf("Accept error!\n");
exit(0);
}else{
printf("Get connection from %s, port: %d. socket: %d\n", \
inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port), clt_sockfd);
}
FD_SET(clt_sockfd, &inset);
}else{
if((len = recv(fd, buff, BUFF_SIZE, 0)) < 0){
printf("Recv failed!\b");
close(fd);
FD_CLR(fd, &inset);
}else if(0 == len || !strncmp("quit", buff, 4)){
printf("Clt_sockfd %d has quited!\n", clt_sockfd);
close(fd);
FD_CLR(fd, &inset);
continue;
}else{
printf("Clt_sockfd %d got message: %s\n", clt_sockfd, buff);
}
}
}
}
continue;
}
}
free(server_addr);
free(client_addr);
}