一、实验目的
1、了解采用Socket通信的原理。
2、掌握Socket的创建及使用方法。
二、实验内容
1、创建一个服务器端和若干个客户端。
服务器端可实现包括:接收并区分来自客户端的数据,将用户输入的内容在服务器上输出,并将原内容群发至所有在线客户端(类似qq群聊形式);服务器可主动向在线客户端发送数据;并可以统计在线人数等。
客户端可实现包括:输入文字并且向服务器发送消息,接收来自服务器端的数据;用户控制客户端退出。
2、在服务器端和客户端基于socket实现其通信过程。
3、 服务器与客户端的具体内容可根据实际工作情况自由发挥,充分体现原创精神即可。需要:1)简述程序功能;2)具体的实现源代码及注释;3)实现的过程和结果截图。
三、实验步骤、源程序及结果截图
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 666
#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();
static void Data_read();
int main() {
setvbuf(stdout, NULL, _IONBF, 0);//定义流如何缓冲
init_client();
pthread_t thread_id;//声明线程ID
if (-1 == pthread_create(&thread_id, NULL, (void *)(&Data_read), (void *)(NULL))) {
perror("客户端创建接收线程失败\n");
exit(EXIT_FAILURE);
}//创建接收线程
char input[64];
printf("请输入SEND后发送信息;输入CLOSE后关闭聊天\n");
do {
gets(input);
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);
}
}
}while(1);
return 0;
}
void init_client() {
sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET,是 IPv4 网络协议的套接字类型;SOCK_STREAM字节流;
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");//绑定本机;inet_addr一个点分十进制字符串表示的IP转换成一个长整型数
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);
}
}
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 666
#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];//链接池中最大人数限制在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;
int i;
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);
memset(data_send, 0, BUFFER_LENGTH);
for(i = 0;i< BUFFER_LENGTH;i++)
data_send[i] = data_recv[i];
for (int i = 0; i < 64; i++) {
if (socket_pool[i] != -1) {
send_data(socket_pool[i]);
}
}
}
}
//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);
}
}
功能实现:
服务器端可以连接多个客户端,客户端可以向服务器端发送消息,然后服务端将消息转发给全部的客户端。服务器端也可以向某一指定的客户端发消息,也可以向全部的连接的客户端发送消息,同时可以统计在线人数。