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

相关推荐
A小辣椒2 天前
TShark:Wireshark CLI 功能
linux
A小辣椒2 天前
TShark:基础知识
linux
AlfredZhao2 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334663 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
网络研究院3 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展