26.高并发服务器
阻塞函数在阻塞期间若收到信号,会被信号终端,errno设置为EINTR,
这个错误不应该看成一个错误。
while (1)
{
cfd = accept();
while (1)
{
n = read(cfd, buf, sizeof(buf));
if (n <= 0)
{
break;
}
}
}
- 解决办法1:
将cfd设置为非阻塞: fcntl
假如有多个客户端连接请求,cfd只会保留最后一个文件描述符的值
- 解决方法2:
使用多进程: 让父进程监听接受新的连接, 子进程处理新的连接(接收和发送数据);
父进程还负责回收子进程
- 处理流程:
1.创建socket,得到一个监听的文件描述符lfd---socket()
2.将lfd和IP和端口port进行绑定-----bind();
3.设置监听----listen()
4.进入
while (1)
{
//等待有新的客户端连接到来
cfd = accept();
//fork一个子进程,让子进程去处理数据
pid = fork();
if (pid < 0)
{
exit(-1);
}
else if (pid > 0)
{
//关闭通信文件描述符cfd
close(cfd);
}
else if (pid == 0)
{
//关闭监听文件描述符
close(lfd);
//收发数据
while (1)
{
//读数据
n = read(cfd, buf, sizeof(buf));
if (n <= 0)
{
break;
}
//发送数据给对方
write(cfd, buf, n);
}
close(cfd);
//下面的exit必须有,防止子进程再去创建子进程
exit(0);
}
}
close(lfd);
这段代码是一个简化版本的基于fork()
的TCP服务器程序。它不断地接受客户端的连接请求,然后通过fork()
创建子进程来处理每个客户端的数据通信。
现在,让我们详细解析这段代码:
-
外层无限循环:
while (1)
这是一个无限循环,意味着服务器会持续地等待新的客户端连接。
-
接受客户端连接:
cfd = accept();
accept()
函数等待并接受一个客户端连接,然后返回一个新的文件描述符cfd
用于与该客户端通信。 -
创建子进程处理连接:
pid = fork();
fork()
函数创建一个新的进程。这个新进程是原始进程的一个复制品。这里的目的是允许子进程处理新客户端的连接,而父进程则继续等待更多的客户端连接。 -
错误处理:
if (pid < 0) { exit(-1); }
如果
fork()
失败,则返回-1,这时,程序会退出。 -
父进程操作:
else if (pid > 0) { //关闭通信文件描述符cfd close(cfd); }
如果
pid
大于0,表示我们当前处于父进程。父进程将关闭与该特定客户端的连接文件描述符cfd
,因为它将由子进程处理。 -
子进程操作:
else if (pid == 0) { //关闭监听文件描述符 close(lfd); //收发数据 while (1) { //读数据 n = read(cfd, buf, sizeof(buf)); if (n <= 0) { break; } //发送数据给对方 write(cfd, buf, n); } close(cfd); //下面的exit必须有, 防止子进程再去创建子进程 exit(0); }
如果
pid
等于0,表示我们现在处于子进程。子进程首先关闭监听文件描述符lfd
,然后进入一个循环,不断地从客户端读取数据并立即将其写回。如果读取的数据长度为0或小于0(表示客户端断开连接或发生错误),子进程将退出循环,关闭与客户端的连接文件描述符cfd
,然后正常退出。 -
关闭监听文件描述符:
close(lfd);
这段代码实际上永远不会被执行到,因为它位于外层无限循环的外部。
总结:这是一个使用fork()
的多进程TCP服务器的简化示例,它可以同时处理多个客户端连接。当有新的客户端连接时,服务器会创建一个新的子进程专门用于处理该客户端的通信。这种方式可以实现多任务,但如果有大量客户端连接,则可能会导致系统资源的过度使用。实际的生产环境中可能会选择使用线程或事件驱动模型来处理大量的并发连接。
还需要添加的功能: 父进程使用SIGCHLD信号完成对子进程的回收
注意点: accept或者read函数是阻塞函数,会被信号打断,此时不应该视为一个错误。
errno=EINTR
父子进程能够共享的:
文件描述符(子进程复制父进程的文件描述符)
mmap共享映射区
问题:
1.子线程能否关闭lfd?
子线程不能关闭监听文件描述符lfd,原因是子线程和主线程共享文件描述符而不是复制的。
2.主线程能否关闭cfd?
主线程不能关闭cfd,主线程和子线程共享一个cfd,而不是复制的,close之后cfd就会被真正关闭。
3.多个子线程共享cfd,会有什么问题发生?
struct INFO
{
int cfd;
pthread_t threadID;
struct sockaddr_in client;
};
struct INFO info[100];
//初始化INFO数组
for (i = 0; i < 100; i++)
{
info[i].cfd = -1;
}
for (i = 0; i < 100; i++)
{
if (info[i].cfd == -1)
{
//这块内存可以使用
}
}
if (i == 100)
{
//拒绝接受新的连接
close(cfd);
}
作业:
1.改进多进程版本的服务器代码。
父进程使用SIGCHLD信号完成对子进程的回收。
2.改进多线程版本的服务器。
多线程版本的服务器开发流程:
{
1 创建socket, 得到一个监听的文件描述符lfd-- - socket()
2 将lfd和IP和端口port进行绑定---- - bind();
3 设置监听----listen()
4 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 (n <= 0)
{
break;
}
//发送数据
write(cfd, buf, n);
}
close(cfd);
}
问题:
1.子线程能否关闭lfd?
子线程不能关闭监听文件描述符lfd,原因是子线程和主线程共享文件描述符
而不是复制的。
2.主线程能否关闭cfd?
主线程不能关闭cfd,主线程和子线程共享一个cfd,而不是复制的,close之后cfd就会
被真正关闭.
3.多个子线程共享cfd,会有什么问题发生?
如何支持多个客户端---支持多并发的服务器
由于accept和read函数都会阻塞,如当read的时候,不能调用accept接受新的连接,当accept阻塞等待的时候不能read读数据.
第一种方案: 使用多进程,可以让父进程接受新连接,让子进程处理与客户端通信
思路: 让父进程accept接受新连接,然后fork子进程,让子进程处理通信,子进程处理完成后退出,父进程使用SIGCHLD信号回收子进程.
代码实现:
第二种方案: 使用多线程,让主线程接受新连接,让子线程处理与客户端通信; 使用多线程要将线程设置为分离属性,让线程在退出之后自己回收资源。
思考:如何不使用多进程或者多线程完成多个客户端的连接请求
可以将accept和read函数设置为非阻塞,调用fcntl函数可以将文件描述符设置为非阻塞, 让后再while循环中忙轮询。
//多进程版本的网络服务器
#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.h"
int main()
{
//创建socket
int 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(pid<0)
{
perror("fork error");
exit(-1);
}
else if(pid>0)
{
//关闭通信文件描述符cfd
close(cfd);
}
else if(pid==0)
{
//关闭监听文件描述符
close(lfd);
int i=0;
int n;
char buf[1024];
while(1)
{
//读数据
n = Read(cfd, buf, sizeof(buf));
if(n<=0)
{
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(i=0; i<n; i++)
{
buf[i] = toupper(buf[i]);
}
//发送数据
Write(cfd, buf, n);
}
//关闭cfd
close(cfd);
exit(0);
}
}
//关闭监听文件描述符
close(lfd);
return 0;
}
#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.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;
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;
}
/*参三: 应该读取的字节数*/
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;
else
return -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;
else
return -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;
} else
return -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;
}
#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.h>
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
标签:26,int,cfd,并发,fd,进程,服务器,include,客户端
From: https://www.cnblogs.com/codemagiciant/p/17684526.html