多线程并发服务器
基于该视频完成
12-多线程并发服务器分析_哔哩哔哩_bilibili
通过的是非阻塞忙轮询的方式实现的
和阻塞等待的区别就是,阻塞是真的阻塞了,而这个方式是一直在问有没有请求有没有请求
linux | c | 多进程并发服务器实现-CSDN博客
可以先看看这篇博客,思路和功能都一样
文章目录
- 多线程并发服务器
- 1.核心思路&功能
- 2.代码实现
- warp.h
- warp.c
- multi_thread_concurrency_sever.c
- 运行图
- 3.代码解释
1.核心思路&功能
实现一个服务器可以连接多个客户端,每当accept函数等待到客户端进行连接时 就创建一个子进程;
核心思路:让accept循环阻塞等待客户端,每当有客户端连接时就fork子进程,让子进程去和客户端进行通信,父进程用于监听并使用信号捕捉回收子进程;(子进程关闭用于监听的套接字lfd,父进程关闭用于通信的cfd)
**功能:**客户端输入小写字符串,服务器转成大写返回给客户端
2.代码实现
warp.h
#ifndef __WRAP_H_
#define __WRAP_H_
#include<sys/epoll.h>
//#include<event2/event.h>
#include<sys/select.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<string.h>
#include<dirent.h>
#include<sys/stat.h>
#include<wait.h>
#include<sys/mman.h>
#include<signal.h>
#include<pthread.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<strings.h>
#include<netinet/ip.h>
#define SRV_PORT 1234void perr_exit(const char *s);
int Accept(int fd,struct sockaddr *sa,socklen_t * salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t addrlen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
size_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd,const void *ptr,size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);#endif
warp.c
#include"warp.h"void perr_exit(const char *s)
{perror(s);exit(1);
}int Accept(int fd,struct sockaddr *sa,socklen_t * salenptr)
{int n;
again:if((n=accept(fd,sa,salenptr))<0){if((errno==ECONNABORTED)||(errno==EINTR))goto again;elseperr_exit("accept error");}return n;
}int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if((n=bind(fd,sa,salen))<0)perr_exit("bind error");return n;
}int Connect(int fd, const struct sockaddr *sa, socklen_t addrlen)
{int n;n=connect(fd,sa,addrlen);if(n<0){perr_exit("connect error");}return n;
}int Listen(int fd, int backlog)
{int n;if((n=listen(fd,backlog))<0)perr_exit("listen error");return n;
}int Socket(int family, int type, int protocol)
{int n;if((n=socket(family,type,protocol))<0)perr_exit("socket error");return n;
}size_t Read(int fd, void *ptr, size_t nbytes)
{ssize_t n;
again:if((n=read(fd,ptr,nbytes))==-1){if(errno==EINTR)goto again;elsereturn -1;}return n;
}ssize_t Write(int fd,const void *ptr,size_t nbytes)
{ssize_t n;
again:if((n=write(fd,ptr,nbytes))==-1){if(errno==EINTR)goto again;elsereturn -1;}return 0;
}int Close(int fd)
{int n;if((n=close(fd))==-1)perr_exit("close error");return n;
}ssize_t Readn(int fd, void *vptr, size_t n)
{size_t nleft;ssize_t nread;char *ptr;ptr=vptr;nleft=n;while(nleft>0){if((nread=read(fd,ptr,nleft))<0){if(errno==EINTR)nread=0;elsereturn -1;}else if(nread==0)break;}
}
ssize_t Writen(int fd, const void *vptr, size_t n)
{size_t nleft;ssize_t nwritten;char *ptr;ptr=(char *)vptr;nleft=n;while(nleft>0){if((nwritten=write(fd,ptr,nleft))<=0){if(nwritten<0&&errno==EINTR)nwritten=0;elsereturn -1;}nleft-=nwritten;ptr+=nwritten;}return n;
}ssize_t my_read(int fd, char *ptr)
{static int read_cnt;static char *read_ptr;static char read_buf[100];if(read_cnt<=0){
again:if((read_cnt=read(fd,read_buf,sizeof(read_buf)))<0){if(errno==EINTR)goto again;return -1;}else if(read_cnt==0)return 0;read_ptr=read_buf;}read_cnt--;*ptr=*read_ptr++;return 1;
}ssize_t Readline(int fd, void *vptr, size_t maxlen)
{ssize_t n,rc;char c,*ptr;ptr=vptr;for(n=1;n<maxlen;n++){if((rc=my_read(fd,&c))==1){*ptr++=c;if(c=='\n')break;}else if(rc==0){*ptr=0;return n-1;}else return -1;}*ptr=0;return n;
}
multi_thread_concurrency_sever.c
#include"warp.h" #define MAXLINE 8192
#define SERV_PORT 1234struct 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_ADDRSTRLEN];while(1){n=Read(ts->connfd,buf,MAXLINE);if(n==0){printf("the client %d closed..\n",ts->connfd);break;}printf("received from %s at port %d\n",inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr,str,sizeof(str)),ntohs((*ts).cliaddr.sin_port));for(i=0;i<n;i++)buf[i]=toupper(buf[i]);Write(STDOUT_FILENO,buf,n);Write(ts->connfd,buf,n);}Close(ts->connfd);return (void *)0;
}int main(int argc,char * argv[])
{ struct sockaddr_in servaddr,cliaddr;socklen_t cliaddr_len;int listenfd,connfd;pthread_t tid;struct s_info ts[256];int i=0;listenfd=Socket(AF_INET,SOCK_STREAM,0);bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(SRV_PORT);servaddr.sin_addr.s_addr=htonl(INADDR_ANY);Bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));Listen(listenfd,128);printf("Accepting client connect ..\n");while(1){cliaddr_len=sizeof(cliaddr);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]);pthread_detach(tid);//子线程分离,防止僵尸线程产生i++; }return 0;
}
gcc warp.c multi_thread_concurrency_sever.c -o multi_process_thread_sever -lpthread
运行图
两个客户端访问服务器端
3.代码解释
1.指定的固定端口号为1234
也可以用argv接受ip和port参数
2.srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
sin_addr
是struct sockaddr_in
结构体中的一个嵌套结构体,其成员s_addr
用于存放 IP 地址信息(以 32 位整数形式表示)。htonl
函数和htons
类似,不过它是将主机字节序的 32 位整数(通常用于 IP 地址)转换为网络字节序。INADDR_ANY
是一个特殊的常量,它表示服务器端套接字可以绑定到本机的任意可用 IP 地址上,通过这行代码,将转换为网络字节序后的INADDR_ANY
值赋给了srv_addr
结构体的sin_addr.s_addr
成员,使得服务器能够监听来自本机所有网络接口上对应端口的连接请求。
3.需要用到的调用都需要错误处理,封装到warp.c后进行使用
4.子线程做的事情:(和子进程做的事情是一样的)
子线程的回调函数
void *tfn(void *arg){
close(lfd)关闭监听套接字
read()
逻辑处理:小写转大写
write()
}
5.主线程做的事情:创建套接字和连接
6.也可以创建一个回收子线程的线程(只回收线程,不干别的)
pthread_join(tid,void **)
也可以用detach,这样就不用管子线程回收的问题了
7.线程分离(pthread_detach
)的基本概念
- 当使用
pthread_detach
函数对一个线程进行分离操作后,该线程就变成了一个 “分离线程”。在这种状态下,线程结束时其资源会由系统自动回收,而不需要其他线程(如主线程)显式地调用pthread_join
函数来回收资源。