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

一、Socket通信基础

1. Socket通信基本流程

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

  2. 绑定地址和端口 (bind())

  3. 监听连接 (listen())

  4. 接受连接 (accept())

  5. 数据通信 (read()/write())

  6. 关闭连接 (close())

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

  2. 连接服务器 (connect())

  3. 数据通信 (read()/write())

  4. 关闭连接 (close())

2. 关键数据结构

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

struct sockaddr_in {short            sin_family;   // 地址族,如AF_INETunsigned 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和choiceswitch (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 portnoargc[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 而不是 clilenaccept()要修改长度值
字节序未转换htons(portno)所有网络传输的数据都要转换
buffer大小计算错误read(fd, buf, sizeof(buf))用sizeof而不是strlen

五、调试技巧

  1. 参数检查清单

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

    • 地址结构体转换了吗?

    • 长度参数传地址了吗?

    • 错误处理都写了吗?

  2. GDB调试命令

    bash

    复制

    (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
链路层:系统自动处理

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/77065.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis-x64-3.2.100.msi : Windows 安装包(MSI 格式)安装步骤

Redis-x64-3.2.100.msi 是 Redis 的 Windows 安装包&#xff08;MSI 格式&#xff09;&#xff0c;适用于 64 位系统。 在由于一些环境需要低版本的Redis的安装包。 Redis-x64-3.2.100.msi 安装包下载&#xff1a;https://pan.quark.cn/s/cc4d38262a15 Redis 是一个开源的 内…

4.7正则表达式

1.字符匹配 一般字符匹配自身. 匹配任意字符(换行符\n除外),一个点占一位\转义字符&#xff0c;使其后一个字符改变原来的意思(\.就是.)[......]字符集,对应的位置可以是字符集中的任意字符.字符集中的字符可以逐个列出,也可以给出范围如[abc]或[a-c] [^abc] 表示取反&#xf…

Fortran 中读取 MATLAB 生成的数据文件

在 Fortran 中读取 MATLAB 生成的数据文件&#xff0c;可以通过以下几种方法实现&#xff0c;包括使用开源工具和手动解析&#xff1a; 1. 使用开源工具&#xff1a;MATFOR MATFOR 是一个商业/开源混合工具&#xff08;部分功能免费&#xff09;&#xff0c;提供 Fortran 与 M…

压测工具开发实战篇(四)——client子窗口功能

你好&#xff0c;我是安然无虞。 文章目录 树控件添加文件补充学习: 函数定义中循环体里的局部变量补充学习: 动态添加对象属性 刷新文件上下文菜单 (右键菜单)实现右键菜单功能 编辑节点文本 在学习本篇文章之前, 建议先看一下上篇介绍MDI子窗口的文章: 压测工具开发实战篇(三…

PyTorch使用(4)-张量拼接操作

文章目录 张量拼接操作1. torch.cat 函数的使用1.1. torch.cat 定义1.2. 语法1.3. 关键规则 1.4. 示例代码1.4.1. 沿行拼接&#xff08;dim0&#xff09;1.4.2. 沿列拼接&#xff08;dim1&#xff09;1.4.3. 高维拼接&#xff08;dim2&#xff09; 1.5. 错误场景分析1.5.1. 维度…

linux命令之yes(Linux Command Yes)

linux命令之yes 简介与功能 yes 命令在 Linux 系统中用于重复输出一行字符串&#xff0c;直到被杀死&#xff08;kill&#xff09;。该命令最常见的用途是自动化控制脚本中的交互式命令&#xff0c;以便无需用户介入即可进行连续的确认操作。 用法示例 基本用法非常简单&am…

《算法笔记》10.3小节——图算法专题->图的遍历 问题 B: 连通图

题目描述 给定一个无向图和其中的所有边&#xff0c;判断这个图是否所有顶点都是连通的。 输入 每组数据的第一行是两个整数 n 和 m&#xff08;0<n<1000&#xff09;。n 表示图的顶点数目&#xff0c;m 表示图中边的数目。如果 n 为 0 表示输入结束。随后有 m 行数据…

使用Prometheus监控systemd服务并可视化

实训背景 你是一家企业的运维工程师&#xff0c;需将服务器的systemd服务监控集成到Prometheus&#xff0c;并通过Grafana展示实时数据。需求如下&#xff1a; 数据采集&#xff1a;监控所有systemd服务的状态&#xff08;运行/停止&#xff09;、资源占用&#xff08;CPU、内…

OpenCV--图像边缘检测

在计算机视觉和图像处理领域&#xff0c;边缘检测是极为关键的技术。边缘作为图像中像素值发生急剧变化的区域&#xff0c;承载了图像的重要结构信息&#xff0c;在物体识别、图像分割、目标跟踪等众多应用场景中发挥着核心作用。OpenCV 作为强大的计算机视觉库&#xff0c;提供…

Rollup详解

Rollup 是一个 JavaScript 模块打包工具&#xff0c;专注于 ES 模块的打包&#xff0c;常用于打包 JavaScript 库。下面从它的工作原理、特点、使用场景、配置和与其他打包工具对比等方面进行详细讲解。 一、 工作原理 Rollup 的核心工作是分析代码中的 import 和 export 语句…

Chapter 7: Compiling C++ Sources with CMake_《Modern CMake for C++》_Notes

Chapter 7: Compiling C Sources with CMake 1. Understanding the Compilation Process Key Points: Four-stage process: Preprocessing → Compilation → Assembly → LinkingCMake abstracts low-level commands but allows granular controlToolchain configuration (c…

5分钟上手GitHub Copilot:AI编程助手实战指南

引言 近年来&#xff0c;AI编程工具逐渐成为开发者提升效率的利器。GitHub Copilot作为由GitHub和OpenAI联合推出的智能代码补全工具&#xff0c;能够根据上下文自动生成代码片段。本文将手把手教你如何快速安装、配置Copilot&#xff0c;并通过实际案例展示其强大功能。 一、…

谢志辉和他的《韵之队诗集》:探寻生活与梦想交织的诗意世界

大家好&#xff0c;我是谢志辉&#xff0c;一个扎根在文字世界&#xff0c;默默耕耘的写作者。写作于我而言&#xff0c;早已不是简单的爱好&#xff0c;而是生命中不可或缺的一部分。无数个寂静的夜晚&#xff0c;当世界陷入沉睡&#xff0c;我独自坐在书桌前&#xff0c;伴着…

Logo语言的死锁

Logo语言的死锁现象研究 引言 在计算机科学中&#xff0c;死锁是一个重要的研究课题&#xff0c;尤其是在并发编程中。它指的是两个或多个进程因争夺资源而造成的一种永久等待状态。在编程语言的设计与实现中&#xff0c;如何避免死锁成为了优化系统性能和提高程序可靠性的关…

深入理解矩阵乘积的导数:以线性回归损失函数为例

深入理解矩阵乘积的导数&#xff1a;以线性回归损失函数为例 在机器学习和数据分析领域&#xff0c;矩阵微积分扮演着至关重要的角色。特别是当我们涉及到优化问题&#xff0c;如最小化损失函数时&#xff0c;对矩阵表达式求导变得必不可少。本文将通过一个具体的例子——线性…

real_time_camera_audio_display_with_animation

视频录制 import cv2 import pyaudio import wave import threading import os import tkinter as tk from PIL import Image, ImageTk # 视频录制设置 VIDEO_WIDTH = 640 VIDEO_HEIGHT = 480 FPS = 20.0 VIDEO_FILENAME = _video.mp4 AUDIO_FILENAME = _audio.wav OUTPUT_…

【Pandas】pandas DataFrame astype

Pandas2.2 DataFrame Conversion 方法描述DataFrame.astype(dtype[, copy, errors])用于将 DataFrame 中的数据转换为指定的数据类型 pandas.DataFrame.astype pandas.DataFrame.astype 是一个方法&#xff0c;用于将 DataFrame 中的数据转换为指定的数据类型。这个方法非常…

Johnson

理论 全源最短路算法 Floyd 算法&#xff0c;时间复杂度为 O(n)跑 n 次 Bellman - Ford 算法&#xff0c;时间复杂度是 O(nm)跑 n 次 Heap - Dijkstra 算法&#xff0c;时间复杂度是 O(nmlogm) 第 3 种算法被 Johnson 做了改造&#xff0c;可以求解带负权边的全源最短路。 J…

Exce格式化批处理工具详解:高效处理,让数据更干净!

Exce格式化批处理工具详解&#xff1a;高效处理&#xff0c;让数据更干净&#xff01; 1. 概述 在数据分析、报表整理、数据库管理等工作中&#xff0c;数据清洗是不可或缺的一步。原始Excel数据常常存在格式不统一、空值、重复数据等问题&#xff0c;影响数据的准确性和可用…

(三十七)Dart 中使用 Pub 包管理系统与 HTTP 请求教程

Dart 中使用 Pub 包管理系统与 HTTP 请求教程 Pub 包管理系统简介 Pub 是 Dart 和 Flutter 的包管理系统&#xff0c;用于管理项目的依赖。通过 Pub&#xff0c;开发者可以轻松地添加、更新和管理第三方库。 使用 Pub 包管理系统 1. 找到需要的库 访问以下网址&#xff0c…