目录
[1. UDP网络编程](#1. UDP网络编程)
[1.1 echo server](#1.1 echo server)
[1.1.1 接口](#1.1.1 接口)
[1.1.1.1 创建套接字](#1.1.1.1 创建套接字)
[1.1.1.2 绑定](#1.1.1.2 绑定)
[1.1.1.3 bzero](#1.1.1.3 bzero)
[1.1.1.4 htons(主机序列转网络序列)](#1.1.1.4 htons(主机序列转网络序列))
[1.1.1.5 inet_addr(主机序列IP转网络序列IP)](#1.1.1.5 inet_addr(主机序列IP转网络序列IP))
[1.1.1.6 recvfrom(让服务器收消息)](#1.1.1.6 recvfrom(让服务器收消息))
[1.1.1.7 sendto(写消息)](#1.1.1.7 sendto(写消息))
[1.1.1.8 netstat -uap(查看网络服务是否启动)](#1.1.1.8 netstat -uap(查看网络服务是否启动))
[1.1.1.9 client不需要bind问题](#1.1.1.9 client不需要bind问题)
[1.1.1.10 inet_ntoa(网络序列IP转主机序列IP)](#1.1.1.10 inet_ntoa(网络序列IP转主机序列IP))
[1.1.1.11 云服务器禁止bind公网IP](#1.1.1.11 云服务器禁止bind公网IP)
[1.1.1.12 inet_ntop(网络序列IP转主机序列IP)](#1.1.1.12 inet_ntop(网络序列IP转主机序列IP))
[1.1.2 代码](#1.1.2 代码)
[1.1.2.1 Version 1](#1.1.2.1 Version 1)
[1.1.2.2 Version 2](#1.1.2.2 Version 2)
[1.1.2.3 Version 3](#1.1.2.3 Version 3)
[1.2 DictServer](#1.2 DictServer)
[1.3 简单聊天室](#1.3 简单聊天室)
[1.4 补充参考内容](#1.4 补充参考内容)
[1.4.1 地址转换函数](#1.4.1 地址转换函数)
[1.4.2 关于inet_ntoa](#1.4.2 关于inet_ntoa)
[2. 网络命令](#2. 网络命令)
[2.1 Ping命令](#2.1 Ping命令)
[2.2 netstat命令](#2.2 netstat命令)
[2.2.1 查看UDP](#2.2.1 查看UDP)
[2.2.2 查看TCP](#2.2.2 查看TCP)
[2.2.3 watch指令](#2.2.3 watch指令)
[2.3 pidof命令](#2.3 pidof命令)
[3. 验证UDP - windows作为client访问Linux](#3. 验证UDP - windows作为client访问Linux)
1. UDP网络编程
1.1 echo server
1.1.1 接口
1.1.1.1 创建套接字
cpp
// man socket
NAME
socket - create an endpoint for communication
// 创建一个套接字
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// domain:域,协议族,协议家族的类型
Name Purpose Man page
AF_UNIX Local communication unix(7) // 本地通信
AF_LOCAL Synonym for AF_UNIX
AF_INET IPv4 Internet protocols ip(7) // 网络通信
AF_INET6 IPv6 Internet protocols ipv6(7) // IPv6暂时不考虑
// type:类型,代表想要创建什么样的套接字
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported. // TCP
SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length). // UDP
// protocol:创建的套接字,传输层的类型用哪一个,TCP还是UDP,但我们不用传这个参数,这个参数
// 默认为0就可以了,因为只要是网络的,类型是数据报套接字,socket创建出来默认就是UDP套接字。
// 后面用的时候设置为0就可以了
// 返回值:
RETURN VALUE
On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropriately.
返回的是文件的描述符,本质是创建了一个文件,创建文件描述符,创建struct file对象,struct file的操作函数初始化。
aurora@wanghao:~/Linux/2025_05_19_EchoServer$ ./server_udp
[2025-05-19 19:18:33] [INFO] [159814] [UdpServer.hpp] [38] - socket success,sockfd is : 3
// 编译运行之后,文件描述符的数字是3
1.1.1.2 绑定
cpp
// man bind
NAME
bind - bind a name to a socket // 给套接字绑定名字
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// sockfd:绑定套接字,传递创建的文件描述符
// addr:sockaddr_in
// 结构体所对应的长度
// 返回值:
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
// 绑定成功,0返回,否则-1被返回
//sockaddr_in man inet_addr
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)];
};

1.1.1.3 bzero
cpp
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family = AF_INET;
sockaddr_in里面是有填充字段的,所以我们追求locak本身的确定性,所以我们建议清零,然后再进行填充。
cpp
// man bzero
NAME
bzero, explicit_bzero - zero a byte string // 把传进来的字符串全部清零
SYNOPSIS
#include <strings.h>
void bzero(void *s, size_t n);
1.1.1.4 htons(主机序列转网络序列)
cpp
// man htons
NAME
htonl, htons, ntohl, ntohs - convert values between host and network byte order
// 本地的序列转换成网络的序列
SYNOPSIS
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
现在有一台服务器,将来别人是给这个服务器发消息的,那么未来这个服务器肯定也是要给别人回消息的,作为服务器端或者客户端,我们的发的消息里面,一定会包含报头,而我们为了让别人能给我们把消息转回来,所以我的报头中一定要有我的源IP和源端口号,所以服务器一定是要把自己的IP地址和端口号也要推送给对方,因为要让对方知道我是谁,,它才可能把消息给我反回来。
port占两个字节,uint16_t,是本地的,所以这个port是大端的还是小端的不知道,所以要把port做主机序列转网络序列。本来就是大端的话也要转一下,因为你不知道你的代码将来在哪个机器下跑,为了代码的可移植性。所以port要由我们的主机序列转换成网络序列。然后才能设置进sin_port。
cpp
local.sin_port = htons(_port); // 要被发生给对方的,即要发到网络中!
1.1.1.5 inet_addr(主机序列IP转网络序列IP)
IP的话有点复杂,点分十进制的IP,首先把字符串风格的IP转换成4字节的,然后把4字节的IP转换成网络序列,起始也就是大端。
cpp
// man inet_addr
NAME
inet_aton, inet_addr, inet_network, inet_ntoa, inet_makeaddr, inet_lnaof, inet_netof - Internet address manipulation rou‐
tines
SYNOPSIS
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp); // 把一个字符串风格的IP地址转成4字节IP
// 同时把4字节IP地址转换成网络序列。
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);
// 这些接口,要么是把字符串转成4字节,同时转成网络序列,要么是把4字节,转成主机序列,同时转成字符串。
cpp
local.sin_addr = ::inet_addr(_ip.c_str()); // 1.string ip -> 4bytes 2. network order
如果我们这样写的话会报错的,首先我们看一下sockaddr_in中有什么东西。
cpp
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)];
};
后面的为填充字段,但是我们可以看到sin_addr是结构体类型的,那我们再进去看一下,这个结构体里面有什么东西。
cpp
/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
它的里面有一个叫做s_addr的变量,因为在C语言当中,结构体只能被整体初始化,不能被整体赋值,所以我们对结构体赋值就得一个一个的赋值。
cpp
local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1.string ip -> 4bytes 2. network order
所以我们要这样写。
还有一个问题,在结构体中怎么部件协议族,也就是sin_family。

1.1.1.6 recvfrom(让服务器收消息)
cpp
// man recvfrom
NAME
recv, recvfrom, recvmsg - receive a message from a socket
SYNOPSIS
#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);
// 从指定的套接字处直接收消息,收到的消息放在buf中,期望收消息收多少字节用len表示,flags表示收的方式,为0就行,我们以阻塞的方式收,实际收到了多少字节由返回值决定,返回值大于0读取成功,小于0读取失败。
// 后两个参数的意思就是我们要知道是谁给我们发的消息,标识另一端就用IP+端口号
// 是输出型参数
struct sockaddr_in peer;
socklen_t len = sizeof(peer)
RETURN VALUE
These calls return the number of bytes received, or -1 if an error occurred. In the event of an error, errno is set to in‐
dicate the error.
// 如果成功,就返回督导的多少字节数,否则-1返回
1.1.1.7 sendto(写消息)
cpp
// man sendto
NAME
send, sendto, sendmsg - send a message on a socket
SYNOPSIS
#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);
// sockfd:未来创建好的一个socket fd,即可以发,也可以收,这种我们称为全双工。
// 管道的文件描述符只能读,或者只能写,这个叫半双工。
// 半双工是任何一个时刻只能一端向另一端发,管道是特殊的半双工,只能一端向另一端发,反过来不行
// buf && len :发送的字符串
// flags:发送的方式,设置为0,阻塞的方式发
// dest_addr && addrlen:目标主机的进程
1.1.1.8 netstat -uap(查看网络服务是否启动)
bash
aurora@wanghao:~/Linux/2025_05_19_EchoServer$ netstat -uap
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
udp 0 0 wanghao:8080 0.0.0.0:* 161017/./server_udp
udp 0 0 localhost:domain 0.0.0.0:* -
udp 0 0 wanghao:bootpc 0.0.0.0:* -
udp 0 0 wanghao:323 0.0.0.0:* -
udp6 0 0 localhost:323 [::]:* - -
- u:UDP的意思
- a:all全部的意思
- p:pid
不带p就少一列,带上p就多一列,顺序随便写。
bash
aurora@wanghao:~/Linux/2025_05_19_EchoServer$ netstat -nuap
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
udp 0 0 127.0.0.1:8080 0.0.0.0:* 161728/./server_udp
udp 0 0 127.0.0.53:53 0.0.0.0:* -
udp 0 0 172.26.160.193:68 0.0.0.0:* -
udp 0 0 127.0.0.1:323 0.0.0.0:* -
udp6 0 0 ::1:323 :::* -
- n:能显示成数字的全部显示成数字
- 0.0.0.0:*:就是允许任何远端向我发消息
- 161728/./server_udp :进程以及服务器名字
- 什么叫网络服务?
网络服务就是一个进程、或者进程池、或者一堆进程、或者一堆线程- 客户端主动,服务器被动,服务器一直等客户端,这种模式叫做CS模式,C:client、S:server
1.1.1.9 client不需要bind问题
1. client 不需要bind吗?
客户端也要有自己的ip和端口,但是客户端不需要自己显示的调用bind!!而是客户端首次sendto消息的时候,由OS自动bind。
2. 如何理解客户端自动bind?
首先客户端对应的ip地址都是固定的,关键在于客户端要自动绑定的话就要自动填充自己的sockaddr_in结构、ip地址、端口号、协议族,然后把客户端信息想办法发送给服务的,服务器才能知道客户点是谁如果再我们的服务器里面,存在很多的端口号,也存在很多的进程,一个端口号只能被一个进程绑定,一个端口号不允许被多个进程绑定的,操作系统也不允许,因为操作系统收到报文要根据端口号查进程,所以端口号到进程必须具有唯一性,所以,一个端口号只能被一个进程绑定,反过来,一个进程是可以绑定多个端口号的,如果客户端实现的绑定端口号的话,会带来一个后果不同公司的客户端之间会互相冲突,比如说淘宝先启动了,抖音就跑不起来了,因为淘宝和抖音使用一个端口号,势必这两个 是不能同时启动的,bind就失败,所以对于客户端,我们要做的是让操作系统自动随机绑定端口号。
3. 如何理解server要显示的bind?
服务器的端口号必须稳定,必须是众所周知且不能轻易改变的!
1.1.1.10 inet_ntoa(网络序列IP转主机序列IP)
cpp
// man inet_ntoa
NAME
inet_aton, inet_addr, inet_network, inet_ntoa, inet_makeaddr, inet_lnaof, inet_netof - Internet address manipulation rou‐
tines
SYNOPSIS
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
// 把4字节ip地址既转成主机序列,然后又转成字符串
1.1.1.11 云服务器禁止bind公网IP

服务器再绑定的时候我们发现绑定失败了,服务器在绑定的时候端口号可以自由指定,ip地址原则上可以自由指定,但有点违和,ip地址本来就是属于自己的机器啊,和进程有什么关系呢?进程拿着端口号来确定进程的唯一性就可以了,关ip地址什么事情,那127.0.0.1怎么能绑呢?
- 云服务器,禁止用户bind公网ip,因为112.126.76.148这个ip就不属于你这个机器,是通过一系列的云产品虚拟出来的ip地址。
最多只能绑图片中的两个,第一个是内网ip,第二个是本地环回的ip。
- 虚拟机,可以bind你的任何IP。
又的服务器上不止一个IP,你也可以理解成通过虚拟化技术虚拟出来的,也可以认为不止一张网卡,只要你绑定了对应的IP,也就是说你只能得到绑定的ip得到的报文,别的收不到,所以服务器不需要bindIP,只需要端口号就可以了。
cpp
local.sin_addr.s_addr = INADDR_ANY;
/* Address to accept any incoming messages. */
#define INADDR_ANY ((in_addr_t) 0x00000000)
我们只需要将s_addr设置为INADDR_ANY,INADDR_ANY其实是0,也就是说设置为INADDR_ANY表示我的服务器可以接收来自这个主机上的所有IP的报文。只要发过来的端口号是我指定的端口号就行。

0.0.0.0.0的意思是任意IP绑定,只要是发给这台机器上的,不论从哪个IP地址收到的报文,只要端口号填的8888,都给我。
1.1.1.12 inet_ntop(网络序列IP转主机序列IP)
cpp
// man inet_ntop
NAME
inet_ntop - convert IPv4 and IPv6 addresses from binary to text form
SYNOPSIS
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
// af:协议族
// src:源数据
// dst && size:目标缓冲区起始地址和长度
// 所以我们要定义一个缓冲区传进去,不存在多线程安全问题,因为缓冲区在栈上定义的话属于线程内部的
cpp
//man inet_ntoa
NAME
inet_aton, inet_addr, inet_network, inet_ntoa, inet_makeaddr, inet_lnaof, inet_netof - Internet address manipulation rou‐
tines
SYNOPSIS
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
这两个方法都是可以的,为什么不太想用第二个呢?
第二个确实是把一个四字节转成一个字符串,但是这里会有一个问题,他返回的是char*,不是字符串,C语言中没有字符串类型,它既然返回char*的话,那么字符串在哪里呢?我们一定要小心返回值为指针的东西,因为inat_ntoa在内部会维护一段静态空间,既然是静态空间的话,此时在静态空间当中进行使用inat_ntoa的时候,返回这个char*,那么有可能在多线程环境当中使用就会出现下一个操作会把上一个覆盖的问题,所以这个函数不太安全,而我们还是习惯用inat_ntop。
1.1.2 代码
1.1.2.1 Version 1
这个代码至少能完成本地通信,UDP虽然不保证可靠性,但我们在本地通信中它不会丢失的,因为没有经过网络,所以这个时候就可以理解进程间通信不要搞太多,因为有TCP/UDP就够用了。
1.1.2.2 Version 2
Linux: This repository is specifically designed to store Linux code - Gitee.comhttps://gitee.com/Axurea/linux/tree/master/2025_05_20_EchoServer在Version1的基础上增加了网络的通信。
1.1.2.3 Version 3
Linux: This repository is specifically designed to store Linux code - Gitee.comhttps://gitee.com/Axurea/linux/tree/master/2025_05_21_EchoServer在version2的基础上对IP地址和端口之间的转换做了封装。
1.2 DictServer
1.3 简单聊天室
Linux: This repository is specifically designed to store Linux code - Gitee.com
聊天本质上就是观察者模型,发消息把消息推送给所有人,而我推送给那些人呢?推送给了关心这条消息的人,所谓的观察者就是观察这个群里有没有事件,有没有消息,如果有消息就推送给所有人,消息转发的本质就是生产消费者模型,因为线程池是一个生产消费模型,我们在进行生产消费过程中,我们不能只关心把任务放入到任务队列,消费者从任务队列中拿走。
在我们对应的消费者在进行任务处理的时候,在进行把任务放入任务队列之前,要做读取数据,获取任务,生产者把任务队列拿走了,拿走了不是光拿走,这不是消费,拿走之后还要基于observer观察者模式,推送消息给所有人。

1.4 补充参考内容
1.4.1 地址转换函数
本博客只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位的IP地址。
但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间的转换:
字符串转in_addr的函数:
cpp
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr转字符串的函数:
cpp
char *inet_ntoa(struct in_addr in);
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。
代码示例:
cpp
#include <stdio.h>
#include<sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
struct sockaddr_in addr;
inet_aton("127.0.0.1",&addr.sin_addr);
uint32_t* ptr = (uint32_t*)(&addr.sin_addr);
printf("addr: %x\n",*ptr);
printf("addr_str: %s\n",inet_ntoa(addr.sin_addr));
return 0;
}
1.4.2 关于inet_ntoa
inet_ntoa这个函数返回一个char*,很显然是这个函数自己在内部为沃尔玛申请了一块内存来保存ip的结构,那么是否需要调用者手动释放呢?
cpp
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函数,是把这个返回结果放到了静态存储区,这个时候不需要我们手动进行释放。
那么问题来了,如果我们调用多次这个函数,会有什么样的效果呢?
cpp
#include <stdio.h>
#include<sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
struct sockaddr_in addr1;
struct sockaddr_in addr2;
addr1.sin_addr.s_addr = 0;
addr2.sin_addr.s_addr = 0xffffffff;
char* ptr1 = inet_ntoa(addr1.sin_addr);
char* ptr2 = inet_ntoa(addr2.sin_addr);
printf("ptr1: %s, ptr2: %s\n",ptr1,ptr2);
return 0;
}
运行结果如下:

因为inet_ntoa把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果。
- 思考:如果有多个线程调用inet_ntoa,是否会出现异常情况呢?
- 在APUE中,明确提出inet_ntoa不是线程安全的函数。
- 但是在centos7上测试,并没有出现问题,可能内部的实现加了互斥锁。
- 我们也可以写程序验证以下在自己的机器上inet_ntoa是否会出现多线程的问题。
- 在多线程环境下,推荐使用inet_ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题。
多线程调用inet_ntoa代码示例如下:
cpp
#include <stdio.h>
#include <unistd.h>
#include<sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
void* Func1(void* p)
{
struct sockaddr_in* addr = (struct sockaddr_in*)p;
while(1)
{
char* ptr = inet_ntoa(addr->sin_addr);
printf("addr1: %s\n",ptr);
}
return NULL;
}
void* Func2(void* p)
{
struct sockaddr_in* addr = (struct sockaddr_in*)p;
while(1)
{
char* ptr = inet_ntoa(addr->sin_addr);
printf("addr2: %s\n",ptr);
}
return NULL;
}
int main()
{
pthread_t tid1 = 0;
struct sockaddr_in addr1;
struct sockaddr_in addr2;
addr1.sin_addr.s_addr = 0;
addr2.sin_addr.s_addr = 0xffffffff;
pthread_create(&tid1,NULL,Func1,&addr1);
pthread_t tid2 = 0;
pthread_create(&tid2,NULL,Func2,&addr2);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
2. 网络命令
2.1 Ping命令
ping命令主要是用来检测网络连通性的一条命令。
bash
aurora@wanghao:~/test$ ping www.baidu.com
PING www.a.shifen.com (220.181.111.232) 56(84) bytes of data.
64 bytes from 220.181.111.232 (220.181.111.232): icmp_seq=1 ttl=51 time=4.20 ms
64 bytes from 220.181.111.232 (220.181.111.232): icmp_seq=2 ttl=51 time=4.18 ms
64 bytes from 220.181.111.232 (220.181.111.232): icmp_seq=3 ttl=51 time=4.19 ms
64 bytes from 220.181.111.232 (220.181.111.232): icmp_seq=4 ttl=51 time=4.20 ms
^C
--- www.a.shifen.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3003ms
rtt min/avg/max/mdev = 4.177/4.190/4.203/0.009 ms
比如说我写了一个服务,一直给发消息但得不到响应,那么我们就先可以ping一下,看一下网络有没有连通。如果网络能连通,那么就说明我们的服务器本身写的有问题。ping命令一旦开始,是不会停止的,我们可以指定ping的次数。
cpp
ping -c1 www.baidu.com
//c可以理解为count的意思
cpp
aurora@wanghao:~/test$ ping -c 1 www.baidu.com
PING www.a.shifen.com (220.181.111.232) 56(84) bytes of data.
64 bytes from 220.181.111.232 (220.181.111.232): icmp_seq=1 ttl=51 time=4.17 ms
--- www.a.shifen.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 4.174/4.174/4.174/0.000 m
ping命令可以看到结果即可证明我们的网络是连通的。
2.2 netstat命令
netstat命令是用来查看网络服务的命令。
**语法:**netstat[选项]
**功能:**查看网络状态
我们编写的后端的UDP、TCP相关的网络服务最终启动起来就是一个进程,但这个进程我们用ps能查到这个进程,但智能查到比较偏向于进程相关的一些属性字段,而我想看一看更多关于网络方面的一些属性字段就可以用netstat命令。
常用选项:
- n拒绝显示别名,能显示数字的全部转化成数字
- I仅列出有在Listen(监听)的服务状态
- p显示建立相关链接的程序名
- t(tcp)仅显示tcp相关选项
- u(udp)仅显示udp相关选项
- a(all)显示所有选项,默认不显示LISTEN相关
2.2.1 查看UDP
netstat默认去查,它会把我们能看到的比如TCP的UDP的,还有域间套接的,都可以看到,可是我们今天就想看UDP的。



那下面的怎么是空的,查不到呢?
有些端口不允许普通进程去绑定的,比如80,443,启动时,需要采用超级用户权限来启动,我们之所以能查到,是因为这个网络进程是我用普通用户来启动的,就能查到我启动的,但是其他服务很明显是系统本身自己自启动的,或者其他人启动的,我们能看到当前的网络服务,但是信息是看不到的。

要想看到,就带sudo。

不想看到主机名称,想要看到IP地址,就带n选项。n可以理解成num的意思。
这几个选项的顺序无所谓,都不影响查看的最终结果。
2.2.2 查看TCP

凡是协议是TCP的就全部会显示出来。

查看所有处于LISTEN状态的所有服务。l就是LISTEN的意思。

不要显示主机名,显示成数字,带上n。

带上p就可以查看进程相关的部分了,短杠部分和我们前面的UDP是一样的。选项的顺序可以随便去写。
2.2.3 watch指令
watch指令是一个周期性执行linux命令的任务。
cpp
// 每隔1s执行一次netstat -nltp
watch -n 1 netstat -nltp
// n表示刷新的次数

时间其实在走,ctrl + c终止。
2.3 pidof命令
pidof命令是用来查看特定网络服务对应的进行pid的命令。在查看服务器的进程id时非常方便。
**语法:**pidof[进程名]
**功能:**通过进程名,查看进程id

这样拿也没问题,我们ctrl + c也可以,但是有的进程是后台进程或者守护进程,那么我们就得拿到进程的pid了,kill -9 进程pid,可是这样的方法,1要查,2要杀,会比较慢,我们更好的做法就是pidof。

这样我们就可以杀掉对应的服务了,这里的xargs,管道是个文件,当我们pidof把pid通过管道传给kill命令时,实际上这个kill命令是通过自己的标准输入文件描述符0来把数据读到kill里面的,很显然,kill命令要杀一个进程一定是把pid,放在自己的命令行参数当中传给kill命令。
xargs的作用就是把管道当中传递过来的数据转化成后续的命令行参数,拼接到后面,所有我们就可以采用命令组合的方式来杀进程。
3. 验证UDP - windows作为client访问Linux
注意:一定要开放云服务器对应的端口号,在你的阿里云或者腾讯云、华为云的网站后台中开放。
我们可以发现udp client(windows)和udp server(Linux)可以通信。
WinSock2.h是Windows Sockets API(应用程序接口)的头文件,用于在Windows平台上进行网络编程。它包含了Windows Sockets 2(WinSock2)所需的数据类型、函数声明和结构定义,使得开发者能够创建和使用套接字(sockets)进行网络通信。
在编写使用Winsock2的程序时,需要在源文件中包含WinSock2.h头文件。这样,编译器就能够识别并理解Winsock2中定义的数据类型和函数,从而能够正确的编译和链接网络相关的代码。
此外,与WinSock2.h头文件相对应的是ws2_32.lib库文件。在链接阶段,需要将这个库文件链接到程序中,以确保运行时能够找到并调用Winsock2 API中实现的函数。
在WinSock2.h中定义了一些重要的数据类型和函数,如:
WSADATA:保存初始化Winsock库时返回的信息。
SOCKET:表示一个套接字描述符,用于在网络中唯一标识一个套接字。
sockaddr_in:IPv4地址结构体,用于存储IP地址和端口号等信息。
socket():创建一个套接字。
bind():将套接字与本地地址绑定。
listen():将套接字设置为监听模式,等待客户端的连接请求。
accept():接受客户端的连接请求,并返回一个新的套接字描述符,用于与客户端
进行通信。
WSAStartup函数是Windows Sockets API的初始化函数,它用于初始化Winsock库。该函数在应用程序或DLL调用任何Windows套接字函数之前必须首先执行,它扮演着初始化的角色。
以下是WSAStartup函数的一些关键的:
它接受两个参数:wVersionRequested和IpWSAData。wVersionRequested用于指定所请求的Winsock版本,通常使用MAKEWORD(major,minor)宏,其中major和minor分别标识请求的主版本号和次版本号。IpWSAData是一个指向WSAData结构的指针用于接收初始化信息。
如果函数调用成功,它会返回0:否则,返回错误代码。
WSAStartup函数的主要作用是向操作系统说明我们将使用哪个版本的Winsock库,从而使得该库文件能与当前的操作系统协同工作。成功调用该函数后,Winsock库的状态会被初始化,应用程序就可以使用Winsock提供的一系列套接字服务,如地址家族识别、地址转换、名字查询和连接控制等。这些服务使得应用程序可以与底层的网络协议栈进行交互,实现网络通信。
在调用WSAStartup函数后,如果应用程序完成了对请求的Socket库的使用,应调用
WSAStartup函数后,如果应用程序完成了对请求的Socket库的使用,应调用WSACleanup
函数来解除与Socket库的绑定并释放所占用的系统资源。