【Linux】socket编程(二)

目录

前言 

TCP通信流程 

TCP通信的代码实现

tcp_server.hpp编写

tcp_server.cc服务端的编写

tcp_client.cc客户端的编写

整体代码


前言 

        上一章我们主要讲解了UDP之间的通信,本章我们将来讲述如何使用TCP来进行网络间通信,主要是使用socket API进行代码的实现。

        我们一共讲了5个socket API接口,分别为socket,bind,listen,accept,connect.但我们在讲解UDP通信时,只使用了socket和bind这两个接口就完成了。而TCP通信会使用后面这三个接口,我们将分别讲解.


TCP通信流程 

        同样地,TCP通信分为服务器端和客户端,它们的流程分别如下:

服务端通信流程:

  1. 创建套接字:使用socket函数创建一个套接字,指定协议族为AF_INET(IPv4)或AF_INET6(IPv6),指定类型为SOCK_STREAM(TCP)。

  2. 绑定套接字:使用bind函数将套接字与服务器的IP地址和端口号绑定在一起。这样服务器将使用指定的IP地址和端口号进行监听。

  3. 监听连接请求:使用listen函数开始监听连接请求。指定参数backlog,表示允许在队列中等待的最大连接数。

  4. 接受连接请求:使用accept函数接受客户端的连接请求。该函数会阻塞程序,直到有客户端连接时才返回一个新的套接字,用于与客户端进行通信。(新的套接字和旧套接字区别:新套接字负责服务建立的连接,包括通信等,旧套接字则一直负责监听连接.)

  5. 通信:使用新的套接字进行通信。可以使用readwrite函数进行数据的接收和发送。

  6. 关闭连接:当通信结束后,使用close函数关闭套接字,释放资源。

客户端通信流程:

  1. 创建套接字:使用socket函数创建一个套接字,指定协议族为AF_INET(IPv4)或AF_INET6(IPv6),指定类型为SOCK_STREAM(TCP)。

  2. 连接服务器:使用connect函数连接到服务器的IP地址和端口号。如果连接成功,返回0;否则返回错误码。

  3. 通信:使用已连接的套接字进行数据的发送和接收,可以使用readwrite函数。

  4. 关闭连接:当通信结束后,使用close函数关闭套接字,释放资源。


TCP通信的代码实现

依然是三个文件,分别为tcp_server.hpp(用来封装tcp socket),tcp_server.cc(服务器通信代码),tcp_client.cc(客户端通信代码).

tcp_server.hpp编写

首先我们要编写tcp_server.hpp,首先第一个接口initServer初始化服务端. 一共分为三步:

  • 1.创建套接字

利用socket函数创建新的套接字,并判断是否成功:

        listensock = socket(AF_INET, SOCK_STREAM, 0);if (listensock < 0){logMessage(FATAL, "%d:%s", errno, strerror(errno));exit(2);}logMessage(NORMAL, "create sock success,  listensock: %d", listensock);
  • 2.bind绑定

        bind将套接字和特定的ip和地址绑定在一起.用法我们上一章也说了,先创建一个sockaddr_in结构体,然后填入相关的数据:sin_family(协议族 AF_INET(IPv4)或AF_INET6(IPv6)),sin_port(端口号),sin_arr.s_addr(ip地址),然后再bind绑定并判断是否成功,代码如下:

        struct sockaddr_in local;memset(&local, 0, sizeof local);local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());if (bind(listensock, (struct sockaddr *)&local, sizeof local) < 0){logMessage(FATAL, "bind error", errno, strerror(errno));exit(3);}
  • 3.listen监听

        listen监听是否有新的连接,TCP与UDP不同的是,当客户端和服务端正式通信的时候,需要先建立连接,而UDP直接发送数据。所以要listen来监听是否有新链接.

        代码如下:

        // 3.因为TCP是面向连接的,意味着当我们正式通信的时候,需要先建立连接//第二个参数我们在讲TCP协议时会详细讲解,这里先暂且设为20if (listen(listensock, gbacklog) < 0){logMessage(FATAL, "listen error", errno, strerror(errno));exit(3);}logMessage(NORMAL, "init server success");

第二个接口Start(),该接口主要负责获取连接,并进行通信.共分为两步:

  • accept获取到客户端连接

        这个我们同样的需要创建一个sockaddr_in结构体,用来存储客户端的连接信息,然后接收新的套接字,这个套接字是接下来我们通信要使用的。

            struct sockaddr_in src;socklen_t len = sizeof src;//servicesock(未来真正进行IO) vs listensock(主要任务:获取新链接)int servicesock = accept(listensock, (struct sockaddr *)&src, &len);if (servicesock < 0){logMessage(ERROR, "accept error", errno, strerror(errno));}
  • 通信流程

这里可以提供两个版本的:一个是单进程版,即每一次只能处理一个客户端.

另一个是 多进程版,通过创建子进程来实现对多个客户端处理.

  • 单进程版

        紧接着上面说的,我们获取到客户端的连接信息后,我们需要对其进行解析,得到其ip地址和端口号:

            uint16_t client_port = ntohs(src.sin_port);//获得端口号string client_ip = inet_ntoa(src.sin_addr);//获得iplogMessage(NORMAL, "Link success, %d | %s : %d\n", servicesock,     client_ip.c_str(), client_port);

        然后直接执行对应的通信函数即可:

 service(servicesock,client_ip,client_port);
  • 多进程版: 

        利用fork函数实现,代码如下:后面的服务端通信和客户端通信都不用改动

            pid_t id = fork();assert(id != -1);if(id == 0){//子进程close(listensock);service(servicesock,client_ip,client_port);exit(0);//僵尸状态}close(servicesock);

        通信函数service的实现:我们从sock中读取消息,客户端没有发消息时,服务端会阻塞在这里等待用户的输入。

static void service(int sock,const string& clientip,const uint16_t& clientport)
{//echo serverchar buffer[1024];memset(buffer, 0, sizeof(buffer));while(true){//read && writessize_t s = read(sock,buffer,sizeof buffer-1);if(s > 0){buffer[s] = 0;//将发过来的数据当做字符串cout << clientip << " : " << clientport << "# "<< buffer << endl;}else if(s== 0)//对端链接关闭{logMessage(NORMAL,"%s : %d shutdown, me too!",clientip.c_str(),clientport);break;}else{logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));break;}write(sock,buffer,strlen(buffer));}close(sock);
}

tcp_server.cc服务端的编写

这个就很简单了,只需要调用initServer初始化和Start开始就行了.

#include "tcp_server.hpp"
#include <memory>static void usage(string proc)
{cout << "Usage: " << proc << "ServerPort\n" << endl;
}//./tcp_server port
int main(int argc, char* argv[])
{if(argc != 2){usage(argv[0]);exit(1);}uint16_t port = atoi(argv[1]);unique_ptr<TcpServer> svr(new TcpServer(port));svr->initServer();svr->Start();return 0;
}

tcp_client.cc客户端的编写

  • 创建套接字:
    int sock = socket(AF_INET, SOCK_STREAM, 0);
  • 调用connect与服务端链接:利用命令行参数,将用户输入的ip地址和port端口号获取到,然后传入sockaddr_in结构体,最后进行connect
    uint16_t serverPort = atoi(argv[2]);string serverIp = argv[1];    struct sockaddr_in server;bzero(&server, sizeof server);server.sin_family = AF_INET;server.sin_port = htons(serverPort);server.sin_addr.s_addr = inet_addr(serverIp.c_str());if (connect(sock, (struct sockaddr *)&server, sizeof server) < 0)
  • 进行通信(send和recv)

   TCP的发送和接收消息不同于UDP的sendto和recvfrom,而是send和recv。我们分别看一下函数的用法:

send:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • sockfd:发送数据的套接字描述符。即想谁发送
  • buf:指向要发送数据的缓冲区的指针。
  • len:要发送的数据的长度(以字节为单位)。
  • flags:附加选项,通常设为0。
  • 作用:send()函数用于将数据从发送端发送到接收端。它返回已发送的字节数,或者在出现错误时返回-1。可以通过设置flags参数来指定传输数据的特定选项,例如设置为MSG_DONTWAIT非阻塞发送等。

recv:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd:要接收数据的套接字描述符。即谁接收
  • buf:接收数据的缓冲区的指针。
  • len:接收数据的最大长度(以字节为单位)。
  • flags:附加选项,通常设为0。
  • 作用:recv()函数用于从套接字接收数据,并将其存储在指定的缓冲区中。它返回接收到的字节数,或者在出现错误时返回-1。可以通过设置flags参数来指定接收数据的特定选项,例如设置为MSG_DONTWAIT非阻塞接收等。

所以通信代码如下:

    while (true){string line;cout << "Please Enter Message# ";getline(cin, line);send(sock, line.c_str(), line.size(), 0);char buffer[1024];ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);if (s > 0){buffer[s] = 0;cout << "server echo# " << buffer << endl;}else if (s == 0){break;}else{break;}}

至此我们的TCP通信就完成了.

当我们使用多进程通信时,可以有多个客户端同时向服务端发送消息:

 至此,TCP的网络通信流程也完成了,这是完整的代码,可以直接 拷贝运行,可去掉logMessage相关的调试信息.

整体代码

注意运行服务器时,使用./tcp_server 端口号

运行客户端连接服务器时,使用./tcp_clinet 服务器ip 服务器端口号

tcp_server.hpp文件

#pragma once
#include <iostream>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <memory>
#include <pthread.h>
#include <signal.h>
#include <cstring>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;static void service(int sock,const string& clientip,const uint16_t& clientport)
{//echo serverchar buffer[1024];memset(buffer, 0, sizeof(buffer));while(true){//read && writessize_t s = read(sock,buffer,sizeof buffer-1);if(s > 0){buffer[s] = 0;//将发过来的数据当做字符串cout << clientip << " : " << clientport << "# "<< buffer << endl;}else if(s== 0)//对端链接关闭{logMessage(NORMAL,"%s : %d shutdown, me too!",clientip.c_str(),clientport);break;}else{logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));break;}write(sock,buffer,strlen(buffer));}
}class TcpServer
{
public:const static int gbacklog = 20;TcpServer(uint16_t port, string ip = ""): _port(port), _ip(ip), listensock(-1){}void initServer(){// 1.创建套接字listensock = socket(AF_INET, SOCK_STREAM, 0);if (listensock < 0){logMessage(FATAL, "%d:%s", errno, strerror(errno));exit(2);}logMessage(NORMAL, "create sock success,  listensock: %d", listensock);// 2.bindstruct sockaddr_in local;memset(&local, 0, sizeof local);local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());if (bind(listensock, (struct sockaddr *)&local, sizeof local) < 0){logMessage(FATAL, "bind error", errno, strerror(errno));exit(3);}// 3.因为TCP是面向连接的,意味着当我们正式通信的时候,需要先建立连接if (listen(listensock, gbacklog) < 0){logMessage(FATAL, "listen error", errno, strerror(errno));exit(3);}logMessage(NORMAL, "init server success");}void Start(){//version2 :signal(SIGCHLD,SIG_IGN); //对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸进程while (true){// sleep(1);// 获取连接struct sockaddr_in src;socklen_t len = sizeof src;// sock(未来真正进行IO) and _sock(主要任务:获取新链接)int servicesock = accept(listensock, (struct sockaddr *)&src, &len);if (servicesock < 0){logMessage(ERROR, "accept error", errno, strerror(errno));}// 获取连接成功uint16_t client_port = ntohs(src.sin_port);string client_ip = inet_ntoa(src.sin_addr);logMessage(NORMAL, "Link success, %d | %s : %d\n", servicesock, client_ip.c_str(), client_port);// 开始进行通信服务// version 1 -- 单进程循环 -- 只能一次处理一个客户端,处理完一个,才能处理下一个// 显然是不能被直接使用的?为什么?单进程.service(servicesock,client_ip,client_port);// version 2 -- 多进程版本 -- 创建子进程,// 让子进程给新的连接提供服务,子进程能不能打开父进程曾经打开的文件fd呢? 答案是当然可以!pid_t id = fork();assert(id != -1);if(id == 0){//子进程close(listensock);service(servicesock,client_ip,client_port);exit(0);//僵尸状态}//父进程close(servicesock);}}~TcpServer(){}private:uint16_t _port;string _ip;int listensock;unique_ptr<ThreadPool<Task>> _threadpool_ptr;
};

tcp_server.cc文件

#include "tcp_server.hpp"
#include <memory>static void usage(string proc)
{cout << "Usage: " << proc << "ServerPort\n" << endl;
}//./tcp_server port
int main(int argc, char* argv[])
{if(argc != 2){usage(argv[0]);exit(1);}uint16_t port = atoi(argv[1]);unique_ptr<TcpServer> svr(new TcpServer(port));svr->initServer();svr->Start();return 0;
}

cline.cc文件

#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <strings.h>
#include <stdlib.h>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
static void usage(string proc)
{cout << "Usage: " << proc << "ServerIP ServerPort" << endl;
}
// ./tcp_clinet IP Prot
int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(-1);}uint16_t serverPort = atoi(argv[2]);string serverIp = argv[1];int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){cerr << "sokcet error" << endl;exit(2);}// client 不需要显式的bind,OS会自动选择// 更不需要监听,但是需要连接的能力connectstruct sockaddr_in server;bzero(&server, sizeof server);server.sin_family = AF_INET;server.sin_port = htons(serverPort);server.sin_addr.s_addr = inet_addr(serverIp.c_str());if (connect(sock, (struct sockaddr *)&server, sizeof server) < 0){cerr << "connect error" << endl;exit(3);}cout << "connect success!" << endl;while (true){string line;cout << "Please Enter Message# ";getline(cin, line);send(sock, line.c_str(), line.size(), 0);char buffer[1024];ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);if (s > 0){buffer[s] = 0;cout << "server echo# " << buffer << endl;}else if (s == 0){break;}else{break;}}close(sock);return 0;
}

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

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

相关文章

计算机竞赛 python的搜索引擎系统设计与实现

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; python的搜索引擎系统设计与实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;5分创新点&#xff1a;3分 该项目较为新颖&#xff…

大数据课程K3——Spark的常用案例

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握Spark的常用案例——WordCount; ⚪ 掌握Spark的常用案例——求平均值; ⚪ 掌握Spark的常用案例——求最大值和最小值; ⚪ 掌握Spark的常用案例——TopK; ⚪ 掌握Spark的常用案例…

华为OD-整数对最小和

题目描述 给定两个整数数组array1、array2&#xff0c;数组元素按升序排列。假设从array1、array2中分别取出一个元素可构成一对元素&#xff0c;现在需要取出k对元素&#xff0c;并对取出的所有元素求和&#xff0c;计算和的最小值 代码实现 # coding:utf-8 class Solution:…

I2S/PCM board-level 约束及同步(latencyskewbitsync)

目录 1.I2S/PCM 同步 2.I2S/PCM的板间latency I2S/PCM是典型的低速串口&#xff0c;在两个方向上分别有两组信号&#xff0c;我们已soc为视角分为soc-adif和外设audio-codec。 那么adif输入&#xff1a; sclk_i, ws_i, sdi 当然并不是三个输入信号同时有效&#xff0c;只…

react 11之 router6路由 (两种路由模式、两种路由跳转、两种传参与接收参数、嵌套路由,layout组件、路由懒加载)

目录 react路由1&#xff1a;安装和两种模式react路由2&#xff1a;两种路由跳转 &#xff08; 命令式与编程式&#xff09;2-1 路由跳转-命令式2-2 路由跳转-编程式 - 函数组件2-2-1 app.jsx2-2-2 page / Home.jsx2-2-3 page / About.jsx2-2-4 效果 react路由3&#xff1a;函数…

模板方法模式(十六)

相信自己&#xff0c;请一定要相信自己 上一章简单介绍了代理模式(十五), 如果没有看过, 请观看上一章 一. 模板模式 引用 菜鸟教程里面的 模板模式介绍: https://www.runoob.com/design-pattern/template-pattern.html 在模板模式&#xff08;Template Pattern&#xff09;…

【HTML】HTML面试知识梳理

目录 DOCTYPE&#xff08;文章类型&#xff09;head标签浏览器乱码的原因及解决常用的meta标签与SEOscript标签中defer和async的区别src&href区别HTML5有哪些更新语义化标签媒体标签表单进度条、度量器DOM查询Web存储Canvas和SVG拖放 &#xff08;HTML5 drag API&#xff0…

高阶数据结构跳表

"想象为翼&#xff0c;起飞~" 跳表简介&#xff1f; skiplist本质上是一种查找结构&#xff0c;用于解决算法中的查找问题&#xff0c;跟平衡搜索树和哈希表的价值是 一样的&#xff0c;可以作为key或者key/value的查找模型。 跳表由来 skiplist是由美国计算…

12、Pinia 快速入门

1、什么是Pinia Pinia 是 Vue 的最新 状态管理工具 &#xff0c;是 Vuex 的 替代品 2、手动添加Pinia到Vue项目 在实际开发项目的时候,关于Pinia的配置,可以在项目创建时自动添加 现在我们初次学习&#xff0c;从零开始&#xff1a; 1.使用 Vite 创建一个空的 Vue3 项目 n…

流媒体服务器SRS的搭建及QT下RTMP推流客户端的编写

一、前言 目前市面上有很多开源的流媒体服务器解决方案&#xff0c;常见的有SRS、EasyDarwin、ZLMediaKit和Monibuca。这几种的对比如下&#xff1a; &#xff08;本图来源&#xff1a;https://www.ngui.cc/zz/1781086.html?actiononClick&#xff09; 二、SRS的介绍 SRS&am…

python操作elasticsearch

python操作elasticsearch_一个高效工作的家伙的博客-CSDN博客 待更新

jstat(JVM Statistics Monitoring Tool):虚拟机统计信息监视工具

jstat&#xff08;JVM Statistics Monitoring Tool&#xff09;&#xff1a;虚拟机统计信息监视工具 用于监视虚拟机各种运行状态信息的命令行工具。 它可以显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据&#xff0c;在没有GUI图形界面、只提…

[Linux]进程状态

[Linux]进程状态 文章目录 [Linux]进程状态进程状态的概念阻塞状态挂起状态Linux下的进程状态孤儿进程 进程状态的概念 了解进程状态前&#xff0c;首先要知道一个正在运行的进程不是无时无刻都在CPU上进行运算的&#xff0c;而是在操作系统的管理下&#xff0c;和其他正在运行…

Keepalived+Lvs(dr)调度器主备配置小实验

目录 前言 一、实验拓扑图 二、配置LVS&#xff08;dr&#xff09;模式 三、配置调配器热备 四、测试 总结 前言 Keepalived和LVS&#xff08;Linux Virtual Server&#xff09;是两个常用的开源软件&#xff0c;通常结合使用以提供高可用性和负载均衡的解决方案。 Keepalive…

如何获取Ck

1. 下载via浏览器 https://viayoo.com/zh-cn/ 2.打开via浏览器, 登录美团外卖 美团网账号登录-手机美团官网 3.点击左上角的盾牌 然后点击这里 最后去我的网站粘贴就行

Matplotlib学习笔记

Matplotlib数据可视化库 jupyter notebook优势 画图优势&#xff0c;画图与数据展示同时进行。数据展示优势&#xff0c;不需要二次运行&#xff0c;结果数据会保留。 Matplotlib画图工具 专用于开发2D图表以渐进、交互式方式实现数据可视化 常规绘图方法 子图与标注 想要…

linux中模拟RTOS中事件集

linux中通常如何处理事件集 在Linux中&#xff0c;没有直接对应于实时操作系统&#xff08;RTOS&#xff09;中事件集&#xff08;Event Set&#xff09;的概念。实时操作系统通常提供了一种机制&#xff0c;允许任务或线程根据事件的发生状态进行等待和唤醒。这通常通过信号量…

opencv 进阶13-Fisherfaces 人脸识别-函数cv2.face.FisherFaceRecognizer_create()

Fisherfaces 人脸识别 PCA 方法是 EigenFaces 方法的核心&#xff0c;它找到了最大化数据总方差特征的线性组合。不可否认&#xff0c;EigenFaces 是一种非常有效的方法&#xff0c;但是它的缺点在于在操作过程中会损失许多特征信息。 因此&#xff0c;在一些情况下&#xff0c…

CTFshow——web入门——反序列化web254-web278 详细Writeup

前言 在做题之前先简要总结一下知识点 private变量会被序列化为&#xff1a;\x00类名\x00变量名 protected变量会被序列化为: \x00\*\x00变量名 public变量会被序列化为&#xff1a;变量名__sleep() &#xff1a;//在对象被序列化之前运行__wakeup() //将在反序列化之后立即…

基于web的成语接龙游戏java jsp趣味学习mysql源代码

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 基于web的成语接龙游戏 系统有1权限&#xff1a;管理…