视频链接
黑马程序员-Linux网络编程_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1iJ411S7UA?p=37
目录
一、高并发服务器
1.1 图示
1.2 分类
多进程并发服务器
多线程并发服务器
二、多进程并发服务器
2.1 回顾前面的内容
服务端创建一个套接字 lfd,用来建立连接。客户端有连接请求的时候,借助 lfd 创建一个 cfd 套接字,客户端跟客户端 cfd 建立连接。再来一个客户端是和新创建的 cfd2 建立连接,所以 lfd 就一直处于空闲状态,等待其他客户端过来建立连接
多进程中 cfd 就是子进程的角色,lfd 就是父进程的角色
2.2 步骤
第1步:创建监听套接字 lfd,使用函数 Socket();
第2步:绑定地址结构 Struct scokaddr_in addr;,使用函数 Bind();
第3步:让套接字进入监听状态并响应客户端请求,使用函数 Listen();
第4步:
while(1) {
cfd = Accpet(); // 接受客户端连接请求
pid = fork(); // 创建子进程
if(pid == 0) { // 子进程执行的操作
close(lfd); // 关闭用于建立连接的套接字 lfd(这是父进程的任务)
read(); // 从套接字中读取客户端发来的消息
小写字母转换成大写字母();
write(); // 写入用于通信的套接字中
} else if (pid > 0) { // 父进程
close(cfd); // 关闭用于客户端通信的套接字
while(1) { //等待子进程
waitpid(0, NULL, 0); // 在这里循环调用不阻塞回收子进程会存在问题,所以后面写在回调函数中了
}
contiue;
}
}
2.3 多进程并发服务器实现
#include<stdio.h>
#include<ctype.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<pthread.h>
#include"wrap.h"
#define SRV_PORT 9999
void perr_exit(const char *s)
{
perror(s);
exit(1);
}
int main(int argc, char *argv[])
{
int lfd, cfd;
pid_t pid;
struct sockaddr_in srv_addr, clt_addr;
socklen_t clt_addr_len;
char buf[BUFSIZ];
int ret, i;
// 将地址结构清零
// 第一种方法:
// memset(&srv_addr, 0, sizeof(srv_addr));
// 第二种方法:
bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = socket(AF_INET, SOCK_STREAM, 0);
bind(lfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
listen(lfd, 128);
clt_addr_len = sizeof(clt_addr);
while (1) {
cfd = accept(lfd, (struct sockaddr *)&clt_addr, &clt_addr_len);
pid = fork();
if (pid < 0) {
perr_exit("fork error");
}
else if (pid == 0) {
close(lfd);
break;
}
else {
close(cfd);
continue;
}
}
// 子进程执行的操作
if (pid == 0) {
while(1) {
ret = read(cfd, buf, sizeof(buf));
if (ret == 0) {
close(cfd);
exit(1);
}
for (i = 0; i < ret; i++) {
buf[i] = toupper(buf[i]);
}
write(cfd, buf, ret);
write(STDOUT_FILENO, buf, ret);
}
}
return 0;
}
因为没回收子进程,所以有很多僵尸进程
增加信号触发用于回收的函数
#include<stdio.h>
#include<ctype.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<pthread.h>
#include"wrap.h"
#define SRV_PORT 9999
void perr_exit(const char *s)
{
perror(s);
exit(1);
}
void catch_child(int signum)
{
while (waitpid(0, NULL, WNOHANG) > 0);
return ;
}
int main(int argc, char *argv[])
{
int lfd, cfd;
pid_t pid;
struct sockaddr_in srv_addr, clt_addr;
socklen_t clt_addr_len;
char buf[BUFSIZ];
int ret, i;
// 将地址结构清零
// 第一种方法:
// memset(&srv_addr, 0, sizeof(srv_addr));
// 第二种方法:
bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = socket(AF_INET, SOCK_STREAM, 0);
bind(lfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
listen(lfd, 128);
clt_addr_len = sizeof(clt_addr);
while (1) {
cfd = accept(lfd, (struct sockaddr *)&clt_addr, &clt_addr_len);
pid = fork();
if (pid < 0) {
perr_exit("fork error");
}
else if (pid == 0) {
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGCHLD, &act, NULL);
if (ret != 0) {
perr_exit("sigaction error");
}
close(lfd);
break;
}
else {
close(cfd);
continue;
}
}
// 子进程执行的操作
if (pid == 0) {
while(1) {
ret = read(cfd, buf, sizeof(buf));
if (ret == 0) {
close(cfd);
exit(1);
}
for (i = 0; i < ret; i++) {
buf[i] = toupper(buf[i]);
}
write(cfd, buf, ret);
write(STDOUT_FILENO, buf, ret);
}
}
return 0;
}
三、多线程并发服务器
3.1 步骤
第1步:创建监听套接字 lfd,使用函数 Socket();
第2步:绑定地址结构 Struct scokaddr_in addr;,使用函数 Bind();
第3步:让套接字进入监听状态并响应客户端请求,使用函数 Listen();
第4步:
while(1) {
cfd = Accpet(lfd); // 接受客户端连接请求
pthread_create(&tid, NULL, tfn, NULL); // 创建子线程
pthread_detach(tid); // 前面是线程分离的函数,如果想接受子线程的返回值,需要 pthread_join(tid, void **); 这个函数,但是这个函数是阻塞等待的。我们可以创建一个新的线程专门用于回收这个子线程
}
第5步:
void *tfn(void *arg)
{
close(lfd);
read(cfd);
小 -> 大
write(cfd);
pthread_exit((void*)10);
}
3.2 多线程并发服务器实现
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define MAXLINE 8192
#define SERV_PORT 8000
struct s_info { // 定义一个结构体, 将地址结构跟cfd捆绑
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]; // #define INET_ADDRSTRLEN 16 可用"[+d"查看
while (1) {
n = read(ts->connfd, buf, MAXLINE); // 读客户端
if (n == 0) {
printf("the client %d closed...\n", ts->connfd);
break; // 跳出循环,关闭cfd
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port)); // 打印客户端信息(IP/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(void)
{
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); // 创建一个socket, 得到lfd
bzero(&servaddr, sizeof(servaddr)); // 地址结构清零
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 指定本地任意IP
servaddr.sin_port = htons(SERV_PORT); // 指定端口号
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;
}
编译和运行