多线程并发服务器

基于该视频完成

12-多线程并发服务器分析_哔哩哔哩_bilibili

通过的是非阻塞忙轮询的方式实现的

和阻塞等待的区别就是,阻塞是真的阻塞了,而这个方式是一直在问有没有请求有没有请求

linux | c | 多进程并发服务器实现-CSDN博客

可以先看看这篇博客,思路和功能都一样

1.核心思路&功能

实现一个服务器可以连接多个客户端,每当accept函数等待到客户端进行连接时 就创建一个子进程;

核心思路:让accept循环阻塞等待客户端,每当有客户端连接时就fork子进程,让子进程去和客户端进行通信,父进程用于监听并使用信号捕捉回收子进程;(子进程关闭用于监听的套接字lfd,父进程关闭用于通信的cfd)

**功能:**客户端输入小写字符串,服务器转成大写返回给客户端

2.代码实现

warp.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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 1234


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 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#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;
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 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;
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 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;
else
return -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;
else
return -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_process_concurrency_sever.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include"warp.h" 

#define MAXLINE 8192
#define SERV_PORT 1234

struct 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;
}
1
gcc warp.c multi_thread_concurrency_sever.c -o multi_process_thread_sever -lpthread

运行图

两个客户端访问服务器端

image-20241212185812899

3.代码解释

1.指定的固定端口号为1234

也可以用argv接受ip和port参数

2.srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);

  • sin_addrstruct 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函数来回收资源。