網絡編程:實現多進程並發回聲服務器端/客戶端
1.直接跳轉到Linux端代碼
一、實驗目的
- 學習進程的創建、銷毀過程。掌握利用信號處理技術sigaction消滅僵屍進程的方法。
- 在Linux操作系統上編寫並發服務器端/客戶端。讓服務器端以多進程方式為多個客戶端同時提供回聲服務。
二、實驗內容
1、在Linux操作系統上編寫程序,實現基於多進程的並發回聲服務器端:
(1)改進實驗三中實現的迭代服務器端程序。通過為每一個客戶端創建進程的方式,同時為多個客戶端提供回聲服務。
(2)客戶端接收用戶輸入的字符串並發送到服務器端,一直到用戶輸入字符 ”Q”/”q” 為止。
(3)啟動服務器后創建兩個以上客戶端並建立連接,驗證服務器端可以同時為不同的客戶端提供回聲服務。
2、回聲客戶端采用I/O程序分割方法,發送的消息記錄到文件中
實驗結果
Linux端效果圖如下:
Linux端的(采用UOS+VScode+g++)
Linux端代碼如下:
1. 服務器端:
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include<fstream>
#include <signal.h>
#include <sys/wait.h>
#pragma comment (lib, "ws2_32.lib") //加載 ws2_32.dll
#define BUF_SIZE 1024
using namespace std;
void read_childproc(int sig);
int main() {
pid_t pid;
struct sigaction act;
int strlen;
//配置信號處理函數
act.sa_handler=read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigaction(SIGCHLD, &act, 0);
//創建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//將套接字和IP、端口綁定
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
//每個字節都用0填充
serv_addr.sin_family = AF_INET;
//使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
//具體的IP地址
serv_addr.sin_port = htons(1234);
//端口
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//進入監聽狀態,等待用戶發起請求
listen(serv_sock, 20);
//接收客戶端請求
char buffer[BUF_SIZE] = {
0
}
;
//緩沖區
for (int i=0;;i++) {
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -1)
cout<<"某一客戶端斷開"<<endl; else
cout<<"已連接客戶端:"<<i + 1<<endl;
pid=fork();
//創建新進程
if(pid==-1) {
close(clnt_sock);
continue;
}
if(pid==0) //子進程運行區域 {
close(serv_sock);
//在子進程中要關閉服務器套接字
while((strlen = read(clnt_sock, buffer, sizeof(buffer)))!=0) {
//接收客戶端發來的數據
write(clnt_sock,buffer,strlen);
//將數據原樣返回
FILE *fp = fopen("result.txt","a");
//客戶端傳過來的數據寫入文件
fputs(buffer,fp);
//客戶端傳過來的數據寫入文件
fclose(fp);
//關閉文件
}
close(clnt_sock);
//關閉套接字
cout<<"客戶端斷開"<<endl;
return 0;
} else //父進程運行區域 {
//調用fork函數后,要將無關的套接字文件描述符關閉掉
close(clnt_sock);
}
// memset(buffer, 0, BUF_SIZE); //重置緩沖區
}
//關閉套接字
close(serv_sock);
return 0;
}
//一旦有子進程結束就調用這個函數
void read_childproc(int sig) {
int status;
pid_t id=waitpid(-1, &status, WNOHANG);
if(WIFEXITED(status)) {
cout<<"結束進程,id: "<<id<<endl;
cout<<"子進程發送: "<<WEXITSTATUS(status)<<endl;
}
}
//g++ 網絡編程作業5服務器端.cpp -o test1
2. 客戶端:
#include <iostream>
#include <cstring>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUFFER_SIZE 1024
void read_routine(int sock, char *buf);
void write_routine(int sock, char *buf);
using namespace std;
int main() {
//設置套接字相關屬性
pid_t pid;
struct sockaddr_in serv_addr;
struct sockaddr_in sock_addr;
memset(&sock_addr,0,sizeof(sock_addr));
sock_addr.sin_family = AF_INET;
sock_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
sock_addr.sin_port = htons(1234);
//發送緩沖區和接收緩沖區
char bufSend[BUFFER_SIZE];
int result;
int num;
//創建套接字
int sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
connect(sock,(struct sockaddr*)&sock_addr,sizeof(sock_addr));
pid=fork();
if(pid==0) //子進程寫
write_routine(sock, bufSend); else //父進程讀
read_routine(sock, bufSend);
}
void read_routine(int sock, char* buf) {
while(1) {
int str_len=read(sock, buf, BUFFER_SIZE);
if(str_len==0)
return;
//接受到EOF結束符時返回
buf[str_len]=0;
cout<<"message from server: "<<buf<<endl;
}
}
void write_routine(int sock, char* buf) {
while(1) {
fgets(buf, BUFFER_SIZE, stdin);
if(!strcmp(buf, "q\n") || !strcmp(buf, "Q\n")) {
shutdown(sock, SHUT_WR);
return;
}
write(sock, buf, strlen(buf));
}
}
//g++ 網絡編程作業5客戶端.cpp -o test2