深入理解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.pswp.cn/diannao/77065.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/77065.shtml
英文地址,請注明出處:http://en.pswp.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…