实验八 进程间通信+多线程

一、实验目的

1、了解采用进程间通信的原理;

2、掌握线程的创建及使用方法,学习进行多线程编程。

二、实验内容

(1)在Linux系统下编写完成。

(2)创建一个服务器和若干个客户端。

(3)用户可以实现包括:通过客户端输入文字并且向服务器发送消息,也可以接收来自服务器端的数据。用户控制客户端退出。

(4)服务器端可以实现包括:接收并区分来自客户端的数据,将用户输入的内容在服务器上打印出来,并将原内容返回客户端。服务器可以通过单播或广播向客户端发送数据,并可以统计在线人数等。

(5)在服务器端和客户机端要综合运用多进程/多线程的编程知识实现并发处理,并且要在进程和线程之间采用相应的同步/互斥机制,实现其通信过程。

(6)最终采用makefile文件的形式予以实现。

三、源程序

(1)Python语言实现

server.py(服务器端):

# -*- coding:utf-8 -*-



from threading import Thread

import socket

import os



# 绑定地址

ADDRESS = ('127.0.0.1', 8712)

# 负责监听的socket

g_socket_server = None

# 连接池

g_conn_pool = dict()



def show_help():

    print_info('help: 获取帮助信息')

    print_info('all:消息: 群发消息')

    print_info('name:消息: 单发消息')

    print_info('close: 关闭服务端')

    print_info('show online number: 显示当前在线人数')

    print_info('show online names: 显示当前在线用户名')



def print_info(s):

    print('SYSTEM>:', s)



def print_client(c, s):

    print(c + '>:', s)



def init():

    global g_socket_server

    # 创建 socket 对象

    g_socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    g_socket_server.bind(ADDRESS)

    # 最大等待数

    g_socket_server.listen(5)



def accept_client():

    """

    接收新连接

    """

    while True:

        # 阻塞,等待客户端连接

        client, _ = g_socket_server.accept()

        # 获取用户名

        client_name = client.recv(1024).decode(encoding='utf8')

        if client_name in g_conn_pool.keys() or client_name == 'all':

            client.sendall("该用户名已被使用".encode("utf8"))

            client.close()

        # 加入连接池

        g_conn_pool[client_name] = client

        # 给每个客户端创建一个独立的线程进行管理

        Thread(target=message_handle, args=(client, client_name), daemon=True).start()



def message_handle(client, client_name):

    """

    消息处理

    """

    while True:

        bs = client.recv(1024)

        if len(bs) == 0:

            client.close()

            # 删除连接

            g_conn_pool.pop(client_name)

            print_client(client_name, "下线了")

            break

        print_client(client_name, bs.decode(encoding='utf8'))



def close_server():

    global g_socket_server, g_conn_pool

    for v in g_conn_pool.values():

        if v is not None:

            v.close()

    g_conn_pool = None

    if g_socket_server is not None:

        g_socket_server.close()



if __name__ == '__main__':

    # 初始化

    init()

    # 新开一个线程,用于接收新连接

    Thread(target=accept_client, daemon=True).start()

    # 主线程逻辑

    inp = None

    try:

        while True:

            inp = input().strip()

            if inp == 'help':

                show_help()

            elif inp == 'close':

                close_server()

                os._exit(0)

            elif inp == 'show online number':

                print_info(len(g_conn_pool.keys()))

            elif inp == 'show online names':

                print_info(list(g_conn_pool.keys()))

            elif inp.startswith('all:') and len(inp) >= 5:

                info = inp[4:]

                for val in g_conn_pool.values():

                    val.sendall(info.encode(encoding='utf8'))

            elif ':' in inp:

                idx = inp.index(':')

                name = inp[:idx]

                info = inp[idx:]

                if name in g_conn_pool.keys() and len(info) >= 1:

                    g_conn_pool[name].sendall(info[1:].encode(encoding='utf8'))

                else:

                    print_info('输入命令错误')

            else:

                print_info('输入命令错误')

    finally:

        close_server()

client.py(客户端):

# -*- coding:utf-8 -*-



from threading import Thread

import random

import socket

import os



ADDRESS = ('127.0.0.1', 8712)  # 绑定地址



g_socket_client = None



g_name = None



def print_server(s):

    print('Server>:', s)



def print_info(s):

    print('SYSTEM>:', s)



def show_help():

    print_info('help: 获取帮助信息')

    print_info('server:消息:发送消息给服务器')

    print_info('close:关闭客户端')



# 生成用户名

def get_name():

    char_list = []

    for i in range(10):

        char_list.append(str(i))

    for i in range(65, 91):

        char_list.append(chr(i))

    for i in range(97, 123):

        char_list.append(chr(i))

    my_slice = random.sample(char_list, 4)

    return ''.join(my_slice)



# 初始化

def init():

    global g_socket_client, g_name

    # 创建 socket 对象

    g_socket_client = socket.socket()

    g_socket_client.connect(ADDRESS)

    # 初始化用户名

    g_name = get_name()

    print_info('初始化用户名为' + g_name)

    g_socket_client.sendall(g_name.encode(encoding='utf8'))



def receive_message():

    global g_socket_client

    try:

        while True:

            msg = g_socket_client.recv(1024).decode(encoding='utf8')

            if len(msg) == 0:

                print_info('服务器端关闭, 客户端退出')

                os._exit(0)

            print_server(msg)

    finally:

        close_client()



# 关闭客户端

def close_client():

    global g_socket_client

    if g_socket_client is not None:

        g_socket_client.close()



if __name__ == '__main__':

    # 初始化

    init()

    # 启动接收线程

    Thread(target=receive_message, daemon=True).start()

    try:

        inp = None

        while True:

            inp = input().strip()

            if inp == 'close':

                print_info('关闭客户端')

                close_client()

                os._exit(0)

            elif inp == 'help':

                show_help()

            elif inp.startswith('server:') and len(inp) > 7:

                info = inp[7:]

                g_socket_client.sendall(info.encode(encoding='utf8'))

            else:

                print_info('输入命令错误')

    finally:

        close_client()

(2)C语言实现

server.c(服务器端):

#include <stdio.h>

#include <stdlib.h>

#include <sys/socket.h>

#include <string.h>

#include <sys/types.h>

#include <arpa/inet.h>

#include <assert.h>

#include <unistd.h>

#include <netinet/in.h>

#include <netinet/ip.h>

#include <errno.h>

#include <pthread.h>

#include <stdbool.h>



#define SERVER_PORT 11332

#define MAX_CONN_LIMIT 8 //MAX connection limit

#define BUFFER_LENGTH 1024



// Server

struct sockaddr_in server_addr;

int server_sock_fd;

// Server Pool

int socket_pool[64];



char data_send[BUFFER_LENGTH];

char data_recv[BUFFER_LENGTH];



void init_server();



void init_pool();



void close_server();



void show_help();



int get_num();



int next_index();



void send_data(int sock_fd);



bool in_pool(int sock_fd);



static void accept_thread();



static void Data_handle(void *sock_fd);



int main() {

    setvbuf(stdout, NULL, _IONBF, 0);

    // 初始化

    init_server();

    init_pool();

    pthread_t thread_id;

    if (-1 == pthread_create(&thread_id, NULL, (void *) (&accept_thread), (void *) (NULL))) {

        perror("服务器端创建接收thread失败\n");

        exit(EXIT_FAILURE);

    }

    char input[64];

    while (1) {

        scanf("%s", input);

        if (strcmp(input, "CLOSE") == 0) {

            close_server();

            break;

        } else if (strcmp(input, "HELP") == 0) {

            show_help();

        } else if (strcmp(input, "ALL") == 0) {

            memset(data_send, 0, BUFFER_LENGTH);

            printf("请输入要发送的消息:");

            scanf("%s", data_send);

            for (int i = 0; i < 64; i++) {

                if (socket_pool[i] != -1) {

                    send_data(socket_pool[i]);

                }

            }

        } else if (strcmp(input, "NUM") == 0) {

            printf("当前在线人数为%d\n", get_num());

        } else if (strcmp(input, "ONE") == 0) {

            int tmp_fd;

            printf("请输入要发送的ID:");

            scanf("%d", &tmp_fd);

            if (!in_pool(tmp_fd)) {

                printf("该ID不存在\n");

                continue;

            }

            memset(data_send, 0, BUFFER_LENGTH);

            printf("请输入要发送的消息:");

            scanf("%s", data_send);

            send_data(tmp_fd);

        } else {

            printf("输入命令错误\n");

        }

    }

    return 0;

}



// 显示帮助

void show_help() {

    printf("NUM: 显示当前在线人数\n");

    printf("ALL: 向全体发消息\n");

    printf("ONE: 向部分发消息\n");

    printf("HELP: 显示帮助信息\n");

    printf("CLOSE: 关闭服务器端\n");

}



// 初始化服务器

void init_server() {

    // 初始化

    memset(&server_addr, 0, sizeof(struct sockaddr_in));

    // 使用 IPv4 进行通信

    server_addr.sin_family = AF_INET;

    // 设置端口

    server_addr.sin_port = htons(SERVER_PORT);

    // 指向IP

    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 初始化空字节

    bzero(&(server_addr.sin_zero), 8);

    // 创建socket

    server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);

    if (-1 == server_sock_fd) {

        perror("服务器端Socket创建失败");

        exit(-1);

    }



    //  close socket后想继续重用该socket

    int on = 1;

    setsockopt(server_sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    // 绑定

    int bind_result = bind(server_sock_fd, (struct sockaddr *) &server_addr, sizeof(server_addr));

    if (-1 == bind_result) {

        perror("服务器端Socket绑定失败");

        exit(-1);

    }



    // 置socket为listen模式

    int listen_result = listen(server_sock_fd, MAX_CONN_LIMIT);

    if (-1 == listen_result) {

        perror("服务器端Socket设置listen模式失败");

        exit(-1);

    }

}



// 初始化链接池

void init_pool() {

    memset(socket_pool, -1, sizeof(int) * 64);

}



// 关闭

void close_server() {

    int shutdown_result = shutdown(server_sock_fd, SHUT_WR);

    if (-1 == shutdown_result) {

        printf("服务器端Socket关闭异常\n");

        exit(-1);

    }

    printf("服务器端Socket正确关闭\n");

}



// 接收线程

void accept_thread() {

    int client_length;

    struct sockaddr_in s_addr_client;

    int client_sock_fd;

    while (1) {

        pthread_t thread_id;

        client_length = sizeof(s_addr_client);

        // Accept

        client_sock_fd = accept(server_sock_fd, (struct sockaddr_ *) (&s_addr_client), (socklen_t *) (&client_length));

        if (client_sock_fd == -1) {

            perror("服务器端Accept错误");

            continue;

        }

        printf("服务器端接收到一个新请求\n");

        if (pthread_create(&thread_id, NULL, (void *) (&Data_handle), (void *) (&client_sock_fd)) == -1) {

            perror("服务器端建立thread失败\n");

            break;

        }

        printf("该客户端ID为%d\n", client_sock_fd);

        // 存储

        int idx = next_index();

        if (idx == -1) {

            perror("服务器链接已满");

            exit(EXIT_FAILURE);

        } else {

            socket_pool[idx] = client_sock_fd;

        }

    }

}



// next INDEX

int next_index() {

    for (int i = 0; i < 64; i++) {

        if (socket_pool[i] == -1) {

            return i;

        }

    }

    return -1;

}



// 得到当前在线人数

int get_num() {

    int num = 0;

    for (int i = 0; i < 64; ++i) {

        if (socket_pool[i] != -1) {

            num += 1;

        }

    }

    return num;

}



// 是否在连接池中

bool in_pool(int sock_fd) {

    for (int i = 0; i < 64; i++) {

        if (socket_pool[i] == sock_fd) {

            return true;

        }

    }

    return false;

}



// 数据处理

static void Data_handle(void *sock_fd) {

    int fd = *((int *) sock_fd);

    int i_recvBytes;



    while (1) {

        //Reset data.

        memset(data_recv, 0, BUFFER_LENGTH);

        i_recvBytes = read(fd, data_recv, BUFFER_LENGTH);

        printf("i_recv = %d\n", i_recvBytes);

        if (i_recvBytes == 0) {

            printf("该客户端%d关闭\n", fd);

            break;

        } else if (i_recvBytes == -1) {

            perror("服务器端读取错误");

            break;

        } else {

            printf("客户端[%d]: %s\n", fd, data_recv);

        }



    }

    //Clear

    printf("客户端[%d]结束\n", fd);



    close(fd);            //close a file descriptor.

    pthread_exit(NULL);   //terminate calling thread!

}



// 发送消息

void send_data(int sock_fd) {

    int res = write(sock_fd, data_send, BUFFER_LENGTH);

    if (res == -1) {

        perror("发送失败");

        exit(EXIT_FAILURE);

    }

}

client.c(客户端):

#include <stdio.h>

#include <stdlib.h>

#include <sys/socket.h>

#include <string.h>

#include <sys/types.h>

#include <arpa/inet.h>

#include <assert.h>

#include <unistd.h>

#include <netinet/in.h>

#include <netinet/ip.h>

#include <errno.h>

#include <pthread.h>

#include <stdbool.h>



#define SOCK_PORT 11332

#define BUFFER_LENGTH 1024



int sock_fd;

struct sockaddr_in s_addr_in;

char data_send[BUFFER_LENGTH];

char data_recv[BUFFER_LENGTH];

void init_client();

void show_help();

static void Data_read();

int main() {

    setvbuf(stdout, NULL, _IONBF, 0);



    init_client();



    pthread_t thread_id;

    if (-1 == pthread_create(&thread_id, NULL, (void *) (&Data_read), (void *) (NULL))) {

        perror("客户端创建接收thread失败\n");

        exit(EXIT_FAILURE);

    }



    char input[64];

    while (1) {

        gets(input);

        if (strcmp(input, "HELP") == 0) {

            show_help();

        } else if (strcmp(input, "CLOSE") == 0) {

            int ret = shutdown(sock_fd, SHUT_WR);

            assert(ret != -1);

            break;

        } else if (strcmp(input, "SEND") == 0) {

            memset(data_send, 0, BUFFER_LENGTH);

            printf("请输入要发送的数据:");

            scanf("%s", data_send);

            int write_res = write(sock_fd, data_send, BUFFER_LENGTH);

            if (write_res == -1) {

                perror("客户端写入失败\n");

                exit(-1);

            }

        }

    }



    return 0;

}



void init_client() {

    sock_fd = socket(AF_INET, SOCK_STREAM, 0);       //ipv4,TCP

    if (sock_fd == -1) {

        perror("客户端Socket创建失败");

        exit(-1);

    }

    memset(&s_addr_in, 0, sizeof(s_addr_in));

    s_addr_in.sin_addr.s_addr = inet_addr("127.0.0.1");

    s_addr_in.sin_family = AF_INET;

    s_addr_in.sin_port = htons(SOCK_PORT);

    int connect_res = connect(sock_fd, (struct sockaddr *) (&s_addr_in), sizeof(s_addr_in));

    if (connect_res == -1) {

        perror("客户端连接失败");

        exit(-1);

    }

    memset(data_send, 0, BUFFER_LENGTH);

    memset(data_recv, 0, BUFFER_LENGTH);

}



static void Data_read() {

    while (true)

    {

        int read_res = read(sock_fd, data_recv, BUFFER_LENGTH);

        assert(read_res != -1);

        if(read_res == 0)

        {

            printf("服务器端已关闭\n");

            exit(EXIT_SUCCESS);

        }

        printf("Server>: %s\n", data_recv);

        memset(data_recv, 0, BUFFER_LENGTH);

    }

}



void show_help() {

    printf("HELP: 显示帮助\n");

    printf("CLOSE: 关闭客户端\n");

    printf("SEND: 发送信息\n");

}

makefile如下:

all: server client



CC = gcc

SRC1 = server.c

SRC2 = client.c

OBJE1 = server

OBJE2 = client



${OBJE1}:

    ${CC} ${SRC1} -lpthread -w -o ${OBJE1}

${OBJE2}:

    ${CC} ${SRC2} -lpthread -w -o ${OBJE2} 

四、实验步骤、结果截图

1Python运行结果:

  1. 服务器端显示帮助信息。输入help,系统会显示帮助信息。

  1. 运行客户端。首先,客户端会生成随机用户名,并将该用户名发送给服务器端,用作该客户端的标识。

再增加一个客户端:

  1. 客户端显示帮助信息。输入help, 会显示帮助信息。如下:

(4)服务器端显示当前在线人数。输入show online number,系统会显示当前在线人数。

  1. 服务器端显示当前登录用户名。输入show online names,系统会显示当前在线用户名。

 

  1. 服务器端单发信息。例如,向用户名为glXc单发信息。如下:

相应客户端收到信息。如下:

  1. 服务器端群发消息。例如,输入all:Hello!。如下:

 

所有客户端都收到了该消息。如下:

glXc客户端:

RL4m客户端:

 

  1. 客户端输入close,来关闭程序。同时,服务器端显示该客户端下线。如下:

  1. 客户端向服务器端发送消息。例如,输入server:Hello From 4VRh!。如下:

服务器端接收到该消息。如下:

  1. 服务器端关闭程序,客户端自动关闭。如下:

客户端自动关闭。如下:

2C语言运行结果:

  1. 编译程序。使用make all,生成server和client文件。

  1. 运行服务器端和两个客户端。服务器端如下:

(3)

  1. 服务器端显示帮助信息。输入HELP命令,如下:

  1. 客户端获取帮助信息。输入HELP命令,如下:

  1. 服务器端单发消息。输入ONE命令,如下:

客户端接收到消息,如下:

  1. 服务器端群发消息。输入ALL命令,如下:

 

客户端结果:

  1. 关闭客户端。使用CLOSE命令:

同时,服务器端收到通知。如下:

  1. 关闭服务器端。使用CLOSE命令。如下:

客户端收到通知自动关闭。如下:

 

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值