帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
服务器资讯 / 香港服务器租用 / 香港VPS租用 / 香港云服务器 / 美国服务器租用 / 台湾服务器租用 / 日本服务器租用 / 官方公告 / 帮助文档
常见的socket函数封装和多进程和多线程实现服务器并发
发布时间:2024-03-03 05:43:45   分类:帮助文档
常见的socket函数封装和多进程和多线程实现服务器并发 常见的socket函数封装和多进程和多线程实现服务器并发 1.常见的socket函数封装2.多进程和多线程实现服务器的并发2.1多进程服务器2.2多线程服务器2.3运行效果 1.常见的socket函数封装 accept函数或者read函数是阻塞函数,会被信号打断,我们不能让它停止,所以我们应该进行一些封装操作。 //wrap.h #ifndef __WRAP_H_ #define __WRAP_H_ #include #include #include #include #include #include #include #include void 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 下面是相关函数的实现 //wrap.c #include #include #include #include #include #include #include #include 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; else perr_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; else return -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; else return -1; } return n; } int Close(int fd) { int n; if ((n = close(fd)) == -1) perr_exit("close error"); return n; } 2.多进程和多线程实现服务器的并发 当有多个客户端向服务器发送数据的时候,我们如何去操作,这就涉及到了我们的多线程和多进程开发了,下面看看如何来实现。 2.1多进程服务器 (1)首先我们想如何通过多进程来实现呢?那么我们得想清楚父子进程分别来干啥,我们可以这样,父进程来获取连接。 (2)然后子进程来进行通信发送数据给服务端。 (3)最后我们利用信号的方式来回收子进程,防止出现僵尸进程。 /*多进程实现并发,主进程中使用sigaction函数回收子进程*/ #include #include #include #include #include "wrap.h" void sighandler(int sig) { pid_t wpid; //回收子进程 while(1) { wpid = waitpid(-1, NULL, WNOHANG); if(wpid <= 0) { break; } } } int main() { int lfd = Socket(AF_INET, SOCK_STREAM, 0); //设置端口复用 int opt = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)); struct sockaddr_in serverAddr; bzero(&serverAddr, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8888); serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); Bind(lfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); Listen(lfd, 128); //将SIGCHLD信号阻塞 sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); sigprocmask(SIG_BLOCK, &mask, NULL); int cfd; pid_t mpid; struct sockaddr_in clientAddr; socklen_t len = sizeof(clientAddr); while(1) { cfd = Accept(lfd, (struct sockaddr*)&clientAddr, &len); mpid = fork(); if (mpid < 0) { perror("fork error:"); exit(0); } else if (mpid > 0) { close(cfd); //signal(SIGCHLD, sighandler); //注册信号处理函数 struct sigaction act; act.sa_handler = sighandler; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGCHLD, &act, NULL); //解除对SIGCHLD信号的阻塞 sigprocmask(SIG_UNBLOCK, &mask, NULL); } else if(mpid == 0) { //子进程中执行消息收发 close(lfd); char buf[1024]; int nLen; char cIP[16]; while(1) { memset(buf, 0, sizeof(buf)); nLen = Read(cfd, buf, sizeof(buf)); if (nLen <= 0) { perror("read error:"); break; } printf("%s--%d: %s\n", inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, cIP, sizeof(cIP)) , ntohs(clientAddr.sin_port), buf); strcat(buf, "---recvied"); Write(cfd, buf, strlen(buf)); } close(cfd); exit(0); //子进程退出,防止子进程继续创建子进程 } } close(lfd); return 0; } 2.2多线程服务器 接下来就是多线程服务器如何去实现呢? 我们可以参考上面的多进程开发: (1)首先我们利用主进程来获取连接。 (2)然后利用子线程来和服务器进行通信给服务器发送数据。 (3)最后设置线程分离属性,任务完成后自动回收子线程。 注意: (1)线程和进程之间是有不同的,线程的文件描述符时共享的,一旦有一个新的连接过来的时候,所有的通信文件描述符cfd都会改变,但是进程时写时拷贝的,所以进程不会出现这种情况。因此在使用线程开发时,我们要分别给他们开辟空间,这里可以用一个结构体,不同线程使用不同的空间。 (2)由于线程的文件描述符是共享的,所以我们不可以关闭父线程的通信文件描述符,这样会导致子线程的通信文件描述符全关闭,导致子线程无法正常通信;而进程程会有计数引用,只会是通信文件描述符的引用次数减1,不会直接全部关闭。 下面是代码: /*多线程实现并发, 解决多个子线程共享cfd存在的问题*/ #include "wrap.h" #include #define MAX_NUM 100 struct PthreadInfo { int cfd; //若为-1表示可用, 大于0表示已被占用 pthread_t threadID; struct sockaddr_in clientAddr; }; //定义结构体数组,不同的线程访问不同的内存 struct PthreadInfo info[MAX_NUM]; //线程执行函数 void* mythread(void* arg) { struct PthreadInfo* p = (struct PthreadInfo*)arg; char buf[1024]; int cfd = p->cfd; ssize_t len; while (1) { memset(buf, 0, sizeof(buf)); len = Read(cfd, buf, sizeof(buf)); if (len <= 0) { perror("read error:"); close(cfd); p->cfd = -1; //设置为-1表示该位置可用 pthread_exit(NULL); } printf("%s\n", buf); strcat(buf, "---recvied"); Write(cfd, buf, strlen(buf)); } } void init_info() { //初始化数组,当cfd = -1时表明这块内存空间可以使用 for (size_t i = 0; i < MAX_NUM; i++) { info[i].cfd = -1; } } int find_index() { int i; for(i = 0; i < MAX_NUM; i++) { if (info[i].cfd == -1) { break; } } if (i == MAX_NUM) { return -1; } return i; } int main() { int lfd = Socket(AF_INET, SOCK_STREAM, 0); //设置端口复用 int opt = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)); struct sockaddr_in serverAddr; bzero(&serverAddr, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8888); serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); Bind(lfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); Listen(lfd, 128); //初始化 init_info(); int cfd; int ret; int idx; socklen_t len; struct sockaddr_in client; while (1) { len = sizeof(client); bzero(&client, len); cfd = Accept(lfd, (struct sockaddr*)&client, &len); //找数组中空闲的位置 idx = find_index(); if (idx == -1) { close(cfd); continue; } //对空闲位置的元素的成员赋值 info[idx].cfd = cfd; memset(&info[idx].clientAddr, &client, len); //创建子线程---该子线程完成对数据的收发 ret = pthread_create(&info[idx].threadID, NULL, mythread, &info[idx]); if(ret!=0) { printf("create thread error:[%s]\n", strerror(ret)); exit(-1); } //设置子线程为分离属性 pthread_detach(info[idx].threadID); } close(lfd); return 0; } 我们在写的时候发现当一些进程完成通信以后,关闭文件描述符,我们的空间是无法进行回收的,这样就会大大浪费空间,因此我们可以写一个函数来返回结束通信的空间位置可利用的空间,来使用这块空间。 int find_index() { int i; for(i = 0; i < MAX_NUM; i++) { if (info[i].cfd == -1) { break; } } if (i == MAX_NUM) { return -1; } return i; } 2.3运行效果 下面我们看看效果 1.这是连接的第一个客户端,可以看到通信正常 2.这是连接的第二个客户端,通信也正常 3.我们用命令来看看连接的状态 可以看到tcp连接是一个双向的可靠连接,我们连接了两个客户端,所以有四个连接,可以看到都处于ESTABLISHESD的状态,可以看出是达到了效果。两个客户端和服务端的通信都是正常的。
香港云服务器租用推荐
服务器租用资讯
·广东云服务有限公司怎么样
·广东云服务器怎么样
·广东锐讯网络有限公司怎么样
·广东佛山的蜗牛怎么那么大
·广东单位电话主机号怎么填写
·管家婆 花生壳怎么用
·官网域名过期要怎么办
·官网邮箱一般怎么命名
·官网网站被篡改怎么办
服务器租用推荐
·美国服务器租用
·台湾服务器租用
·香港云服务器租用
·香港裸金属服务器
·香港高防服务器租用
·香港服务器租用特价