Socket之网络聊天室-server
实现流程:
一个简单的TCP服务器,使用了epoll进行事件驱动编程。以下是代码的主要部分的解释:
1. 创建套接字:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
2. 设置SO_REUSEADDR选项:
这里创建了一个IPv4的TCP套接字。
int val = 1;
int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(int));
3. 绑定套接字到地址和端口:
这确保在套接字关闭后,可以立即重新使用该地址和端口进行绑定。
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(9999);
ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
4. 监听套接字:
服务器套接字被绑定到所有可用的IPv4地址(INADDR_ANY
)和端口9999。
ret = listen(sockfd, 1024);
5. 创建epoll实例:
服务器开始监听连接,最多可以有1024个连接在等待队列中。
int epid = epoll_create(1);
6. 添加服务器套接字到epoll:
创建一个epoll实例,用于管理事件。
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
ret = epoll_ctl(epid, EPOLL_CTL_ADD, sockfd, &ev);
7. 主循环:
将服务器套接字添加到epoll实例中,并设置事件为EPOLLIN
,即当套接字上有数据可读时,epoll会通知。
while (1) { ... }
主循环中,服务器等待客户端的连接或消息,然后进行相应的处理。
接受新连接: 当`epfd`(epoll的文件描述符)上有数据可读时,表示有新的连接或消息。如果是服务器套接字`sockfd`上有数据可读,那么表示有新的客户端连接。服务器接受这个连接,并将新的客户端套接字添加到epoll中。
处理客户端消息: 如果是客户端套接字上有数据可读,服务器读取这些数据,并进行处理。首先,它检查是否是从客户端接收到的第一条消息(即客户端的名字)。如果是,它将保存这个名字。否则,它将这个消息广播给所有连接的客户端(除了发送该消息的客户端)。
8. 关闭资源:
//关闭 close(epid);
close(sockfd);
在程序结束时,关闭epoll实例和服务器套接字。
完整实现:
#include<iostream>
#include<string>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<map>
const int MAX_CONN = 1024;
struct Clinent
{
int sockfd;
std::string name;
};
int main() {
//std::cout << "hello" << std::endl;
//信箱
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("sockfd create error");
return -1;
}
// 解决在close之后会有一个WAIT_TIME,导致bind失败的问题
int val = 1;
int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(int));
if (ret == -1)
{
printf("setsockopt");
exit(1);
}
//
struct sockaddr_in addr;
addr.sin_family = AF_INET; //ipv4家族
addr.sin_addr.s_addr = htonl(INADDR_ANY);//所有ip地址 host to net
addr.sin_port = htons(9999);//进行主机字节序和网络字节序之间的转换
ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
perror("bind error");
return -1;
}
ret = listen(sockfd, 1024);
if (ret < 0) {
perror("listen error");
return -1;
}
int epid = epoll_create(1);
if (epid < 0) {
perror("epoll create error");
return -1;
}
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
ret = epoll_ctl(epid, EPOLL_CTL_ADD, sockfd, &ev);
if (ret < 0) {
perror("epoll_ctl error");
return -1;
}
//保存客户端
std::map<int, Clinent> clients;
while (1) {
struct epoll_event evs[MAX_CONN];
int n = epoll_wait(epid, evs, MAX_CONN, -1);
if (n < 0)
{
perror("epoll_wait error");
break;
}
for (int i = 0; i < n; i++) {
int fd = evs[i].data.fd;
if (fd == sockfd) {
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_sockfd = accept(sockfd, (struct sockaddr*)& client_addr, &client_addr_len );
if (client_sockfd < 0 ) {
perror("accept error");
continue;
}
struct epoll_event ev_client;
ev_client.events = EPOLLIN;
ev_client.data.fd = client_sockfd;
ret = epoll_ctl(epid, EPOLL_CTL_ADD, client_sockfd, &ev_client);
if (ret < 0) {
perror("epoll_ctl error");
return -1;
}
std::cout << client_addr.sin_addr.s_addr << "conneting..." << std::endl;
//保存该客户端信息
Clinent client;
client.sockfd = client_sockfd;
client.name = "";
clients[client_sockfd] = client;
}
else//客户端有消息
{
char buffer[1024];
//接受消息
int n = read(fd, buffer, 1024);
if (n < 0) {
break;
}
else if (n == 0) {
//断开
close(fd);
epoll_ctl(epid, EPOLL_CTL_DEL, fd, 0);
clients.erase(fd);
}
else {
std::string msg(buffer, n);
if (clients[fd].name == "")
{
clients[fd].name = msg;
}
else {
std::string name = clients[fd].name;
for (auto& c : clients) {
if (c.first != fd) {
//发送消息
write(c.first, ('[' + name + ']' + ": " + msg).c_str(), msg.size() + name.size()+4);
}
}
}
}
}
}
}
//关闭
close(epid);
close(sockfd);
}
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果