杭州哪家网站建设公司好,保山市住房和城乡建设厅网站,农商1号的网站建设费,wordpress自定义页面跳转常见的socket函数封装和多进程和多线程实现服务器并发 1.常见的socket函数封装2.多进程和多线程实现服务器的并发2.1多进程服务器2.2多线程服务器2.3运行效果 1.常见的socket函数封装 accept函数或者read函数是阻塞函数#xff0c;会被信号打断#xff0c;我们不能让它停止会被信号打断我们不能让它停止所以我们应该进行一些封装操作。
//wrap.h#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
下面是相关函数的实现
//wrap.c#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;
}
2.多进程和多线程实现服务器的并发
当有多个客户端向服务器发送数据的时候我们如何去操作这就涉及到了我们的多线程和多进程开发了下面看看如何来实现。
2.1多进程服务器
(1)首先我们想如何通过多进程来实现呢那么我们得想清楚父子进程分别来干啥我们可以这样父进程来获取连接。
(2)然后子进程来进行通信发送数据给服务端。
(3)最后我们利用信号的方式来回收子进程防止出现僵尸进程。
/*多进程实现并发主进程中使用sigaction函数回收子进程*/
#include signal.h
#include sys/wait.h
#include sys/types.h
#include unistd.h
#include wrap.hvoid 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 pthread.h#define MAX_NUM 100struct 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的状态可以看出是达到了效果。两个客户端和服务端的通信都是正常的。