一、实验目的
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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}
四、实验步骤、结果截图
1》Python运行结果:
再增加一个客户端:
(4)服务器端显示当前在线人数。输入show online number,系统会显示当前在线人数。
相应客户端收到信息。如下:
所有客户端都收到了该消息。如下:
glXc客户端:
RL4m客户端:
服务器端接收到该消息。如下:
客户端自动关闭。如下:
2》C语言运行结果:
客户端接收到消息,如下:
客户端结果:
同时,服务器端收到通知。如下:
客户端收到通知自动关闭。如下: