一、理解源IP地址和目的IP地址
因特网上的每台计算机都有一个唯一的 IP 地址,如果一台主机上的数据要传输到另一台主机,那么对端主机的 IP 地址就应该作为该数据传输时的目的 IP 地址。但仅仅知道目的 IP 地址是不够的,当对端主机收到该数据后,对端主机还需要对该主机做出响应,因此对端主机也需要发送数据给该主机,此时对端主机就必须知道该主机的 IP 地址。因此一个传输的数据当中应该涵盖其源IP地址 和 目的IP地址,目的 IP 地址表明该数据传输的目的地,源 IP 地址作为对端主机响应时的目的 IP 地址。
在数据进行传输之前,会先自顶向下贯穿网络协议栈完成数据的封装,其中在网络层封装的 IP 报头当中就涵盖了源 IP 地址和目的 IP 地址。而除了源 IP 地址和目的 IP 地址之外,还有源 MAC 地址和目的 MAC 地址的概念。
二、socket通信的本质
首先我们需要明确的是,两台主机之间通信的目的不仅仅是为了将数据发送给对端主机,而是为了访问对端主机上的某个服务。比如我们在用百度搜索引擎进行搜索时,不仅仅是想将我们的请求发送给对端服务器,而是想访问对端服务器上部署的百度相关的搜索服务。
现在通过 IP 地址和 MAC 地址已经能够将数据发送到对端主机了,但实际我们是想将数据发送给对端主机上的某个服务进程,此外,数据的发送者也不是主机,而是主机上的某个进程,比如当我们用浏览器访问数据时,实际就是浏览器进程向对端服务进程发起的请求。

也就是说,socket 通信本质上就是两个进程之间在进行通信,只不过这里是跨网络的进程间通信。比如逛淘宝和刷抖音的动作,实际就是手机上的淘宝进程和抖音进程在和对端服务器主机上的淘宝服务进程和抖音服务进程之间在进行通信。
因此进程间通信的方式除了管道、消息队列、信号量、共享内存等方式外,还有套接字,只不过前者是不跨网络的,而后者是跨网络的。
三、端口号
实际在两台主机上,可能会同时存在多个正在进行跨网络通信的进程,因此当数据到达对端主机后,必须要通过某种方法找到该主机上对应的服务进程,然后将数据交给该进程处理。而当该进程处理完数据后还要对发送端进行响应,因此对端主机也需要知道,是发送端上的哪一个进程向它发送的数据请求。
- 端口号(
port)的作用实际就是标识一台主机上的一个进程。告诉操作系统,当前的这个数据要交给哪一个进程来处理。 - 端口号是传输层协议的内容。
- 端口号是一个
2 byte,也就是16位的整数。 - 在一台主机中,一个端口号只能被一个进程占用。
- 一个进程可以绑定多个端口号,但是一个端口号不能被多个进程同时绑定。
- 由于
IP地址能够唯一标识公网内的一台主机,而端口号能够唯一标识一台主机上的一个进程,因此用IP地址 + 端口号 就能够唯一标识网络上的某一台主机的某一个进程。
当数据在传输层进行封装时,就会添加上对应源端口号和目的端口号的信息。这时通过 源IP地址 + 源端口号 就能够在网络上唯一标识发送数据的进程,通过目的 IP地址 + 目的端口号 就能够在网络上唯一标识接收数据的进程,此时就实现了跨网络的进程间通信。
💥注意: 因为端口号是隶属于某台主机的,所以端口号可以在两台不同的主机当中重复,但是在同一台主机上进行网络通信的进程的端口号不能重复。
四、TCP和UDP的区别
- UDP:传输层协议、无连接、不可靠传输、面向数据报
- TCP:传输层协议、有连接、可靠传输、面向字节流
Ⅰ. 网络字节序
一、网络中的大小端问题
计算机在存储数据时是有大小端的概念的:
- 大端模式:数据的高字节内容保存在内存的低地址处,数据的低字节内容保存在内存的高地址处。
- 小端模式:数据的高字节内容保存在内存的高地址处,数据的低字节内容保存在内存的低地址处。
如果编写的程序只在本地机器上运行,那么是不需要考虑大小端问题的,因为同一台机器上的数据采用的存储方式都是一样的,要么采用的都是大端存储模式,要么采用的都是小端存储模式。但如果涉及网络通信,那就必须考虑大小端的问题,否则对端主机识别出来的数据可能与发送端想要发送的数据是不一致的。
如下图,现在两台主机之间在进行网络通信,其中发送端是小端机,而接收端是大端机。发送端将发送缓冲区中的数据按内存地址从低到高的顺序发出后,接收端从网络中获取数据依次保存在接收缓冲区时,也是按内存地址从低到高的顺序保存的。

但由于发送端和接收端采用的分别是小端存储和大端存储,此时对于内存地址从低到高为 44332211 的序列,发送端按小端的方式识别出来是 0x11223344,而接收端按大端的方式识别出来是 0x44332211,此时接收端识别到的数据与发送端原本想要发送的数据就不一样了,这就是由于大小端的偏差导致数据识别出现了错误。
由于我们不能保证通信双方存储数据的方式是一样的,因此网络当中传输的数据必须考虑大小端问题。因此 TCP/IP 协议规定,网络数据流采用大端字节序,即低地址高字节。无论是大端机还是小端机,都必须按照 TCP/IP 协议规定的网络字节序来发送和接收数据。
- 如果发送端是小端,需要先将数据转成大端,然后再发送到网络当中。
- 如果发送端是大端,则可以直接进行发送。
- 如果接收端是小端,需要先将接收到数据转成小端后再进行数据识别。
- 如果接收端是大端,则可以直接进行数据识别。
在这个例子中,由于发送端是小端机,因此在发送数据前需要先将数据转成大端,然后再发送到网络当中,而由于接收端是大端机,因此接收端接收到数据后可以直接进行数据识别,此时接收端识别出来的数据就与发送端原本想要发送的数据相同了。

需要注意的是,这些大小端的转化工作是由操作系统来完成的,因为该操作属于通信细节,不过也有部分的信息需要我们自行进行处理,比如端口号和IP地址。具体的接口函数会在下面讲到!
二、为什么网络字节序采用的是大端,而不是小端❓❓❓
网络字节序采用的是大端,而主机字节序一般采用的是小端,那为什么网络字节序不采用小端呢?如果网络字节序采用小端的话,发送端和接收端在发生和接收数据时就不用进行大小端的转换了。
该问题有很多不同说法,下面列举了两种说法:
- 说法一:
TCP在Unix时代就有了,以前Unix机器都是大端机,因此网络字节序也就采用的是大端,但之后人们发现用小端能简化硬件设计,所以现在主流的都是小端机,但协议已经不好改了。 - 说法二: 大端序更符合现代人的读写习惯。
Ⅱ. 网络编程常用接口
一、套接字常用 API
这里简单的列出来,下面会具体讲解它们!
#include <sys/types.h>
#include <sys/socket.h>
// 创建套接字(TCP/UDP,客户端+服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号(TCP/UDP,服务器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 监听套接字(TCP,服务器)
int listen(int sockfd, int backlog);
// 接收请求(TCP,服务器)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 建立TCP连接(TCP,客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 发送信息
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
// 接收信息
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
#include <unistd.h>
int close(int fd); // 用于关闭套接字文件 其实我们可以看到参数中有一个结构体 struct sockaddr,这是什么呢,请看下面!
💥收发数据接口的一些区别
这里提一嘴收发数据函数中的一些区别,这里以发送函数为例,也就是
send()函数和sendto()函数的区别,它们都是用于通信之间传递数据的,但是它们的细节不太一样,接收数据函数也是一样的!
参数数量:
send()函数接受的参数比sendto()函数少。具体而言,send()只需传入套接字描述符、数据缓冲区和数据长度三个参数。而sendto()则额外需要指定目标地址和目标端口等参数。使用范围:
send()函数适用于连接类型的套接字,如TCP套接字。它可以在已经连接的套接字上发送数据,系统会根据连接状态选择传输的路径。目标地址:
sendto()函数适用于无连接类型的套接字,如UDP套接字。它需要指定数据报的目标地址和目标端口,每次发送数据时都可以指定不同的目标地址。阻塞行为:在默认情况下,
send()和sendto()都是阻塞的函数。调用这两个函数时,如果发送缓冲区没有足够的空间,则函数会阻塞等待直到能够发送数据。可以使用设置非阻塞模式或设置超时时间的方式来改变其行为。返回值:两个函数的返回值含义略有区别。
send()函数返回实际发送的字节数量,如果发送失败则返回-1。而sendto()函数返回实际发送的字节数量或者发送错误码,也可能返回-1。 另外还有一个
sendmsg()函数也是用于发送数据的,但是它比上面两个函数更加的灵活,它提供了更强大的灵活性和功能。函数原型如下:ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
sockfd是套接字的文件描述符。
msg是指向msghdr结构的指针,用于指定发送的数据和其他控制信息。
flags是可选的标志参数,用于控制发送操作的行为。 其中
sendmsg()函数通过指定msghdr结构来发送数据和其他控制信息。msghdr结构包含以下成员:struct msghdr { void* msg_name; // 指向目标地址的指针 socklen_t msg_namelen; // 目标地址的长度 struct iovec* msg_iov; // 待发送的数据的缓冲区 iovec 结构数组 int msg_iovlen; // 缓冲区的数量 void* msg_control; // 控制消息的缓冲区 socklen_t msg_controllen; // 控制消息的长度 int msg_flags; // 接收到的标志 }; /* 分散/聚集I/O的结构 */ struct iovec { void *iov_base; // Pointer to data. size_t iov_len; // Length of data. };使用
sendmsg()函数可以实现以下功能:
- 可以发送多个数据块,每个数据块可以有不同的长度。这些数据块被封装在
iovec结构的数组中。- 可以发送控制信息,如
IP头部选项。- 可以指定目标地址。
综上所述,
sendmsg()函数在发送数据方面提供了更多的灵活性和功能,适用于更复杂的网络编程需求。
💥write() 和 send() 接口的区别
send()和write()都可以用于将数据从一个文件描述符发送到另一个文件描述符,但它们之间存在一些区别。这种区别其实对于read()和recv()函数来说也是一样的,这里只举前者的区别。下面是区别:
- 参数数量:
send()函数需要指定套接字描述符、数据缓冲区、数据长度和标志等参数。而write()函数只需指定文件描述符、数据缓冲区和数据长度三个参数。- 返回值:
send()函数在成功发送数据后会返回实际发送的字节数量,如果发送失败则返回-1,并设置相应的错误码。而write()函数在成功写入数据后会返回实际写入的字节数量,如果写入失败则返回-1,并设置相应的错误码。- 使用范围:
send()函数适用于套接字操作,主要用于网络编程中。而write()函数适用于文件描述符操作,可用于读写文件或管道等。- 错误处理:
send()函数在发送数据时可以使用MSG_NOSIGNAL标志来防止发送时触发SIGPIPE信号,这在网络编程中很常见。而write()函数在写入失败时会默认触发SIGPIPE信号,如果不希望触发该信号可以使用signal(SIGPIPE, SIG_IGN)来忽略该信号。- 堵塞行为:默认情况下,
send()和write()都是堵塞调用。但是,可以通过将套接字或文件描述符设置为非堵塞模式,或者使用超时控制,来改变其行为。 总的来说,
send()函数是专门用于套接字操作的,它提供了更多的灵活性和功能,适用于网络编程。write()函数则是更通用的写入操作,适用于各种文件描述符操作。
二、sockaddr结构
sockaddr 结构的出现
套接字不仅支持跨网络的进程间通信,还支持本地的进程间通信(域间套接字)。在进行跨网络通信时我们需要传递的端口号和 IP 地址,而本地通信则不需要,因此套接字提供了 sockaddr_in 结构体和 sockaddr_un 结构体,其中 sockaddr_in 结构体是用于跨网络通信的,而 sockaddr_un 结构体是用于本地通信的。
为了让套接字的网络通信和本地通信能够使用同一套函数接口,于是就出现了 sockeaddr 结构体,该结构体与 sockaddr_in 和 sockaddr_un 的结构都不相同,但这三个结构体头部的 16 位都是一样的,这个字段叫做协议家族。

当我们在传递在传参时,就不用传入 sockeaddr_in 或 sockeaddr_un 这样的结构体,而统一传入 sockeaddr 这样的结构体。在设置参数时就可以通过设置协议家族这个字段,来表明我们是要进行网络通信还是本地通信,在这些 API 内部就可以提取 sockeaddr 结构头部的 16 位进行识别,进而得出我们是要进行网络通信还是本地通信,然后执行对应的操作。此时我们就通过统一的 sockaddr 结构,将套接字网络通信和本地通信的参数类型进行了统一。
💥注意: 实际我们在进行网络通信时,定义的还是 sockaddr_in 这样的结构体,只不过在传参时需要将该结构体的地址类型进行强转为 sockaddr* 罢了。
sockaddr结构体如下所示:
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
}; sockaddr_in结构体:虽然 socket api 的接口是 sockaddr, 但是我们真正在基于 IPv4 编程时, 使用的数据结构是 sockaddr_in。
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* 端口号,in_port_t是uint16_t类型 */
struct in_addr sin_addr; /* IP地址 */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
}; 其中 IP 地址的类型是 in_addr 结构体类型:
/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};为什么会有这么多本地进程间通信的方式?
本地进程间通信的方式已经有管道、消息队列、共享内存、信号量等方式了,现在在套接字这里又出现了可以用于本地进程间通信的域间套接字,为什么会有这么多通信方式,并且这些通信方式好像并不相关❓❓❓
实际是因为早期有很多不同的实验室都在研究通信的方式,由于是不同的实验室,因此就出现了很多不同的通信方式,比如常见的有 System V 标准的通信方式和 POSIX 标准的通信方式。
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结构体指针做为参数。
为什么没有用void*代替struct sockaddr*类型?
我们可以将这些函数的 struct sockaddr* 参数类型改为 void*,此时在函数内部也可以直接指定提取头部的16个比特位进行识别,最终也能够判断是需要进行网络通信还是本地通信,那为什么还要设计出 sockaddr 这样的结构呢❓❓❓
实际在设计这一套网络接口的时候C语言还不支持 void*,于是就设计出了 sockaddr 这样的解决方案。并且在C语言支持了 void* 之后也没有将它改回来,因为这些接口是系统接口,系统接口是所有上层软件接口的基石,系统接口是不能轻易更改的,否则引发的后果是不可想的,这也就是为什么现在依旧保留 sockaddr 结构的原因。
三、ip地址转换常用函数
// 头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>- int inet_aton(const char *cp, struct in_addr *inp);
- 将点分十进制字符串形式的
IPv4地址转换为 网络字节序的32位IPv4地址,并存储在struct in_addr *inp指向的结构体中。成功返回1,失败返回0。
- 将点分十进制字符串形式的
- in_addr_t inet_addr(const char *cp);
- 将点分十进制字符串形式的
IPv4地址转换为 网络字节序的32位IPv4地址(in_addr_t类型)。如果转换失败,返回INADDR_NONE。
- 将点分十进制字符串形式的
- in_addr_t inet_network(const char *cp);
- 将点分十进制字符串形式的
IPv4地址转换为 主机字节序的32位IPv4地址(in_addr_t类型)。可以使用函数ntohl()将其转换为网络字节序。
- 将点分十进制字符串形式的
char *inet_ntoa(struct in_addr in);- 将
32位IPv4地址转换为点分十进制字符串形式的IPv4地址。返回一个指向静态缓冲区的指针,请注意不要同时使用多个inet_ntoa函数,因为会覆盖掉上一次的结果!
- 将
struct in_addr inet_makeaddr(int net, int host);- 通过网络号和主机号,创建一个新的
struct in_addr结构体来表示一个IPv4地址。
- 通过网络号和主机号,创建一个新的
in_addr_t inet_lnaof(struct in_addr in);- 从给定的
32位IPv4地址中获取主机号,返回主机字节序的32位值。
- 从给定的
in_addr_t inet_netof(struct in_addr in);- 从给定的
32位IPv4地址中获取网络号,返回主机字节序的32位值。
- 从给定的
四、网络字节序与主机字节序之间的转换接口
为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,系统提供了四个函数,可以通过调用以下库函数实现网络字节序和主机字节序之间的转换:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);-
字母的含义:
h:表示主机hostn:表示networkl:表示32位长整数s:表示16位短整数
-
例如
htonl()函数表示将32位长整数从主机字节序转换为网络字节序。 -
如果主机是小端字节序,则这些函数将参数做相应的大小端转换然后返回。
-
如果主机是大端字节序,则这些函数不做任何转换,将参数原封不动地返回。
五、popen函数 -- pipe + fork + exec*
popen 函数是一个 C 标准库中的函数,用于创建一个管道,并启动一个子进程执行指定的 shell 命令。这个函数就相当于我们以前学的几个函数的一个封装了,在以前我们自己写 shell 指令的时候,其实可以直接用这个函数,但是因为当时要学那几个特定的函数,所以就没介绍和使用这个函数!但是平时我们都是直接使用这个函数的!
函数原型如下:
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);command参数是一个字符串,表示要执行的shell命令。type参数是一个字符串,指定管道的模式。可以是"r"(读取模式)或"w"(写入模式)。- 返回值是一个标准
I/O流FILE指针
需要注意的是,popen函数创建的子进程的输入/输出是一次性的,即只能进行一次数据传输,然后就会关闭管道。
后面我们会用该函数来实现一个简单的远程获取服务器终端操作信息的小实验!
六、bzero函数
bzero 函数是一个在 C 语言中用于将指定的内存区域清零的函数。该函数的原型如下:
#include <strings.h>
void bzero(void *s, size_t n); bzero 函数接受两个参数:第一个参数 s 是待清零的内存区域的起始地址,第二个参数 n 是要清零的字节数。函数会将从起始地址 s 开始的 n 个字节的内容都设置为零。
bzero 函数在很多平台上已经被废弃,推荐使用更安全的 memset 函数来替代。
💥七、关于inet_ntoa()函数
该这个函数返回了一个 char*,很显然是这个函数自己在内部为我们申请了一块内存来保存 ip 的结果。那么是否需要调用者手动释放呢❓❓❓
查一下 man 手册:
The inet_ntoa() function converts the Internet host address in, given in network byte order, to a string in IPv4 dotted-decimal notation. The string is returned in a statically allocated buffer, which subsequent calls will overwrite.
man 手册上说,inet_ntoa 函数,是把这个返回结果也就是字符串放到了静态存储区,这个时候不需要我们手动进行释放。
那么问题来了,如果我们调用多次这个函数,会有什么样的效果呢?参见如下代码:
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
struct sockaddr_in addr1;
addr1.sin_addr.s_addr = 0;
struct sockaddr_in addr2;
addr2.sin_addr.s_addr = 0xffffffff;
char* ptr1 = inet_ntoa(addr1.sin_addr);
char* ptr2 = inet_ntoa(addr2.sin_addr);
printf("ptr1: %s\nptr2: %s\n", ptr1, ptr2);
return 0;
}
// 执行结果:
[liren@VM-8-7-centos test]$ gcc test.c
[liren@VM-8-7-centos test]$ ./a.out
ptr1: 255.255.255.255
ptr2: 255.255.255.255 因为 inet_ntoa 把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果!所以说,在多线程环境下建议还是使用 inet_ntop 函数来代替!
其原型如下所示:
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);参数说明:
af:地址族(Address Family),可以是AF_INET(IPv4)或AF_INET6(IPv6)。src:指向存储二进制格式地址的指针。dst:指向存储转换后字符串格式地址的缓冲区的指针。size:缓冲区的大小。
函数返回值:
- 如果转换成功,返回指向转换后字符串格式地址的指针(即dst参数的值)。
- 如果转换失败,返回
NULL,并设置errno来指示错误的原因。
#include <arpa/inet.h>
#include <stdio.h>
int main()
{
struct in_addr addr;
inet_pton(AF_INET, "192.0.2.1", &addr);
char ip[INET_ADDRSTRLEN];
const char *result = inet_ntop(AF_INET, &addr, ip, INET_ADDRSTRLEN);
if (result != NULL)
printf("IPv4 address: %s\n", ip);
else
perror("inet_ntop");
return 0;
}
// 执行结果:
[liren@VM-8-7-centos test]$ gcc test.c
[liren@VM-8-7-centos test]$ ./a.out
IPv4 address: 192.0.2.1Ⅲ. 网络常用指令
一、netstat
netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Memberships) 等等。常见参数如下:
- -a(
all):显示所有选项,默认不显示LISTEN相关 - -t(
tcp):仅显示tcp相关选项 - -u(
udp):仅显示udp相关选项 - -l(
listen):仅列出有在Listen(监听) 的服务状态 - -p:显示正在使用
socket的程序识别码和程序名称 - -n:不显示别名,能显示数字的全部转化成数字
下面是拓展的参数选项,不太经常用到:
- -r:显示路由信息,路由表
- -e:显示扩展信息,例如
uid等 - -s:按各个协议进行统计
- -c:每隔一个固定时间,执行该
netstat命令
通常我们都会配合 grep 指令来筛选出我们想要看到的网络信息:
[liren@VM-8-7-centos udp]$ sudo netstat -unap
[sudo] password for liren:
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
udp 0 0 0.0.0.0:8080 0.0.0.0:* 31565/./udpServer
udp 0 0 0.0.0.0:68 0.0.0.0:* 1007/dhclient
udp 0 0 10.0.8.7:123 0.0.0.0:* 1803/ntpd
udp 0 0 127.0.0.1:123 0.0.0.0:* 1803/ntpd
udp6 0 0 fe80::5054:ff:fe52::123 :::* 1803/ntpd
udp6 0 0 ::1:123 :::* 1803/ntpd
[liren@VM-8-7-centos udp]$ netstat -unap | grep udpServer # 配合grep指令筛选端口号或者进程名
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
udp 0 0 0.0.0.0:8080 0.0.0.0:* 31565/./udpServer
[liren@VM-8-7-centos udp]$ 其中 netstat 命令显示的信息中,Proto 表示协议的类型,Recv-Q 表示网络接收队列,Send-Q 表示网络发送队列,Local Address 表示本地地址,Foreign Address 表示外部地址,State 表示当前的状态,PID 表示该进程的进程ID,Program name 表示该进程的程序名称。
💥提示:LISTEN 和 LISTENING 的状态只有用 -a 或者 -l 才能看到。
二、lsof
lsof 是一个查看打开文件和网络连接的命令行工具,用于显示当前系统中所有打开文件和被进程所使用的网络连接。
基本用法:
lsof [选项]一些常用的选项有:
-i:显示网络连接信息。-p:显示指定进程ID的打开文件和网络连接。-c:显示指定进程名的打开文件和网络连接。-u:显示指定用户的打开文件和网络连接。-t:仅显示进程ID。-f:以树形结构显示进程的关联文件。
lsof 提供了详细的信息,包括文件类型、文件描述符、文件路径、进程ID、用户、网络协议等。它可以帮助你查找特定进程正在使用的文件和网络连接,以及定位文件锁问题、被删除但仍然被占用的文件等。
一些示例:
显示所有打开文件和网络连接:lsof
显示指定进程ID的打开文件和网络连接:lsof -p PID
显示指定进程名的打开文件和网络连接:lsof -c process_name
显示指定用户的打开文件和网络连接:lsof -u username
仅显示进程ID:lsof -t
以树形结构显示进程的关联文件:lsof -f -p PID
[liren@VM-8-7-centos udp]$ sudo lsof -i:8080 # 此时检测我们开启的8080端口服务器有哪些进程访问
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
udpServer 31565 liren 3u IPv4 40104072 0t0 UDP *:webcache三、ping
ping 是一个常用的网络诊断工具,用于测试与目标主机之间的连通性。使用 ping 命令可以发送 ICMP Echo 请求数据包到目标主机,并测量往返时间**(Round Trip Time,RTT)**和丢包率。
基本用法:
ping [选项] 目标主机名或IP地址-c + 次数:设置完成要求回应的次数-f:极限测试-v:详细显示指令的执行过程-w + 时间:在设置的秒数后退出-W + 时间:在等待设置的秒数后开始执行-t + 存活数值:设置存活数值TTL的大小-s + 数据包大小:设置数据包的大小-q:不显示指令执行过程,开头和结尾的相关信息除外
[liren@VM-8-7-centos udp]$ ping 81.71.97.127
PING 81.71.97.127 (81.71.97.127) 56(84) bytes of data.
64 bytes from 81.71.97.127: icmp_seq=1 ttl=63 time=0.310 ms
64 bytes from 81.71.97.127: icmp_seq=2 ttl=63 time=0.317 ms
64 bytes from 81.71.97.127: icmp_seq=3 ttl=63 time=0.314 ms
64 bytes from 81.71.97.127: icmp_seq=4 ttl=63 time=0.314 ms
64 bytes from 81.71.97.127: icmp_seq=5 ttl=63 time=0.306 ms
64 bytes from 81.71.97.127: icmp_seq=6 ttl=63 time=0.307 ms
64 bytes from 81.71.97.127: icmp_seq=7 ttl=63 time=0.290 ms
^C
--- 81.71.97.127 ping statistics ---
7 packets transmitted, 7 received, 0% packet loss, time 5999ms
rtt min/avg/max/mdev = 0.290/0.308/0.317/0.015 ms
[liren@VM-8-7-centos udp]$ 四、ifconfig
ifconfig 是一个用于显示和配置网络接口的命令行工具。它可以用来查看当前计算机上的网络接口信息,如 IP 地址、子网掩码、MAC 地址等。
基本用法:
ifconfig [选项] [接口名]一些常用的选项有:
-a:显示所有接口的信息,包括停止的接口。-s:显示简洁的接口统计信息。接口名:指定要显示或配置的接口名字。
一些示例:
- 显示所有接口的信息:
ifconfig -a - 显示指定接口的信息:
ifconfig eth0 - 启用接口:
ifconfig eth0 up - 禁用接口:
ifconfig eth0 down - 配置接口的IP地址和子网掩码:
ifconfig eth0 192.168.0.100 netmask 255.255.255.0
五、traceroute
traceroute 是一个网络诊断工具,用于追踪数据包从源主机到目标主机的路径。它可显示数据包经过的路由器和网络节点,并测量到目标主机的时延。预设数据包大小是 40Bytes,用户可另行设置。
基本用法:
traceroute [选项] 主机名或IP地址一些常用的选项有:
-n:禁用DNS反向解析,直接显示IP地址。-q:设置每个TTL(生存时间)的数据包个数。-m:设置数据包最大生存时间(TTL)。-w:设置等待每个回复的超时时间。
一些示例:
- 追踪数据包到目标主机:
traceroute hostname或traceroute IP-address - 禁用DNS反解析:
traceroute -n hostname或traceroute -n IP-address - 设置数据包个数和生存时间:
traceroute -q count -m max_ttl hostname - 设置回复超时时间:
traceroute -w timeout hostname
六、telnet
Telnet 是一种远程登录协议,用于在网络上远程登录到远程计算机并执行命令。通过 Telnet,你可以以文本方式在一个计算机上远程控制另一个计算机。
基本用法:
telnet [选项] [主机名]一些常用的选项有:
-l:指定登录的用户名。-p:指定远程主机的端口号(默认为23)
一些示例:
- 连接到远程主机:
telnet hostname - 指定登录的用户名:
telnet -l username hostname - 指定远程主机的端口号:
telnet -p port hostname
使用 telnet 也可以用来测试 http,相当于做 http 的请求和响应测试,登录之后使用 ctrl+] 即可输入报文格式!
需要注意的是,使用 Telnet 协议登录是不加密的,所有数据都是明文传输。因此,在不受信任的网络上使用 Telnet 时,可能会导致信息泄露或被攻击者截取。推荐使用更安全的 SSH 协议来取代 Telnet,它提供了加密和安全的远程登录方式。
七、ssh
**SSH(Secure Shell)**是一种网络协议,用于在不安全的网络上安全地进行远程登录和执行命令。它提供了加密的通信渠道,以防止信息被窃听、篡改和身份伪造。
通过 SSH,你可以通过终端或命令行界面远程连接到其他计算机,并在远程计算机上执行命令和操作,就像你直接位于该计算机上一样。
基本用法:
ssh [选项] [用户名@]主机名 [命令]一些常用的选项有:
-p:指定远程主机的 SSH 端口号(默认为 22)。-i:指定用于身份验证的私钥文件。-X:启用 X11 转发,允许远程主机上的 X 程序显示在本地。-L:在本地主机和远程主机之间建立端口转发。-R:在远程主机和本地主机之间建立端口转发。
一些示例:
- 连接到远程主机:
ssh username@hostname - 指定远程主机的端口号:
ssh -p port username@hostname - 执行远程命令并返回结果:
ssh username@hostname command
除了基本的远程登录和执行命令,SSH 还可以用于文件传输(使用 scp 或 sftp 命令)和远程端口转发等功能。
需要注意的是,远程主机必须启用 SSH 服务,并且你需要有正确的用户名和密码或者私钥来进行身份验证。
八、tcpdump
tcpdump 是一个广泛使用的命令行网络抓包工具,用于捕获和分析网络数据包。它可以帮助网络管理员和开发人员诊断和调试网络问题,或者进行网络流量的监控和分析。
基本用法:
tcpdump [options] [expression]一些常用的选项和表达式有:
-i:指定抓包的网络接口。-s:指定每个数据包抓取的最大大小(字节)。-c:指定抓取的数据包数量。-w:将抓取到的数据包保存到文件中。-r:从文件中读取数据包进行分析。-n:禁用域名解析,直接显示IP地址。- 表达式:用于过滤和指定需要抓取的的数据包。
一些常用的表达式示例:
host:指定特定主机的网络流量。port:指定特定端口的网络流量。tcp:只显示 TCP 协议的网络流量。udp:只显示 UDP 协议的网络流量。icmp:只显示 ICMP 协议的网络流量。
更详细的用法和参数可以通过 man tcpdump 查看该命令的帮助文档。
注意:使用 tcpdump 需要以 root 权限运行,或者具有特定的权限和网络设备的访问权限。