网络聊天室是一种基于网络的实时通讯工具,它能够让用户在不同的地方通过网络进行交流。
程序原理
网络聊天室程序主要包含以下几个部分:
服务器端:用于接收和转发客户端发送的消息。
客户端:用于与服务器进行连接,并发送和接收消息。
当客户端连接服务器时,服务器会为每个客户端分配一个唯一的套接字描述符,用于标识该客户端。客户端可以通过套接字描述符向服务器发送消息,服务器收到消息后会将其转发给其他客户端。
服务器端实现
服务器端的实现主要包括以下几个步骤:
创建套接字:使用 socket
函数创建一个套接字,用于监听客户端连接请求。
绑定端口:使用 bind
函数将套接字与指定的端口号绑定,以便客户端能够连接服务器。
监听连接请求:使用 listen
函数监听客户端连接请求。
接受连接请求:使用 accept
函数接受客户端连接请求,并为该客户端分配一个唯一的套接字描述符。
接收和转发消息:使用 recv
函数接收客户端发送的消息,并使用 send
函数将消息转发给其他客户端。
下面是服务器端的代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX_CLIENTS 10
#define BUF_SIZE 1024
int main(int argc, char *argv[])
{
int server_fd, client_fd[MAX_CLIENTS], max_fd, i, activity, valread;
struct sockaddr_in server_addr, client_addr;
char buffer[BUF_SIZE] = {0};
fd_set read_fds;
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
{
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(atoi(argv[1]));
// 绑定端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接请求
if (listen(server_fd, MAX_CLIENTS) < 0)
{
perror("listen failed");
exit(EXIT_FAILURE);
}
// 初始化客户端套接字描述符数组
for (i = 0; i < MAX_CLIENTS; i++)
{
client_fd[i] = 0;
}
while (1)
{
// 清空文件描述符集合
FD_ZERO(&read_fds);
// 将服务器套接字加入文件描述符集合
FD_SET(server_fd, &read_fds);
max_fd = server_fd;
// 将所有客户端套接字加入文件描述符集合
for (i = 0; i < MAX_CLIENTS; i++)
{
if (client_fd[i] > 0)
{
FD_SET(client_fd[i], &read_fds);
}
if (client_fd[i] > max_fd)
{
max_fd = client_fd[i];
}
}
// 等待活动的套接字
activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (activity < 0)
{
perror("select failed");
exit(EXIT_FAILURE);
}
// 如果有新的连接请求,接受连接,并将其加入客户端套接字描述符数组
if (FD_ISSET(server_fd, &read_fds))
{
int addr_len = sizeof(client_addr);
if ((client_fd[i] = accept(server_fd, (struct sockaddr *)&client_addr, (socklen_t *)&addr_len)) < 0)
{
perror("accept failed");
exit(EXIT_FAILURE);
}
// 发送欢迎消息给新连接的客户端
char *welcome_msg = "Welcome to the chat room!";
send(client_fd[i], welcome_msg, strlen(welcome_msg), 0);
}
// 接收和转发消息
for (i = 0; i < MAX_CLIENTS; i++)
{
if (FD_ISSET(client_fd[i], &read_fds))
{
valread = recv(client_fd[i], buffer, BUF_SIZE, 0);
if (valread <= 0)
{
// 如果客户端关闭连接,从客户端套接字描述符数组中移除该套接字
close(client_fd[i]);
client_fd[i] = 0;
}
else
{
// 将客户端发送的消息转发给其他客户端
for (int j = 0; j < MAX_CLIENTS; j++)
{
if (j != i && client_fd[j] > 0)
{
send(client_fd[j], buffer, strlen(buffer), 0);
}
}
}
}
}
}
return 0;
}
客户端实现
客户端的实现主要包括以下几个步骤:
创建套接字:使用 socket
函数创建一个套接字。
连接服务器:使用 connect
函数连接服务器。
发送和接收消息:使用 send
函数向服务器发送消息,使用 recv
函数接收服务器转发的消息。
下面是客户端的代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#define BUF_SIZE 1024
int main(int argc, char *argv[])
{
int client_fd;
struct sockaddr_in server_addr;
char buffer[BUF_SIZE] = {0};
// 创建套接字
if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
{
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
// 将IP地址从点分十进制转换为二进制格式
if (inet_pton(AF_INET, argv[1], &server_addr.sin_addr) <= 0)
{
perror("inet_pton failed");
exit(EXIT_FAILURE);
}
// 连接服务器
if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("connect failed");
exit(EXIT_FAILURE);
}
// 接收欢迎消息
if (recv(client_fd, buffer, BUF_SIZE, 0) > 0)
{
printf("%s", buffer);
}
// 发送和接收消息
while (1)
{
fgets(buffer, BUF_SIZE, stdin);
send(client_fd, buffer, strlen(buffer), 0);
memset(buffer, 0, BUF_SIZE);
recv(client_fd, buffer, BUF_SIZE, 0);
printf("%s", buffer);
}
return 0;
}
编译和运行
将服务器端和客户端的代码保存到两个不同的文件中,并使用以下命令编译:
gcc server.c -o server
gcc client.c -o client
然后在一个终端中运行服务器:
./server 8080
其中 8080
是服务器监听的端口号。
在另一个终端中运行客户端:
./client 127.0.0.1 8080
其中 127.0.0.1
是服务器的IP地址,8080
是服务器的端口号。
运行客户端后,将能够在终端中输入消息,并将其发送给服务器。服务器将把该消息转发给其他连接的客户端。