做网站 负责 域名备案,网站推广营销案例,自己做淘宝优惠券网站,企业网站建设视频目录
理解源IP地址和目的IP地址
认识端口号
端口号和进程ID的区别
源端口号和目的端口号
认识TCP和UDP协议
TCP协议
UDP协议
网络字节序
socket编程接口
socket常见API
sockaddr结构
简单的UDP网络程序
UDP服务端
创建套接字
填充本地网络信息
绑定
收取消息 …目录
理解源IP地址和目的IP地址
认识端口号
端口号和进程ID的区别
源端口号和目的端口号
认识TCP和UDP协议
TCP协议
UDP协议
网络字节序
socket编程接口
socket常见API
sockaddr结构
简单的UDP网络程序
UDP服务端
创建套接字
填充本地网络信息
绑定
收取消息
完整的服务端封装代码
UDP客户端
创建套接字
填充服务端信息
操作系统自动绑定
发送消息 客户端完整代码
一些补充
本地回环测试
netstat指令 理解源IP地址和目的IP地址 源IP地址Source IP Address源IP地址是指发送数据包的设备或主机的IP地址。它是数据包的来源地址标识了数据包从哪个设备发送出来。当你发送数据到网络上的其他设备时你的设备会将数据包标记上源IP地址以便接收设备知道数据来自哪里。 目的IP地址Destination IP Address目的IP地址是指接收数据包的设备或主机的IP地址。它是数据包的目标地址标识了数据包应该被发送到哪个设备。当你发送数据到网络上的另一个设备时你的设备会将数据包标记上目的IP地址以便网络路由器和接收设备知道将数据包传送到哪里。 认识端口号 端口号(port)是传输层协议的内容.端口号是一个2字节16位的整数;端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;IP地址 端口号能够标识网络上的某一台主机的某一个进程;一个端口号只能被一个进程占用. 注上篇文章我们说了两台主机可以通过网络进行通信在准确一点就是两台主机中的进程通过网络进行通信就像我们可以在QQ给自己的基友发消息通过网络你的基友机会在自己主机的QQ上收到你的消息两台主机上的两个QQ进程就通过网络这同一份资源进行远距离通信。
今天我们是通过网络对于双方而言 数据首先要到达目标主机IP找到指定的进程IP地址的是用来表示互联网中唯一的主机端口号用来标识该指定的机器中进程的唯一性因此IP加端口号可以用来表示互联网中唯一的一个进程。IP加端口就是套接字socket 端口号和进程ID的区别
端口号和进程ID是两个不同层次、不同领域的标识符。端口号用于网络通信中标识不同的应用程序或服务而进程ID用于操作系统中标识不同的进程。在某些情况下可以将端口号和进程ID关联起来例如查看特定端口上运行的进程但它们仍然是不同的概念。 注一个进程可以有多个端口号但是一个端口号只能代表一个进程 源端口号和目的端口号 源端口号Source Port Number源端口号是发送数据包的设备或主机上的应用程序或服务使用的端口号。在建立连接时发送端的应用程序会随机选择一个空闲端口作为源端口号并将其包含在发送的数据包中。源端口号帮助目标设备知道从哪个端口接收到了数据包以便回复响应。 目的端口号Destination Port Number目的端口号是接收数据包的设备或主机上的应用程序或服务期望接收数据包的端口号。在发送数据包时发送端会指定目标设备的IP地址和目标端口号。接收设备根据目的端口号确定将数据包传递给哪个应用程序或服务。
其实我们可以简单理解为就是在描述 数据是谁发的, 要发给谁; 认识TCP和UDP协议
我们先对TCP(Transmission Control Protocol 传输控制协议)、UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面的文章我们再详细讨论TCP、UDP的一些细节问题.
TCP协议 传输层协议有连接可靠传输面向字节流 UDP协议 传输层协议无连接不可靠传输面向数据报 网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢? 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可; 为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。 注简单来说就是通过网络必须传送的是大端序因此在传送之前不清楚自己主机是大端还是小端必须进行转换为大端序 socket编程接口
socket常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
网络编程的时候socket是有很多分类的 unix socket(域间套接字) :同一台机器上的文件路径类似于命名管道用于本主机内部进行通信网络socketipport用于网络通信原始socket跳过运输层直接访问网络层用于编写一些网络工具 sockaddr结构
网络编程的时候有不同的应用场景理论上而言我们应该给每一种场景都设计一套编程接口但是设计者想使用一套接口因此sockaddr是一个通用的接口
sockaddr就相当与基类当传入不同的类型是判断每个结构体的前两个字节进行类型的强转换。 IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址.
IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容. socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;
简单的UDP网络程序
UDP服务端
初始化服务器
创建套接字
int socket(int domain, int type, int protocol);返回值 socket 函数的返回值代表了新创建的套接字的文件描述符如果创建失败则返回 -1。在成功创建套接字后socket 函数会返回一个非负整数表示新创建的套接字的文件描述符。 参数
domain地址族
类型int意义指定套接字的地址族即通信所采用的协议族。常见取值 AF_INETIPv4 地址族。AF_INET6IPv6 地址族。AF_UNIX 或 AF_LOCALUnix 域本地套接字。
上面提到有三种套接字的分类我们是网络套接字并且只介绍IPv4因此选择第一个取值
type套接字类型
类型int意义指定套接字的类型即套接字的通信模式。常见取值 SOCK_STREAM流套接字提供可靠的、面向连接的、基于字节流的服务使用 TCP 协议。SOCK_DGRAM数据报套接字提供不可靠的、无连接的、基于数据报的服务使用 UDP 协议。SOCK_RAW原始套接字允许对底层协议进行直接访问常用于网络监控和特殊应用。 编写的是UDP服务器因此选择第二个参数
protocol协议
类型int意义指定套接字所使用的协议通常为 0表示使用默认的协议。对于 SOCK_STREAM 和 SOCK_DGRAM 类型的套接字协议通常可以省略因为它们分别与 TCP 和 UDP 相关联。对于 SOCK_RAW 类型的套接字可以指定底层的协议如 IP、ICMP 等。
一般为0
填充本地网络信息
因为我们是网络套接字因此需要一个struct sockaddr_in的结构体将我们的IP地址、端口号和套接字类型填充到结构体中
注 创建好结构体后需要对结构体里的内容清零填充端口号时我们要将我们的主机序列转为网络序列填充IP地址时我我们要将原始的点分十进制的字符串转换为四字节的网络序列我们不可以使用我们的服务器的IP地址作为我们程序的IP地址下一步绑死后只能收到该IP发送的报文。我更推荐使用任意IP地址INADDR_ANY 是一个特殊的 IP 地址在网络编程中经常用于绑定套接字到本地计算机的所有网络接口上。具体来说INADDR_ANY 表示接受任何来自本地计算机所有网络接口包括所有网卡的数据包。这在服务器编程中非常有用因为服务器通常需要监听来自所有网络接口的连接请求。 绑定
上篇文章我们提到传输层作用于内核中我们上一步填充的各种信息只存在于栈中因此我们需要告诉操作系统内核某个特定的套接字对应于网络上的某个地址。这个过程就是绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);返回值
bind 函数的返回值代表了函数执行的结果它通常有以下两种可能 如果绑定成功bind 函数返回值为 0。这表示套接字已成功绑定到指定的地址和端口上。 如果绑定失败bind 函数返回值为 -1并且会设置相应的错误码以指示失败的原因。这种情况可能 参数
套接字描述符
类型int整数意义要绑定的套接字的文件描述符。在调用绑定函数时需要传递已经创建好的套接字的文件描述符。
地址结构指针
类型const struct sockaddr *意义指向存储目标地址信息的结构体的指针。绑定函数需要知道要绑定的目标地址和端口号。通常使用的是 sockaddr 结构体或其派生结构体如 sockaddr_inIPv4 地址或 sockaddr_in6IPv6 地址等。使用 const 关键字修饰指针表示绑定函数不会修改该地址结构。
地址结构的长度
类型socklen_t整数意义地址结构的长度。用于指定地址结构的实际长度以确保绑定函数能够正确地解析地址结构。
启动服务器
收取消息
启动服务器就是启动一个程序且程序没收到特定的指令不退出即就是一个死循环。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);recvfrom 函数用于从指定的套接字接收数据并将数据存储到指定的缓冲区中同时还可以获取数据发送方的地址信息。这个函数通常在使用 UDP 协议进行通信时使用因为 UDP 是面向数据报的每个数据包都有自己的源地址和目标地址。
返回值 recvfrom 函数的返回值是接收到的数据的字节数即实际读取到缓冲区中的数据量。如果发生错误返回值为 -1。 参数
套接字描述符
类型int整数意义要接收数据的套接字的文件描述符。在调用 recvfrom 函数时需要传递已经创建好的套接字的文件描述符。
缓冲区指针
类型void *意义指向存储接收数据的缓冲区的指针。接收到的数据将被存储到这个缓冲区中。
缓冲区大小
类型size_t无符号整数意义缓冲区的大小即接收数据的最大长度。在调用 recvfrom 函数之前应该确保缓冲区足够大以容纳接收到的数据。
标志
类型int整数意义用于指定接收数据的选项。可以设置为 0表示没有特殊的选项。
发送方地址结构指针
类型struct sockaddr *意义用于存储发送方的地址信息的结构体指针。如果不需要获取发送方的地址信息可以将这个参数设置为 NULL。
发送方地址结构的长度指针
类型socklen_t *意义发送方地址结构的长度。在调用 recvfrom 函数之前需要将这个参数设置为一个指向长度变量的指针用于接收实际的地址结构长度。
完整的服务端封装代码
Udpserver.hpp
#pragma once
#includeiostream
#includestring
#includesys/types.h
#includesys/socket.h
#includecerrno
#includeunistd.h
#includecstring
#includenetinet/in.h
#includearpa/inet.h
#includestrings.h
using namespace std;
static const uint16_t defaultport 8888;
static const int sockfd-1;
static const int size1024;
class UdpServer
{
public:UdpServer(uint16_t portdefaultport):_port(port),_sockfd(sockfd){}//初始化服务器void Init(){//创建套接字_sockfd socket(AF_INET,SOCK_DGRAM,0);if(_sockfd0){//表示创建失败coutFatal Errorerrnostrerror(errno)endl;exit(2);}else{coutsocket success sockfd : _sockfdendl;}//绑定套接字struct sockaddr_in local;//初始化local //指定的一块内存清零bzero(local,sizeof(local));//填充//进行网络通信local.sin_family AF_INET;//端口号//转为网络序列local.sin_port htons(_port);//1.4字节点分十进制字符串 2. 转为网络序列//local.sin_addr.s_addr inet_addr(_ip.c_str());//使用地址任意//实现IP动态绑定local.sin_addr.s_addr INADDR_ANY;//到此还没有设置到内核中只存在于栈//绑定//强制类型转换int n ::bind(_sockfd,(struct sockaddr*)local,sizeof(local));if(n!0){coutFatal Error , bind error errno strerror(errno)endl;exit(3);}}//启动服务器//服务器永远不退出//是一个死循环void Start(){//收发消息//返回值实际收到的消息//第一个参数为文件描述符//第二个//第三个为期望//第四个参数为收数据的模式通常设置为0 阻塞式//最后两个参数为输出型参数//保存我们客户端的信息 ip portchar buffer[size];while(1){//预留一个\0struct sockaddr_in peer;socklen_t len sizeof(peer);ssize_t n recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)peer,len);if(n0){//拿到客户端的信息//网络序列转主机序列uint16_t clientport ntohs(peer.sin_port);string clientip inet_ntoa(peer.sin_addr);string info clientip;info:;infostd::to_string(clientport);buffer[n]0;cout[info]bufferendl;//返回消息sendto(_sockfd,buffer,n,0,(struct sockaddr*)peer,len);}}}~UdpServer(){if(_sockfd!-1){close(_sockfd);}}
private:uint16_t _port;//端口号int _sockfd;
}; Main.cc
#includeiostream
#includememory
#includestring
#includeUdpserver.hpp
using namespace std;
enum comm
{Usage_Err1
};
void Usage(string proc)
{coutusage: \n\tproclocak_portendl;
}
int main(int argc , char * argv[])
{//告诉如何使用if(argc!2){Usage(argv[0]);return Usage_Err;}// string ip argv[1];uint16_t port stoi(argv[1]);UdpServer* usvr new UdpServer(port);usvr-Init();usvr-Start();delete usvr;return 0;
}
UDP客户端
作为客户端我们一定知道服务端的IP和端口号作为服务端IP和端口号是不可能随便改变的
创建套接字
和服务端类似
填充服务端信息
和服务端类似
操作系统自动绑定
客户端不需要显示使用bind函数进行绑定而是操作系统自动隐式绑定因为如果客户端能够绑定特定的端口号那么可能会导致端口冲突的问题。假设多个客户端应用都试图绑定到相同的固定端口号上那么在同一台计算机上运行这些应用时就会发生冲突。为了避免这种情况操作系统会自动为客户端分配一个可用的临时端口。
发送消息
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);返回值 如果成功sendto 函数返回发送的字节数。这通常与 len 参数相同但也可能更少例如如果套接字是非阻塞的并且没有足够的缓冲区空间来容纳整个消息。如果发生错误sendto 函数返回 -1并设置全局变量 errno 以指示错误类型。可能的错误包括 ECONNREFUSED连接被拒绝、EHOSTUNREACH主机不可达、EMSGSIZE消息太大、ENOBUFS没有可用的缓冲区空间等。 参数
sockfd这是一个打开的 socket 文件描述符用于标识一个打开的 socket。
buf这是一个指向要发送数据的缓冲区的指针。
len这是要发送的数据的字节数。
flags这是可选的标志参数可以用来指定不同的操作行为。常见的标志包括
MSG_CONFIRM告知底层传输层协议确认数据。MSG_DONTROUTE告知底层传输层协议不要路由数据。MSG_EOR表示发送的数据是一个消息的末尾。等等。具体的标志因操作系统和网络协议栈的不同而有所不同。
dest_addr这是一个指向目标地址信息的结构体的指针。它是一个 sockaddr 结构体可以是 sockaddr_in 或 sockaddr_in6 结构体具体取决于使用的网络协议版本。
addrlen这是目标地址结构体的大小以字节为单位。通常情况下它可以通过 sizeof(struct sockaddr) 来获取。 客户端完整代码
Client.cc
#include iostream
#include sys/types.h
#include sys/socket.h
#include cerrno
#include string.h
#include string
#include unistd.h
#includearpa/inet.h
#includenetinet/in.husing namespace std;
void Usage(const string process)
{coutUsage : process server_ip server_portendl;
}
int main(int argc, char*argv[])
{if (argc ! 3){Usage(argv[0]);return 1;}string serverip argv[1];uint16_t serverport stoi(argv[2]);int sock socket(AF_INET, SOCK_DGRAM, 0);if (sock 0){cout socket error : strerror(errno) endl;return 1;}cout socket success! endl;// 作为客户端必须知道服务器的ip和端口号// 作为客户端一定要进行绑定但是不是显示绑定客户端会在首次发送数据的时候进行自动绑定// 服务端端口号一定是总所周知的不可改变的// 客户端需要prot绑定随机端口// why?// 因为客户端非常的多// 绑定确定的端口号可能会在某一时刻启动失败// 让本地操作系统自动随机绑定随机选择端口struct sockaddr_in server;memset(server,0,sizeof(server));server.sin_family AF_INET;server.sin_port htons(serverport);server.sin_addr.s_addr inet_addr(serverip.c_str());// 发消息while (1){//我们要发的数据string inbuffer;cout plase Entre#: ;std::getline(std::cin, inbuffer);//发给谁ssize_t n sendto(sock,inbuffer.c_str(),inbuffer.size(),0,(struct sockaddr*)server,sizeof(server));if(n0) {//收消息//UDP支持全双工通信char buffer[1024];struct sockaddr_in temp;socklen_t len sizeof(temp);ssize_t m recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)temp,len);if(m0){buffer[m]\0;coutserver echo# bufferendl;}else{break;}}else {break;}}//套接字类型为文件描述符//使用结束后需要关闭close(sock);return 0;
} 一些补充
本地回环测试
我们可以让封装的服务端带上IP地址使用127.0.0.1这个IP地址在本地的一台服务器上开上两个窗口启动服务端和客户端进行本地回环测试检查代码和程序上面的代码可以做到一些简单的交互联动。
netstat指令
netstat 命令你可以查看本地计算机上的网络连接、路由表、网络接口统计信息等
常用选项
-a显示所有连接和监听端口。-t显示 TCP 协议的连接。-u显示 UDP 协议的连接。-l显示监听状态的连接。-n以数字形式显示地址和端口。-p显示与连接相关的进程 ID。-r显示路由表。-i显示网络接口的统计信息。-s显示网络协议的统计信息。 今天对网络套接字的分享到这就结束了希望大家读完后有很大的收获也可以在评论区点评文章中的内容和分享自己的看法个人主页还有很多精彩的内容。您三连的支持就是我前进的动力感谢大家的支持!