参考了网上的代码,自己改了一下,并不完善但可以实现我想要的功能。记事本中的代码排序是好的,粘贴在这里就乱了。
服务器代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#define BACKLOG 100
#define SERIP "192.168.149.133"
#define SERPORT 9021
#define CONCURRENT_MAX 10 //并发的连接数
#define QUIT_CMD "quit"
#define CMD_REGISTER 1001 // 注册指令
#define CMD_CHAT 1002 // 聊天指令
typedef struct
{
int fd; //客户端对应的描述符
char name[20]; //姓名
char ip[20]; //IP地址
char msg[100]; //收发的消息
int cmd; //命令码
}comstr;
comstr client_info[CONCURRENT_MAX]; //存储用户区信息
comstr client_root = {0}; //服务器消息缓冲区,主要用来发送数据
comstr client_buf = {0}; //缓存接收信息
void transmit(int num);
int main(int argc, char **argv)
{
struct sockaddr_in seraddr;
struct sockaddr_in cliaddr;
fd_set server_fd_set;
int max_fd = -1;
struct timeval tv; //超时时间设置
time_t tNow; //时间相关
struct tm *pt;
//1.打开socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("socket");
return -1;
}
//assigning a name to a socket
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SERPORT);
seraddr.sin_addr.s_addr = inet_addr(SERIP);
//2.绑定socket端口
int ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("bind error");
return -1;
}
//3.监听
ret = listen(sockfd, BACKLOG);
if (ret < 0)
{
perror("listen error");
return -1;
}
strcpy(client_root.name, "root");
strcpy(client_root.ip, SERIP);
while (1)
{
//1.添加标准输入IO到集合
tv.tv_sec = 20;
tv.tv_usec = 0;
FD_ZERO(&server_fd_set);
FD_SET(STDIN_FILENO, &server_fd_set); //STDIN_FILENO类型为int(open、read、write等使用),stdin类型为FILE*(fopen、fread、fwrite等使用)
if(max_fd < STDIN_FILENO) //记录描述符最大值
{
max_fd = STDIN_FILENO;
}
//2.添加socket服务端IO到集合
FD_SET(sockfd, &server_fd_set); //服务端sockfd
if (max_fd < sockfd) {
max_fd = sockfd;
}
//3.for循环添加客户端IO到集合
for (int i = 0; i < CONCURRENT_MAX; i++)
{
if (client_info[i].fd != 0)
{
FD_SET(client_info[i].fd, &server_fd_set);
if (max_fd < client_info[i].fd)
{
max_fd = client_info[i].fd;
}
}
}
//4.select等待IO事件
int ret = select(max_fd + 1, &server_fd_set, NULL, NULL, &tv);
if (ret < 0)
{
perror("select error");
return -1;
}
else if (ret == 0)
{
printf("超时.\n");
}
else //返回IO数量
{
//5.键盘输入事件
if(FD_ISSET(STDIN_FILENO, &server_fd_set))
{
memset(client_root.msg, 0, sizeof(client_root.msg));
read(STDIN_FILENO, client_root.msg, sizeof(client_root.msg)); //读键盘
if (strcmp(client_root.msg, QUIT_CMD) == 0) //输入quit则退出服务器
{
exit(0);
}
printf("服务器发送的消息:\n");
for (int i = 0; i < CONCURRENT_MAX; i++)
{
if (client_info[i].fd != 0)
{
printf("client_fds[%d]=%d\n", i, client_info[i].fd);
send(client_info[i].fd, &client_root, sizeof(client_root), 0); //发送结构体信息
}
}
}
//6.客户端连接事件,得到fd,解析后得到IP和端口号
if(FD_ISSET(sockfd, &server_fd_set))
{
socklen_t len = sizeof(struct sockaddr); //len必须初始化为第二个参数的字节长度
int clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len); //得到客户端socket描述符
if (clifd > 0)
{
int index = -1;
for(int i = 0; i < CONCURRENT_MAX; i++) //将文件描述符放入结构体数组
{
if(client_info[i].fd == 0)
{
index = i;
client_info[i].fd = clifd;
strcpy(client_info[i].ip, inet_ntoa(cliaddr.sin_addr));
break;
}
}
if(index >= 0)
{
printf("新客户端(%d)加入成功 %s:%d\n", index, inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
}
else //没有位置放置新的客户端信息
{
memset(client_root.msg, 0, sizeof(client_root.msg)); //清空输入buf缓冲区
strcpy(client_root.msg, "服务器加入的客户端数达到最大值,无法加入!\n");
send(clifd, &client_root, sizeof(client_root), 0); //发送服务器的信息
printf("客户端连接数达到最大值,新客户端加入失败 %s:%d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
}
}
}
//7.客户端发过来消息事件
for(int i = 0; i < CONCURRENT_MAX; i++)
{
if(0 != client_info[i].fd)
{
if(FD_ISSET(client_info[i].fd, &server_fd_set)) //判断每个客户端是否发了消息
{
memset(&client_buf, 0, sizeof(client_buf)); //清空输入buf缓冲区
long byte_num = recv(client_info[i].fd, &client_buf, sizeof(client_buf), 0); //将收到的信息放到client_buf中
if (byte_num > 0)
{
if (client_buf.cmd == CMD_REGISTER) //用户注册
{
strcpy(client_info[i].name, client_buf.name); //存储客户端发送过来的姓名
}
else if (client_buf.cmd == CMD_CHAT) //聊天
{
memset(client_info[i].msg, 0, sizeof(client_info[i].msg));
strcpy(client_info[i].msg, client_buf.msg); //存储了客户端发送过来的消息
tNow = time(NULL); //获取时间戳
if (tNow == -1)
{
perror("time error");
return -1;
}
pt = localtime(&tNow); //本地时间
printf("%s <%s> %d/%d/%d %d:%d:%d\n", client_info[i].name, client_info[i].ip, pt->tm_year+1900, pt->tm_mon+1, \
pt->tm_mday, pt->tm_hour, pt->tm_min, pt->tm_sec);
printf("- %s", client_info[i].msg);
transmit(i); //转发消息到其它客户端
}
}
else if(byte_num < 0)
{
printf("从客户端(%d)接受消息出错.\n", i);
}
else
{
FD_CLR(client_info[i].fd, &server_fd_set);
client_info[i].fd = 0;
printf("客户端(%d)退出了\n", i);
}
}
}
}
}
}
return 0;
}
// 转发消息给其他客户端
void transmit(int num)
{
for(int i = 0; i < CONCURRENT_MAX; i++)
{
if((0 != client_info[i].fd) && (i != num)) //当前客户端需要转发消息
{
send(client_info[i].fd, &client_info[num], sizeof(client_info[num]), 0); //向socket文件转发信息
}
}
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#define SERIP "192.168.149.133"
#define SERPORT 9021
#define CMD_REGISTER 1001 // 注册指令
#define CMD_CHAT 1002 // 聊天指令
typedef struct
{
int fd; //客户端对应的描述符
char name[20]; //姓名
char ip[20]; //IP地址
char msg[100]; //收发的消息
int cmd; //命令码
}comstr;
int main(int argc, const char * argv[])
{
struct sockaddr_in seraddr;
fd_set client_fd_set;
struct timeval tv;
comstr chat; //存储用户自身的信息
comstr serinfo; //存储服务器发送过来的消息
time_t tNow; //时间相关
struct tm *pt;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SERPORT);
seraddr.sin_addr.s_addr = inet_addr(SERIP);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("socket error");
return 1;
}
if(connect(sockfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr_in)) == 0)
{
printf("请输入您的姓名: ");
scanf("%s", chat.name);
printf("欢迎%s来到本群.\n", chat.name);
chat.cmd = CMD_REGISTER; //向服务器发送注册信息
int ret = send(sockfd, &chat, sizeof(chat), 0);
if(ret == -1)
{
perror("发送消息出错!\n");
}
while(1)
{
tv.tv_sec = 20;
tv.tv_usec = 0;
FD_ZERO(&client_fd_set);
FD_SET(STDIN_FILENO, &client_fd_set);
FD_SET(sockfd, &client_fd_set);
select(sockfd + 1, &client_fd_set, NULL, NULL, &tv);
if(FD_ISSET(STDIN_FILENO, &client_fd_set)) //客户端发送消息
{
memset(chat.msg, 0, sizeof(chat.msg));
read(STDIN_FILENO, chat.msg, sizeof(chat.msg)); //读键盘
chat.cmd = CMD_CHAT; //聊天信息指令
ret = send(sockfd, &chat, sizeof(chat), 0); //发送用户区信息
if(ret == -1)
{
perror("发送消息出错!\n");
}
}
if(FD_ISSET(sockfd, &client_fd_set)) //客户端接收消息
{
memset(&serinfo, 0, sizeof(serinfo));
long byte_num = recv(sockfd, &serinfo, sizeof(serinfo), 0);
if(byte_num > 0)
{
tNow = time(NULL);
if (tNow == -1) {
perror("time error");
return -1;
}
pt = localtime(&tNow); //本地时间
printf("%s <%s> %d/%d/%d %d:%d:%d\n", serinfo.name, serinfo.ip, pt->tm_year+1900, pt->tm_mon+1, pt->tm_mday, \
pt->tm_hour, pt->tm_min, pt->tm_sec);
printf("- %s", serinfo.msg);
}
else if(byte_num < 0)
{
printf("接受消息出错!\n");
}
else
{
printf("服务器端退出!\n");
exit(0);
}
}
}
}
return 0;
}
实际效果:
参考网址: https://blog.csdn.net/Ctrl_qun/article/details/52524086