当前位置: 首页 > article >正文

1.基于TCP的简单套接字服务器实现

目录

1. TCP通信流程

2. 服务器端的通信流程

2.1 创建用于监听的套接字

2.2 绑定本地IP地址和端口

2.3 设置监听

2.4 等待接受客户端的连接请求

2.5 与客户端进行通信

2.6 客户端连接服务器

3.代码实现

client.cpp

server.cpp

运行方式


在本文中,我们将深入了解套接字(socket)及其在网络通信中的应用,特别是如何在服务器端创建一个基于TCP的简单通信框架。套接字是程序员进行网络通信的一组接口,主要分为客户端和服务器端。在这篇文章中,我们将重点关注服务器端的实现。

1. TCP通信流程

TCP(传输控制协议)是一个面向连接、安全可靠的流式传输协议,它位于传输层,确保数据的准确传输。

2. 服务器端的通信流程

下面是整个服务器端的通信流程:

2.1 创建用于监听的套接字

首先,我们需要创建一个套接字来监听客户端的连接请求。代码示例如下:

// 创建一个套接字,函数原型
int socket(int domain, int type, int protocol);

// 使用
int fd = socket(AF_INET, SOCK_STREAM, 0);

包含的头文件: #include <sys/socket.h>

参数说明:

  • domain:地址族协议(如 AF_INET 表示使用IPv4,AF_INET6 表示使用IPv6)
  • type:数据传输协议(SOCK_STREAM 表示TCP,SOCK_DGRAM 表示UDP)
  • protocol:一般设为0,表示使用默认协议

返回值: 成功返回可用于套接字通信的文件描述符,失败返回 -1。

2.2 绑定本地IP地址和端口

接下来,我们将文件描述符与本地IP地址和端口进行绑定。

// 函数原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 使用
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
addr.sin_addr.s_addr = htonl(INADDR_ANY);

int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));

参数说明:

  • sockfd:监听的文件描述符
  • addr:要绑定的IP和端口信息
  • addrlenaddr指向的内存大小(sizeof(struct sockaddr)

返回值: 成功返回0,失败返回 -1。

2.3 设置监听

绑定后,接下来需要设置监听。

// 函数原型
int listen(int sockfd, int backlog);

// 使用
int ret = listen(fd, 128);

参数说明:

  • sockfd:之前绑定的文件描述符
  • backlog:指定最大连接请求数

返回值: 成功返回0,失败返回 -1。

2.4 等待接受客户端的连接请求

现在我们需要等待并接受客户端的连接请求。

// 函数原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

// 使用
struct sockaddr caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t len = sizeof(caddr);
int cfd = accept(fd, (struct sockaddr*)&caddr, &len);

参数说明:

  • sockfd:之前创建的文件描述符
  • addr:传出参数,存储客户端的地址信息
  • addrlen:传出参数,存储地址大小

返回值: 成功返回一个文件描述符,用于与客户端通信,失败返回 -1。注意,这个函数是阻塞的,直到有新的连接请求到来。

2.5 与客户端进行通信

接下来,可以通过readwrite函数与客户端进行通信。

接收数据:

ssize_t read(int sockfd, void *buf, size_t size);
// 使用
char buf[1024] = {0};
int len = read(cfd, buf, sizeof(buf));

发送数据:

ssize_t write(int fd, const void *buf, size_t len);
// 使用
char msg[] = "Hello, Client!";
int size = write(cfd, msg, strlen(msg));

返回值说明:

  • 接收数据(read):成功时返回接收到的字节数,连接断开返回0,失败返回-1。
  • 发送数据(write):成功返回实际发送的字节数,失败返回-1。

2.6 客户端连接服务器

客户端时使用connect()函数连接服务器,代码示例如下:

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 使用
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
inet_pton(AF_INET, "1.1.1.1", &addr.sin_addr.s_addr); // 服务器IP地址

int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));

3.代码实现

 server.cpp

#include <stdlib.h>      // 提供exit函数
#include <stdio.h>       // 提供printf和perror函数
#include <unistd.h>      // 提供close函数
#include <arpa/inet.h>   // 提供socket、bind、listen、accept等函数
#include <string.h>      // 提供memset函数

int main()
{
    // 1. 创建用于监听的套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        perror("socket"); // 输出错误信息
        exit(1);          // 退出程序
    }

    // 2. 绑定IP地址和端口
    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr)); // 清空结构体
    saddr.sin_family = AF_INET; // 使用IPv4
    saddr.sin_port = htons(10000); // 监听端口,使用网络字节序
    saddr.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用的接口

    int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (ret == -1) {
        perror("bind"); // 输出错误信息
        exit(1);        // 退出程序
    }

    // 3. 设置监听
    ret = listen(fd, 128); // 监听套接字,最大连接请求数为128
    if (ret == -1) {
        perror("listen"); // 输出错误信息
        exit(1);          // 退出程序
    }

    // 4. 等待并接受客户端的连接
    struct sockaddr_in cliaddr; // 保存客户端的地址信息
    socklen_t len = sizeof(cliaddr); // 地址结构体的大小

    int cfd = accept(fd, (struct sockaddr*)&cliaddr, &len); // 阻塞等待连接
    if (cfd == -1) {
        perror("accept"); // 输出错误信息
        exit(1);          // 退出程序
    }

    // 打印新连接的客户端信息
    char ip[64] = { 0 };
    printf("new client fd:%d ip:%s, port:%d\n", cfd,
           inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip)), // 获取客户端IP
           ntohs(cliaddr.sin_port)); // 获取客户端端口

    // 5. 与客户端进行通信
    char buf[512];
    while (1) {
        memset(buf, 0, sizeof(buf)); // 清空缓冲区
        int len = read(cfd, buf, sizeof(buf)); // 从客户端读取数据
        if (len > 0) { // 成功接收到数据
            printf("client says: %s\n", buf); // 打印客户端发送的消息
            write(cfd, buf, len); // 将相同的数据回发给客户端
        }
        else if (len == 0) { // 客户端关闭了连接
            printf("client is disconnect..\n");
            break; // 退出循环
        }
        else { // 读取失败
            perror("read"); // 输出错误信息
            break; // 退出循环
        }
    }

    close(fd); // 关闭监听套接字
    close(cfd); // 关闭客户端连接套接字
    return 0; // 程序结束
}

client.cpp

#include <stdlib.h>      // 提供exit函数
#include <stdio.h>       // 提供printf和perror函数
#include <unistd.h>      // 提供close函数
#include <arpa/inet.h>   // 提供socket、connect等函数
#include <string.h>      // 提供memset和strlen函数

int main()
{
    // 1. 创建套接字用于连接服务器
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        perror("socket");  // 输出错误信息
        exit(1);          // 退出程序
    }

    // 2. 定义服务器地址和端口
    struct sockaddr_in saddr; // 创建一个结构体用于存储服务器地址
    memset(&saddr, 0, sizeof(saddr)); // 清空该结构体
    saddr.sin_family = AF_INET; // 使用IPv4地址
    saddr.sin_port = htons(10000); // 设置监听端口,使用网络字节序
    inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr); // 设置服务器IP地址为localhost

    // 3. 连接服务器
    int ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (ret == -1) {
        perror("connect");  // 输出错误信息
        exit(1);            // 退出程序
    }

    // 4. 与服务器进行通信
    int n = 0; // 计数器
    while (1)
    {
        // 发送数据
        char buf[512] = { 0 }; // 初始化缓冲区
        sprintf(buf, "hi, I am client...%d\n", n++); // 格式化字符串
        write(fd, buf, strlen(buf)); // 发送数据到服务器
        
        // 接收服务器反馈
        memset(buf, 0, sizeof(buf)); // 清空缓冲区
        int len = read(fd, buf, sizeof(buf)); // 从服务器读取数据
        if (len > 0) // 成功接收到数据
        {
            printf("server say: %s\n", buf); // 打印服务器返回的数据
        }
        else if (len == 0) // 服务器关闭了连接
        {
            printf("server disconnect...\n");
            break; // 退出循环
        }
        else // 读取失败
        {
            perror("read"); // 输出错误信息
            break; // 退出循环
        }
        sleep(1);   // 每隔1秒发送一条数据
    }

    close(fd); // 关闭套接字
    return 0; // 程序结束
}

运行方式

  1. 编译代码: 在终端中使用 g++ 编译器编译 client.cppserver.cpp。假设在同一目录下,输入以下命令:

    g++ server.cpp -o server
    g++ client.cpp -o client
    
  2. 运行服务器: 首先打开一个终端,运行服务器程序:

    ./server
    
  3. 运行客户端: 然后打开另一个终端,运行客户端程序:

    ./client
    
  4. 观察输出: 客户端将每隔一秒发送一条消息,服务器将在终端显示接收到的消息,并回发给客户端。您可以观察客户端和服务器的输出信息。

  5. 结束运行: 在客户端和服务器之间的通信完成后,可以在客户端终端按 Ctrl+C 结束客户端,服务器将自动检测到客户端的断开并输出相应消息。


http://www.kler.cn/a/601082.html

相关文章:

  • 【SOC 芯片设计 DFT 学习专栏 -- IDDQ 测试 与 Burn-In 测试】
  • 【数据结构初阶八大排序】---冒泡、选择、插入、希尔、堆排、快排、归并、计数
  • 数据库索引相关的面试题以及答案
  • 医院挂号预约小程序|基于微信小程序的医院挂号预约系统设计与实现(源码+数据库+文档)
  • 双指针技巧在C++中的应用:从基础到进阶
  • 在 Ubuntu 中配置开机自启动脚本并激活 Anaconda 环境
  • Vue学习笔记集--create-vue
  • 宝塔ssl 证书申请流程
  • PDR的matlab实现
  • Android音视频多媒体开源库基础大全
  • C++进阶——哈希表的实现
  • STM32蜂鸣器播放音乐
  • 【Linux-驱动开发-GPIO子系统】
  • ECharts实现数据可视化
  • xiaozhi-esp32-server在linux下源码安装
  • msyql--基本操作之运维篇
  • SOLIDEDGE 至 STL 转换:数字化设计制造的关键衔接
  • 使用UDP消息与iptables实现TCP端口敲门安全技术
  • Rust从入门到精通之进阶篇:20.项目实践
  • 算法-动态规划二