思维导图
作业
作业: 运行1个服务器和2个客户端 实现效果: 服务器和2个客户端互相聊天,服务器和客户端都需要使用select模型去实现 服务器要监视2个客户端是否连接,2个客户端是否发来消息以及服务器自己的标准输入流 客户端要监视服务器是否发来消息以及客户端自己的标准输入流 在不开线程的情况下,实现互相聊天
服务器代码:
#include <myhead.h>
#define SER_PORT 6666 // 端口号
#define SER_IP "192.168.0.151" // 服务器ip地址
// 添加函数
void insert_newfd(int *newfd_arr, int *len, int newfd)
{
newfd_arr[*len] = newfd;
(*len)++;
}
// 查询函数
int find_newfd(int *newfd_arr, int len, int newfd)
{
for (int i = 0; i < len; i++)
{
if (newfd_arr[i] == newfd)
{
return i;
}
}
return -1;
}
// 删除函数
void remove_newfd(int *newfd_arr, int *len, int newfd)
{
int tar = find_newfd(newfd_arr, *len, newfd);
if (tar == -1)
{
return;
}
int i = -1;
for (i = tar; i < *len - 1; i++)
{
newfd_arr[i] = newfd_arr[i + 1];
}
(*len)--;
}
/**********************主函数*********************/
int main(int argc, char const *argv[])
{
// 1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
// 参数1表示ipv4网络通信
// 参数2表示tcp通信方式
// 参数3表示默认使用一个协议
if (sfd == -1)
{
perror("socket error");
return -1;
}
printf("socket succes,sfd=%d\n", sfd);
// 将端口号快速重用
int reuse = 1;
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
{
perror("setsockopt error");
return -1;
}
printf("端口快速重用成功\n");
// 2.为套接字绑定ip地址和端口号
// 2.1填充地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; // 通信域
sin.sin_port = htons(SER_PORT); // 端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); // ip地址
// 2.2绑定地址
if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
// 3.将套接字设置成被动监听状态
if (listen(sfd, 128) == -1)
{
perror("listen error");
return -1;
}
printf("listen success\n");
// 4.阻塞等待客户端连接请求
// 4.1定义变量用于接受客户端的信息
struct sockaddr_in cin; // 用于接收地址信息
socklen_t addrlen = sizeof(cin); // 用于接受长度
// 创建readfds集合
fd_set readfds;
FD_ZERO(&readfds); // 初始化
FD_SET(sfd, &readfds); // 套接字添加进描述符集合
FD_SET(0, &readfds); // 把标准输入流添加进描述符集合
// 套接字集合数组
int newfd_arr[128];
// 数组大小表示客户端连接个数
int newfd_len = 0;
while (1)
{
fd_set temp = readfds;
select(FD_SETSIZE, &temp, 0, 0, 0);
//激活标准输入流
if (FD_ISSET(0, &temp))
{
// 标准输入流激活,把消息发送给所有客户端
char buf[128] = "";
read(0, buf, sizeof(buf));
buf[strlen(buf) - 1] = 0;
for (int i = 0; i < newfd_len; i++)
{
int newfd = newfd_arr[i];
send(newfd, buf, strlen(buf), 0);
printf("发送成功\n");
}
}
//激活套接字
if (FD_ISSET(sfd, &temp) == 1)
{
int newfd = accept(sfd, (struct sockaddr *)&cin, &addrlen);
if (newfd == -1)
{
perror("accept error");
return -1;
}
printf("%s;%d:已成功连接\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
//添加新的客户端套接字到描述符集合
FD_SET(newfd, &readfds);
insert_newfd(newfd_arr, &newfd_len, newfd);
}
//激活客户端套接字
for (int i = 0; i < newfd_len; i++)
{
int newfd = newfd_arr[i];
if (FD_ISSET(newfd, &temp))
{
// 4.收数据
char buf[128] = "";
int res = read(newfd, buf, 128);
if (res == 0)
{
printf("%s;%d:已断开连接\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
FD_CLR(newfd, &readfds);
remove_newfd(newfd_arr, &newfd_len, newfd);
close(newfd);
i--;
break;
}
printf("%s;%d:发来消息:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),buf);
for (int j = 0; j < newfd_len; j++)
{
if (newfd_arr[j]!=newfd)
{
send(newfd_arr[j], buf, strlen(buf), 0);
}
}
}
}
}
close(sfd);
return 0;
}
客户端代码:
#include <myhead.h>
#define SER_PORT 6666 // 服务器端口
#define SER_IP "192.168.0.151" // 客户端IP地址
#define CLI_PORT 8888 // 客户端端口
#define CLI_IP "192.168.0.151" // 客户端IP地址
/***********************主函数**************************/
int main(int argc, char const *argv[])
{
// 1.创建用于通信的套接字文件描述符
int cfd = socket(AF_INET, SOCK_STREAM, 0);
if (cfd == -1)
{
perror("socket error");
return -1;
}
printf("cfd=%d\n", cfd); // 3
// 将端口号快速重用
int reuse = 1;
if (setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
{
perror("setsockopt error");
return -1;
}
printf("端口快速重用成功\n");
// 2.绑定ip地址和端口号
// 2.1填充地址信息结构体
struct sockaddr_in cin;
cin.sin_family = AF_INET; // 通信域
cin.sin_port = htons(CLI_PORT); // 端口号
cin.sin_addr.s_addr = inet_addr(CLI_IP); // ip地址
// 3.连接到服务器
struct sockaddr_in sin;
sin.sin_family = AF_INET; // 通信域
sin.sin_port = htons(SER_PORT); // 服务器端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); // 服务器ip地址
// 3.2连接服务器
if (connect(cfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
{
perror("connect error");
return -1;
}
printf("连接服务器成功\n");
fd_set readfds;
FD_ZERO(&readfds);//
FD_SET(cfd, &readfds);//套接字添加进描述符集合
FD_SET(0, &readfds); // 把标准输入流添加进
// 4.数据收发
char buf[128] = "";
while (1)
{
fd_set temp = readfds;
select(FD_SETSIZE, &temp, 0, 0, 0);
//激活套接字
if (FD_ISSET(cfd, &temp))
{
char buf[128] = "";
recv(cfd, buf, sizeof(buf), 0);
printf("%s\n", buf);
}
//激活标准输入流
if (FD_ISSET(0, &temp))
{
char buf[128] = "";
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = 0;
if (strcmp(buf, "quit") == 0)
{
break;
}
// 将数据发送给服务器
send(cfd, buf, strlen(buf), 0);
}
}
// 5.关闭套接字
close(cfd);
return 0;
}