做煤网站,营销型网站搭建公司,沈阳定制网站开发,雏光 网络推广 网站建设Linux网络编程2-多进程和多线程版本服务器 1.套接字相关函数的封装wrap.h wrap.c2.支持多并发的服务器3.多进程版本分析4.多进程版本实现5.多线程版本分析6.多线程版本实现 1.套接字相关函数的封装wrap.h wrap.c
像accept#xff0c;read这样的能够引起阻塞的函数#xff0c… Linux网络编程2-多进程和多线程版本服务器 1.套接字相关函数的封装wrap.h wrap.c2.支持多并发的服务器3.多进程版本分析4.多进程版本实现5.多线程版本分析6.多线程版本实现 1.套接字相关函数的封装wrap.h wrap.c
像acceptread这样的能够引起阻塞的函数若被信号打断由于信号的优先级较高, 会优先处理信号, 信号处理完成后会使accept或者read解除阻塞, 然后返回, 此时返回值为 -1设置errnoEINTR;
在/usr/include/asm-generic/errno.h文件中包含了errno所有的宏和对应的错误描述信息.
#ifndef __WRAP_H_
#define __WRAP_H_
#include stdlib.h
#include stdio.h
#include unistd.h
#include errno.h
#include string.h
#include sys/socket.h
#include arpa/inet.h
#include strings.hvoid 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 salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_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);
int tcp4bind(short port,const char *IP);
#endif#include stdlib.h
#include stdio.h
#include unistd.h
#include errno.h
#include string.h
#include sys/socket.h
#include arpa/inet.h
#include strings.hvoid 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 salen)
{int n;if ((n connect(fd, sa, salen)) 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;
}ssize_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 n;
}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; //usigned int 剩余未读取的字节数ssize_t nread; //int 实际读到的字节数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;nleft - nread;ptr nread;}return n - nleft;
}ssize_t Writen(int fd, const void *vptr, size_t n)
{size_t nleft;ssize_t nwritten;const char *ptr;ptr 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;
}static 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;} elsereturn -1;}*ptr 0;return n;
}int tcp4bind(short port,const char *IP)
{struct sockaddr_in serv_addr;int lfd Socket(AF_INET,SOCK_STREAM,0);bzero(serv_addr,sizeof(serv_addr));if(IP NULL){//如果这样使用 0.0.0.0,任意ip将可以连接serv_addr.sin_addr.s_addr INADDR_ANY;}else{if(inet_pton(AF_INET,IP,serv_addr.sin_addr.s_addr) 0){perror(IP);//转换失败exit(1);}}serv_addr.sin_family AF_INET;serv_addr.sin_port htons(port);Bind(lfd,(struct sockaddr *)serv_addr,sizeof(serv_addr));return lfd;
}2.支持多并发的服务器
如何支持多个客户端—支持多并发的服务器 由于accept和read函数都会阻塞, 如当read的时候, 不能调用accept接受新的连接, 当accept阻塞等待的时候不能read读数据. 第一种方案: 使用多进程, 可以让父进程接受新连接, 让子进程处理与客户端通信 思路: 让父进程accept接受新连接, 然后fork子进程, 让子进程处理通信, 子进程处理完成后退出, 父进程使用SIGCHLD信号回收子进程. 第二种方案: 使用多线程, 让主线程接受新连接, 让子线程处理与客户端通信; 使用多线程要将线程设置为分离属性, 让线程在退出之后自己回收资源. 如何不使用多进程或者多线程完成多个客户端的连接请求 可以将accept和read函数设置为非阻塞, 调用fcntl函数可以将文件描述符设置为非阻塞, 让后再while循环中忙轮询.
3.多进程版本分析
阻塞函数在阻塞期间若收到信号会被信号终端errno设置为EINTR这个错误不应该看成一个错误.
while(1)
{cfd accept();while(1){n read(cfd, buf, sizeof(buf));if(n0){break;}}
}解决办法1: 将cfd设置为非阻塞: fcntl 缺点假如有多个客户端连接请求, cfd只会保留最后一个文件描述符的值 解决方法2: 使用多进程: 让父进程监听接受新的连接, 子进程处理新的连接(接收和发送数据); 父进程还负责回收子进程 多进程版本思路子进程复制父进程的文件描述符
//1 创建socket, 得到一个监听的文件描述符lfd---socket()
//2 将lfd和IP和端口port进行绑定-----bind();
//3 设置监听----listen()
//4 进入while
while(1)
{//等待有新的客户端连接到来cfd accept();//fork一个子进程, 让子进程去处理数据pid fork();if(pid0){exit(-1);}else if(pid0){//关闭通信文件描述符cfdclose(cfd);}else if(pid0){//关闭监听文件描述符close(lfd);//收发数据while(1){//读数据n read(cfd, buf, sizeof(buf));if(n0){break;}//发送数据给对方write(cfd, buf, n);}close(cfd);//下面的exit必须有, 防止子进程再去创建子进程exit(0);}
}
close(lfd);还需要添加的功能: 父进程使用SIGCHLD信号完成对子进程的回收 注意点: accept或者read函数是阻塞函数会被信号打断此时不应该视为一个错误。errnoEINTR
父子进程能够共享的: 文件描述符(子进程复制父进程的文件描述符) mmap共享映射区
4.多进程版本实现
//多进程版本的网络服务器
#include stdio.h
#include stdlib.h
#include string.h
#include sys/types.h
#include unistd.h
#include arpa/inet.h
#include netinet/in.h
#include ctype.h
#include wrap.hint main()
{//创建socketint lfd Socket(AF_INET, SOCK_STREAM, 0);//绑定struct sockaddr_in serv;bzero(serv, sizeof(serv));serv.sin_family AF_INET;serv.sin_port htons(8888);serv.sin_addr.s_addr htonl(INADDR_ANY);Bind(lfd, (struct sockaddr *)serv, sizeof(serv));//设置监听Listen(lfd, 128);pid_t pid;int cfd;char sIP[16];socklen_t len;struct sockaddr_in client;while(1){//接受新的连接len sizeof(client);memset(sIP, 0x00, sizeof(sIP));cfd Accept(lfd, (struct sockaddr *)client, len);printf(client:[%s] [%d]\n, inet_ntop(AF_INET, client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));//接受一个新的连接, 创建一个子进程,让子进程完成数据的收发操作pid fork();if(pid0){perror(fork error);exit(-1);}else if(pid0){//关闭通信文件描述符cfdclose(cfd); }else if(pid0){//关闭监听文件描述符close(lfd);int i0;int n;char buf[1024];while(1){//读数据n Read(cfd, buf, sizeof(buf));if(n0){printf(read error or client closed, n[%d]\n, n);break;}//printf(client:[%s] [%d]\n, inet_ntop(AF_INET, client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));printf([%d]----:n[%d], buf[%s]\n, ntohs(client.sin_port), n, buf);//将小写转换为大写for(i0; in; i){buf[i] toupper(buf[i]);}//发送数据Write(cfd, buf, n);}//关闭cfdclose(cfd);exit(0);}}//关闭监听文件描述符close(lfd);return 0;
}改进增加对子进程的回收
//多进程版本的服务器
#include stdio.h
#include stdlib.h
#include string.h
#include sys/types.h
#include unistd.h
#include arpa/inet.h
#include netinet/in.h
#include signal.h
#include sys/wait.h
#include ctype.h
#include wrap.h//信号处理函数
void waitchild(int signo)
{pid_t wpid;while(1){wpid waitpid(-1, NULL, WNOHANG);if(wpid0){printf(child exit, wpid[%d]\n, wpid);}else if(wpid0 || wpid-1){break;}}
}int main()
{//创建socketint lfd Socket(AF_INET, SOCK_STREAM, 0);//设置端口复用int opt 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(int));//绑定-bindstruct sockaddr_in serv;serv.sin_family AF_INET;serv.sin_port htons(8888);serv.sin_addr.s_addr htonl(INADDR_ANY);Bind(lfd, (struct sockaddr *)serv, sizeof(serv));//监听-listenListen(lfd, 128);//阻塞SIGCHLD信号sigset_t mask;sigemptyset(mask);sigaddset(mask, SIGCHLD);sigprocmask(SIG_BLOCK, mask, NULL);int cfd;socklen_t len;char sIP[16];pid_t pid;struct sockaddr_in client;while(1){//等待客户端连接--acceptmemset(sIP, 0x00, sizeof(sIP));len sizeof(client);bzero(client, sizeof(client));cfd Accept(lfd, (struct sockaddr *)client, len); printf(client--[%s]:[%d]\n, inet_ntop(AF_INET, client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));//创建子进程pid fork();if(pid0){perror(fork error);close(lfd);return -1;} else if(pid0) //父进程{//关闭通信的文件描述符close(cfd);//注册SIGCHLD信号处理函数struct sigaction act;act.sa_handler waitchild;act.sa_flags 0;sigemptyset(act.sa_mask);sigaction(SIGCHLD, act, NULL);//解除对SIGCHLD信号的阻塞sigprocmask(SIG_UNBLOCK, mask, NULL);}else if(pid0) //子进程{//关闭监听文件描述符close(lfd); int i 0;int n 0;char buf[1024];while(1){memset(buf, 0x00, sizeof(buf));n Read(cfd, buf, sizeof(buf));if(n0) {printf(read error or client closed, n[%d]\n, n);break; }printf(read over, n[%d],buf[%s]\n, n, buf);for(i0; in; i){buf[i] toupper(buf[i]);}write(cfd, buf, n);}close(cfd);exit(0);}}//关闭监听文件描述符close(lfd);return 0;
}5.多线程版本分析
//1 创建socket, 得到一个监听的文件描述符lfd---socket()
//2 将lfd和IP和端口port进行绑定-----bind();
//3 设置监听----listen()
//4 while循环
while(1)
{//接受新的客户端连接请求cfd accept();//创建一个子线程pthread_create(threadID, NULL, thread_work, cfd);//设置线程为分离属性pthread_detach(threadID);}
close(lfd);void *thread_work(void *arg)
{//获得参数: 通信文件描述符int cfd *(int *)arg;while(1){//读数据n read(cfd, buf, sizeof(buf));if(n0){break;}//发送数据write(cfd, buf, n);}close(cfd);
}问题: 1 子线程能否关闭lfd? 子线程不能关闭监听文件描述符lfd原因是子线程和主线程共享文件描述符而不是复制的.
2 主线程能否关闭cfd? 主线程不能关闭cfd主线程和子线程共享一个cfd而不是复制的close之后cfd就会被真正关闭.
3 多个子线程共享cfd, 会有什么问题发生? 只有一个子线程能够通信。
6.多线程版本实现
//多线程版本的高并发服务器
#include stdio.h
#include stdlib.h
#include string.h
#include sys/types.h
#include unistd.h
#include arpa/inet.h
#include netinet/in.h
#include ctype.h
#include pthread.h
#include wrap.h//子线程回调函数
void *thread_work(void *arg)
{sleep(20);int cfd *(int *)arg;printf(cfd[%d]\n, cfd);int i;int n;char buf[1024];while(1){//read数据memset(buf, 0x00, sizeof(buf));n Read(cfd, buf, sizeof(buf));if(n0){printf(read error or client closed,n[%d]\n, n);break;}printf(n[%d], buf[%s]\n, n, buf);for(i0; in; i){buf[i] toupper(buf[i]);}//发送数据给客户端Write(cfd, buf, n); }//关闭通信文件描述符close(cfd);pthread_exit(NULL);
}int main()
{//创建socketint lfd Socket(AF_INET, SOCK_STREAM, 0);//设置端口复用int opt 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(int));//绑定struct sockaddr_in serv;bzero(serv, sizeof(serv));serv.sin_family AF_INET;serv.sin_port htons(8888);serv.sin_addr.s_addr htonl(INADDR_ANY);Bind(lfd, (struct sockaddr *)serv, sizeof(serv));//设置监听Listen(lfd, 128);int cfd;pthread_t threadID;while(1){//接受新的连接cfd Accept(lfd, NULL, NULL);//创建子线程pthread_create(threadID, NULL, thread_work, cfd);//设置子线程为分离属性pthread_detach(threadID);}//关闭监听文件描述符close(lfd);return 0;
}上面代码中accept接收3个连接创建3个子线程这些子线程共享threadId结构与cfd变量因此需要一个线程对应一个。数组。
// 创建100个INFO结构最多可以接收100个连接
struct INFO
{int cfd;pthread_t threadID;struct sockaddr_in client;
};
struct INFO info[100];//初始化INFO数组
for(i0; i100; i)
{info[i].cfd-1;
}// 接收连接以后从数组中分配一个INFO结构
for(i0; i100; i)
{if(info[i].cfd-1){//这块内存可以使用}
}
if(i100) // 没有空闲的INFO结构拒绝接收新的连接
{//拒绝接受新的连接close(cfd);
}//多线程版本的服务器
#include stdio.h
#include stdlib.h
#include string.h
#include sys/types.h
#include unistd.h
#include arpa/inet.h
#include netinet/in.h
#include ctype.h
#include pthread.h
#include wrap.htypedef struct info
{int cfd; //若为-1表示可用, 大于0表示已被占用int idx;pthread_t thread;struct sockaddr_in client;
}INFO;INFO thInfo[1024];//线程执行函数
void *thread_work(void *arg)
{INFO *p (INFO *)arg;printf(idx[%d]\n, p-idx);char sIP[16];memset(sIP, 0x00, sizeof(sIP));printf(new client:[%s][%d]\n, inet_ntop(AF_INET, (p-client.sin_addr.s_addr), sIP, sizeof(sIP)), ntohs(p-client.sin_port));int n;int cfd p-cfd;struct sockaddr_in client;memcpy(client, (p-client), sizeof(client));char buf[1024];while(1){memset(buf, 0x00, sizeof(buf));//读数据n Read(cfd, buf, sizeof(buf));if(n0){printf(read error or client closed, n[%d]\n, n);Close(cfd);p-cfd -1; //设置为-1表示该位置可用pthread_exit(NULL);}for(int i0; in; i){buf[i] toupper(buf[i]);}//发送数据Write(cfd, buf, n);}
}void init_thInfo()
{int i 0;for(i0; i1024; i){thInfo[i].cfd -1;;}
}int findIndex()
{int i;for(i0; i1024; i){if(thInfo[i].cfd-1){break;}}if(i1024){return -1;}return i;
}int main()
{//创建socketint lfd Socket(AF_INET, SOCK_STREAM, 0);//设置端口复用int opt 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(int));//绑定--将lfd 和 IP PORT绑定struct sockaddr_in serv;bzero(serv, sizeof(serv));serv.sin_family AF_INET;serv.sin_port htons(8888);serv.sin_addr.s_addr htonl(INADDR_ANY);Bind(lfd, (struct sockaddr *)serv, sizeof(serv));//监听Listen(lfd, 128);//初始化init_thInfo();int cfd;int ret;int idx;socklen_t len;pthread_t thread;struct sockaddr_in client;while(1){len sizeof(client);bzero(client, sizeof(client));//获得一个新的连接cfd Accept(lfd, (struct sockaddr *)client, len);//创建一个子进程, 让子进程处理连接---接收数据和发送数据//找数组中空闲的位置idx findIndex();if(idx-1){Close(cfd);continue;}//对空闲位置的元素的成员赋值thInfo[idx].cfd cfd;thInfo[idx].idx idx;memcpy(thInfo[idx].client, client, sizeof(client));//创建子线程---该子线程完成对数据的收发ret pthread_create(thInfo[idx].thread, NULL, thread_work, thInfo[idx]);if(ret!0){printf(create thread error:[%s]\n, strerror(ret));exit(-1);}//设置子线程为分离属性pthread_detach(thInfo[idx].thread);}Close(lfd);return 0;}