理解端口号

理解源ip地址与目的IP地址

我们知道ip地址,在网络中可以用来标识主机的唯一性,也就是说我知道你的ip地址,我也就能向你发送数据,但我发送给你的数据,最终是一定要交付给进程的,但交给你哪个进程呢?我应该知道吗?应该,所以就有了端口号,端口号是用来标识主机中的唯一进程的,所以我们在网络中传输数据就只需要拿着目的ip地址和对应进程端口号即可把数据发送到指定进程,所以网络通信又被称为进程间通信,只不过这里是通过网络进行不同主机间通信,不是同一台主机的进程间进行通信。
在这里插入图片描述

认识端口号

端口号(port)是传输层协议的内容。

  • 端口号是一个 2 字节 16 位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用。

端口号也是有着范围划分的:

  • 0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的
    端口号都是固定的.
  • 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作
    系统从这个范围分配的

理解端口号与pid关系

我们的进程不是都有进程pid吗,既然有了可以唯一标识一个进程的标志,那为什么我们还要再引入端口号的概念呢???其实是因为进程ID是内核级的,而端口号属于用户级。

  • 进程pid是用来标识正在运行中的进程,进程退出就找不到了。
  • 进程pid每次运行都是变化的,不能保证每次运行的pid都是一样的。
  • 进程 ID 属于系统概念, 技术上也具有唯一性, 确实可以用来标识唯一的一个进程, 但是这样做, 会让系统进程管理和网络强耦合, 实际设计的时候, 并没有选择这样做。

理解socket编程

由于ip+port能够唯一标识某台主机上的唯一一个进程,所以我们把这种ip + port起个名字叫套接字socket,我们只需要源主机的ip+port,目的主机的ip+port就能进行进程间通信了!

理解网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大小端之分,那么怎么定义网络中的数据流的地址呢?在网络中我们是采用的大端字节序存储的,假如A主机向B主机发送数据,A主机是小端存储,B主机是大端存储,A主机发送的数据到达B主机,如果我们不对传输的数据进行统一处理,B主机接收到的数据就可能会是乱码,为了避免这种情况,我们规定进入网络的数据都要转为网络字节序,而TCP/IP协议规定网络字节序是采用的大端存储,低字节放在高地址处。

为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运
行,可以调用以下库函数做网络字节序和主机字节序的转换。
在这里插入图片描述
这些函数名很好记,h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。

  • htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • htons表示将 16 位的短整数从主机字节序转换为网络字节序,例如将端口号地址转换后准备发送。
  • ntohl 表示将 32 位的长整数从网络字节序转换为主机字节序,例如将接收的IP地址转换后准备发送。
  • ntohs表示将 16 位的短整数从网络字节序转换为主机字节序,例如将接收的端口号地址转换后准备发送。

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套接字

使用socket创建一个套接字进行通信在这里插入图片描述

  • domain:地址簇,常见的右AF_INET(ipv4)和AF_INET6(ipv6)
  • type:套接字类型,常见的有SOCK_STREAM(TCP)和SOCK_DGRAM(UDP).
  • protocol:协议,通常设置为0,也可指定协议。
  • 返回值:成功返回一个套接字文件描述符,失败返回-1,error被设置。

bind绑定套接字

bind函数可以绑定一个ip地址和端口号
在这里插入图片描述

  • sockfd:套接字文件描述符
  • addr:指向一个sockaddr结构体的结构体指针,该结构体包含了ip地址与端口号等信息,对于IPv4定义在sockaddr_in结构体中,IPv6则在sockaddr_in中定义。
  • addrlen:该结构体对象的大小,通常用sizeof获取。
  • 返回值:成功返回0,错误返回-1,error被设置。

listen开始监听

listen函数用在tcp中用来监听客户端的连接,类似一个老板要不断的监管自己的公司。
在这里插入图片描述

  • sockfd:需要监听的套接字文件描述符
  • backlog:要求连接的最大数量

accept接收请求

accept是一个系统调用,用于在服务器端接受来自客户端的连接请求。
在这里插入图片描述

  • sockfd:套接字文件描述符,用来监听客户端连接请求的套接字,在tcp中可以理解为用来接收外部连接的套接字文件描述符,该函数会返回一个提供IO服务新的套接字文件描述符。
  • addr:输入输出型参数,获取发送方的IP地址端口号等各种信息。
  • addrlen:发放方sockaddr结构体的大小,调用时设置为结构体的总大小,返回时返回实际使用的大小。
  • 返回值是一个新的文件描述符,可以提供IO服务,这个新的这个文件描述符服务器端就可以与客户端进行通信。

connect建立连接

connect用于客户端程序,将其套接字连接到服务器端的套接字,以便进行通信。
在这里插入图片描述

  • sockfd:表示要建立连接的套接字描述符。
  • addr:表示指向目标服务器端套接字的地址结构体的指针。在IPv4中,该结构体类型为struct sockaddr_in,而在IPv6中,该结构体类型为struct sockaddr_in6。
  • addrlen:表示目标服务器端地址结构体的长度。
  • 返回时connect连接成功后会进行bind,返回成功,返回0,失败为-1,错误码被设置

recvfrom接收数据

recvfrom()函数的一个常见用法是在UDP套接字上接收数据报。在这种情况下,可以使用它来接收来自任意源地址的UDP数据报,并获取发送方的地址,以便进行后续处理。
在这里插入图片描述

  • sockfd:指定要接收数据的套接字
  • buf:指定接收数据的缓冲区
  • len:指定要接收数据缓冲区的最大长度
  • flags:指定接收数据的方式(一般设置为0)
  • src_addr:输入输出型参数,获取发送方的IP地址端口号等各种信息。
  • addrlen:发放方sockaddr结构体的大小,调用时设置为结构体的总大小,返回时返回实际使用的大小。

sendto发送数据

sendto函数将数据报发送到指定的目的地址。
在这里插入图片描述

  • sockfd:指定要发送数据的套接字文件描述符。
  • buf:指定发送数据的缓冲区。
  • len:指定要发送数据的长度。
  • flags:指定发送数据的方式(一般设置为0)
  • dest_addr:输入输出型参数,指向目的地址的的结构体指针,可以是IP地址和端口号。
  • addrlen:目的地址sockaddr结构体的大小。
  • 成功则返回发送的字节数,失败返回-1,error被设置。

sockaddr结构

由于各种网络协议的格式并不相同,所以就有了sockaddr结构来,在使用sockaddr结构时底层上派生了其他相对应的具体结构,sockaddr只是作为基类来使用,例如就派生的结构体有:struct sockaddr_in,struct sockaddr_un。这其实是利用了多态的思想实现的!!!
在这里插入图片描述

  • 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 结构体指针做为参数。

sockaddr底层结构

struct sockaddr
{
    __SOCKADDR_COMMON (sa_);	/* Common data: address family and length.  */
    char sa_data[14];		/* Address data.  */
};

sockaddr_in底层结构

/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;			/* Port number.  */
    struct in_addr sin_addr;		/* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr)
			   - __SOCKADDR_COMMON_SIZE
			   - sizeof (in_port_t)
			   - sizeof (struct in_addr)];
};

in_addr结构

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
{
    in_addr_t s_addr;
};

in_addr 用来表示一个 IPv4 的 IP 地址. 其实就是一个 32 位的整数。

网络命令

Ping命令

ping www.baidu.com可以查看自己的网络是否正常运行
在这里插入图片描述
ping -c 5 www.baidu.com可以查看5条后停止
在这里插入图片描述

netstat

netstat用来查看网络状态

  • n 拒绝显示别名, 能显示数字的全部转化成数字
  • l 仅列出有在 Listen (监听) 的服務状态
  • p 显示建立相关链接的程序名
  • t (tcp)仅显示 tcp 相关选项
  • u (udp)仅显示 udp 相关选项
  • a (all)显示所有选项, 默认不显示 LISTEN 相关

可以查看自己启动的网络服务状态
在这里插入图片描述
普通用户默认是看不到别的pid等信息的,只有超级用户全部都能看到。

watch

watch -n 1 netstat -uap 每隔一秒执行一次netstat -uap命令
在这里插入图片描述

pidof

pidof用来查看服务器的进程id时非常方便

pidof + 进程名
在这里插入图片描述
pidof UdpServerMain | xargs kill -9 xargs让我们从标准输入中读转为在从命令行参数中去读
在这里插入图片描述

地址转换函数

inet_ntoa

在这里插入图片描述
inet_ntoa可以把四字节地址转换为字符串类型。
但是在多线程使用inet_ntoa时可能不是线程安全的(因为这个函数转换时会自己维护一个类似缓冲区的地方,类似文件fopen的返回值),一般我们不会使用它,而使用inet_ntop。

inet_ntop

在这里插入图片描述
inet_ntop可以把四字节地址转换为字符串类型。
但是这个缓冲区可以由我们自己来维护了,更加方便。

  • af:网络协议,一般传AF_INET
  • src:传入四字节地址
  • dst:目的缓冲区
  • size:缓冲区大小

inet_pton

在这里插入图片描述
inet_pton是把字符串类型转换为四字节ip地址

  • af:网络协议,一般传AF_INET
  • src:传入缓冲区地址
  • dst:转换的目的位置

这里的p:process, n: net

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部