视频链接
黑马程序员-Linux网络编程_哔哩哔哩_bilibili
https://www.bilibili.com/video/BV1iJ411S7UA?p=37
目录

多进程并发服务器
多线程并发服务器
服务端创建一个套接字 lfd,用来建立连接。客户端有连接请求的时候,借助 lfd 创建一个 cfd 套接字,客户端跟客户端 cfd 建立连接。再来一个客户端是和新创建的 cfd2 建立连接,所以 lfd 就一直处于空闲状态,等待其他客户端过来建立连接

多进程中 cfd 就是子进程的角色,lfd 就是父进程的角色
第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;
}
}
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #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
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #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;
- }
第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);
}
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #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;
- }
编译和运行
