聊天室應用後臺實現:基於TCP連接的多進程併發服務器

聊天室功能:
1、響應多個客戶端連接請求
2、服務端及客戶端信息的交互由用戶指定輸入
3、服務端採用多進程處理,每一個客戶端連接,都有單獨的進程去處理信息的接受與發送

服務端實現:
1、採用TCP socket建立通信連接
2、採用多進程方式處理多個客戶端的連接請求
3、採用信號處理子進程退出後的資源回收問題,防止出現殭屍進程
4、採用write()、read()系統接口進行信息讀寫

服務端代碼實現 chat_serve.cpp

#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>

using namespace std;

/*
   接收到子進程退出的信號後,調用waitpid()函數,獲取子進程的結束狀態值 
   避免操作系統一直保存子進程信息,進而出現殭屍進程,消耗系統資源。
*/
void childprocess(int sig)
{
    if(sig == SIGCHLD)
    {
        int status;
        pid_t pid = waitpid(-1, &status, WNOHANG);
        if(WIFEXITED(status))
        {
            cout << "child process end. move it. child pid = " << pid;
            cout << " and child return value = " << WEXITSTATUS(status) << endl;
        }
    }
}

int main(int argc, char* argv[])
{
	// 創建socket()
    int serv_sock = socket(PF_INET, SOCK_STREAM, 0); 
	if(serv_sock == -1)
	{
		cout << "socket error" << endl;
		exit(1);
	}
	
	struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET; // IPv4地址族
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));
	
	// 地址信息綁定
    int bind_res = bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(bind_res == -1)
    {
		cout << "bind error" << endl;
		exit(1);
    }

    int listen_res = listen(serv_sock, 5);
    if(listen_res == -1)
    {
		cout << "listen error" << endl;
		exit(1);
    }
	
    // 信號註冊: 在子進程結束後,將進程信息返回給父進程,防止出現殭屍進程
    struct sigaction sigact;
    sigact.sa_flags = 0;
    sigact.sa_handler = childprocess;
    sigemptyset(&sigact.sa_mask);
    sigaction(SIGCHLD, &sigact, 0);

    int child_count = 0; 
	int clnt_sock;
	socklen_t clnt_adr_sz;
	struct sockaddr_in clnt_addr;
    while(1)
    {
		clnt_adr_sz = sizeof(clnt_addr);
		clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_adr_sz);
		if(clnt_sock != -1)
		{
			cout << "client connect!!!" << endl;
			++child_count;
		}
          
		pid_t pid = fork();
		if(pid == 0 && clnt_sock != -1)
		{
			cout << "it's child process, num = " << child_count << endl;
              
            close(serv_sock); // 關閉無用的套接字,只在父進程中使用
            while(1)
            {
				char message[1024] = { 0 };
				int read_len = read(clnt_sock, message, sizeof(message) - 1);
				message[read_len] = 0;
				cout << "<child-" << child_count << ">: " << message << endl;
					 
				string serv_msg;
				if(message[0] == 'q' || message[0] == 'Q')
				{
					serv_msg = "byby, client.";
				}
				else
				{
					cout << "<child-" << child_count << ">: " << "please input msg send to client: ";
					getline(cin, serv_msg);
				}
				
				write(clnt_sock, serv_msg.c_str(), serv_msg.length());
				
				if(message[0] == 'q' || message[0] == 'Q')
				{
					cout << "child process end. child num = " << child_count << endl;
					return 1;
				}
            }
        }
		else
		{
			close(clnt_sock); // 關閉無用的套接字,只在子進程中使用
		}
    }
    close(serv_sock);
    return 0;
}

客戶端實現:
1、採用TCP socket建立通信連接
2、採用write()、read()系統接口進行信息讀寫

客戶端代碼實現 chat_client.cpp :

#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
using namespace std;

#define BUF_SIZE 100

int main(int argc, char* argv[])
{
    char message[BUF_SIZE] = { 0 };
    int sock = socket(PF_INET, SOCK_STREAM, 0);

    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));
    
    if(connect(sock,(struct sockaddr*)&serv_addr, sizeof(serv_addr)) != -1)
    {
        cout << "connected success..." << endl;
    }
    
    while(1)
    {
        cout << "input msg you want send to server:";
        string msg;
        getline(cin, msg);
        write(sock, msg.c_str(), msg.length());
        if(msg.compare("q") == 0 || msg.compare("Q") == 0)
        {
            cout << "client end, byby." << endl;
            break;
        }

        char msg2[1000];
        int read_len = read(sock, msg2, sizeof(msg2) - 1);
        msg2[read_len] = 0;
        cout << "server:" << msg2 << endl;
    }
    close(sock);
    return 0;
}

代碼在Linux下使用g++進行編譯:
在這裏插入圖片描述
運行一個服務端程序,三個客戶端程序,並進行信息交換:

(1)服務端運行結果:
在這裏插入圖片描述
(2)客戶端1運行結果:
在這裏插入圖片描述
(3)客戶端2運行結果:
在這裏插入圖片描述
(4)客戶端3運行結果:
在這裏插入圖片描述

上面的測試只開啓了的三個客戶端程序,可開啓更多客戶端進行測試。

謝謝閱讀,有任何疑問可以留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章