【計算機網絡學習之路】TCP socket編程

文章目錄

  • 前言
  • 一. 服務器
    • 1. 初始化服務器
    • 2. 啟動服務器
  • 二. 客戶端
  • 三. 多進程服務器
  • 結束語

前言

本系列文章是計算機網絡學習的筆記,歡迎大佬們閱讀,糾錯,分享相關知識。希望可以與你共同進步。

本篇博客基于UDP socket基礎,介紹TCP socket編程接口和細節

UDP socket編程可參看【計算機網絡學習之路】UDP socket編程

本次編寫的服務器和客戶端依然是最簡單的echo服務器

一. 服務器

服務器的基本框架:

tcp_server.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>namespace ns_server
{const uint16_t default_port = 8888;class TcpServer{public:TcpServer(uint16_t port = default_port) : _port(port){}void InitServer(){}//初始化服務器void Start(){}//啟動服務器~TcpServer(){}private:int _sock; // 監聽套接字uint16_t _port;  // 端口號};
}

tcp_server.cc

#include"tcp_server.hpp"
#include<memory>using namespace std;
using namespace ns_server;static void usage(char*argv)
{cout<<"Usage\n\t"<<argv<<" serverPort"<<endl;
}
int main(int argc,char*argv[])
{if(argc!=2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);unique_ptr<TcpServer> usvr(new TcpServer(echo,port));usvr->InitServer();usvr->Start();return 0;
}

1. 初始化服務器

服務器的初始化,還是一樣的

  1. 創建套接字
  2. 綁定套接字
 void InitServer()
{// 1.創建套接字_sock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){std::cerr << "create sock error," << strerror(errno) << std::endl;exit(1);}std::cout << "create listensock success: " << _sock << std::endl;// 2.綁定套接字struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;if (bind(_sock , (struct sockaddr *)&local, sizeof(local)) < 0){std::cerr << "bind error," << strerror(errno) << std::endl;exit(2);}
}

需要注意的是,socket的第二個參數為SOCK_STREAM面向字節流

TCP與UDP不同的地方是,TCP是面向連接的,UDP是無連接的
所以TCP還需要listen

在這里插入圖片描述

返回值:成功返回0,失敗返回-1并設置錯誤碼

backlog參數需要在后續TCP詳解中學習,先定義大小為32

const int backlog = 32;
void InitServer()
{// 1.創建套接字_sock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){std::cerr << "create sock error," << strerror(errno) << std::endl;exit(1);}std::cout << "create listensock success: " << _sock << std::endl;// 2.綁定套接字struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;if (bind(_sock , (struct sockaddr *)&local, sizeof(local)) < 0){std::cerr << "bind error," << strerror(errno) << std::endl;exit(2);}// 3.監聽if (listen(_listensock, backlog) < 0){std::cerr << "listen error," << strerror(errno) << std::endl;exit(3);}
}

初始化到此就結束了
接下來是啟動服務器

2. 啟動服務器

TCP通過accept獲取客戶端連接

在這里插入圖片描述

  • sockfd:socket返回的文件描述符
  • addr:輸入輸出型參數,客戶端信息的結構體
  • addrlen:輸入輸出型參數,結構體大小。注意:需要傳入addr的大小
  • 返回值是網絡文件描述符

在TCP中,socket返回的網絡文件可以理解為連接文件,內部保存了連接信息
而accept是從連接文件中獲取連接,然后創建套接字,網絡文件。
真正通信的是connect創建的網絡文件

我們將私有成員的_sock改為_listensock

void Start()
{while (true){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sock = accept(_listensock, (struct sockaddr *)&client, &len);if (sock < 0){std::cerr << "accept error" << std::endl;continue;}// 提取客戶端信息std::string clientIp = inet_ntoa(client.sin_addr);uint16_t clientPort = ntohs(client.sin_port);std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";std::cout << "create sock " << sock << " from " << _listensock << std::endl;}
}

接下來就可以在connect返回的套接字中讀寫數據了。
本次使用readwrite

void Start()
{while (true){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sock = accept(_listensock, (struct sockaddr *)&client, &len);if (sock < 0){std::cerr << "accept error" << std::endl;continue;}// 提取客戶端信息std::string clientIp = inet_ntoa(client.sin_addr);uint16_t clientPort = ntohs(client.sin_port);std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";std::cout << "create sock " << sock << " from " << _listensock << std::endl;char buffer[1024];while (true){int n = read(sock, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';std::cout << name << "# " << buffer << std::endl;std::string responce = buffer;//返回收到的數據int m = write(sock, responce.c_str(), responce.size());}else if (n == 0){// 寫端關閉std::cout << name << " quit,me to" << std::endl;close(sock);break;}else{// 讀數據異常std::cerr << "read error" << std::endl;break;}}}
}

完整代碼:
tcp_server.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>namespace ns_server
{const uint16_t default_port = 8888;const int backlog = 32;class TcpServer{public:TcpServer(func_t func, uint16_t port = default_port) : _port(port), _func(func){}void InitServer(){// 1.創建套接字_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){std::cerr << "create sock error," << strerror(errno) << std::endl;exit(1);}std::cout << "create listensock success: " << _listensock << std::endl;// 2.綁定套接字struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0){std::cerr << "bind error," << strerror(errno) << std::endl;exit(2);}// 3.監聽if (listen(_listensock, backlog) < 0){std::cerr << "listen error," << strerror(errno) << std::endl;exit(3);}}void Start(){while (true){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sock = accept(_listensock, (struct sockaddr *)&client, &len);if (sock < 0){std::cerr << "accept error" << std::endl;continue;}// 提取客戶端信息std::string clientIp = inet_ntoa(client.sin_addr);uint16_t clientPort = ntohs(client.sin_port);std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";std::cout << "create sock " << sock << " from " << _listensock << std::endl;char buffer[1024];while (true){int n = read(sock, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';std::cout << name << "# " << buffer << std::endl;std::string responce = buffer;int m = write(sock, responce.c_str(), responce.size());}else if (n == 0){// 寫端關閉std::cout << name << " quit,me to" << std::endl;close(sock);break;}else{// 讀數據異常std::cerr << "read error" << std::endl;break;}}}}~TcpServer(){}private:int _listensock; // 監聽套接字uint16_t _port;  // 端口號};
}

PS:上述的服務器是單進程,所以只能同時處理一個客戶端,讀者可以嘗試添加一下多進程,多線程或者線程池

本篇博客最后會貼出多進程的方案

二. 客戶端

客戶端就不作封裝了

最開始也是要創建套接字
然后TCP的客戶端需要connect服務器

在這里插入圖片描述

  • sockfd:socket返回的文件描述符
  • addr:服務器信息的結構體
  • addrlen:結構體大小。
  • 返回值:成功返回0,失敗返回-1并設置錯誤碼

注意:connect時OS會bind客戶端
UDP是在發送數據時才會bind

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;static void usage(char *argv)
{cout << "Usage:\n\t" << argv << " serverIp serverPort" << endl;
}int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(USAGE_ERR);}string serverIp = argv[1];uint16_t serverPort = atoi(argv[2]);// 1.創建套接字int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){cerr << "create sock error," << strerror(errno) << endl;exit(SOCKET_ERR);}cout<<"create sock sucess:"<<sock<<endl;// 2. 連接struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverIp.c_str());server.sin_port = htons(serverPort);int cnt = 5; // 記錄重連次數// connect時會bindwhile (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0){cout << "正在重連...還有" << cnt-- << "次" << endl;if (cnt <= 0){   cerr<<"連接失敗"<<endl;exit(CONNECT_ERR);}sleep(1);}// 連接成功string name = "["+serverIp + ":" + to_string(serverPort)+"]";cout << "connect " << name << " sucess" << endl;return 0;
}

然后也可以開始讀寫數據了

完整代碼:

tcp_client.cc

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;static void usage(char *argv)
{cout << "Usage:\n\t" << argv << " serverIp serverPort" << endl;
}int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(USAGE_ERR);}string serverIp = argv[1];uint16_t serverPort = atoi(argv[2]);// 1.創建套接字int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){cerr << "create sock error," << strerror(errno) << endl;exit(SOCKET_ERR);}cout<<"create sock sucess:"<<sock<<endl;// 2. 連接struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverIp.c_str());server.sin_port = htons(serverPort);int cnt = 5; // 記錄重連次數// connect時會bindwhile (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0){cout << "正在重連...還有" << cnt-- << "次" << endl;if (cnt <= 0){   cerr<<"連接失敗"<<endl;exit(CONNECT_ERR);}sleep(1);}// 連接成功string name = "["+serverIp + ":" + to_string(serverPort)+"]";cout << "connect " << name << " sucess" << endl;// 發送消息while (true){cout << "please enter your message# ";string message;getline(cin, message);int n = write(sock, message.c_str(), message.size());if (n < 0){cerr << "write error," << strerror(errno) << endl;break;}else if (n == 0){cout << "讀端關閉,停止寫" << endl;break;}char buffer[1024];int m = read(sock, buffer, sizeof(buffer) - 1);if (m > 0){buffer[n] = '\0';cout<<name<<" echo "<<buffer<<endl;}else if (m == 0){// 寫端關閉std::cout << name << " quit,me to" << std::endl;close(sock);break;}else{// 讀數據異常std::cerr << "read error" << std::endl;break;}}return 0;
}

三. 多進程服務器

tcp_server.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>namespace ns_server
{const uint16_t default_port = 8888;const int backlog = 32;class TcpServer{public:TcpServer(uint16_t port = default_port) : _port(port){}void InitServer(){// 1.創建套接字_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){std::cerr << "create sock error," << strerror(errno) << std::endl;exit(1);}std::cout << "create listensock success: " << _listensock << std::endl;// 2.綁定套接字struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0){std::cerr << "bind error," << strerror(errno) << std::endl;exit(2);}// 3.監聽if (listen(_listensock, backlog) < 0){std::cerr << "listen error," << strerror(errno) << std::endl;exit(3);}}void Start(){//忽略子進程的信號,不需要等待子進程退出(推薦!!!)signal(SIGCHLD,SIG_IGN);while (true){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sock = accept(_listensock, (struct sockaddr *)&client, &len);if (sock < 0){std::cerr << "accept error" << std::endl;continue;}std::cout << "create sock " << sock << " from " << _listensock << std::endl;// 多進程pid_t id = fork();if (id < 0){close(sock);continue;}else if (id == 0){//子進程close(_listensock);//建議關掉不需要的fdif(fork()>0)exit(0);//子進程退掉,后續為孫子進程// 提取客戶端信息std::string clientIp = inet_ntoa(client.sin_addr);uint16_t clientPort = ntohs(client.sin_port);service(sock, clientIp, clientPort);exit(0);}//父進程//一定關掉不需要的fd,防止fd泄露close(sock);//pid_t ret=waitpid(id,nullptr,0);//默認為阻塞等待//pid_t ret=waitpid(id,nullptr,WNOHANG);//非阻塞//if(ret==id) std::cout<<"wait "<<id<<" sucess"<<std::endl;}}void service(int sock, std::string &clientIp, uint16_t&clientPort){std::string name = "[" + clientIp + ":" + std::to_string(clientPort) + "]";char buffer[1024];while (true){int n = read(sock, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';std::cout << name << "# " << buffer << std::endl;std::string responce = buffer;int m = write(sock, responce.c_str(), responce.size());}else if (n == 0){// 寫端關閉std::cout << name << " quit,me to" << std::endl;close(sock);break;}else{// 讀數據異常std::cerr << "read error" << std::endl;break;}}}~TcpServer(){}private:int _listensock; // 監聽套接字uint16_t _port;  // 端口號};
}

結束語

本篇博客到此結束,感謝看到此處。
歡迎大家糾錯和補充
如果覺得本篇文章對你有所幫助的話,不妨點個贊支持一下博主,拜托啦,這對我真的很重要。
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/161597.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/161597.shtml
英文地址,請注明出處:http://en.pswp.cn/news/161597.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Oracle的控制文件多路復用,控制文件備份,控制文件手工恢復

一.配置控制文件多路復用 1.查詢Oracle的控制文件所在位置 SQL> select name from v$controlfile;NAME -------------------------------------------------------------------------------- /u01/app/oracle/oradata/orcl/control01.ctl /u01/app/oracle/fast_recovery_a…

【docker】docker總結

一、Docker簡介 Docker是開源應用容器引擎&#xff0c;輕量級容器技術。基于Go語言&#xff0c;并遵循Apache2.0協議開源Docker可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中&#xff0c;然后發布到任何流行的Linux系統上&#xff0c;也可以實現虛擬化容…

No matching variant of com.android.tools.build:gradle:7.4.2 was found.

一、報錯信息 創建個新項目&#xff0c;運行直接報錯&#xff0c;信息如下&#xff1a; No matching variant of com.android.tools.build:gradle:7.4.2 was found. The consumer was configured to find a runtime of a library compatible with Java 8, packaged as a jar,…

shell 條件語句

目錄 測試 test測試文件的表達式 是否成立 格式 選項 比較整數數值 格式 選項 字符串比較 常用的測試操作符 格式 邏輯測試 格式 且 &#xff08;全真才為真&#xff09; 或 &#xff08;一真即為真&#xff09; 常見條件 雙中括號 [[ expression ]] 用法 &…

springboot啟動過程

1、SpringApplication new一個對象會優先調用initialize方法 public SpringApplication(Object... sources) {initialize(sources); } private void initialize(Object[] sources) {//添加配置類SpringBootApplicationif (sources ! null && sources.length > 0) …

關于一些bug的解決1、el-input的輸入無效2、搜索之后發現數據不對3、el多選框、單選框點擊無用4、

el-input輸入無效 原來的代碼是 var test null 但是我發現不能輸入任何值 反倒修改test的初始值為123是可以的 于是我確定綁定沒問題 就是修改的問題 于是改成 var test ref&#xff08;&#xff09; v-model綁定的值改成test.value就可以了 因為ref是相應式的 可以通過輸入…

【算法】奇偶游戲(帶權并查集)

題目 小 A 和小 B 在玩一個游戲。 首先&#xff0c;小 A 寫了一個由 0 和 1 組成的序列 S&#xff0c;長度為 N。 然后&#xff0c;小 B 向小 A 提出了 M 個問題。 在每個問題中&#xff0c;小 B 指定兩個數 l 和 r&#xff0c;小 A 回答 S[l~r] 中有奇數個 1 還是偶數個 …

cocos2dx ??Animate3D(三)

一些總結 動作&#xff08;Actions&#xff09; move移動&#xff1a;moveto/moveby 從一個位置移動到另外一個位置 從一個位置移動多少數量級rotate旋轉&#xff1a;rotateto/rotateby 從一個角度旋轉到另外一個角度 旋轉多少個數量級scale縮放&#xff1a;scaleto/scaleby …

vue實現瀏覽器禁止鼠標選中文字禁止右鍵禁止F12鍵

1. 禁止鼠標選中文字 document.onselectstart new Function("event.returnValuefalse");2.禁止右鍵 document.oncontextmenu new Function("event.returnValuefalse");3. 禁止F12鍵 document.addEventListener("keydown", function (e) {if…

Go語言多線程爬蟲萬能模板它來了!

對于長期從事爬蟲行業的技術員來說&#xff0c;通過技術手段實現抓取海量數據并且做到可視化處理&#xff0c;我在想如果能寫一個萬能的爬蟲模板&#xff0c;后期遇到類似的工作只要套用模板就能解決大部分的問題&#xff0c;如此提高工作效率何樂而不為&#xff1f; 以下是一個…

有關Vue、微信小程序、UniApp中的CSS中的寬度width單位、自適應

在Vue中&#xff0c;可以使用以下單位來設置寬度&#xff08;width&#xff09; 像素&#xff08;px&#xff09;&#xff1a;最常用的單位&#xff0c;表示一個絕對長度單位。例如&#xff0c;width: 200px; 表示寬度為200像素。百分比&#xff08;%&#xff09;&#xff1a;…

Mac自帶的看圖如何連續查看多張圖片

一、問題 mac看訪達里的圖片時&#xff0c;雙擊打開一張圖片&#xff0c;然后按上下左右鍵都沒法切換到另外的圖片。而且也沒找到像window一樣單擊縮略圖可以看到預覽圖。其實是自己不懂得怎么使用&#xff0c;哈哈哈&#x1f602; 二、方法 2.1、圖標方式 可以看到縮略圖&a…

新的centos7.9安裝jenkins(二)

更多ruoyi-nbcio功能請看演示系統 gitee源代碼地址 前后端代碼&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后臺管理系統 接上一節文章。 這個版本默認git也安裝好了&#xff0c;所以全局配置這個不需要了。 maven安裝3.9.3版本…

前綴和——DP35 【模板】二維前綴和

文章目錄 &#x1f34e;1. 題目&#x1f352;2. 算法原理&#x1f345;3. 代碼實現 &#x1f34e;1. 題目 題目鏈接&#xff1a;【模板】二維前綴和_牛客題霸_牛客網 (nowcoder.com) 描述 給你一個 n 行 m 列的矩陣 A &#xff0c;下標從1開始。 接下來有 q 次查詢&#xff0…

ElasticSearch的日志配置

ElasticSearch默認情況下使用Log4j2來記錄日志&#xff0c;日志配置文件的路徑為$ES_HOME/config/log4j2.properties&#xff0c;配置方法見Log4j2的官方文檔。 參考path-settings&#xff0c;通過指定path.logs&#xff0c;可以指定日志文件的保存路徑。 在日志配置文件$ES_…

【OpenCV實現圖像:使用OpenCV生成拼圖效果】

文章目錄 概要通用配置不考慮間隔代碼實現考慮間隔代碼實現小結 概要 概要&#xff1a; 拼圖效果是一種將圖像切割為相鄰正方形并重新排列的藝術效果。在生成拼圖效果時&#xff0c;可以考慮不同的模式&#xff0c;包括是否考慮間隔和如何處理不能整除的部分。 不考慮間隔&a…

【NLP】GPT 模型如何工作

介紹 2021 年&#xff0c;我使用 GPT 模型編寫了最初的幾行代碼&#xff0c;那時我意識到文本生成已經達到了拐點。我要求 GPT-3 總結一份很長的文檔&#xff0c;并嘗試了幾次提示。我可以看到結果比以前的模型先進得多&#xff0c;這讓我對這項技術感到興奮&#xff0c;并渴望…

HQL刷題 50道

HQL刷題 50道 尚硅谷HQL刷題網站 答案 1.查詢累積銷量排名第二的商品 select sku_id from (select sku_id, dense_rank() over (order by total desc) rnfrom (select sku_id, sum(sku_num) totalfrom order_detailgroup by sku_id) t1) t2 where rn 2;2.查詢至少連續三天下…

php 時區查看和設置

php的時區&#xff0c;關系到相關時間函數的結果 其他相關&#xff1a; linux時區設置&#xff1a;鏈接 pgsql時區設置&#xff1a; 一、查看可以用的時區列表 新建一個php文件&#xff0c;輸入下面程序即可 <?php echo "<pre>"; var_dump(timezone_id…

基于go-zero的rpc服務示例

以下是一個基于 go-zero 框架的簡單 RPC 服務示例&#xff0c;該示例包括一個服務端和一個客戶端通過 gRPC 進行通信。 服務端 1、定義 .proto 文件 在 rpc/add 目錄下創建 adder.proto 文件&#xff0c;定義 RPC 服務&#xff1a; syntax "proto3";package add…