【Linux网络编程部分----多进程高并发poll模型】
目錄
前言
背景
分析
編寫步驟
服務器:
客戶端:
?服務器端代碼
附:文件操作部分
附:目錄操作部分
客戶端代碼
全部代碼
頭文件部分
服務器全部代碼
客戶端所有代碼
總結:
?
前言
????????本文采用? Visual Studio 2022 運用遠程登陸,來對虛擬機內的? Linux 進行操作
? ? ? ? 如果不知道怎么使用的話可參考:?linux遠程開發——使用vs2019遠程連接linux_vs2019遠程調試linux_似末的博客-CSDN博客https://blog.csdn.net/wmcy123/article/details/123415371?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167741273216782427438492%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167741273216782427438492&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-3-123415371-null-null.142^v73^insert_down1,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=vs%E9%93%BE%E6%8E%A5linux&spm=1018.2226.3001.4187
背景
? ? ? ? 在TCP協議下,使用套接字(socket)多進程(fork)之間進行通信,并且客戶端能夠從服務器設定下的地址上獲取文件。
分析
? ? ? ? 要分別從服務器和客戶端兩個方面入手,采取網絡編程所固定的格式來進行編寫
? ? ? ? 服務器:要不斷的監聽是否有客戶端來進行通信,并且采用多進程的方式使得客戶端之間互不影響
? ? ? ? 客戶端:要可隨時加入服務器,并且能夠自主下載服務端下面的指定文件
編寫步驟
服務器:
????????第一步:創建套接字
????????第二步:監聽套接字
????????第三步:等待客戶端連接
????????第四步:發送數據給客戶端
? ? ? ? 第五步:關閉套接字
客戶端:
????????第一步:創建套接字
????????第二步:鏈接服務器
????????第三步:讀寫服務器
????????第四步:關閉套接字
?服務器端代碼
? ? ? ? 一、我們先做前期準備,先創建套接字(socket),并先確定其中的內容
int tcp_sock = socket(AF_INET, SOCK_STREAM, 0); //創建套接字struct sockaddr_in Ipv4 {}; //定義一個Ipv4的結構體信息Ipv4.sin_family = AF_INET; //協議為IPV4Ipv4.sin_addr.s_addr = 0; //Ip地址Ipv4.sin_port = htons(PORT); //端口號 此處PORT為宏定義? ? ? ? 二、創建完后,在對其進行綁定,并且開始監聽
if (bind(tcp_sock, (struct sockaddr*)&Ipv4, sizeof(Ipv4)) < 0) //綁定{perror("bind");return 1;}listen(tcp_sock, 10); //監聽將主動套接字變為被動套接字 監聽10路? ? ? ?三、創建? pollfd? 類型的結構來確保我們使用 poll 函數
struct pollfd fds[SIZE]{}; // 將服務器加入到輪詢表中 fds[index].fd = tcp_sock; // 有數據可讀事件 */? ? ? ? 四、準備工作已經做完了,就開始服務器的操作
while (true){if (poll(fds, SIZE, 3000) == 0){//printf("time out \n"); //檢測continue;}for (int i = 0; i < index + 1; i++){if (tcp_sock == fds[i].fd) //主服務器 {if (fds[i].revents & POLLIN){int cid = accept(tcp_sock, NULL, NULL);fds[index].fd = cid; //將服務器加入到輪詢表中fds[index].events = POLLIN; //有數據可讀事件index++;printf("someone coming\n");}}else //與客戶端交互{if (fork() == 0) //多進程 子進程進行操作。避免出現孤兒進程{if (fds[i].revents & POLLIN){memset(buf, 0, SIZE); //將buf的內容全部置為0buf[SIZE - 1] = 1;len = read(fds[i].fd, buf, SIZE); //讀取數據if (len > 0){if (strcmp(buf, "file") == 0){printf("sent file to %d\n", i);dir(fds[i].fd); // 目錄文件操作read(fds[i].fd, buf, SIZE);file(fds[i].fd, buf); memset(buf, 0, SIZE);}if (buf[SIZE - 1] != 0){printf("%d --- :%s\n", i, buf); }write(fds[i].fd, buf, len);}}exit(-1);} }waitpid(-1, NULL, WNOHANG);}}附:文件操作部分
void file(int tcp_socket,char name[20]) { data_t cmd{};/**** 獲取文件名 + 文件大小 *****/struct stat stat_buf {};if (stat(name, &stat_buf) != 0) //獲取文件信息{perror("stat");return;}cmd.file_len = stat_buf.st_size;char* str = strrchr(name, '/');if (str != NULL) //找到了 {str += 1; //往后移動一位}else{str = name;}strcpy(cmd.file_name, str); //獲取文件名printf("file name:%s\n", cmd.file_name);printf("file size:%d\n", cmd.file_len);int fd = open(name, O_RDONLY);if (fd < 0){perror("open");return;}write(tcp_socket, &cmd, sizeof(cmd)); //發送包頭:文件名 + 文件大小/***** 循環讀取文件并發送 ****/char buf[1024]{};int len = 0;while (cmd.file_len > 0){len = read(fd, buf, 1024); //讀文件write(tcp_socket, buf, len); //寫入cmd.file_len -= len;}/****4.關閉套接字 *****/close(fd); }附:目錄操作部分
void dir(int cid) {DIR* dir = opendir("/home/student/projects/ProjectDemo/bin/x64/Debug"); // 打開一個目錄char buf[SIZE]{};if (dir == NULL){perror("opendir");return ;}struct dirent* dnt;while ((dnt = readdir(dir)) != NULL) // 只要返回結果不為NULL,就一直遍歷{printf("%s\n", dnt->d_name);strcpy(buf, dnt->d_name);write(cid, buf, sizeof(buf));}strcpy(buf, "quitquit");write(cid, buf, sizeof(buf));closedir(dir); }客戶端代碼
? ? ? ? 客戶端代碼與服務器端代碼大同小異,可直接參考服務器端來編寫
int cid = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in IPV4 {};IPV4.sin_family = AF_INET;IPV4.sin_addr.s_addr = 0; IPV4.sin_port = htons(PORT); char name[20]{};if (connect(cid, (struct sockaddr*)&IPV4, sizeof(IPV4)) < 0){perror("conncet");return 1;}char buf[128]{};while (true){printf("sent message to service:\n");gets(buf);write(cid, buf, sizeof(buf));if (strncmp(buf, "file",4) == 0){dir(cid);printf("\n");printf("receive a file is \n");scanf("%s", name);write(cid, name, sizeof(buf));file(cid,name); //文件操作} if (strncmp(buf, "quit", 4) == 0){break;}}全部代碼
? ? ? ? 在此,給出全部的代碼以供大家學習
頭文件部分
#include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/ip.h> #include <netinet/in.h> #include <string.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <poll.h> #include <sys/wait.h> #include <dirent.h>服務器全部代碼
typedef struct {char file_name[20]; //文件名int file_len; //文件大小 } data_t;constexpr auto PORT = 10000; constexpr auto IP = "192.168.248.128"; constexpr auto SIZE = 1024;void dir(int cid) {DIR* dir = opendir("/home/student/projects/ProjectDemo/bin/x64/Debug"); // 打開一個目錄char buf[SIZE]{};if (dir == NULL){perror("opendir");return ;}struct dirent* dnt;while ((dnt = readdir(dir)) != NULL) // 只要返回結果不為NULL,就一直遍歷{printf("%s\n", dnt->d_name);strcpy(buf, dnt->d_name);write(cid, buf, sizeof(buf));}strcpy(buf, "quitquit");write(cid, buf, sizeof(buf));closedir(dir); }void file(int tcp_socket,char name[20]) { data_t cmd{};/**** 獲取文件名 + 文件大小 *****/struct stat stat_buf {};if (stat(name, &stat_buf) != 0) //獲取文件信息{perror("stat");return;}cmd.file_len = stat_buf.st_size;char* str = strrchr(name, '/');if (str != NULL) //找到了 {str += 1; //往后移動一位}else{str = name;}strcpy(cmd.file_name, str); //獲取文件名printf("file name:%s\n", cmd.file_name);printf("file size:%d\n", cmd.file_len);int fd = open(name, O_RDONLY);if (fd < 0){perror("open");return;}write(tcp_socket, &cmd, sizeof(cmd)); //發送包頭:文件名 + 文件大小/***** 循環讀取文件并發送 ****/char buf[1024]{};int len = 0;while (cmd.file_len > 0){len = read(fd, buf, 1024); //讀文件write(tcp_socket, buf, len); //寫入cmd.file_len -= len;}/****4.關閉套接字 *****/close(fd); }int main() //多路復用高并發 {int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in Ipv4 {};Ipv4.sin_family = AF_INET;Ipv4.sin_addr.s_addr = 0;Ipv4.sin_port = htons(PORT);if (bind(tcp_sock, (struct sockaddr*)&Ipv4, sizeof(Ipv4)) < 0){perror("bind");return 1;}listen(tcp_sock, 10);int index = 0;struct pollfd fds[SIZE]{};/* 將服務器加入到輪詢表中 */fds[index].fd = tcp_sock;/* 有數據可讀事件 */fds[index].events = POLLIN;index++;char buf[SIZE]{};int len = 0;while (true){if (poll(fds, SIZE, 3000) == 0){//printf("time out \n");continue;}for (int i = 0; i < index + 1; i++){if (tcp_sock == fds[i].fd)// 主服務器 {if (fds[i].revents & POLLIN){int cid = accept(tcp_sock, NULL, NULL);fds[index].fd = cid; //將服務器加入到輪詢表中fds[index].events = POLLIN; //有數據可讀事件index++;printf("someone coming\n");}}else //與客戶端交互{if (fork() == 0){if (fds[i].revents & POLLIN){memset(buf, 0, SIZE);buf[SIZE - 1] = 1;len = read(fds[i].fd, buf, SIZE);if (len > 0){if (strcmp(buf, "file") == 0){printf("sent file to %d\n", i);dir(fds[i].fd);read(fds[i].fd, buf, SIZE);file(fds[i].fd, buf);memset(buf, 0, SIZE);}if (buf[SIZE - 1] != 0){printf("%d --- :%s\n", i, buf);}write(fds[i].fd, buf, len);}}exit(-1);} }waitpid(-1, NULL, WNOHANG);}}close(tcp_sock);return 1; }客戶端所有代碼
constexpr auto PORT = 10000; constexpr auto IP = "192.168.248.128"; constexpr auto SIZE = 1024;typedef struct {char file_name[20]; //文件名int file_len; //文件大小 } data_t;void dir(int cid) {char buf[SIZE]{};printf("you can get:\n");read(cid, buf, sizeof(buf));int i = 0;while (strcmp(buf,"quitquit") != 0){ printf("%s ", buf);read(cid, buf, sizeof(buf));i++;if (i == 5){printf("\n");i = 0;}}}void file(int cid,char name[20]) {data_t cmd;read(cid, &cmd, sizeof(cmd)); //接收文件名 + 大小remove(name);int fd = open(name, O_CREAT, 0666);if (fd < 0){perror("open");return;}char* buf = (char*)malloc(1024);int len = 0;while (cmd.file_len > 0){len = read(cid, buf, 1024);if (len <= 0){perror("read");break;}cmd.file_len -= len;write(fd, buf, len);}printf("over\n"); } int main()//客戶端 {int cid = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in IPV4 {};IPV4.sin_family = AF_INET;IPV4.sin_addr.s_addr = 0; //ip地址IPV4.sin_port = htons(PORT); //端口號char name[20]{};if (connect(cid, (struct sockaddr*)&IPV4, sizeof(IPV4)) < 0){perror("conncet");return 1;}char buf[128]{};while (true){printf("sent message to service:\n");gets(buf);write(cid, buf, sizeof(buf));if (strncmp(buf, "file",4) == 0){dir(cid);printf("\n");printf("receive a file is \n");scanf("%s", name);write(cid, name, sizeof(buf));file(cid,name);} if (strncmp(buf, "quit", 4) == 0){break;}}close(cid);return 1; }總結:
? ? ? ? 網絡編程以固定的套路走,只要熟練了,按照一定的步驟走,網絡編程就有跡可循。搞懂并不是一個特別困難的過程。加油!
總結
以上是生活随笔為你收集整理的【Linux网络编程部分----多进程高并发poll模型】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hihoCoder 1166 交换代数
- 下一篇: linux 其他常用命令