mmo游戏服务器的一般架构
loginServer :登录
GatewayServer:网关
GameServer :游戏核心逻辑服务 有2个水平扩展方式,多复制几个gameserver分成区,单个区再水平拆分成多个场景seceneServer
SessionServer:会话管理多个场景地图--游戏逻辑的水平拆分多个场景SceneServer,由session来管理协调
recordScrver :数据存储服务器 --数据持久化
SuperServer :管理服务器进程,用来管理普通服务器,同时也是处理对外区服相关操作的桥梁,一般需要连接rolechangeserver,从而完成转区操作。
LogServer :日志服务器
优点:这种多进程单线程,架构相比于多线程更容易扩展,
缺点:无法解决单场景分割问题,共享内存和DB服务器挂掉只能靠日志回档。
socket 的buffer缓冲区
linux系统提供的socket缓冲区大小为8K,你可以将之设置为64K,尤其在传输实时视频时。
使用socket发送数据时先把数据发送到socket缓冲区中,之后接收函数从缓冲区中读取数据,如果发送端特别快的时候,缓冲区很快就被填满(默认1024×8=8192字节),我们可以根据情况设置缓冲区的大小,通过setsockopt函数实现
memcpy函数
用于内存复制的函数,声明在 string.h 中(C++是 cstring)。其原型是:
void *memcpy(void *destin, void *source, unsigned n); //参数1目标地址2源地址3拷贝数据长度,拷贝是从收地址开始
strtok函数
socket 传输结构体
struct UsrData{
char usr_id[16];
char usr_pwd[16];
char usr_nickname[16];
};
//socket发送
UsrData sendUser;
memcpy( sendUser.usr_id, “100001”, sizeof(“100001”) );
memcpy( sendUser.usr_pwd, “123456”, sizeof(“123456”) );
memcpy( sendUser.usr_nickname, “Rock”, sizeof(“Rock”) );
send( m_socket, (char *)&sendUser, sizeof(UsrData), 0 );
//socket接收解析
char buffer[1024];
UsrData recvUser;
recv( m_socket, buffer, sizeof(buffer), 0 );
memcpy( &recvUser, buffer, sizeof(buffer) );
//解析得到的和发送的对象数据相同,原因是:结构体对象的内存区域连续,
同时每个成员的区块大小都分配好了,当接收完自己的区块,其实自己的数据已经接收完成。
socket 传输中文
C/C++中的socket通讯使用缓冲区(buffer)来存储接收和发送的数据。缓冲区是一个字符数组,其大小通常为操作系统所支持的最大数据包大小。当从socket中接收数据时,数据会被存储在缓冲区中,等待程序读取;当发送数据时,数据会被写入缓冲区,等待操作系统发送。
中文消息的传输需要注意编码格式。因为C/C++中的字符串是以ASCII码为基础的,而中文字符通常是以Unicode编码存储。在使用socket传输中文消息时,需要先将Unicode编码转换为其他编码,例如UTF-8或GBK,然后再使用socket传输。接收方需要将接收到的数据先转换为Unicode编码,再进行处理。
以下是使用UTF-8编码传输中文消息的示例代码:
// 发送方
char* msg = "你好,世界!";
int len = strlen(msg) + 1;
send(sock, (char*)&len, sizeof(int), 0); // 发送消息长度
send(sock, msg, len, 0); // 发送消息内容
// 接收方
int len = 0;
recv(sock, (char*)&len, sizeof(int), 0); // 接收消息长度
char* buf = new char[len];
recv(sock, buf, len, 0); // 接收消息内容
wstring wstr = utf8_decode(buf); // 将接收到的UTF-8编码转换为Unicode编码
delete[] buf;
// 处理接收到的消息
其中,utf8_decode是一个将UTF-8编码转换为Unicode编码的函数
io多路复用
概念:用一个线程管理多个socket就是io多路复用。一个线程就可以处理多个socket/io。
io多路复用技术,select poll epoll 数据库一般使用select因为select可以支持跨平台。而epoll则不行。
epoll封装reactor
epoll的几个函数功能介绍:
创建epoll epoll_creat
删除
增加socket epoll_
移除socket
检测socket事件 epoll_wait
标记socket 相关信息的结构体。
socketitem
线程池的设计
执行队列:
任务队列:
管理组件:
封装API: 接口设计1.初始化 2.增减线程 3.
异步的解决:
线程池的解决什么问题
线程个数的确定
如何做到同步的
缓存的队列
如何那节点
消息队列
消息队列一般用于分布式;
使用类似于线程池里的任务队列。
k8s和docker技术
linux下使用protobuf 具体步骤
1. 安装protobuf
在Linux上安装protobuf,可以使用以下命令:
```
sudo apt-get install protobuf-compiler
```
2. 编写.proto文件
在使用protobuf之前,需要先编写.proto文件。这是一种用于定义数据结构的语言,类似于XML和JSON。以下是一个简单的.proto文件示例:
```
syntax = "proto3";
message Person {
string name = 1;
int32 age = 2;
string email = 3;
}
```
3. 编译.proto文件
使用protobuf编译器将.proto文件编译成可执行文件。以下是一个示例命令:
```
protoc --cpp_out=. person.proto
```
这将生成一个名为person.pb.cc的C++文件和一个名为person.pb.h的头文件。
4. 编写代码
在编写代码之前,需要将protobuf库添加到项目中。以下是一个示例命令:
```
g++ -std=c++11 -I/usr/local/include -L/usr/local/lib -lprotobuf mycode.cpp person.pb.cc -o mycode
```
然后,可以使用protobuf库中的类来序列化和反序列化数据。以下是一个示例代码:
```
#include <iostream>
#include <fstream>
#include "person.pb.h"
using namespace std;
int main() {
Person person;
person.set_name("John Doe");
person.set_age(30);
person.set_email("johndoe@example.com");
fstream output("person.pb", ios::out | ios::binary);
person.SerializeToOstream(&output);
output.close();
fstream input("person.pb", ios::in | ios::binary);
Person new_person;
new_person.ParseFromIstream(&input);
input.close();
cout << "Name: " << new_person.name() << endl;
cout << "Age: " << new_person.age() << endl;
cout << "Email: " << new_person.email() << endl;
return 0;
}
```
5. 编译代码
使用以下命令编译代码:
```
g++ -std=c++11 -I/usr/local/include -L/usr/local/lib -lprotobuf mycode.cpp person.pb.cc -o mycode
```
6. 运行代码
使用以下命令运行代码:
```
./mycode
```
这将输出以下内容:
```
Name: John Doe
Age: 30
Email: johndoe@example.com
```
在C++中除了类中可以有构造函数和析构函数外,结构体中也可以包含构造函数和析构函数,这是因为结构体和类基本雷同,唯一区别是,类中成员变量默认为私有,而结构体中则为公有。注意,C++中的结构体是可以有析构函数和构造函数,而C则不允许。而共用体,它是不可能有析构函数和构造函数的。结构体变量所占内存长度是各成员占的内存长度之和,每个成员分别占有自己的内存单元。共用体变量所占的内存长度等于最长的成员的长度。即它是一种内存覆盖,也就是说,同一块内存在不同的时刻存储不同的值(可以是不同的类型)。
读取配置
.xml 文件
execl文件 转换成.dbc文件 字节流式读取
协议的创建+协议注册(消息的订阅)
1 .lua 修改/添加协议内容 工具生成到文件___Msg.h ./command
2 注册协议 在./Common CmdInfoCheck.cpp
DEfRegCmdLua(RQ_PETEQUIP_RECASTCMD,rqPet___,TOSESSION);
3 接收消息
4 发送消息
Cmd::rt____ cmd;
ower_->sendCmdToMe(cmd);
linux下的gdb调试
gdb -p 进程id //断点进程
b 函数名 //断点函数-带上作用域 可断点函数
c //断点运行
r //运行函数
layout //显示断点函数的代码
n //下一步
set var 变量名=值 //指定变量赋值
p 变量名 //显示变量的值
待补充:
linux根目录
1./bin(可执行文件)
这里放的都是一些可执行文件,也就是我们在 linux上可以执行的那些命令,然而linux上存放命令的目录也是不止一个,也是经常有人会弄混,这里也简单区分下
/bin:里面包含了所有用户都可以执行的命令文件,普通用户和超级用户都可以执行的
/usr/bin:系统自带的一些必备基础命令,如ls,mkdir等等基础命令,这些文件轻易不要动,影响较大
/usr/local/bin:用户自己安装程序的时候,默认的可执行程序的存放路径,一般不会影响整个系统
2./boot(核心文件)
启动Linux的核心文件,这个目录我们一般不用去操心,也不用去管
3./dev
在linux 一切皆文件,/dev目录就是存放硬件相关文件的目录,该目录包含了所有linux用到的外部设备,但存放的并不是驱动程序,存放的是一个访问这些外部设备的端口,我们访问这些设备,和访问一个文件或者目录没有任何区别
4./etc
系统主要的配置文件几乎都放置到这个目录内,包括你安装程序的时候默认的配置文件路径也是这里
例如mysql的配置文件:/etc/my.conf
或者你安装gitlab,配置文件默认存放路径为:/etc/gitlab/gitlab.rb
5./home
如果建立一个用户,用户名是"xx",那么在/home目录下就有一个对应的/home/xx路径,用来存放用户的主目录。
而root用户的家目录不在/home下面,单独出来是/root目录
6./lib
这个目录存放的是一些库文件,很多命令或者程序需要的库文件都在这里,你可以用strace -e open XXX 来看明朗了都用到了那些库文件,如;
可以看到pwd命令的执行用到了两个库文件
7./opt
这个目录不是很重要,主要存放那些可选的程序,删除影响也不大
同时 /usr/local目录 也是 软件安装的目录,不过这里一般是用户手动安装(手动编译安装)的程序,不是通过apt-get或者yum安装的
/usr/local:用户级的程序目录,可以理解为C:/Progrem Files/。用户自己编译的软件默认会安装到这个目录下。
*比如Hadoop,Tomcat的安装目录我一般会放在/usr/目录下
/opt:给主机额外安装软件所摆放的目录。用户级的程序目录,可以理解为D:/Software,opt有可选的意思,这里可以用于放置第三方大型软件(或游戏),当你不需要时,直接rm -rf掉即可。在硬盘容量不够时,也可将/opt单独挂载到其他磁盘上使用。
*比如临时上传到Linux里的软件;tar.gz rpm等文件,用作软件仓库。
8./proc (操作系统信息)
Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
用户可以在这个目录中获取到系统硬件相关信息和进程的信息
每个进程在/proc目录下都有一个进程pid命名的目录
9./root(超级用户目录)
root用户的家目录
10./run (程序运行时文件)
11../sbin (可执行命令)
12./svr
13./sys
14./tmp
15./usr
16./var
define 用法锦集
1. 简单的define定义 #define MAXTIME 1000
2. “函数定义” define可以像函数那样接受一些参数,如下
#define max(x,y) ((x)>(y)?(x):(y));
3.宏函数 多行时 用\分开
#define ADD(a,b) do{}while(0);
定义宏函数
为什么宏函数 要定义成#define fun(a,b) do(){}while(0); 而不能直接定义成 #define Fun(a,b) {};
1.避免宏展开导致语法错误
if(1) fun(1); --宏展开 if(1) {}; //导致语法错误
另一种展开 if(1) do(){}while(0); //不会报错
宏函数的定义可以有多种形式,但是使用do-while循环的形式可以确保宏函数的执行次数只有一次,而且可以避免一些潜在的问题。具体来说,使用do-while循环的形式可以避免以下两个问题:
1. 宏函数中可能包含多条语句,如果直接使用{}包裹这些语句,可能会导致宏函数在某些情况下无法正常执行。例如,如果宏函数中包含了if语句,那么在使用{}包裹宏函数时,if语句的else分支可能会被错误地执行。
2. 宏函数可能会被多次调用,如果没有使用do-while循环的形式,可能会导致宏函数的执行次数不确定。例如,如果宏函数中包含了return语句,那么在使用{}包裹宏函数时,可能会导致宏函数在第一次执行return语句后就结束了,而后续的调用将无法正常执行。
因此,使用do-while循环的形式可以确保宏函数的执行次数只有一次,而且可以避免上述问题。
日志方案 log4cxx
log4cxx 是logj 的一种
日志的优先级
c++ socket数据存储
001
c++ 使用 socket发送数据时或者发送消息发送的数据一般消息数据包怎么存
储?
在C++中使用socket发送数据时,可以使用缓冲区来存储消息数据包。可以使用char类型数组或者std::vector<char>来存储数据包。具体的存储方式取决于消息的格式和大小。
例如,如果消息是一个字符串,可以使用char类型数组来存储:
```c++
char buffer[1024];
strcpy(buffer, "Hello, world!");
send(socket, buffer, strlen(buffer), 0);
```
如果消息是一个结构体,可以使用std::vector<char>来存储:
```c++
struct Message {
int id;
std::string content;
};
Message message = {1, "Hello, world!"};
std::vector<char> buffer(sizeof(Message));
memcpy(buffer.data(), &message, sizeof(Message));
send(socket, buffer.data(), buffer.size(), 0);
```
在发送数据时,需要注意数据的大小和格式,以及网络字节序的转换。可以使用htonl、htons、ntohl、ntohs等函数来进行字节序转换。
c++虚函数多态的应用场景
001
c++做服务器消息解析用虚函数解析为什么
使用虚函数解析服务器消息可以实现多态性,即在运行时根据实际对象类型调用相应的函数。这样可以避免使用大量的if-else语句或switch语句来判断消息类型,提高代码的可读性和可维护性。
同时,使用虚函数还可以方便地扩展服务器消息的解析功能,只需要添加新的子类并重写相应的虚函数即可。
c++做服务器消息解析用虚函数解析为什么? 能否写个代码列子
使用虚函数解析服务器消息的好处是可以实现多态,即在不同的情况下使用不同的解析方式,提高代码的可扩展性和可维护性。
以下是一个简单的代码示例:
```c++
class Message {
public:
virtual void parse() = 0;
};
class LoginMessage : public Message {
public:
void parse() override {
// 解析登录消息的具体实现
}
};
class ChatMessage : public Message {
public:
void parse() override {
// 解析聊天消息的具体实现
}
};
int main() {
// 接收到登录消息
Message* msg = new LoginMessage();
msg->parse();
// 接收到聊天消息
msg = new ChatMessage();
msg->parse();
return 0;
}
```
在上面的示例中,`Message` 是一个抽象基类,定义了一个纯虚函数 `parse()`,表示解析消息的方法。`LoginMessage` 和 `ChatMessage` 是具体的消息类,继承自 `Message` 并实现了 `parse()` 方法。
在 `main()` 函数中,我们可以根据接收到的不同消息类型创建不同的消息对象,并调用它们的 `parse()` 方法进行解析。由于 `parse()` 方法是虚函数,因此可以根据对象的实际类型来调用对应的解析方法,实现多态。
c++写一个线程池
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
class ThreadPool {
public:
ThreadPool(size_t num_threads) : stop(false) {
for (size_t i = 0; i < num_threads; ++i) {
workers.emplace_back([this] {
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });
if (this->stop && this->tasks.empty()) {
return;
}
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
});
}
}
template <typename F, typename... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
if (stop) {
throw std::runtime_error("enqueue on stopped ThreadPool");
}
tasks.emplace([task]() { (*task)(); });
}
condition.notify_one();
return res;
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for (std::thread& worker : workers) {
worker.join();
}
}
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
protobuf协议
1. vi .proto 文件
语法:
syntax = "proto3";
message 消息名{
字段类型 字段名 = 编号 ; //编号1始
.
.
.
}
数据类型
标准基本类型
数组
枚举
lua的使用
.lua文件 生成 .h
c++前置命名空间
Linux下编译的优化; 可以使用前置命名来减少头文件的依赖减少编译时间。
假设我们有一个名为"utils"的前置命名空间,包含多个头文件和源文件,其中包括以下文件: 1. "utils.h":包含一些常见工具函数的声明; 2. "math_util.cpp":包含一些数学相关的函数的实现; 3. "string_util.cpp":包含一些字符串相关的函数的实现; 如果我们在每个文件中都使用了"utils"前置命名空间,那么编译时会重复处理相同的命名空间,导致编译时间变长。 为了优化编译时间,我们可以将命名空间的声明和定义都放在一个单独的头文件"utils_namespace.h"中,并将其与其他头文件的包含分离开来。如下所示: ```c++ // utils_namespace.h namespace utils { // 命名空间的声明 } // utils.h #include "utils_namespace.h" // 包含常见工具函数的声明 // math_util.cpp #include "utils_namespace.h" // 包含数学相关的函数的实现 // string_util.cpp #include "utils_namespace.h" // 包含字符串相关的函数的实现 ```这样做的好处是,在编译时只会对"utils_namespace.h"进行一次解析和编译,其他文件只需要包含它即可,避免了重复编译命名空间的问题,提高了编译效率。
C++中的结构体 与类的区别
c++结构体用法和类相同,但是有区别,结构体的数据是公有的,类可以设置成共有私有等。
socket实现http服务
socket实现的http服务器 是如何接收和解析数据的
当客户端发送HTTP请求到服务器时,服务器的socket会接收到请求数据。服务器会根据HTTP协议规定的格式解析请求数据,包括请求行、请求头和请求体等部分。服务器会根据请求行中的方法类型(GET、POST等)和请求路径(URI)来确定需要执行的操作,比如获取资源、提交数据等。
如果请求中包含请求体(比如POST请求中的表单数据),服务器会根据请求头中的Content-Type字段来确定请求体的类型,并对请求体进行解析。解析后的数据可以用于处理请求,比如保存到数据库中。
服务器在处理完请求后,会根据HTTP协议规定的格式构造响应数据,并通过socket发送给客户端。客户端接收到响应数据后,会根据HTTP协议规定的格式解析响应数据,并根据响应头中的Content-Type字段来确定响应数据的类型,比如HTML、JSON等。最终,客户端会根据响应数据的类型将数据渲染到页面上或者进行其他操作。
socket实现的http服务器 是如何接收和解析数据的,请用c写一个简单的列子
以下是一个简单的示例代码,演示了如何使用socket接收和解析HTTP请求:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
char buffer[1024] = {0};
char response[] = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nHello World!";
// 创建socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 绑定端口
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听端口
if (listen(server_fd, 3) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
// 接收连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept failed");
exit(EXIT_FAILURE);
}
// 接收数据
int bytes_received = recv(new_socket, buffer, sizeof(buffer), 0);
if (bytes_received < 0) {
perror("recv failed");
exit(EXIT_FAILURE);
}
// 解析请求
char *method = strtok(buffer, " ");
char *path = strtok(NULL, " ");
char *http_version = strtok(NULL, "\r\n");
printf("Method: %s\n", method);
printf("Path: %s\n", path);
printf("HTTP Version: %s\n", http_version);
// 发送响应
send(new_socket, response, strlen(response), 0);
// 关闭连接
close(new_socket);
close(server_fd);
return 0;
}
```
该示例代码创建了一个socket服务器,监听8080端口。当有客户端连接时,服务器接收客户端发送的HTTP请求,并解析出请求方法、路径和HTTP版本。然后服务器发送一个简单的HTTP响应,关闭连接。
socket实现的http服务器 是如何接收和解析数据的,如果socket发送的数据超过socket中buffer最大值 代码如何处理 请用c写一个列子
Socket实现的HTTP服务器接收和解析数据的过程如下:
1. 服务器创建一个Socket对象,并绑定到指定的IP地址和端口号上,等待客户端连接。
2. 客户端连接到服务器后,服务器接受连接请求,创建一个新的Socket对象,并与客户端的Socket对象建立连接。
3. 服务器从客户端Socket对象中读取HTTP请求数据,包括请求行、请求头和请求体等信息。
4. 服务器解析HTTP请求数据,获取请求方法、请求路径、请求参数等信息。
5. 服务器根据请求路径和请求方法,执行相应的业务逻辑,生成HTTP响应数据。
6. 服务器将HTTP响应数据写入到客户端Socket对象中,发送给客户端。
如果Socket发送的数据超过Socket中buffer最大值,代码可以通过以下方式处理:
1. 使用循环发送数据,每次发送一部分数据,直到所有数据都发送完毕。
2. 使用缓存机制,将数据分成多个小块,每次发送一块数据,直到所有数据都发送完毕。
3. 使用异步IO机制,将数据写入到操作系统的缓冲区中,等待操作系统将数据发送出去,不需要等待发送完成再继续执行后续代码。
下面是一个使用循环发送数据的示例代码:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAX_BUFFER_SIZE 1024
int main(int argc, char *argv[]) {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
char buffer[MAX_BUFFER_SIZE];
int buffer_len, sent_len, total_sent_len;
// 创建Socket对象
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 绑定IP地址和端口号
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(8080);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
// 监听连接请求
if (listen(server_fd, 10) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
while (1) {
// 接受连接请求
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept");
continue;
}
// 读取HTTP请求数据
buffer_len = recv(client_fd, buffer, MAX_BUFFER_SIZE, 0);
if (buffer_len == -1) {
perror("recv");
close(client_fd);
continue;
}
// 处理HTTP请求数据
// ...
// 发送HTTP响应数据
total_sent_len = 0;
while (total_sent_len < buffer_len) {
sent_len = send(client_fd, buffer + total_sent_len, buffer_len - total_sent_len, 0);
if (sent_len == -1) {
perror("send");
close(client_fd);
break;
}
total_sent_len += sent_len;
}
// 关闭连接
close(client_fd);
}
// 关闭Socket对象
close(server_fd);
return 0;
}
```
ient_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd == -1) {
perror(“accept”);
continue;
}
// 读取HTTP请求数据
buffer_len = recv(client_fd, buffer, MAX_BUFFER_SIZE, 0);
if (buffer_len == -1) {
perror("recv");
close(client_fd);
continue;
}
// 处理HTTP请求数据
// ...
// 发送HTTP响应数据
total_sent_len = 0;
while (total_sent_len < buffer_len) {
sent_len = send(client_fd, buffer + total_sent_len, buffer_len - total_sent_len, 0);
if (sent_len == -1) {
perror("send");
close(client_fd);
break;
}
total_sent_len += sent_len;
}
// 关闭连接
close(client_fd);
}
// 关闭Socket对象
close(server_fd);
return 0;
}