网络编程之套接字

端口 && IP

在学习套接字编程之前,我们必须了解一下前缀知识。首先是IP和端口的作用。

在这之前,我们要明白一件事。那就是把数据从一台主机发送到另一台主机,是目的吗???当然不是!!我们要把数据从一台主机发送到另一台主机指定中的一个进程,再由这个进程对数据进行处理后返回给用户。这才是目的!!那么我们通过IP找到了指定的主机,如何找到主机上指定的进程呢?? 那么我们就需要用到端口号(port)。

IP : 标识全网唯一一台主机(不太准确,暂时这么理解)

port 端口号 : 标识主机中唯一的一个进程

所以,IP + port 是不是就可以确定全网唯一 一个进程? 因为 IP确定全网唯一一台主机,端口号确定主机上唯一的一个进程。所以 IP + port 就可以确定全网唯一 一个进程。

而这个时候,我们主机与主机之间的通信,就变成了进程与进程之间的通信!进程间通信的本质是什么?是让多个进程可以看到同一份资源!在本地主机中这份资源可以是内存,也可以是文件。但是在多台主机中,这份资源就是我们熟悉的网络!!

所以我们可以用server把数据发送到网络中,再由对端的client从网络中读。就类似于读文件和写文件的操作一样,就可以实现跨网络的进程间通信!!

注意!

端口一旦确定,那么就不能改变!!因为在对应的客户端必须要指定服务器的端口,而此时如果服务器改变了自己的端口号,那么客户端也必须做到相应的改变!但显然客户并不会意识到这一点,所以端口号一旦确定,不要轻易改变!!!

TCP && UDP

接下来我们再浅浅了解一下TCP和UDP 。

TCP协议的特点

  • 传输层协议
  • 会建立连接
  • 可靠传输
  • 面向字节流

UDP协议的特点

  • 传输层协议
  • 不建立连接
  • 不可靠传输
  • 面向数据报

有连接 && 无连接

显然,UDP和TCP都是传输层协议,那么建立连接这个怎么理解呢?这就很类似两个人打电话,必须要先有一方给另一方播电话。而另一方看到电话来了只有在接通之后两人才能进行通信。而这个过程就类似于TCP的连接过程。

而UDP是无连接的,就意味着你给对方发送数据压根就不用等对方确认。就相当于你给对方打电话,对方不能自己接通,而是电话自动接通。这就是无连接 。

**可靠传输 && 不可靠传输 **

首先我们要知道,可靠和不可靠在这里都是一个中性词。并不是一个贬义词,因此不能因为UDP是不可靠传输就说UDP差。我们要知道,保证可靠性是有代价的!! 因为TCP提供了各种可靠性机制,所以这也就意味着TCP会非常复杂!而UDP是不可靠性传输,就意味着它比较简单。各有各的优势,还是看场景进行选择。

注意!!无论是UDP和TCP,没有明显的谁慢谁快。因为跨网络传输看的是两台主机之间的距离和当前的网络状态!!与协议关系并不大。

面向字节流 && 面向数据报

面向字节流就是以字节流的形式进行报文的发送,那么这就意味着发送的时候,报文可能并不完整!比如你主机的传输层缓冲区已经满了,但是你的报文只接收了前面一点点。就只能等缓冲区被提取后才会接收后面的数据。这种现象就是面向字节流的!

面向数据报则是每次发送/接收,都必须是完整的报文!不存在断截之后再续上的现象,这就叫做面向数据报传输!!

网络字节序

我们都知道内存的字节存储方式有大端和小端。

大端存储 高位放在低字节序,低位放在高字节序 如:0X11223344 在内存中存储是 11 22 33 44

小端存储 低位放在低字节序,高位放在高字节序 如:0X11223344 在内存中存储是 44 33 22 11

既然不同的机器有不同的存储方式,那你怎么保证你小端发送的数据和大端接收的数据是匹配的?

很简单!!只要规定在网络上传输的数据都必须按大端字节序来存储!!发送发如果是小端则只需要转到大端发送即可,接收方是小端再由大端转回小端即可,如果是大端则不需要操作。

但是如果让我们自己来转换,那是不是太麻烦了呢?所以操作系统提供了下面的接口来供我们转换序列。

#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong); //主机转网络序列,转成long类型
uint16_t htons(uint16_t hostshort); //主机转网络序列,转成short类型
uint32_t ntohl(uint32_t netlong); //网络序列转主机序列,转成long类型
uint16_t ntohs(uint16_t netshort); //网络序列转主机序列,转成short类型

其中的h代表主机host,to就是到的意思,n则是network网络的意思。l对应long,s对应short。所以上面的函数其实很好记忆。

Socket套接字编程

Socket是一个套接字,它的本质就是一个文件描述符。对应的是网卡文件,我们只需要往文件描述符里面写入,就可以把数据发送到网络。

函数介绍

套接字的创建(UDP/TCP/服务端/客户端)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int socket(int domain, int type, int protocol);

int domain : 通信范围,例如AF_UNIX,AF_LOCAL是本地通信,AF_INET是IPV4,AF_INET6是IPV6
int type: 通信类型,最常见的是TCP的SOCK_STREAM面向字节流,UDP的SOCK_DGRAM面向数据报
int protocol: 确定socket支持的哪个协议,一般默认为0即可。
返回值:一个文件描述符,小于0则代表创建套接字(打开文件)失败

端口号绑定(UDP/TCP/服务端/客户端)

对套接字绑定端口,一般服务器显示绑定。客户端OS自动绑定。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

*int sockfd: 要绑定的套接字文件描述符
const struct sockaddr addr :一个结构体的指针,该结构体包含了domain,port,ip等信息…
socklen_t addrlen:结构体体长度
返回值:成功返回0,绑定失败返回-1,或者返回错误码

这个结构体的首元素是一个16位地址类型,虽然下面的内容都不一样,但只要统一强转成 struct sockaddr,那么就可以拿到前16位。再根据前16位判断出结构体的类型,再做出对应的处理。我们可以把sockaddr 看成是一个父类,sockaddr_in 和 sockaddr_un 是它的子类。而无论我们是使用sockaddr_in还是sockaddr_un,我们都可以用sockaddr来接收。再根据前16位判断地址类型,做出选择。这就很像我们的多态,因为这个功能出来的时候,C语言没有void*指针这个功能,所以我们传入时必须要进行类型强转。

在这里插入图片描述

监听套接字(TCP/服务端)

UDP不会用到监听套接字这个函数,因为UDP是无连接的。而TCP是有连接的,所以必须把这个套接字设置为监听状态。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int listen(int sockfd, int backlog);

int sockfd:要监听的套接字
int backlog:连接同时存在的最大数量(因为监听是有消耗的,如果大量连接过来,监听不过来了,那么就会把这些连接暂时存储起来,而backlog就是存储的最大数量。)
返回值: 成功返回0,监听失败返回-1,或者返回错误码

接收连接请求(TCP/服务端)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int sockfd  监听后的套接字

**struct sockaddr addr: 存储对端domain,port,ip的结构体
socklen_t addrlen: 结构体的大小
返回值: 一个可以直接通信的套接字

建立连接(TCP/客户端)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

int sockfd:的套接字

**const struct sockaddr *addr: ** 对端的addr结构体,可以提取到对端的IP和端口

**socklen_t addrlen: ** 要接收的长度。

UDP通信测试

介绍2个函数,UDP传输要用的两个函数。 因为udp是无连接的,所以需要特定的函数来进行通信。

sendto函数

#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);

该函数的功能是往网络套接字中发送数据,如果调用该函数时套接字还没有绑定端口,那么该函数会自动为套接字随机分配端口。

**int sockfd: 套接字 **

*void buf, size_t len: 发送的数据

size_t len:发送数据的大小

int flags:发送方式,0为阻塞发送

*const struct sockaddr dest_addr:该参数包含着目标主机的IP和端口,根据该参数找到服务器

socklen_t addrlen:传入的sockaddr的长度

recvfrom函数

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

该函数的功能是往从网络套接字中读取数据。

int sockfd: 套接字

*void buf, size_t len: 读取数据的缓冲区

size_t len:读取数据的大小

int flags:是否阻塞读取,0为阻塞

*const struct sockaddr dest_addr:输出型参数,该参数对端主机的IP和端口,可以用来获取对端的主机IP和端口

socklen_t addrlen:接收的sockaddr 的长度

udp代码演示

服务器代码

server.cc文件

#include "server.hpp"
#include <memory>int main(int argc , char* argv[])
{if(argc != 2) //命令行参数不为2就退出{std::cout << "Usage : " << argv[0] << "   bindport" << std::endl;  //打印使用手册exit(1);}uint16_t port = atoi(argv[1]); //命令行传的端口转换成16位整形std::unique_ptr<UdpServer> s(new UdpServer(port)); //创建UDP服务器s->init(); //初始化服务器,创建 + 绑定s->start(); //运行服务器
}

server.hpp 代码:

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>class UdpServer
{
private:int _sock; uint16_t _port;
public:UdpServer(uint16_t port): _port(port) { }~UdpServer() { close(_sock); }void init(){_sock = socket(AF_INET,SOCK_DGRAM,0);  //创建套接字if(_sock < 0){//创建失败std::cout << "create socket failed...." << std::endl;abort();}//绑定 struct sockaddr_in ser; ser.sin_port = htons(_port);  //填入端口ser.sin_family = AF_INET; // 填入地址类型ser.sin_addr.s_addr = INADDR_ANY; //填入IP地址if(bind(_sock,(sockaddr*)&ser,sizeof ser) != 0) //绑定{//绑定失败std::cout << "bind socket failed...." << std::endl;abort();}}void start(){struct sockaddr_in peer; //对端socklen_t peer_len = sizeof peer;char buff[1024] = {0};   while(1){//接收对端发来的消息int n = recvfrom(_sock,buff,1023,0,(struct sockaddr*)&peer,&peer_len); buff[n] = 0;if(read == 0) //对端关闭了{std::cout << "one client quit..." << std::endl;continue;}else if(read < 0) //读取出错了{std::cout << "read error..." << std::endl;break;}//读取正常...std::string clientip = inet_ntoa(peer.sin_addr); //获取对端的IPuint16_t clientport = ntohs(peer.sin_port);// 获取对端的端口//打印信息....std::cout << "["<< clientip <<"] "<< clientport << ": say : " << buff << std::endl; }} 
};

客户端代码

client.cc:

#include "client.hpp"
#include <memory>int main(int argc , char* argv[])
{if(argc != 3) //命令行参数少于3个退出,因为必须 ./client 服务器ip 端口号  才可以调用{std::cout << "Usage : " << argv[0] << "   serverip  serverport" << std::endl; exit(1);}uint16_t port = atoi(argv[2]); //获取服务器端口std::string ip = argv[1]; //获取服务器IPstd::unique_ptr<UdpClient> cli(new UdpClient(port,ip)); //创建客户端cli->init(); //初始化客户端cli->start(); //客户端运行
}

client.hpp

#pragma once
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <iostream>
#include <arpa/inet.h>class UdpClient
{
public: UdpClient(uint16_t port , const std::string& ip) : _port(port), _svr_ip(ip){}~UdpClient(){ close(_sock); }void init(){//创建套接字_sock = socket(AF_INET,SOCK_DGRAM,0); if(_sock < 0){std::cout << "create socket failed...." << std::endl;abort();}   }void start(){struct sockaddr_in svr; //该结构体填入服务器的端口和IPsvr.sin_port = htons(_port);  //填入端口svr.sin_addr.s_addr = inet_addr(_svr_ip.c_str());  //填入IPsvr.sin_family = AF_INET; //设置地址类型,ipv4int i = 1;  //打印数字,随意,可要可不要//char buff[1024] = {0};  如果需要读数据可用的输出缓冲区while(1){std::string message = "hello server " + std::to_string(i++); //构建发送消息//发送消息sendto(_sock,message.c_str(),message.size(),0,(struct sockaddr*)&svr,sizeof svr);//可以通过下面代码再接收服务器发送的数据,如果服务器有发送的话// int n = recvfrom(_sock,buff,1023,0,nullptr,nullptr);// buff[n] = 0 ;// std::cout << "server say# " << buff << std::endl;sleep(1); // 睡眠1s}}private: int _sock; //套接字uint16_t _port; //服务器的端口std::string _svr_ip; //服务的IP};

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

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

相关文章

qt-C++笔记之识别点击鼠标右键、点击位置以及Qt坐标系详解

qt-C笔记之识别点击鼠标右键、点击位置以及Qt坐标系详解 文章目录 qt-C笔记之识别点击鼠标右键、点击位置以及Qt坐标系详解1.示例运行2.event->pos();详解3.event->pos()的坐标系原点4.Qt中的坐标系详解5.QMainWindow::mousePressEvent(event);详解 1.示例运行 截图时鼠…

【SpringMVC】Spring Web MVC入门(一)

文章目录 前言什么是Spring Web MVC&#xff1f;什么是MVC什么是Spring MVC&#xff1f; Spring Boot 和 Spring MVC 的区别什么是Spring Boot&#xff1f;关系和区别 Spring MVC 学习注解介绍1. SpringBootApplication2. RestController3. RequestMapping3.1 RequestMapping 使…

css所有属性介绍

文章目录 1️⃣ CSS属性介绍1.1 CSS3 动画属性&#xff08;Animation&#xff09;1.2 CSS 背景属性&#xff08;Background&#xff09;1.3 CSS 边框属性&#xff08;Border 和 Outline&#xff09;1.4 Box 属性1.5 Color 属性1.6 Content for Paged Media 属性1.7 CSS 尺寸属性…

CyclicBarrier实战应用——批量数据多线程协调异步处理(主线程执行事务回滚)

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; CCyclicBarrier实战应用——批量数据多线程协调异步处理(主线程执行事务…

Linux学习笔记之七(shell脚本的基本语法)

Shell 1、Shell脚本2、常用运算符2、特殊语法4、关于变量的一些命令4.1、echo4.2、export4.3、read4.4、declare/typeset4.5、local4.6、unset 5、基本逻辑语法5.1、if判断5.2、for循环5.3、while循环5.4、case语句 6、函数定义7、多脚本链接 1、Shell脚本 学习shell脚本开发之…

C-语言每日刷题

目录 [蓝桥杯 2015 省 A] 饮料换购 题目描述 输入格式 输出格式 输入输出样例 # [蓝桥杯 2023 省 A] 平方差 题目描述 输入格式 输出格式 输入输出样例 说明/提示 【样例说明】 [NOIP2001 普及组] 数的计算 题目描述 输入格式 输出格式 输入输出样例 说明/提示 样例 1 解释 数据…

RK3568平台开发系列讲解(Linux系统篇)netlink 监听广播信息

** 🚀返回专栏总目录 文章目录 一、什么是netlink 机制二、netlink 的使用2.1、创建 socket2.2、绑定套接字2.3、接收数据沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍如何通过 netlink 监听广播信息。 一、什么是netlink 机制 Netlink 是 Linux 内核中…

企业计算机服务器locked1勒索病毒数据恢复,locked1勒索病毒解密流程

随着计算机技术的不断发展&#xff0c;越来越多的企业走向数字化办公时代&#xff0c;计算机技术为企业的生产运营提供了有利条件&#xff0c;但也为企业带来了网络安全威胁。在本月&#xff0c;云天数据恢复中心陆续接到很多企业的求助&#xff0c;企业的速达办公软件遭到了lo…

js中setinterval怎么用?怎么才能让setinterval停下来?

setinterval()是定时调用的函数&#xff0c;可按照指定的周期&#xff08;以毫秒计&#xff09;来调用函数或计算表达式。 setinterval()的作用是在播放动画的时&#xff0c;每隔一定时间就调用函数&#xff0c;方法或对象。 setInterval() 方法会不停地调用函数&#xff0c;…

Leetcode刷题详解——乘积最大子数组

1. 题目链接&#xff1a;152. 乘积最大子数组 2. 题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;请你找出数组中乘积最大的非空连续子数组&#xff08;该子数组中至少包含一个数字&#xff09;&#xff0c;并返回该子数组所对应的乘积。 测试用例的答案是一个 32-位…

【大学英语视听说上】Mid-term Test 2

Section A 【短篇新闻1】 You probably think college students are experts at sleeping, but parties, preparations for tests, personal problems and general stress can rack a students sleep habits, which can be bad for the body and the mind. Texas Tech Univer…

JavaWeb(一)

一、Javaweb介绍 Web&#xff1a;全球广域网&#xff0c;也称为万维网(www)&#xff0c;能够通过浏览器访问的网站。 JavaWeb&#xff1a;使用Java技术进行web互联网开发。 总结: 1、JavaWeb就是使用Java技术进行web互联网开发 2、一个web项目包含三个部分&#xff0c;分别…

分页助手入门以及小bug,报sql语法错误

导入坐标 5版本以上的分页助手 可以不用手动指定数据库语言&#xff0c;它会自动识别 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.3.2</version> </dependency&g…

(C语言)逆序输出字符串

#include<stdio.h> #include<string.h> int main() {int i;char s[100];scanf("%s",&s);int count strlen(s);for(int i count -1;i > 0; i --)printf("%c",s[i]);return 0;} 代码运行截图&#xff1a; 注&#xff1a;侵权可删

五子棋AI算法自动测试方法

先前发了几篇五子棋游戏程序设计的博文&#xff0c;设计了游戏程序&#xff0c;也设计了AI智能奕棋的算法&#xff0c;运行程序检测算法的可行性&#xff0c;完成人机模式游戏功能的设置。 本文主要介绍自动测试算法的方法。 AI智能奕棋的算法testAIq( )&#xff0c;主要是检测…

前端项目打包和自动化部署(jenkins+gitee+nginx)

项目打包和自动化部署 一. 项目部署和DevOps 1. 传统的开发模式 在传统的开发模式中&#xff0c;开发的整个过程是按部就班就行&#xff1a; 但是这种模式存在很大的弊端&#xff1a; 工作的不协调&#xff1a;开发人员在开发阶段&#xff0c;测试和运维人员其实是处于等待…

SmartSoftHelp8,文件/文件夹,Web服务器IIS文件安全设置工具

工作文件&#xff0c;文件夹 web服务器 iis 文件&#xff0c;文件夹安全设置 用户&#xff1a; administrator user guest everyone 权限&#xff1a; 完全控制&#xff08;读、写&#xff09; 只读 读取 写入 修改 读取和执行 下载地址&#xff1a; https://p…

WPF绘图---Canvas中Polygon屏幕居中显示

问题描述 在一个Canvas中绘制了多个Polygon&#xff0c;由于坐标可能超出界面显示范围&#xff0c;需要将绘制的Polygon居中显示&#xff0c;并且缩放至界面大小&#xff0c;效果如下&#xff1a; xaml代码 <Borderx:Name"border"Background"#fff"Cli…

WebGL笔记:矩阵旋转运算的原理和实现

矩阵 矩阵&#xff08;Matrix&#xff09;是一个按照矩形纵横排列的复数集合 矩阵就像一个矩形的阵盘&#xff0c;通过其中纵横排列的元素我们可以摆出不同功能的阵法&#xff0c;比如位移矩阵、旋转矩阵、缩放矩阵 …在矩阵中的每一行&#xff0c;或者每一列数字构成的集合&a…

每日一练2023.12.2——正整数A+B【PTA】

题目链接&#xff1a;L1-025 正整数AB 题目要求&#xff1a; 题的目标很简单&#xff0c;就是求两个正整数A和B的和&#xff0c;其中A和B都在区间[1,1000]。稍微有点麻烦的是&#xff0c;输入并不保证是两个正整数。 输入格式&#xff1a; 输入在一行给出A和B&#xff0c;…