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

目录

[1. TCP通信流程](#1. TCP通信流程)

[2. 服务器端的通信流程](#2. 服务器端的通信流程)

[2.1 创建用于监听的套接字](#2.1 创建用于监听的套接字)

[2.2 绑定本地IP地址和端口](#2.2 绑定本地IP地址和端口)

[2.3 设置监听](#2.3 设置监听)

[2.4 等待接受客户端的连接请求](#2.4 等待接受客户端的连接请求)

[2.5 与客户端进行通信](#2.5 与客户端进行通信)

[2.6 客户端连接服务器](#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 结束客户端,服务器将自动检测到客户端的断开并输出相应消息。

相关推荐
叱咤少帅(少帅)25 分钟前
Go环境相关理解
linux·开发语言·golang
浩特-ht33 分钟前
Linux 下 FTP 工具的安装和使用方式详解:附服务器文件备份实战
linux·运维·服务器
Zack No Bug1 小时前
Linux CentOS7 安装emqx详细教程
linux·运维·服务器·mqtt
孤独打铁匠Julian1 小时前
【Linux】Ubuntu 24.04 LTS 安装 OpenJDK 8
linux·ubuntu
Mars--1 小时前
网络地址转换技术(2)
网络·智能路由器
榆榆欸1 小时前
2.基于多线程的TCP服务器实现
服务器·tcp/ip
Dream Algorithm1 小时前
独立组网和非独立组网
网络
2301_764602232 小时前
网络体系架构
网络·架构
1haooo2 小时前
[计算机三级网络技术]第二章:中小型网络系统总体规划与设计方法
网络·经验分享·笔记·计算机网络·智能路由器
AredRabbit2 小时前
微软和Linux
linux·微软·操作系统