线程类似于进程。如同进程,线程由内核按时间分片进行管理。在单处理器系统中,内核使用时间分片来模拟线程的并发执行,这种方式和进程的相同。而在多处理器系统中,如同多个进程,线程实际上一样可以并发执行。
预备知识:
Linux Socket编程入门——浅显易懂
Linux Socket编程——多进程并发
那么为什么对于大多数合作性任务,多线程比多个独立的进程更优越呢?这是因为,线程共享相同的内存空间。不同的线程可以存取内存中的同一个变量。所以,程序中的所有线程都可以读或写声明过的全局变量。如果曾用 fork() 编写过重要代码,就会认识到这个工具的重要性。为什么呢?虽然 fork() 允许创建多个进程,但它还会带来以下通信问题: 如何让多个进程相互通信,这里每个进程都有各自独立的内存空间。对这个问题没有一个简单的答案。虽然有许多不同种类的本地 IPC (进程间通信),但它们都遇到两个重要障碍:强加了某种形式的额外内核开销,从而降低性能。
pthread_create()函数
#include
int pthread_create(pthread_t* thread_out, pthread_attr_t const* attr,
void* (*start_routine)(void*), void* arg)
与fork()调用创建一个进程的方法不同,pthread_create()创建的线程并不具备与主线程(即调用pthread_create()的线程)同样的执行序列,而是使其运行start_routine(arg)函数。
PS:另外,在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库
参数:
返回值:
Linux系统中程序的线程资源是有限的,表现为对于一个程序其能同时运行的线程数是有限的。而默认的条件下,一个线程结束后,其对应的资源不会被释放,于是,如果在一个程序中,反复建立线程,而线程又默认的退出,则最终线程资源耗尽,进程将不再能建立新的线程。
和进程一样,线程也需要回收。
回收方式:
linux线程执行和windows不同,Linux线程有两种类型:joinable和unjoinable。(可结的和分离的)。创建线程默认是joinable线程。
线程退出:在线程创建后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出的一种方法。另一种退出线程的方法是使用函数pthread_exit(),这是线程的主动行为。这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数来进行出错处理。由于exit()的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后,该进程中的所有线程都终止了。在线程中就可以使用pthread_exit()来代替进程中的exit()。
PS:在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库。
/***
Server端
Author:Liang jie
objective:服务端将客户端输入的小写转换为大写,再传回客户端。
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SRV_PORT 10005
#define MAXLINE 8192
#define INET 100
//定义一个结构体,将地址结构和cfd捆绑在一块
struct s_info {
struct sockaddr_in cliaddr;
int connfd;
};
//线程函数
void *do_work(void *arg){
int n,i;
struct s_info *ts =(struct s_info*)arg;
char buf[MAXLINE];
char str[INET];
while(1){
n=read(ts->connfd,buf,MAXLINE); //读客户端
if (n==0){
printf("the client %d has been closed...\n",ts->connfd);
break;
}
printf("receive from %s at PORT %d\n",
inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr.s_addr,str,sizeof(str)),
ntohs((*ts).cliaddr.sin_port)); //打印客户端信息 ,ip和端口号
for(i=0;i<n;i++)
buf[i]= toupper(buf[i]);
write(ts->connfd,buf,n); //回写到客户端
write(STDOUT_FILENO,buf,n); //写到桌面
printf("\n");
}
close(ts->connfd);
pthread_exit(0);
}
int main(int argc,char *argv[] ){
int lfd,cfd;
pthread_t tid;
int i=0;
struct s_info ts[256]; //创建结构体数组
struct sockaddr_in servaddr,cliaddr;
socklen_t cliaddr_len;
//创建一个socket
lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd<0)
{
printf("socket creation failed\n");
exit(-1);
}
//地址结构清0
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(SRV_PORT);
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
//绑定
if(bind(lfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==-1)
{
printf("Bind error.\n");
exit(-1);
}
//设置同一时刻链接服务器上限数
if(listen(lfd,10)==-1)
{
printf("Listen error!\n");
exit(-1);
}
printf("Start to listen!\n");
cliaddr_len=sizeof(cliaddr);
while(1)
{
//阻塞监听客户端请求
cfd=accept(lfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
ts[i].cliaddr=cliaddr; //客户端的地址结构
ts[i].connfd=cfd;
//创建子线程,因为是只读类型,所以最后一个参数可以传地址
pthread_create(&tid,NULL,do_work,(void *)&ts[i]);
//子线程分离,防止僵尸线程的产生
pthread_detach(tid);
i++;
}
return 0;
}
Client端和上一篇文章一样,不用改动。
点击查看~
使用XShell,链接虚拟机,模拟client端。
文章从线程的概念开始讲解,到创建子线程,然后回收子线程。多线程相比于多进程来说,相对简单。回收也比较简单。
下一篇文章将会讲解Select()下的socket编程。
文章代码如有困难,可以联系博主~~