一、实验目的
1、了解采用Socket通信的原理。
2、掌握Socket的创建及使用方法。
二、实验内容
1、创建一个服务器端和若干个客户端。
服务器端可实现包括:接收并区分来自客户端的数据,并将用户输入的内容群发至所有在线客户端(类似qq群聊形式);此外服务器可主动向在线客户端发送数据(类似qq通知信息);并可以统计在线人数等。
客户端可实现包括:输入文字并且向服务器发送消息、接收来自服务器端的消息,发送和接收来自另一客户端的消息(类似qq私聊形式);用户控制客户端退出。
2、在服务器端和客户端基于socket实现其通信过程。
3、 服务器与客户端的具体内容可根据实际工作情况发挥,体现原创精神即可。
需要:1)简述程序功能;2)具体的实现源代码及注释;3)实现的过程和结果截图。
三、实验具体分工
组内人员分别独立完成实验
四、实验步骤、源程序及结果截图
1、创建一个服务器端
源程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdbool.h>
#define NAME_SIZE (20)//名字的最大字节数
#define CLIENT_COUNT (50)//最大人数
#define BUF_SIZE (4096)//接收消息的最大字节数
typedef struct Client
{
int fd;
struct sockaddr_in addr;
char name[NAME_SIZE];
} Client;
Client clients[CLIENT_COUNT];//成员集合,存放进来聊天的人的描述符
int get_num(void)//统计当前人数
{
int k = 0;
for (int i = 0; i < CLIENT_COUNT; i++)
{
if (0 != clients[i].fd)
{
k++;
}
}
return k;
}
Client* get_not_used(void)//遍历成员 查找当前人数是否满
{
for (int i = 0; i < CLIENT_COUNT; i++)
{
if (0 == clients[i].fd)
{
return &clients[i];//找到空位置,没满,返回
}
}
return NULL;//满了
}
void send_all(Client* client, char* buf) // 群发消息
{
char send_buf[BUF_SIZE + NAME_SIZE + 3]; // 姓名长度加上冒号、空格和消息内容
sprintf(send_buf, "%s: %s", client->name, buf);
for (int i = 0; i < CLIENT_COUNT; i++)
{
if (0 != clients[i].fd && client->fd != clients[i].fd)
{
write(clients[i].fd, send_buf, strlen(send_buf) + 1);
}
}
}
void send_one(Client* client, char* buf, char* toname) // 私聊
{
char send_buf[BUF_SIZE + NAME_SIZE + 3]; // 姓名长度加上冒号、空格和消息内容
sprintf(send_buf, "%s: %s(私聊消息)", client->name, buf);
for (int i = 0; i < CLIENT_COUNT; i++)
{
if (0 == strcmp(clients[i].name, toname))
{
send(clients[i].fd, send_buf, strlen(send_buf) + 1,0);
}
}
}
void* server(void* arg)
{
Client* client = arg;
// 准备缓冲区
char buf[BUF_SIZE];
char bufone[BUF_SIZE];
// 接收姓名
read(client->fd, client->name, NAME_SIZE);
// 拼接欢迎信息
sprintf(buf, "欢迎来自%s的%s!", inet_ntoa(client->addr.sin_addr), client->name);
// 告诉所有客户端有人进入
send_all(client, buf);
printf("当前在线人数为:%d\n", get_num());
// 返回欢迎信息
send(client->fd, "欢迎你进入聊天室!", 31,0);
for (;;)
{
char buf1[BUF_SIZE];
int ret_size = read(client->fd, buf1, BUF_SIZE);
if (0 >= ret_size)
{
sprintf(buf1, "%s掉线!", client->name);
close(client->fd);
client->fd = 0;
send_all(client, buf1);
}
else if (0 == strcmp("退出聊天", buf1))
{
sprintf(buf1, "%s退出聊天室!", client->name);
close(client->fd);
client->fd = 0;
printf("当前在线人数为:%d\n", get_num());
send_all(client, buf1);
}
else if (0 == strcmp("私聊", buf1)) //给某人单独发消息
{
char toname[NAME_SIZE];
char bufone[BUF_SIZE];
read(client->fd, toname, NAME_SIZE);
read(client->fd, bufone, BUF_SIZE);
send_one(client, bufone, toname);
}
else
{
char send_buf[BUF_SIZE + 3]; // 姓名长度加上冒号、空格和消息内容
sprintf(send_buf, "%s\n", buf1); // 使用sprintf格式化消息
send_all(client, send_buf);
send_all(client, send_buf);
}
}
}
void* input_listener(void* arg)
{
for (;;)
{
char input[5];
fgets(input, 5, stdin);
if (strstr(input, "send") != NULL)
{
printf("请输入要发送的消息:\n");
char message[BUF_SIZE];
fgets(message, BUF_SIZE, stdin);
for (int i = 0; i < CLIENT_COUNT; i++)
{
if (0 != clients[i].fd)
{
send(clients[i].fd, message, strlen(message) + 1,0);
}
}
}
}
}
int main()
{
// 创建socket
int svr_fd = socket(AF_INET, SOCK_STREAM, 0);
if (0 > svr_fd)
{
perror("socket");
return -1;
}
// 准备地址
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(6789);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
socklen_t addrlen = sizeof(addr);
// 绑定
if (bind(svr_fd, (struct sockaddr*)&addr, addrlen))
{
perror("bind");
return -1;
}
// 监听
if (listen(svr_fd, 10))
{
perror("listen");
return -1;
}
// 创建输入监听线程
pthread_t input_tid;
pthread_create(&input_tid, NULL, input_listener, NULL);
// 等待
for (;;)
{
Client* client = get_not_used();//把新进来的人装进空位置
if (NULL == client)
{
printf("客户端已满!\n");
sleep(10);
continue;
}
client->fd = accept(svr_fd, (struct sockaddr*)&client->addr, &addrlen);//连接
if (0 > client->fd)
{
perror("accept");
return 0;
}
// 创建服务线程
pthread_t tid;
pthread_create(&tid, NULL, server, client);
}
}
2.创建客户端程序:
源代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#define NAME_SIZE 20 // 名字的最大字节数
#define CLIENT_COUNT 50 // 最大人数
#define BUF_SIZE 4096 // 接收消息的最大字节数
void* run(void* arg) {
int cli_fd = *(int*)arg;
char buf[BUF_SIZE];
for (;;) {
int ret_size = read(cli_fd, buf, sizeof(buf));
if (ret_size <= 0) {
printf("210753程闯提醒您:您的网络异常,请检查网络设置!\n");
close(cli_fd);
exit(1);
} else {
printf("\r%s\n>>", buf);
fflush(stdout);
}
}
}
int main() {
// 创建socket
int cli_fd = socket(AF_INET, SOCK_STREAM, 0);
if (cli_fd < 0) {
perror("socket");
return -1;
}
// 准备地址
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6789); // 随意,保证客户端和服务端一样就行
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 这里要修改成自己的ip
// 连接
if (connect(cli_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("connect");
return -1;
}
char buf[BUF_SIZE] = {};
printf("210753程闯提醒您,请输入您的昵称:");
fgets(buf, sizeof(buf), stdin);
buf[strcspn(buf, "\n")] = '\0'; // 去除输入中的换行符
write(cli_fd, buf, strlen(buf) + 1);
// 创建接收线程
pthread_t tid;
pthread_create(&tid, NULL, run, &cli_fd);
// 写数据并发送
for (;;) {
printf(">>");
fflush(stdout);
fgets(buf, sizeof(buf), stdin);
buf[strcspn(buf, "\n")] = '\0'; // 去除输入中的换行符
int ret_size = write(cli_fd, buf, strlen(buf) + 1);
if (ret_size <= 0) {
printf("210753程闯提醒您,您已掉线,请检查网络!\n");
close(cli_fd);
return 1;
} else if (strcmp("退出聊天", buf) == 0) {
printf("您已经退出聊天室!\n");
close(cli_fd);
return 0;
} else if (strcmp("私聊", buf) == 0) {
printf("请输入您要私聊的对象昵称:");
char toname[NAME_SIZE];
fgets(toname, sizeof(toname), stdin);
toname[strcspn(toname, "\n")] = '\0'; // 去除输入中的换行符
write(cli_fd, toname, strlen(toname) + 1);
printf("请输入您要发送到消息:");
char bufone[BUF_SIZE];
fgets(bufone, sizeof(bufone), stdin);
bufone[strcspn(bufone, "\n")] = '\0'; // 去除输入中的换行符
write(cli_fd, bufone, strlen(bufone) + 1);
}
}
}
主要功能实现:
客户可以在聊天室内发送消息,服务端将客户发送的消息广播到聊天室,凡是处于该聊天室的客户都可以接收到。
客户与客户之间可以进行私聊,只要客户输入需要私聊的对象名称,就可以进行私聊,除此之外的其他客户是看不到消息的。
用户可以自己控制是否继续聊天,输入退出聊天即可退出聊天室,并且聊天室内的其他用户都可以看到你退出的消息。
服务端可以以广播的形式发送消息,通知在该聊天室的所有用户
并且谁发送的信息都是有标记的,如果没有标记,只有提示符的话那就是服务端发送到广播消息。
接收并区分来自客户端的数据,并将用户输入的内容群发至所有在线客户端(类似qq群聊形式);此外服务器可主动向在线客户端发送数据(类似qq通知信息);并可以统计在线人数。
五、实验问题总结
这个实验是我自己独立完成的,因为不同主机之间的通信仍然是进程间通信,所以用一台电脑,也可以实现的。Ip地址设置为本地ip:127.0.0.1,端口号设置一个没有被占用的就可以了。
在本次实验中,探索了使用Socket进行进程间通信的方法。Socket是一种网络通信协议,可以在不同的主机之间传输数据。本次实验的目标是通过Socket实现两个进程之间的通信,并验证其可行性和效果。
一个作为服务器端,负责监听来自客户端的连接请求;另一个作为客户端,负责向服务器端发送请求并接收响应。
在服务器端创建了一个socket对象,并指定了IP地址和端口号。然后,我们通过调用bind()函数将socket对象绑定到指定的IP地址和端口上。接着,我们调用listen()函数开始监听来自客户端的连接请求。
在客户端创建了一个socket对象,并指定了服务器端的IP地址和端口号。然后,我们调用connect()函数与服务器端建立连接。
一旦连接建立成功,客户端可以向服务器端发送请求,服务器端则可以接收并处理这些请求。在本次实验中,我们简单地实现了服务器端向客户端发送一条欢迎消息,并接收客户端发送的字符串,并将其转换为大写形式返回给客户端。