Linux多线程并发式服务器的创建
背景:
服务器(server)在处理多个客户端(client)的连接请求时,如果服务器里面只有一个进程已经不能满足现有需求,客户端需要排队等待与服务器连接;为了实现服务器同时接受处理多个客户端的连接请求和数据传输,此时可以用三种方式来解决这个问题;
1.创建一个多线程的并发式服务器
2.创建一个多进程的并发式服务器
3.创建一个I/O多路复用的服务器
这次来学习第一种方法多线程的并发式服务器的创建;
需求:
<1>可以同时完成对多个服务器的监听
<2>服务器和每一个客户端都能进行简单的数据传输
思路:
<1>首先创建一个服务器进行初始化
(1)创建服务器套接字的文件描述符
(2)设置服务器的IP地址的端口号
(3)启动服务器
<2>对多个客户端的连接请求进行处理
此步骤中每个客户端与服务器建立连接后就进入一个子线程去执行数据传输任务,然后服务器就可以继续对其他要来连接的客户端进行监听连接请求;就是每有一个客户端来发出连接请求并且连接成功之后,服务器的主线程里面就创建一个新的子线程去执行这个新连接的客户端的数据传输任务;以此来完成服务器和多个客户端的连接;
<3>创建子线程的处理函数
在子线程处理函数中利用read和write函数来完成简单的数据发送传输;
<4>将子线程和主线程进行分离
可以调用线程中的 pthread_detach() 函数(详解见附1)
实现步骤:
<1>创建一个服务器进行初始化
pthread_server.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
int main()
{
int ret;
int listenfd; //监听套接字
struct sockaddr_in srvaddr; //存储服务器的ip地址和端口号
struct sockaddr_in cltvaddr; //存储客户端的ip地址和端口号
/* 1.创建服务器套接字文件描述符 */
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1)
{
perror("server->socket");
return -1;
}
/* 2.设置服务器的IP地址的端口号 */
memset(&srvaddr, 0, sizeof(srvaddr));
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(8888);
srvaddr.sin_addr.s_addr = inet_addr("192.168.35.130");
ret = bind(listend, (const struct sockaddr *)&srvaddr, sizeof(srvaddr));
if(ret == -1)
{
perror("server->bind");
return -1;
}
printf("bind success!\n");
/* 3.启动服务器 */
ret = listen(listenfd, 5); //设置一次处理5个客户端的数据
if(ret == -1)
{
perror("server->listen");
return -1;
}
printf("listen success!\n");
}
<2>对多个客户端的连接请求进行处理
<4>将子线程和主线程进行分离
这里我们可以使用一个while()循环来实现对多个客户端的连接请求进行处理,每和客户端建立连接成功后就使用 pthread_create() 函数来创建一个新的线程,所以此时我们要新定义一个 pthread_t 类型的变量来存储线程号;
注意:
1.accept()函数的第二个参数类型和(struct sockaddr_in)cltaddr类型不匹配需要强转一下为(struct sockaddr *)
2.在线程创建函数传参数时需要将通信套接字进行强制的类型转换为(void *)型;
3.pthread_create()创建失败时返回值不为0,不能修改errno的值,所以不可以用perror()函数,要用fprintf()函数进行错误输出;
#include <pthread.h>
/********************/
.......
服务器的创建及初始化
.......
/********************/
int connfd; //通信套接字
struct sockaddr_in cltvaddr; //存储客户端的ip地址和端口号
socklen_t addrlen; //存储客户端ip地址和端口号长度
pthread_t clt_thread; //线程号
while(1)
{
/* 4. 监听客户端的连接请求,建立连接 */
addrlen = sizeof(cltaddr);
connfd = accept(listend, (struct sockaddr *)&cltaddr, &addrlen);
if(connfd == -1)
{
perror("server->accept");
return -1;
}
printf("connfd = %d, IP: %s, Port: %d\n", connfd, inet_ntoa(clt.sin_addr), ntohs(cltaddr.sin_port));
/* 5.创建新的子线程,用子线程处理函数来处理客户端的数据传输,主线程继续执行监听客户端的连接申请 */
ret = pthread_create(&clt_thread, NULL, clt_fun, (void *)&connfd);
if(ret != 0)
{
fprintf(stderr, "creat client fail\n");
return -1;
}
/* 子线程和主线程的分离 */
pthread_detach(clt_thread);
}
<3>创建子线程的处理函数
实现功能:
1.利用read()函数对客户端发送的数据进行读取输出;
2.将读取到的值用write()函数再返回给客户端;
注意:
1.参数connfd传入时被强转为了(void *)型,这里再调用时要将它强转为int 型;
2.处理函数是一个指针函数,它的返回值为一个指针;
void *clt_fun(void *arg)
{
int ret;
int connfd = *(int *)arg;
char buf[256] = {0};
while(1)
{
ret = read(connfd, buf, sizeof(buf));
if(ret == -1)
{
perror("server->read");
return NULL;
}
else if(ret == 0)
{
printf("client quit\n");
break;
}
printf("client:%s", buf);
ret = write(connfd, buf, sizeof(buf));
if(ret == -1)
{
perror("server->write");
return NULL;
}
}
}
完整代码:
pthread_server.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <pthread.h>
void *clt_fun(void *arg)
{
int ret;
int connfd = *(int *)arg;
char buf[256] = {0};
while(1)
{
ret = read(connfd, buf, sizeof(buf));
if(ret == -1)
{
perror("server->read");
return NULL;
}
else if(ret == 0)
{
printf("client quit\n");
break;
}
printf("client:%s", buf);
ret = write(connfd, buf, sizeof(buf));
if(ret == -1)
{
perror("server->write");
return NULL;
}
}
}
int main()
{
int ret;
int listenfd; //监听套接字
struct sockaddr_in srvaddr; //存储服务器的ip地址和端口号
struct sockaddr_in cltvaddr; //存储客户端的ip地址和端口号
int connfd; //通信套接字
socklen_t addrlen; //存储客户端ip地址和端口号长度
pthread_t clt_thread; //线程号
/* 1.创建服务器套接字文件描述符 */
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1)
{
perror("server->socket");
return -1;
}
/* 2.设置服务器的IP地址的端口号 */
memset(&srvaddr, 0, sizeof(srvaddr));
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(8888);
srvaddr.sin_addr.s_addr = inet_addr("192.168.35.130");
ret = bind(listend, (const struct sockaddr *)&srvaddr, sizeof(srvaddr));
if(ret == -1)
{
perror("server->bind");
return -1;
}
printf("bind success!\n");
/* 3.启动服务器 */
ret = listen(listenfd, 5); //设置一次处理5个客户端的数据
if(ret == -1)
{
perror("server->listen");
return -1;
}
printf("listen success!\n");
while(1)
{
/* 4. 监听客户端的连接请求,建立连接 */
addrlen = sizeof(cltaddr);
connfd = accept(listend, (struct sockaddr *)&cltaddr, &addrlen);
if(connfd == -1)
{
perror("server->accept");
return -1;
}
printf("connfd = %d, IP: %s, Port: %d\n", connfd, inet_ntoa(clt.sin_addr), ntohs(cltaddr.sin_port));
/* 5.创建新的子线程,用子线程处理函数来处理客户端的数据传输,主线程继续执行监听客户端的连接申请 */
ret = pthread_create(&clt_thread, NULL, clt_fun, (void *)&connfd);
if(ret != 0)
{
fprintf(stderr, "creat client fail\n");
return -1;
}
/* 子线程和主线程的分离 */
pthread_detach(clt_thread);
}
close(connfd);
return 0;
}
实验现象:
附1:
pthread_detach()函数介绍>>
内容:
#include <pthread.h>
int pthread_detach(pthread_t thread);
编译:
Compile and link with -pthread;
描述:
When a detached thread terminates, its resources are automatically released back to the system without the need for another thread to join with the terminated thread.
当一个线程停止时,资源不需要在另一个线程停止时自动释放回系统;
自己理解:
每当有一个线程结束时就会回收他的资源,不需要阻塞等待上一个线程的结束;这样就将每个线程的独立分开运行,结束时互不影响;
本博客记录学习过程中的总结,可能会存在诸多不严谨和部分的理解错误,仅供参考,互相交流