云计算百科
云计算领域专业知识百科平台

深入理解Socket编程:构建简单的计算器服务器

一、Socket通信基础

1. Socket通信基本流程

服务器端流程:
  • 创建Socket (socket())

  • 绑定地址和端口 (bind())

  • 监听连接 (listen())

  • 接受连接 (accept())

  • 数据通信 (read()/write())

  • 关闭连接 (close())

  • 客户端流程:
  • 创建Socket (socket())

  • 连接服务器 (connect())

  • 数据通信 (read()/write())

  • 关闭连接 (close())

  • 2. 关键数据结构

    struct sockaddr_in 用于存储IPv4地址信息:

    struct sockaddr_in {
    short sin_family; // 地址族,如AF_INET
    unsigned short sin_port; // 端口号
    struct in_addr sin_addr; // IP地址
    char sin_zero[8]; // 填充字段
    };

    struct in_addr {
    unsigned long s_addr; // 32位IPv4地址
    };

    二、服务器代码详解

    1. 头文件引入

    #include <stdio.h> // 标准输入输出
    #include <stdlib.h> // 标准库函数
    #include <string.h> // 字符串处理
    #include <unistd.h> // POSIX系统调用
    #include <sys/types.h> // 系统数据类型
    #include <sys/socket.h> // Socket相关函数
    #include <netinet/in.h> // Internet地址族

    这些头文件提供了Socket编程所需的基本功能。

    2. 错误处理函数

    void error(const char *msg) {
    perror(msg); // 打印错误信息
    exit(1); // 异常退出
    }

    perror()会根据全局变量errno打印描述性错误信息。

    3. 主函数结构

    int main(int argc, char *argv[]) {
    if(argc < 2) {
    fprintf(stderr,"Port no not provided, Program terminated");
    exit(1);
    }
    // … 其余代码
    }

    检查命令行参数,确保提供了端口号。

    4. 变量声明

    int sockfd, newsockfd, portno;
    char buffer[255];
    struct sockaddr_in serv_addr, cli_addr;
    socklen_t clilen;

    • sockfd: 监听Socket的文件描述符

    • newsockfd: 与客户端通信的Socket文件描述符

    • serv_addr/cli_addr: 服务器/客户端地址信息

    • clilen: 客户端地址结构长度

    5. 创建Socket

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0) {
    error("Error opening Socket.");
    }

    socket()函数参数:

    • AF_INET: IPv4地址族

    • SOCK_STREAM: 面向连接的TCP Socket

    • 0: 默认协议(TCP)

    6. 初始化服务器地址

    bzero((char *)&serv_addr, sizeof(serv_addr));
    portno = atoi(argv[1]);

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    • bzero(): 清零内存区域

    • INADDR_ANY: 监听所有网络接口

    • htons(): 将端口号转换为网络字节序(大端)

    7. 绑定Socket

    if(bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    error("Binding Failed.");

    bind()将Socket与地址和端口绑定。

    8. 监听连接

    listen(sockfd,5);

    listen()开始监听连接,参数5指定等待连接队列的最大长度。

    9. 接受连接

    newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr,&clilen);
    if(newsockfd < 0)
    error("Error on Accept");

    accept()接受客户端连接,返回新的Socket文件描述符用于通信。

    10. 计算器业务逻辑

    int num1, num2, ans, choice,n;
    S: n = write(newsockfd,"Enter Number 1 : ",strlen("Enter Number 1"));
    if (n < 0) error("Error writing to socket");
    read(newsockfd, &num1, sizeof(int));
    printf("Client – Number 1 is : %d \\n",num1);

    // … 类似处理num2和choice

    switch (choice) {
    case 1:
    ans = num1 + num2;
    break;
    case 2:
    ans = num1 – num2;
    break;
    case 3:
    ans = num1 * num2;
    break;
    case 4:
    ans = num1 / num2;
    break;
    case 5:
    goto Q;
    break;
    }

    write(newsockfd,&ans,sizeof(int));
    if(choice != 5)
    goto S;

    这里实现了简单的计算器功能,使用goto实现循环逻辑。

    11. 关闭连接

    Q:
    close(newsockfd);
    close(sockfd);
    return 0;

    三、关键函数深度解析

    1. socket()

    int socket(int domain, int type, int protocol);

    • 功能:创建通信端点

    • 参数:

      • domain: 通信域(AF_INET, AF_INET6等)

      • type: 通信语义(SOCK_STREAM, SOCK_DGRAM等)

      • protocol: 通常为0,表示默认协议

    • 返回值:成功返回文件描述符,失败返回-1

    2. bind()

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

    • 功能:将Socket与地址绑定

    • 参数:

      • sockfd: Socket文件描述符

      • addr: 指向地址结构的指针

      • addrlen: 地址结构大小

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

    3. listen()

    int listen(int sockfd, int backlog);

    • 功能:开始监听连接请求

    • 参数:

      • sockfd: Socket文件描述符

      • backlog: 等待连接队列的最大长度

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

    4. accept()

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

    • 功能:接受连接请求

    • 参数:

      • addr: 用于存储客户端地址

      • addrlen: 地址结构大小

    • 返回值:成功返回新的Socket文件描述符,失败返回-1

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

    void error(const char *msg)
    {
    perror(msg);
    exit(1);
    }
    int main(int argc, char *argv[])
    {
    if(argc < 2)
    {
    fprintf(stderr,"Port no not provided, Program terminated");
    exit(1);
    }

    int sockfd, newsockfd, portno;
    char buffer[255];

    struct sockaddr_in serv_addr, cli_addr;
    socklen_t clilen;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
    error ("Error opening Socket.");
    }

    bzero((char *)&serv_addr, sizeof(serv_addr));
    portno = atoi(argv[1]);

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    if(bind(sockfd , (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    error("Binding Failed.");

    listen(sockfd,5);
    clilen = sizeof(cli_addr);

    newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr,&clilen);

    if(newsockfd < 0)
    error("Error on Accept");

    int num1, num2, ans, choice,n;
    S: n = write(newsockfd,"Enter Number 1 : ",strlen("Enter Number 1"));
    if (n < 0) error("Error writing to socket");
    read(newsockfd, &num1, sizeof(int));
    printf("Client – Number 1 is : %d \\n",num1);

    n = write(newsockfd,"Enter Number 2 : ",strlen("Enter Number 2"));
    if (n < 0) error("Error writing to socket");
    read(newsockfd, &num2, sizeof(int));
    printf("Client – Number 2 is : %d \\n",num2);

    n = write(newsockfd,"Enter your choice: \\n1.Addition\\n2.subtraction\\n3.Multiplication\\n4.Division\\n5.Exit\\n",strlen("Enter your choice: \\n1.Addition\\n2.subtraction\\n3.Multiplication\\n4.Division\\n5.Exit\\n"));
    if(n < 0) error("ERROR writing to socket");
    read(newsockfd, &choice, sizeof(int));
    printf("Client – choice is :%d\\n",choice);

    switch (choice)
    {
    case 1:
    ans = num1 + num2;
    break;

    case 2:
    ans = num1 – num2;
    break;

    case 3:
    ans = num1 * num2;
    break;

    case 4:
    ans = num1 / num2;
    break;

    case 5:
    goto Q;
    break;

    }

    write(newsockfd,&ans,sizeof(int));
    if(choice != 5)
    goto S;

    Q:
    close(newsockfd);
    close(sockfd);
    return 0;

    }
    /*
    filename server_ipaddress portno

    argc[0] filename
    argv[1] serve_ipaddress
    argv[2] portno
    */

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>

    void error(const char *msg)
    {
    perror(msg);
    exit(0);
    }
    int main(int argc, char *argv[])
    {
    int sockfd, portno, n;
    char buffer[256];

    struct sockaddr_in serv_addr;
    struct hostent *server;
    if (argc < 3)
    {
    fprintf(stderr, "usage %s hostname port\\n", argv[0]);
    exit(1);
    }

    portno = atoi(argv[2]);
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
    error ("Error opening Socket.");
    }

    server = gethostbyname(argv[1]);
    if(server == NULL)
    {
    fprintf(stderr, "Error,no such host");
    }

    bzero((char *)&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
    serv_addr.sin_port = htons(portno);
    if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
    error("Connection Failed");
    }

    int num1, num2, choice, ans;
    S:
    bzero(buffer,256);
    n = read (sockfd, buffer, 255);
    if( n < 0 )
    error("ERROR reading from socket");
    printf("Server – %s\\n",buffer);
    scanf("%d",&num1);
    write(sockfd, &num1,sizeof(int));

    bzero(buffer,256);
    n = read (sockfd, buffer, 255);
    if( n < 0 )
    error("ERROR reading from socket");
    printf("Server – %s\\n",buffer);
    scanf("%d",&num2);
    write(sockfd, &num2,sizeof(int));

    bzero(buffer,256);
    n = read (sockfd, buffer, 255);
    if( n < 0 )
    error("ERROR reading from socket");
    printf("Server – %s\\n",buffer);
    scanf("%d",&choice);
    write(sockfd, &choice,sizeof(int));

    if (choice == 5)
    goto Q;

    read(sockfd, &ans ,sizeof(int));
    printf("Server- The answer is :%d\\n",ans);

    if(choice !=5)
    goto S;

    Q:
    printf("You have selected to exit.Exit Successful");
    close(sockfd);
    return 0;

    }

    一、函数参数记忆框架(TCP Socket版)

    1. 核心思维:"3W1H"模型
    • Where (地址相关):sockaddr_in, addrlen

    • What (数据相关):buffer, strlen

    • Which (标识符):sockfd, newsockfd

    • How (方式):flags (通常填0)

    2. 函数参数速记表
    函数参数顺序助记口诀必须记住的参数
    socket() "家-门-钥匙" AF_INET, SOCK_STREAM, 0
    bind() "门牌-地址本-地址长度" sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)
    listen() "门牌-候客室大小" sockfd, 5 (backlog)
    accept() "门牌-客户登记表-表长度" sockfd, cli_addr, &clilen
    connect() "门牌-目的地地址-地址长度" sockfd, serv_addr, sizeof(serv_addr)
    read()/write() "信箱-纸条-纸条大小" newsockfd, buffer, sizeof(buffer)

    二、参数分类记忆法

    1. 地址家族三件套

    serv_addr.sin_family = AF_INET; // IPv4
    serv_addr.sin_addr.s_addr = INADDR_ANY; // 所有IP
    serv_addr.sin_port = htons(portno); // 端口转换

    • 记忆口诀:"家-门-方向牌"(family-address-port)

    2. 类型转换四重奏

    // 指针转换
    (struct sockaddr*)&serv_addr

    // 字节序转换
    htons() // host to network short
    htonl() // host to network long
    ntohs() // network to host short
    ntohl() // network to host long

    • 记忆技巧:"h→n是出门,n→h是回家"

    三、实战填参模板

    1. 服务端标准流程

    // 1. 创建socket(三固定参数)
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    // 2. 绑定地址(结构体强制转换)
    bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    // 3. 接受连接(注意&clilen)
    accept(sockfd, (struct sockaddr*)&cli_addr, &clilen);

    2. 客户端连接模板

    connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    四、常见参数填错场景

    错误场景正确写法记忆要点
    忘记地址结构体转换 (struct sockaddr*)&serv_addr "套外套"法则
    传值 vs 传地址混淆 &clilen 而不是 clilen accept()要修改长度值
    字节序未转换 htons(portno) 所有网络传输的数据都要转换
    buffer大小计算错误 read(fd, buf, sizeof(buf)) 用sizeof而不是strlen

    五、调试技巧

  • 参数检查清单:

    • 所有网络字节序转换了吗?

    • 地址结构体转换了吗?

    • 长度参数传地址了吗?

    • 错误处理都写了吗?

  • GDB调试命令:

      (gdb) p serv_addr # 查看地址结构体
    (gdb) p &serv_addr # 确认指针类型

  • 六、类比记忆法

    函数现实类比关键参数对应关系
    socket() 买手机 手机类型=AF_INET
    bind() 办手机卡 电话号码=portno
    listen() 开机 最大未接来电=backlog
    accept() 接听来电 来电显示=cli_addr
    connect() 拨打电话 对方号码=serv_addr

    七、进阶记忆法(协议栈层次)

    应用层:buffer数据
    传输层:SOCK_STREAM/SOCK_DGRAM
    网络层:AF_INET/AF_INET6
    链路层:系统自动处理

    记住:从上到下越来越底层,从下到上越来越抽象

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 深入理解Socket编程:构建简单的计算器服务器
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!