Linux网络编程之TCP通信

前言

        TCP和UDP是两种常见的网络通信形式,其中TCP 是 Transmission Control Protocol (传输控制协议)的简写,意为「对数据传输过程的控制」特征为:传输过程中数据不会消失、按序传输数据、传输的数据不存在数据边界,而UDP的特征为:强调快速传输而不是有序传输、传输的数据可能丢失损毁、传输的数据有边界以及限制每次传输数据的大小,因此TCP的可靠性要高于UDP。

下图为TCP通信的方式。

实现基于TCP的服务端

下图为TCP服务端函数的调用顺序

         

结合代码来理解

// 声明变量:sockfd用于服务器的套接字描述符,temp_result用于存储函数返回值,clientfd用于客户端的套接字描述符
    int sockfd, temp_result, clientfd;

    // 声明两个线程ID变量,分别用于读和写操作的线程
    pthread_t pid_read, pid_write;

    // 声明两个sockaddr_in结构体变量,分别用于存储服务器和客户端的地址信息
    struct sockaddr_in server_addr, client_addr;

    // 将server_addr和client_addr结构体中的所有字节置为0,以确保结构体中所有字段初始化为0
    memset(&server_addr, 0, sizeof(server_addr));
    memset(&client_addr, 0, sizeof(client_addr));

    // 设置服务器地址信息
    server_addr.sin_family = AF_INET;                // 设置地址族为IPv4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置服务器的IP地址,INADDR_ANY表示绑定到所有可用的网络接口
    server_addr.sin_port = htons(6666);              // 设置服务器端口号,并将其从主机字节序转换为网络字节序,端口号为6666

    // 创建套接字
    //第一个参数指定用哪个协议族
    //第二个参数指定用哪个数据传输方式
    //第三个参数指定具体的协议信息
    sockfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建一个TCP套接字
    handle_error("socket", sockfd);  // 错误处理,如果失败则打印错误信息并退出

    // 绑定套接字到服务器地址
    temp_result = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));  // 绑定套接字到指定的服务器地址和端口
    handle_error("bind", temp_result);  // 错误处理,如果失败则打印错误信息并退出

    // 监听连接
    temp_result = listen(sockfd, 128);  // 监听连接请求,128表示等待队列的最大长度
    handle_error("listen", temp_result);  // 错误处理,如果失败则打印错误信息并退出

    // 接受客户端连接
    socklen_t cliaddr_len = sizeof(client_addr);  // 定义变量存储客户端地址的长度
    clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &cliaddr_len);  // 接受客户端连接,并返回客户端文件描述符
    handle_error("accept", clientfd);  // 错误处理,如果失败则打印错误信息并退出

    printf("与客户端%s %d建立连接 文件描述符是%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), clientfd);  // 打印连接成功的信息

其中如果没有连接请求就调用accept函数的话,会一直挂起等待请求。

服务端完整代码如下:

#include <iostream>       // 用于标准输入输出
#include <sys/socket.h>   // 包含socket相关函数和数据结构的头文件
#include <sys/types.h>    // 包含数据类型定义的头文件
#include <netinet/in.h>   // 包含网络地址结构体定义的头文件
#include <stdlib.h>       // 包含标准库函数的头文件
#include <string.h>       // 包含字符串操作函数的头文件
#include <arpa/inet.h>    // 包含网络操作函数的头文件
#include <pthread.h>      // 包含线程相关函数和数据结构的头文件
#include <unistd.h>       // 包含POSIX操作系统API的头文件

using namespace std;      // 使用标准命名空间,简化代码

// 宏定义用于错误处理
#define handle_error(cmd, result) \
    if (result < 0)               \
    {                             \
        perror(cmd);              \
        return -1;                \
    }

// 线程函数:从客户端读取数据并输出到标准输出
void *read_from_client(void *arg)
{
    char read_buf[1024]; // 定义一个缓冲区,用于存储从客户端读取的数据
    int client_fd = *(int *)arg;  // 从参数中获取客户端文件描述符
    ssize_t count;

    // 循环读取客户端数据并输出到标准输出
    while ((count = recv(client_fd, read_buf, sizeof(read_buf), 0)) > 0)
    {
        fwrite(read_buf, 1, count, stdout);  // 将读取的数据写入标准输出
    }

    // 检查读操作结果
    if (count == 0)
    {
        printf("客户端请求关闭\n");  // 如果读取到的数据长度为0,表示客户端关闭了连接
    }
    else
    {
        perror("recv");  // 如果读取数据出错,打印错误信息
    }
    return NULL;
}

// 线程函数:读取标准输入并发送数据到客户端
void *write_to_client(void *arg)
{
    char write_buf[1024]; // 定义一个缓冲区,用于存储将要发送的数据
    int client_fd = *(int *)arg;  // 从参数中获取客户端文件描述符
    ssize_t count;

    // 循环读取标准输入并发送到客户端
    while (fgets(write_buf, sizeof(write_buf), stdin) != NULL)
    {
        count = send(client_fd, write_buf, strlen(write_buf), 0);  // 发送数据到客户端
        if (count < 0)
        {
            perror("send");  // 如果发送数据出错,打印错误信息
            break;
        }
    }
    printf("接收到关闭请求,关闭连接\n");
    shutdown(client_fd, SHUT_WR); // 关闭写端,表示不再发送数据
    return NULL;
}

int main()
{
    // 声明变量:sockfd用于服务器的套接字描述符,temp_result用于存储函数返回值,clientfd用于客户端的套接字描述符
    int sockfd, temp_result, clientfd;

    // 声明两个线程ID变量,分别用于读和写操作的线程
    pthread_t pid_read, pid_write;

    // 声明两个sockaddr_in结构体变量,分别用于存储服务器和客户端的地址信息
    struct sockaddr_in server_addr, client_addr;

    // 将server_addr和client_addr结构体中的所有字节置为0,以确保结构体中所有字段初始化为0
    memset(&server_addr, 0, sizeof(server_addr));
    memset(&client_addr, 0, sizeof(client_addr));

    // 设置服务器地址信息
    server_addr.sin_family = AF_INET;                // 设置地址族为IPv4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置服务器的IP地址,INADDR_ANY表示绑定到所有可用的网络接口
    server_addr.sin_port = htons(6666);              // 设置服务器端口号,并将其从主机字节序转换为网络字节序,端口号为6666

    // 创建套接字
    //第一个参数指定用哪个协议族
    //第二个参数指定用哪个数据传输方式
    //第三个参数指定具体的协议信息
    sockfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建一个TCP套接字
    handle_error("socket", sockfd);  // 错误处理,如果失败则打印错误信息并退出

    // 绑定套接字到服务器地址
    temp_result = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));  // 绑定套接字到指定的服务器地址和端口
    handle_error("bind", temp_result);  // 错误处理,如果失败则打印错误信息并退出

    // 监听连接
    temp_result = listen(sockfd, 128);  // 监听连接请求,128表示等待队列的最大长度
    handle_error("listen", temp_result);  // 错误处理,如果失败则打印错误信息并退出

    // 接受客户端连接
    socklen_t cliaddr_len = sizeof(client_addr);  // 定义变量存储客户端地址的长度
    clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &cliaddr_len);  // 接受客户端连接,并返回客户端文件描述符
    handle_error("accept", clientfd);  // 错误处理,如果失败则打印错误信息并退出

    printf("与客户端%s %d建立连接 文件描述符是%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), clientfd);  // 打印连接成功的信息

    // 创建读写线程
    pthread_create(&pid_read, NULL, read_from_client, (void *)&clientfd);  // 创建读线程
    pthread_create(&pid_write, NULL, write_to_client, (void *)&clientfd);  // 创建写线程

    // 等待线程结束
    pthread_join(pid_read, NULL);  // 等待读线程结束
    pthread_join(pid_write, NULL);  // 等待写线程结束

    printf("释放资源\n");
    close(clientfd);  // 关闭客户端套接字,释放资源
    close(sockfd);  // 关闭服务器套接字,释放资源
    return 0;
}

实现基于TCP的客户端

客户端则比服务端逻辑简单一些了,客户端没有listen和accept函数了。

这是客户端的完整代码

#include <iostream>       // 用于标准输入输出
#include <sys/socket.h>   // 包含socket相关函数和数据结构的头文件
#include <sys/types.h>    // 包含数据类型定义的头文件
#include <netinet/in.h>   // 包含网络地址结构体定义的头文件
#include <stdlib.h>       // 包含标准库函数的头文件
#include <string.h>       // 包含字符串操作函数的头文件
#include <arpa/inet.h>    // 包含网络操作函数的头文件
#include <pthread.h>      // 包含线程相关函数和数据结构的头文件
#include <unistd.h>       // 包含POSIX操作系统API的头文件

using namespace std;      // 使用标准命名空间,简化代码

// 宏定义用于错误处理
#define handle_error(cmd, result) \
    if (result < 0) {             \
        perror(cmd);              \
        return -1;                \
    }

// 线程函数:从服务器读取数据并输出到标准输出
void *read_from_server(void *arg) {
    char read_buf[1024];  // 定义一个缓冲区,用于存储从服务器读取的数据
    int server_fd = *(int *)arg;  // 从参数中获取服务器文件描述符
    ssize_t count;

    // 循环读取服务器数据并输出到标准输出
    while ((count = recv(server_fd, read_buf, sizeof(read_buf), 0)) > 0) {
        fwrite(read_buf, 1, count, stdout);  // 将读取的数据写入标准输出
    }

    // 检查读操作结果
    if (count == 0) {
        printf("服务端请求关闭\n");  // 如果读取到的数据长度为0,表示服务器关闭了连接
    } else {
        perror("recv");  // 如果读取数据出错,打印错误信息
    }
    return NULL;
}

// 线程函数:读取标准输入并发送数据到服务器
void *write_to_server(void *arg) {
    char write_buf[1024];  // 定义一个缓冲区,用于存储将要发送的数据
    int server_fd = *(int *)arg;  // 从参数中获取服务器文件描述符
    ssize_t count;

    // 循环读取标准输入并发送到服务器
    while (fgets(write_buf, sizeof(write_buf), stdin) != NULL) {
        count = send(server_fd, write_buf, strlen(write_buf), 0);  // 发送数据到服务器
        if (count < 0) {
            perror("send");  // 如果发送数据出错,打印错误信息
            break;
        }
    }
    printf("接收到关闭请求,关闭连接\n");
    shutdown(server_fd, SHUT_WR);  // 关闭写端,表示不再发送数据
    return NULL;
}

int main() {
    int sockfd, temp_result;
    pthread_t pid_read, pid_write;  // 线程ID,用于创建读写线程
    struct sockaddr_in server_addr, client_addr;  // 用于存储服务器和客户端地址信息

    memset(&server_addr, 0, sizeof(server_addr));  // 初始化服务器地址结构体,清零
    memset(&client_addr, 0, sizeof(client_addr));  // 初始化客户端地址结构体,清零

    // 设置客户端地址信息
    client_addr.sin_family = AF_INET;  // 设置地址族为IPv4
    inet_pton(AF_INET, "192.168.198.128", &client_addr.sin_addr);  // 将IP地址从字符串转换为二进制格式
    client_addr.sin_port = htons(6667);  // 设置客户端端口号,并将其从主机字节序转换为网络字节序

    // 设置服务器地址信息
    server_addr.sin_family = AF_INET;  // 设置地址族为IPv4
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);  // 将服务器IP地址从字符串转换为二进制格式
    server_addr.sin_port = htons(6666);  // 设置服务器端口号,并将其从主机字节序转换为网络字节序

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建一个TCP套接字
    handle_error("socket", sockfd);  // 错误处理,如果失败则打印错误信息并退出

    // 绑定套接字到客户端地址
    temp_result = bind(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr));  // 绑定套接字到指定的客户端地址和端口
    handle_error("bind", temp_result);  // 错误处理,如果失败则打印错误信息并退出

    // 连接到服务器
    temp_result = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));  // 尝试连接到服务器
    handle_error("connect", temp_result);  // 错误处理,如果失败则打印错误信息并退出

    printf("连接上服务端%s %d\n", inet_ntoa(server_addr.sin_addr), ntohs(server_addr.sin_port));  // 打印连接成功的信息

    // 创建读写线程
    pthread_create(&pid_read, NULL, read_from_server, (void *)&sockfd);  // 创建读线程
    pthread_create(&pid_write, NULL, write_to_server, (void *)&sockfd);  // 创建写线程

    // 等待线程结束
    pthread_join(pid_read, NULL);  // 等待读线程结束
    pthread_join(pid_write, NULL);  // 等待写线程结束

    printf("释放资源\n");
    close(sockfd);  // 关闭套接字,释放资源
    return 0;
}

注意,ip地址要改成自己的ip地址。

然后编译运行结果如下

实现了TCP通信

总结

       TCP通信的代码量不多,如果需要实现两台Linux系统进行通信,则需要看http://t.csdnimg.cn/LFN6O这一篇进行ip配置等一系列工作,然后在代码中将客户端和服务端的ip地址改成两台电脑的ip就可以实现了

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值