线程作为程序执行的最小单位,一个进程中可以拥有多条线程,所有线程可以共享进程的内存区域,线程通常在运行时也需要一组寄存器、内存、栈等资源的支撑。
在使用线程模型开发服务器时需考虑以下问题:
1.调整进程内最大文件描述符上限
2.线程如有共享数据,考虑线程同步
3.服务于客户端线程退出时,退出处理。(退出值,分离态)
4.系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU
注:以下server端和client段的代码中的接口函数均采用错误封装处理后的函数(首字母大写),便于调试时定位错误出处,具体错误封装代码可参考以下博文:https://blog.csdn.net/weixin_42734533/article/details/123531214?spm=1001.2014.3001.5502
PS:若不考虑错误函数封装也可,只需将代码中的涉及的接口函数(如:Socket、Listen、Connect…)的首字母改成小写(如:socket、listen、connect…),程序运行时直接调用系统库中的函数,将不会调用错误函数封装中的函数。
#include
#include
#include
#include
#include
#include
#include //IP转换函数
#include //toupper函数头文件
#include
#include
#include
#include
#include
#include //线程头文件
#include "wrap.h" //
#define MAXLINE 800
#define INET_ADDRSTRLEN 16
#define SERV_PORT 6666
struct s_info{ //定义一个结构体,将客户端的地址与connfd进行捆绑
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_ADDRSTRLEN];
while(1)
{
n=Read(ts->connfd,buf,MAXLINE);
if(n==0)
{
printf("the other size has beens closed\n");
break;
}
printf("Data from client is:%s",buf);
for(i=0;i<n;i++)
{
buf[i]=toupper(buf[i]);
}
Write(ts->connfd,buf,MAXLINE); //写回客户端
memset(buf,0,n);
}
Close(ts->connfd);
pthread_exit(0);
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[MAXLINE];
int i, n;
pthread_t tid; //定义pthread_t型的变量
struct s_info ts[100]; //创建结构体数组,设置线程上限
i=0;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20);
printf("Accepting connections ...\n");
cliaddr_len=sizeof(cliaddr);
while(1)
{
connfd=Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
ts[i].cliaddr=cliaddr;
ts[i].connfd=connfd;
pthread_create(&tid,NULL,do_work,(void*)&ts[i]); //将ts[i]作为参数传递给子线程do_work()函数
pthread_detach(tid); //将线程设置成分离的,线程运行结束后会自动释放所有资源
i++; //起下一线程
}
// return 0;
pthread_exit(0);
}
#include
#include
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) {
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
} else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}
pthread_creat()函数创建线程
函数原型声明:
#include
int pthread_creat(
pthread_t *restrict tidp, //新创建的线程ID指向的内存单元。
const pthread_attr_t *restrict attr, //线程属性,默认为NULL
void *(*start_rtn)(void *), //新创建的线程从start_rtn函数的地址开
始运行,该函数的参数就是 void*类型的指针,也就是第四个参数【void *restrict arg】
void *restrict arg //默认为NULL。若上述函数需要参数,将参数放入结构
中并将地址作为arg传入。
)
pthread_detach()作用:线程分离
创建一个线程在运行结束后,存在一部分资源没有回收(退出状态码),所以需要调用pthread_join来等待线程运行结束,从而回收资源。
但是调用pthread_join(pthread_id)后,该线程没有运行结束,调用者会被阻塞。当主线程再创建子线程时,子线程被阻塞,影响使用。基于此,需要在子线程加入代码pthread_detach(pthread_self)或者主线程中调用pthread_detach(thread_id),这就将子线程的状态设置为detached(脱离的)即子线程运行结束后会自动释放所有资源
上述多线程程序在编译过程中会出现:【undefined reference to ‘pthread_create’】
原因在于:pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,需要链接该库。
解决:
在编译中要加 -lpthread参数
gcc thread.c -o thread -lpthread
thread.c为你些的源文件,不要忘了加上头文件#include
编译运行指令如下:
gcc server.c wrap.c -o server -lpthread
PS:接下来将详细介绍,基于多线程的多客户端与服务器交互:
多线程,多客户端与服务器交互