C语言游戏服务器框架与串行通信实践:comtest_RTU源码剖析

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本文详细解析了基于C语言的comtest_RTU项目,该项目集成了游戏服务器框架和使用MFC进行RS232串行通信及RTU数据格式校验的技术。C语言以其低级内存管理和高效性能,特别适合系统级编程和游戏服务器开发。文章涵盖了线程管理、网络编程、数据结构、内存管理和错误处理等多个关键概念,旨在帮助开发者深入理解游戏服务器开发和通信协议的实际应用。

1. C语言游戏服务器框架实现

1.1 游戏服务器框架的概念

在现代游戏开发中,服务器框架扮演着至关重要的角色。游戏服务器框架是指一组软件组件和服务,它为运行游戏逻辑提供了支持,包括玩家数据处理、游戏状态同步、网络通信等功能。服务器框架通常是可扩展的,可以根据游戏需求进行定制,以确保满足性能和稳定性要求。

1.2 C语言的优势与挑战

C语言由于其高效性和接近硬件层面的控制能力,一直是游戏开发的首选语言之一。C语言编写的服务器框架能够提供出色的性能,特别是在处理大量并发连接时。然而,C语言对内存管理的要求较高,开发者需要手动管理内存分配和释放,这增加了编程的复杂性和错误的风险。

1.3 实现步骤概览

要实现一个基本的C语言游戏服务器框架,可以按照以下步骤进行:

  1. 设计服务器架构:首先,需要设计游戏服务器的架构,确定游戏逻辑的分布和数据流程。
  2. 编写网络通信模块:实现基于socket的网络通信,处理客户端和服务器之间的数据交换。
  3. 实现数据处理逻辑:根据游戏需求编写处理游戏数据的逻辑,如玩家行为、游戏状态更新等。
  4. 线程管理:为并发处理玩家请求,实现多线程或线程池管理,优化资源利用和性能。
  5. 安全和错误处理:确保服务器能够安全运行,实现必要的错误处理和日志记录机制。

通过这一章节,我们对C语言游戏服务器框架有了初步的认识,并概述了它的实现步骤。后续章节将详细探讨每一部分的实现细节,为读者提供完整的框架实现指导。

2. MFC实现RS232串行通信

2.1 MFC串行通信基础

2.1.1 MFC串行通信原理

MFC(Microsoft Foundation Classes)提供了对串行端口的高层次抽象,简化了基于Windows平台的串行通信编程。它封装了Win32 API的串行通信功能,允许开发者通过类库接口实现数据的发送和接收。

MFC实现串行通信的核心是CSerialPort类,该类提供了设置串口参数、打开关闭串口、读写数据等操作。通信原理方面,MFC使用的是Windows的通信驱动程序,通信双方必须预先约定好通信协议,包括波特率、数据位、停止位和校验位等参数。

2.1.2 MFC串行通信环境配置

为了使用MFC进行串行通信,需要按照以下步骤配置开发环境:

  1. 创建一个MFC应用程序项目。
  2. 在项目中包含串行通信支持,通常使用CAsyncSocket类及其派生类CSerialSocket。
  3. 配置串口参数,如COM端口号、波特率、数据位数、停止位和奇偶校验位。
  4. 在代码中通过CSerialPort或CSerialSocket对象管理串口的打开、读写和关闭操作。

2.2 RS232通信协议详解

2.2.1 RS232协议标准介绍

RS232是一种广泛应用的串行通信标准,主要用于计算机和终端或外设之间的通信。它是基于全双工通信的串行标准,能够支持多种数据传输速率和异步操作。

RS232标准规定了电气特性和信号引脚的功能,包括逻辑电平的高低、信号的传输速率和距离限制、连接器的形状和引脚排列等。RS232协议通常使用DB9或者DB25连接器,通信双方的设备必须通过电缆连接到对应的引脚上。

2.2.2 RS232通信参数设置

在实现RS232通信时,需要正确设置以下参数:

  • 波特率:通信双方通信速率的匹配,常见值有9600、115200等。
  • 数据位:数据包中实际数据位的数量,通常是8位。
  • 停止位:表示数据包结束的位数,可以是1位、1.5位或2位。
  • 校验位:用于检测数据错误的位,有无校验、奇校验、偶校验等选择。

2.3 MFC中RS232通信的实现

2.3.1 MFC串行通信类的设计

在MFC中实现串行通信通常涉及自定义一个串行通信类,该类继承自CAsyncSocket或CSerialSocket,并覆盖其相关方法。下面是一个简化版的串行通信类的设计示例:

class CMySerialPort : public CSerialSocket
{
public:
    virtual void OnConnect(int nErrorCode);
    virtual void OnReceive(int nErrorCode);
    virtual void OnClose(int nErrorCode);
    // ...其他方法
};
  • OnConnect :当串口连接成功或失败时被调用。
  • OnReceive :当串口接收到数据时被调用。
  • OnClose :当串口关闭时被调用。

在实际使用中,还需要实现数据接收缓冲区的处理逻辑,保证数据的正确读取和处理。

2.3.2 数据收发机制与实践

数据收发机制的实现基于CAsyncSocket的异步操作,通过重载事件处理函数来响应特定事件。以下是串行通信中的数据收发机制的实现示例:

void CMySerialPort::OnReceive(int nErrorCode)
{
    if (nErrorCode == 0)
    {
        char szBuffer[1024]; // 缓冲区大小
        int nBytesRead = Receive(szBuffer, 1024); // 异步读取数据

        if (nBytesRead > 0)
        {
            // 处理接收到的数据
            ProcessData(szBuffer, nBytesRead);
        }
    }
    else
    {
        // 错误处理
        handleError(nErrorCode);
    }

    // 继续准备接收下一批数据
    CSerialSocket::OnReceive(nErrorCode);
}

在实际应用中,发送数据时通常将数据放入缓冲区,然后调用Send方法发送。接收数据时,需要等待OnReceive事件触发,然后从缓冲区中读取接收到的数据进行处理。

通过上述示例,可以看出MFC通过事件驱动的方式实现了高效的串行通信机制,开发者可以根据自己的需求进一步封装和扩展相关的功能。

3. RTU数据格式校验与实现

3.1 RTU数据格式概述

3.1.1 RTU协议框架解析

远程终端单元(RTU)是一种工业通信协议,广泛应用于智能仪表和控制器之间的数据交换。RTU协议基于主-从架构,其设计简洁高效,被大量用于能源管理和自动化控制系统中。RTU协议框架中,主站负责轮询从站,从而获取数据或向从站发送控制命令。每个数据包都包含设备地址、功能码、数据、校验码等部分,确保数据传输的正确性和完整性。

RTU协议通过数据帧的格式化规定,保证了数据传输的透明性。从站接收到主站的数据请求后,会根据请求类型,从自己的数据区读取或写入数据,并将响应数据以特定格式发送回主站。为了确保数据传输的可靠性,RTU协议使用了CRC校验码,对数据包中的地址码、功能码、数据、以及从站地址等部分进行校验。

3.1.2 数据格式校验的重要性

在工业环境中,通信的数据往往包含关键的生产控制信息,任何数据的错误或丢失都可能导致严重的后果。因此,数据校验在RTU通信中起着至关重要的作用。校验机制能够检测并防止数据在传输过程中可能出现的错误,保证数据在传输过程中的完整性和准确性。

数据校验通常包括奇偶校验、校验和、循环冗余校验(CRC)等多种方法。在RTU通信协议中,最常用的校验方法是CRC校验。CRC校验能够检测出多个错误位,且误判率极低,因而在数据量较大或对可靠性要求较高的场合下得到了广泛应用。

3.2 RTU数据校验方法

3.2.1 CRC校验算法原理

CRC校验是一种基于二进制运算的校验方法,它通过多项式除法计算出一个固定长度的校验码,附加在原始数据之后进行传输。接收方收到数据后,使用同样的多项式对数据进行校验,以确定数据在传输过程中是否发生变化。

CRC校验的核心在于选择一个适当的生成多项式,这个多项式决定了计算校验码的算法。一般来说,生成多项式的位数决定了最终计算出的CRC校验码的长度。例如,一个常用的CRC-16算法使用的是16位的生成多项式。在发送数据时,发送方和接收方必须使用相同的生成多项式才能正确完成校验。

3.2.2 CRC校验在RTU中的应用

在RTU协议中,CRC校验通常用于检测数据帧在传输过程中的完整性。一个RTU数据帧在发送前,通过CRC算法计算出一个校验码,并将其附加在数据帧的末尾。在接收到数据帧后,接收方再次进行CRC计算,并将结果与帧尾的校验码进行对比。如果不一致,则表示数据在传输过程中出现了错误,接收方将丢弃错误的数据帧,并根据通信协议,可选择请求重发。

在设计RTU通信系统时,开发者需要选择合适的CRC算法。例如,Modbus RTU通常使用CRC-16算法,而其他一些应用可能需要使用CRC-32或其他变体。开发者必须确保所选算法和多项式与系统的具体要求相匹配。

3.3 RTU数据处理实践

3.3.1 数据封装与解析流程

数据封装是将数据按照RTU协议格式化为一帧的过程,而解析则是将接收到的数据帧还原为原始数据的过程。在实际的项目中,数据封装和解析是系统设计的核心部分。

数据封装的流程通常包括: 1. 根据RTU协议,将要发送的数据组织成帧格式。 2. 使用CRC算法计算数据帧的校验码,并添加到数据帧的末尾。 3. 将完整的数据帧通过通信媒介发送出去。

数据解析的过程则相反: 1. 接收到数据帧后,首先验证帧格式的有效性。 2. 使用相同的CRC算法重新计算校验码,并与帧尾的校验码进行对比。 3. 如果校验无误,则进行数据帧的解析,提取出地址码、功能码、数据等信息。 4. 将解析出的数据转换成应用层能够理解的格式,如整型、浮点型等。

3.3.2 实际项目中RTU数据处理案例

假设在一个自动化监控项目中,需要采集多个传感器数据,并通过串行通信发送至监控中心。在这个场景中,我们可能需要使用RTU协议进行数据的封装和解析。

  1. 封装过程:
  2. 从传感器获取数据,比如温度、湿度等。
  3. 根据RTU协议格式化数据,并组装成数据帧。这可能包括设置设备地址、功能码,以及数据本身。
  4. 使用CRC-16算法计算数据帧的校验码,并将其添加到帧尾。

  5. 发送过程:

  6. 将封装好的数据帧通过RS232串行通信发送到监控中心。

  7. 解析过程:

  8. 监控中心接收到数据帧后,首先计算校验码进行验证。
  9. 如果校验无误,则对数据帧进行解析,提取出传感器数据。
  10. 将解析出的数据用于显示、记录或触发警报。

示例代码

这里展示了一个简单的CRC-16校验算法实现的代码段,用于数据封装和解析的校验过程:

#include <stdio.h>
#include <stdint.h>

#define POLYNOMIAL 0xA001 // CRC-16标准多项式

// CRC校验函数
uint16_t crc16(const uint8_t *buffer, uint16_t buffer_length) {
    uint16_t crc = 0xFFFF; // 初始值
    for (uint16_t byte_count = 0; byte_count < buffer_length; ++byte_count) {
        crc ^= (uint16_t)buffer[byte_count] << 8; // 字节左移8位
        for (uint8_t bit_count = 8; bit_count > 0; --bit_count) {
            if (crc & 0x8000) {
                crc = (crc << 1) ^ POLYNOMIAL;
            } else {
                crc = crc << 1;
            }
        }
    }
    return crc;
}

// 模拟数据封装过程
void encapsulate_data(uint8_t *frame, const uint8_t *data, uint16_t data_length, uint16_t device_id) {
    frame[0] = device_id >> 8; // 设备地址高字节
    frame[1] = device_id;      // 设备地址低字节
    frame[2] = 0x03;           // 功能码(读取数据)
    frame[3] = data_length;    // 数据长度低字节
    frame[4] = 0x00;           // 数据长度高字节(因为数据长度小于256)
    for (int i = 0; i < data_length; i++) {
        frame[5 + i] = data[i]; // 数据部分
    }
    uint16_t crc = crc16(frame, data_length + 4); // 计算CRC校验码
    frame[data_length + 5] = crc & 0x00FF;         // 校验码低字节
    frame[data_length + 6] = (crc >> 8) & 0x00FF;  // 校验码高字节
}

// 主函数,用于演示
int main() {
    uint8_t frame[128];
    uint8_t data[] = {0x01, 0x02, 0x03}; // 假设的传感器数据

    encapsulate_data(frame, data, sizeof(data), 0x0001); // 封装数据

    // 解析数据帧,这里只展示CRC校验部分
    uint16_t calculated_crc = crc16(frame, sizeof(data) + 4);
    uint16_t received_crc = (frame[sizeof(data) + 5] << 8) | frame[sizeof(data) + 6];

    if (calculated_crc == received_crc) {
        printf("数据帧校验成功。\n");
    } else {
        printf("数据帧校验失败。\n");
    }
    return 0;
}

在这个示例中,我们首先定义了CRC-16的多项式,然后创建了一个 crc16 函数用于计算CRC校验码。在 encapsulate_data 函数中,我们模拟了数据封装过程,将设备ID、功能码、数据长度和数据本身加入到数据帧中,并计算出CRC校验码附加在帧尾。在主函数中,我们执行了封装过程,并进行了CRC校验。

需要注意的是,这只是一个简化的示例,实际应用中可能需要处理更多细节,比如字节序问题(大端或小端)、超时重发机制等。

通过这个案例,我们可以看到在实际项目中,如何将RTU数据封装和解析的理论知识转化为可执行的代码。这种转换需要开发者对RTU协议有深刻理解,同时也需要具备扎实的编程能力。

4. 线程管理在游戏服务器中的应用

4.1 线程管理基础

4.1.1 线程与进程的区别

线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以拥有多个线程,这些线程可以同时并发执行,也可以被调度执行。线程与进程之间的主要区别如下:

  • 资源分配 :进程是资源分配的基本单位,线程共享所属进程的资源。
  • 调度 :线程是调度的基本单位,线程的切换不会影响同一进程的其他线程。
  • 独立性 :进程有独立的地址空间,而线程共享进程的地址空间。
  • 通信 :线程间通信(IPC)比进程间通信(IPC)更简单,因为共享相同的内存地址空间。

4.1.2 线程创建与同步机制

线程创建

在C++中,可以使用POSIX线程库(pthread)或者C++11标准库中的线程支持来创建线程。以下是使用C++11创建线程的一个简单示例:

#include <iostream>
#include <thread>

void printNumbers() {
    for (int i = 0; i < 10; ++i) {
        std::cout << i << std::endl;
    }
}

int main() {
    std::thread t(printNumbers);
    t.join(); // 等待线程结束
    return 0;
}

在这个示例中,我们创建了一个线程 t ,它执行 printNumbers 函数,并等待线程执行完毕。

线程同步

多线程环境下,线程同步是确保数据一致性、避免竞态条件的关键。常用的同步机制有互斥锁(mutexes)、条件变量(condition variables)、信号量(semaphores)和原子操作(atomic operations)。

互斥锁是最基本的同步机制。C++11提供了 std::mutex 类,用于创建互斥锁。以下是一个简单的使用互斥锁的例子:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int counter = 0;

void add(int number) {
    for (int i = 0; i < 1000; ++i) {
        mtx.lock(); // 锁定互斥锁
        counter += number;
        mtx.unlock(); // 解锁互斥锁
    }
}

int main() {
    std::thread t1(add, 1), t2(add, -1);
    t1.join();
    t2.join();
    std::cout << "Final counter value is " << counter << '\n';
    return 0;
}

在这个例子中,两个线程分别增加和减少计数器的值。使用 std::mutex 确保在任何时刻只有一个线程可以修改 counter 变量。

4.2 游戏服务器中的多线程应用

4.2.1 多线程服务器架构设计

游戏服务器通常需要处理大量的并发连接和实时数据传输。为了提高性能和响应速度,多线程架构是游戏服务器设计中常用的方法。以下是多线程服务器架构设计的一些关键点:

  • 线程模型 :游戏服务器可以采用多种线程模型,如单线程模型、多线程模型和线程池模型。选择哪种模型取决于具体的需求和应用场景。
  • 负载均衡 :合理分配线程资源,避免某些线程过载而其他线程空闲的情况。
  • 无状态设计 :为了简化多线程操作,服务器应当尽量设计为无状态的,避免在不同线程间传递和同步复杂的状态信息。

4.2.2 线程池技术在游戏服务器中的应用

线程池是一种有效的线程管理方式,它可以复用一组固定数量的线程来执行任务。游戏服务器中的线程池可以带来以下好处:

  • 减少线程创建和销毁的开销 :通过复用线程,避免了频繁的线程创建和销毁带来的性能损耗。
  • 提高响应速度 :预先创建的线程可以立即投入到任务的执行中,加快了任务的响应时间。
  • 管理资源消耗 :线程池通过限制最大线程数来控制资源消耗,防止系统资源被过度消耗。

下面是一个简化的线程池的C++实现示例:

#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>

class ThreadPool {
public:
    ThreadPool(size_t);
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) 
        -> std::future<typename std::result_of<F(Args...)>::type>;
    ~ThreadPool();
private:
    // 需要跟踪线程的引用
    std::vector< std::thread > workers;
    // 任务队列
    std::queue< std::function<void()> > tasks;
    // 同步
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

// 构造函数启动一定数量的工作线程
ThreadPool::ThreadPool(size_t threads)
    :   stop(false)
{
    for(size_t i = 0;i<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<class F, class... Args>
auto ThreadPool::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::~ThreadPool()
{
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    for(std::thread &worker: workers)
        worker.join();
}

在这个简单的线程池实现中,我们定义了一个 ThreadPool 类,它包含了一个任务队列和一组工作线程。工作线程会等待队列中出现新任务,然后执行它们。我们通过 enqueue 方法添加任务到线程池。

4.3 线程安全管理与性能优化

4.3.1 线程安全的常见问题

线程安全是指在多线程环境下,一段代码或一个变量的访问是安全的,不会因为线程的并发访问而产生不一致的结果。在游戏服务器中常见的线程安全问题包括:

  • 竞态条件 :当多个线程访问和修改共享资源时,如果没有适当的同步机制,可能会产生不可预测的结果。
  • 死锁 :两个或多个线程相互等待对方释放资源,导致程序无法继续执行。
  • 资源泄漏 :线程在结束时没有正确释放分配的资源,导致内存或系统资源的浪费。

4.3.2 线程性能调优方法

为了提升游戏服务器的性能,需要对线程进行优化。下面是一些性能调优的方法:

  • 避免锁竞争 :锁竞争是导致性能下降的一个主要原因。使用细粒度的锁、读写锁(读者-写者锁)等可以减少锁竞争。
  • 使用无锁编程技术 :无锁编程使用原子操作来保证数据的一致性,避免了线程等待时间。
  • 线程亲和性 :将线程与特定的处理器核心绑定,可以减少上下文切换,提高性能。

此外,监控和分析工具可以帮助诊断线程安全问题和性能瓶颈。例如,使用 strace gdb 等工具对线程行为进行追踪,使用 top htop 等工具监控系统资源使用情况。

在本章节中,我们深入探讨了线程管理的基础知识、在游戏服务器中的应用,以及如何进行安全管理与性能优化。接下来,我们将着眼于网络编程的基础,特别是socket API的使用,这将是构建网络功能强大的游戏服务器不可或缺的一部分。

5. 网络编程基础与socket API使用

5.1 网络编程基础

网络编程允许不同的计算机和设备通过网络相互通信,实现资源共享和数据交换。在网络编程中,我们通常关注两个核心要素:数据传输和协议。

5.1.1 网络通信模型概述

计算机网络遵循一定的模型来确保不同系统间的通信。最常用的模型有OSI七层模型和TCP/IP四层模型。

OSI七层模型 将通信过程分为了7层: 1. 物理层 2. 数据链路层 3. 网络层 4. 传输层 5. 会话层 6. 表示层 7. 应用层

TCP/IP模型 简化了通信层次,分为4层: 1. 网络接口层(链路层) 2. 网络层(IP层) 3. 传输层(TCP/UDP层) 4. 应用层

5.1.2 基本的网络编程接口

在编写网络程序时,程序员通常使用套接字(sockets)接口。套接字是通信的端点,可以视为网络通信的API。

套接字类型 主要包括: - 流式套接字(SOCK_STREAM):提供可靠、有序和无重复的数据传输,典型使用TCP协议。 - 数据报套接字(SOCK_DGRAM):提供无连接的数据传输服务,典型使用UDP协议。

5.2 socket API详解

5.2.1 socket编程原理

socket API提供了一套接口,允许程序员在不同的计算机之间发送和接收数据。socket编程主要涉及到以下几个核心函数:

  • socket() :创建一个新的socket。
  • bind() :将socket与地址绑定。
  • connect() :连接到远程服务器。
  • listen() :在套接字上设置监听,对于TCP服务器套接字。
  • accept() :接受连接请求,返回一个新的套接字。
  • send() / recv() :发送和接收数据。

5.2.2 socket API函数使用指南

下面以创建TCP服务器为例,展示socket API的使用。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[1024] = {0};
    const char *hello = "Hello from server";

    // 创建socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 绑定socket到地址和端口
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8080);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 开始监听
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    // 接受连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    // 读取数据
    read(new_socket, buffer, 1024);
    printf("Message from client: %s\n", buffer);

    // 发送数据
    send(new_socket, hello, strlen(hello), 0);
    printf("Hello message sent\n");

    // 关闭socket
    close(server_fd);

    return 0;
}

5.3 实战socket编程

5.3.1 简单的客户端和服务器实现

在上面的代码示例中,我们已经看到了如何实现一个简单的TCP服务器。类似地,客户端的实现也非常直观。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    struct sockaddr_in serv_addr;
    int sock = 0;
    char *hello = "Hello from client";
    char buffer[1024] = {0};

    // 创建socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);

    // 将IPv4地址从文本转换为二进制形式
    if(inet_pton(AF_INET, "***.*.*.*", &serv_addr.sin_addr) <= 0) {
        printf("\nInvalid address/ Address not supported \n");
        return -1;
    }

    // 连接到服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }

    // 发送数据
    send(sock, hello, strlen(hello), 0);
    printf("Hello message sent\n");

    // 接收数据
    read(sock, buffer, 1024);
    printf("Message from server: %s\n", buffer);

    // 关闭socket
    close(sock);

    return 0;
}

5.3.2 socket通信中的异常处理和优化

在实际应用中,网络通信可能会遇到各种异常情况,如连接失败、数据接收不完整、网络延迟等。对于这些问题,需要我们进行异常处理和程序优化。

异常处理 通常涉及: - 重试机制 - 超时设置 - 异常捕获和处理

性能优化 可以考虑: - 非阻塞IO - 事件驱动模型 - 缓冲区优化

// 示例:设置socket为非阻塞模式
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);

在实际开发中,异常处理和性能优化是提高程序稳定性和用户体验的重要环节。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本文详细解析了基于C语言的comtest_RTU项目,该项目集成了游戏服务器框架和使用MFC进行RS232串行通信及RTU数据格式校验的技术。C语言以其低级内存管理和高效性能,特别适合系统级编程和游戏服务器开发。文章涵盖了线程管理、网络编程、数据结构、内存管理和错误处理等多个关键概念,旨在帮助开发者深入理解游戏服务器开发和通信协议的实际应用。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值