欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
目录
- 👉🏻IP地址和端口号
- pid和port的关系
- 👉🏻TCP和UDP
- 👉🏻网络字节序
- 👉🏻socket网络编程以及接口函数
- socket概念
- socket
- bind
- listen
- accept
- connect
- 运用socker接口函数实现服务端和客户端的实例代码
- 👉🏻sockaddr结构
- struct sockaddr
- struct sockaddr_in
👉🏻IP地址和端口号
IP地址和端口号是网络通信中重要的概念,它们用于标识网络中的设备和应用程序。这两者结合在一起,可以唯一确定网络中的一个进程,从而实现进程间的通信。
-
IP地址(Internet Protocol Address):IP地址是网络上设备的标识符,用于在网络中唯一标识一个设备(如计算机、路由器等)。IP地址分为IPv4和IPv6两种格式。IPv4地址通常由四个用点分隔的十进制数组成(例如,192.168.1.1),而IPv6地址则更长,由八组十六进制数构成(例如,2001:0db8:85a3:0000:0000:8a2e:0370:7334)。在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址
-
端口号(Port Number):端口号是一个逻辑地址,用于标识一个特定的进程或服务。在通信中,每个数据包都会标记源端口和目标端口。端口号的范围是从0到65535,其中0到1023是被系统保留的一些常用端口,用于一些特定的服务(比如HTTP的端口80,HTTPS的端口443等)。端口号是一个2字节16位的整数;一个端口号只能被一个进程占用
一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定
IP地址和端口号结合在一起,可以唯一地标识网络中的一个进程。例如,一个Web服务器可能会使用IP地址192.168.1.1和端口号80来提供HTTP服务。当客户端想要访问这个Web服务器时,它会使用服务器的IP地址和端口号来建立连接并发送请求。服务器收到请求后,会通过端口号将请求交给相应的Web服务进程来处理,并将响应返回给客户端。
pid和port的关系
-
PID(进程标识符):
- PID是操作系统给每个正在运行的进程分配的唯一标识符。
- PID在系统范围内是唯一的,它用于操作系统在管理和跟踪进程时进行标识。
- PID通常是一个整数值,其范围取决于操作系统。在大多数Unix-like系统中,PID从1开始递增,直到达到系统所能分配的最大值。
-
端口号:
- 端口号是用于标识正在运行的网络服务或应用程序的数字标识符。
- 端口号是网络通信中的概念,在进程间通信(IPC)中通常不涉及。
- 端口号的范围是从0到65535,其中0到1023是系统保留端口,用于一些常见的网络服务,如HTTP(端口80)和HTTPS(端口443)。
虽然PID和端口号都用于标识进程或服务,但它们在不同的上下文中发挥作用。PID是操作系统级别的标识符,用于管理和跟踪进程,而端口号是网络通信中用于标识正在运行的网络服务或应用程序的标识符。
总的来说就是port的主要作用就是将操作系统中的进程管理与网络解耦,port用来专门进行网络通信的,在网络中进行进程标识,虽然二者都是标识进程的id,但是作用域不同,所以也不冲突。
👉🏻TCP和UDP
-
TCP(Transmission Control Protocol)传输控制协议:
- TCP 是一种
面向连接
的、可靠的
、基于字节流
的传输协议。 - TCP 提供了可靠的数据传输,通过确认机制、重传机制和流量控制来确保数据的可靠性和顺序性。
- TCP 在通信开始前需要建立连接(三次握手),然后在通信结束后进行连接的断开(四次挥手)。
- TCP 适用于需要可靠数据传输和顺序传输的应用场景,如网页浏览、文件传输等。
- TCP 是一种
-
UDP(User Datagram Protocol)用户数据报协议:
- UDP 是一种
简单的
、无连接的
、不可靠
的传输协议。 - UDP 不提供数据传输的可靠性和顺序性,数据包可能会丢失、重复或乱序到达。
- UDP 的优点是
速度快
、开销小
,适用于一些实时性要求高、数据丢失可容忍的应用场景。 - UDP 不需要建立连接,通信双方直接发送数据包,因此在通信开始和结束时没有连接建立和断开的过程。
- UDP 是一种
总的来说,TCP 提供了可靠的数据传输服务,适用于需要数据完整性和顺序性的场景;而 UDP 则提供了更快的传输速度,适用于一些实时性要求高、对数据丢失不敏感的场景,如音视频传输、在线游戏等。选择使用哪种协议取决于应用的需求和对数据传输特性的要求。
👉🏻网络字节序
在C语言的学习的时候,我们学习过了大小端的知识,大端就是将数据的高位字节存储在内存地址中的低位地址,小端则反之。
由于每个机器采用的大小端都不一样,所以容易导致数据传输的字节顺序会出问题。
所以网络规定:所有到达网络的数据,必须是大端,所有从网络收到数据的机器,都会知道数据是大端(之所以采取大端,是因为可读性比较高)
网络字节序
是一种规范,用于在网络上跨越不同体系结构的计算机之间进行数据通信时统一数据的字节顺序。它定义了在网络传输中使用的标准字节顺序,以确保数据在发送和接收端正确解释。
在网络字节序中,数据被以大端序(Big-Endian)的方式传输,即最高有效字节在前,最低有效字节在后。这与许多计算机体系结构的本地字节序(如x86架构的小端序)可能不同。
网络字节序通常用于以下方面:
-
网络通信协议:许多网络通信协议(如TCP/IP、UDP、HTTP)都要求在网络上传输数据时使用网络字节序,以确保数据在不同计算机上的解释一致。
-
网络编程:在进行网络编程时,开发人员通常需要手动将数据从本地字节序转换为网络字节序,以便正确发送到网络上。类似地,在接收数据时,也需要将数据从网络字节序转换为本地字节序。
-
数据存储:有时候,数据需要以网络字节序的形式存储到文件或数据库中,以便后续在网络上传输或从网络接收时能够正确解释。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络
字节序和主机字节序的转换。
🌨这些函数通常用于在网络编程中进行字节序的转换,以确保在不同计算机之间正确地传输数据。这些函数包括:
-
htonl
(host to network long):- 函数原型:
uint32_t htonl(uint32_t hostlong)
- 作用:将32位无符号整数从主机字节序转换为网络字节序(大端序)。
- 参数意义:
hostlong
是要转换的32位整数。 - 返回值:返回转换后的网络字节序的32位无符号整数。
- 函数原型:
-
htons
(host to network short):- 函数原型:
uint16_t htons(uint16_t hostshort)
- 作用:将16位无符号短整数从主机字节序转换为网络字节序(大端序)。
- 参数意义:
hostshort
是要转换的16位短整数。 - 返回值:返回转换后的网络字节序的16位无符号短整数。
- 函数原型:
-
ntohl
(network to host long):- 函数原型:
uint32_t ntohl(uint32_t netlong)
- 作用:将32位无符号整数从网络字节序(大端序)转换为主机字节序。
- 参数意义:
netlong
是要转换的32位整数。 - 返回值:返回转换后的主机字节序的32位无符号整数。
- 函数原型:
-
ntohs
(network to host short):- 函数原型:
uint16_t ntohs(uint16_t netshort)
- 作用:将16位无符号短整数从网络字节序(大端序)转换为主机字节序。
- 参数意义:
netshort
是要转换的16位短整数。 - 返回值:返回转换后的主机字节序的16位无符号短整数。
- 函数原型:
- 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
- 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回 ;
- 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
这些函数通常用于网络编程中,以便在发送和接收数据时正确地处理字节序,确保数据在不同主机之间的传输和解释一致。在大多数情况下,主机字节序是指本地计算机的字节序,而网络字节序是指大端序。
👉🏻socket网络编程以及接口函数
socket概念
Socket(套接字)是在网络编程中用于实现网络通信的一种机制。它提供了一种在不同主机之间进行双向通信的方式,使得客户端和服务器之间可以进行数据交换。
具体来说,Socket是一种通信端点,可以用于在两个计算机之间建立连接并进行数据传输。它可以看作是一种抽象接口,用于通过网络在进程之间传递数据。在网络编程中,Socket通常通过套接字地址(包括IP地址和端口号)来标识。套接字地址用于唯一地标识网络上的每个通信端点。
🌈Socket通常分为两种类型:
-
流套接字(Stream Socket):也称为TCP套接字,提供面向连接的、可靠的、双向的字节流通信。在使用流套接字时,通信双方必须先建立连接,然后才能进行数据传输。TCP协议就是基于流套接字实现的。
-
数据报套接字(Datagram Socket):也称为UDP套接字,提供无连接的、不可靠的数据报通信。在使用数据报套接字时,通信双方无需建立连接,每个数据报都是独立的,可能会丢失或重复。UDP协议就是基于数据报套接字实现的。
🌈Socket编程通常涉及以下步骤:
- 创建Socket:在编程中创建一个套接字对象,以便于后续的通信。
- 绑定地址:将套接字绑定到一个特定的地址和端口上,以便其他计算机可以找到并与之通信。
- 监听连接(仅针对服务器端):对于服务器端程序,需要开始监听传入的连接请求。
- 建立连接(仅针对客户端):对于客户端程序,需要与服务器端建立连接。
- 数据传输:通过套接字进行数据的发送和接收。
- 关闭连接:通信完成后,关闭套接字以释放资源。
socket
Socket函数是用于创建套接字的系统调用,在网络编程中非常重要。具体来说,Socket函数用于创建一个套接字对象,以便后续的通信。
以下是Socket函数的一般信息:
-
函数原型:
int socket(int domain, int type, int protocol);
-
作用:创建一个套接字对象,用于后续的网络通信。
-
参数意义:
-
domain:指定套接字的协议族,常用的有以下几种:
AF_INET
:IPv4协议族AF_INET6
:IPv6协议族AF_UNIX
:Unix域协议族- 等等,还有其他协议族
-
type:指定套接字的类型,常用的有以下几种:
SOCK_STREAM
:流套接字,提供面向连接的、可靠的、双向的字节流通信(如TCP套接字)。SOCK_DGRAM
:数据报套接字,提供无连接的、不可靠的数据报通信(如UDP套接字)。SOCK_RAW
:原始套接字,用于直接访问网络层,通常需要特权。- 等等,还有其他类型的套接字
-
protocol:指定套接字所使用的协议,通常为0表示由系统根据
domain
和type
参数自动选择合适的协议。例如,在IPv4下,TCP协议的值为IPPROTO_TCP
,UDP协议的值为IPPROTO_UDP
。
-
-
返回值:若成功创建套接字,则返回一个新的套接字描述符(非负整数),用于后续的操作;若失败,则返回-1,并设置全局变量
errno
表示错误类型。
在使用Socket函数创建套接字后,通常需要根据具体的网络通信需求,调用其他函数来设置套接字选项、绑定地址、建立连接等操作,从而实现完整的网络通信功能。
bind
bind函数用于将套接字与特定的地址(IP地址和端口号)绑定在一起,以便其他计算机可以找到并与之通信。具体来说,bind函数在服务器端程序中通常用于指定服务器的监听地址和端口。
以下是bind函数的一般信息:
-
函数原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
作用:将套接字与特定的地址(IP地址和端口号)绑定在一起,以便进行通信。
-
参数意义:
-
sockfd:表示要绑定的套接字描述符,即通过socket函数创建的套接字对象。
-
addr:指向存储地址信息的结构体的指针,通常是sockaddr结构体或其派生结构体(如sockaddr_in结构体),用于指定要绑定的地址信息。
-
addrlen:表示存储在addr指针中的地址结构体的长度。
-
-
返回值:若成功绑定地址,则返回0;若失败,则返回-1,并设置全局变量
errno
表示错误类型。
在调用bind函数之前,通常需要先创建套接字对象,并设置好地址信息。成功绑定地址后,服务器端就可以通过监听该地址来接受客户端的连接请求,从而建立通信连接。需要注意的是,同一时间内,一个端口只能被一个套接字绑定,否则会导致绑定失败。
listen
listen函数用于将一个套接字设置为被动监听状态,使其能够接受连接请求。通常在服务器端程序中,调用listen函数来准备接受客户端的连接请求。
以下是listen函数的一般信息:
-
函数原型:
int listen(int sockfd, int backlog);
-
作用:将一个套接字设置为被动监听状态,以便接受连接请求。
-
参数意义:
-
sockfd:表示要设置监听状态的套接字描述符,即通过socket函数创建的套接字对象。
-
backlog:表示在内核中允许的连接请求的最大排队数量。当有新的连接请求到达时,如果等待队列已满,则后续的连接请求将被拒绝。注意,该参数不同操作系统的取值范围可能有所不同。
-
-
返回值:若成功设置监听状态,则返回0;若失败,则返回-1,并设置全局变量
errno
表示错误类型。
调用listen函数之后,套接字将进入被动监听状态,开始等待客户端的连接请求。一旦有新的连接请求到达,并且内核等待队列未满,那么服务器端就可以通过accept函数接受这些连接请求,并建立通信连接。需要注意的是,调用listen函数并不会阻塞程序的执行,而是将套接字设置为监听状态后立即返回。
accept
accept函数用于接受客户端的连接请求,并创建一个新的套接字与客户端进行通信。通常在服务器端程序中,调用accept函数来处理客户端的连接请求,并建立通信连接。
以下是accept函数的一般信息:
-
函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-
作用:接受客户端的连接请求,并创建一个新的套接字与客户端进行通信。
-
参数意义:
-
sockfd:表示已经处于监听状态的套接字描述符,即通过socket函数创建的套接字对象。
-
addr:用于存储客户端地址信息的结构体指针。当成功接受客户端连接后,该指针所指向的结构体将被填充为客户端的地址信息。
-
addrlen:表示存储在addr指针中的地址结构体的长度。在调用accept函数之前,需要将其设置为可修改的地址结构体的长度。
-
-
返回值:若成功接受连接请求,则返回一个新的套接字描述符,用于与客户端进行通信;若失败,则返回-1,并设置全局变量
errno
表示错误类型。
调用accept函数之后,服务器端程序将会阻塞,直到有新的客户端连接请求到达。一旦有客户端连接成功,accept函数将返回一个新的套接字描述符,服务器可以使用这个新的套接字与客户端进行通信。同时,accept函数还会将客户端的地址信息存储在提供的addr结构体中,方便服务器端获取客户端的地址和端口信息。
connect
connect函数用于建立与远程服务器的连接。通常在客户端程序中,调用connect函数来连接服务器,以便进行数据通信。
以下是connect函数的一般信息:
-
函数原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
作用:建立与远程服务器的连接。
-
参数意义:
-
sockfd:表示客户端本地套接字的描述符,即通过socket函数创建的套接字对象。
-
addr:指向远程服务器地址信息的结构体指针,通常是通过调用
getaddrinfo
或手动构建的。 -
addrlen:表示提供给addr参数的地址结构体的长度。
-
-
返回值:若连接成功,则返回0;若失败,则返回-1,并设置全局变量
errno
表示错误类型。
调用connect函数后,客户端会尝试与指定的服务器建立连接。如果连接成功,那么客户端就可以通过已连接的套接字进行数据通信。如果连接失败,则通常是由于服务器不可达、端口未开放或网络问题等原因。在连接函数返回之前,通常会阻塞程序的执行,直到连接建立成功或失败。
运用socker接口函数实现服务端和客户端的实例代码
服务器端代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#define PORT 8080
#define BACKLOG 5
#define BUFFER_SIZE 1024int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[BUFFER_SIZE] = {0};const char *hello = "Hello from server";// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置套接字选项,允许地址重用if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 将套接字绑定到指定的端口if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {perror("bind failed");exit(EXIT_FAILURE);}// 开始监听连接请求if (listen(server_fd, BACKLOG) < 0) {perror("listen");exit(EXIT_FAILURE);}// 等待并接受客户端连接if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {perror("accept");exit(EXIT_FAILURE);}// 从客户端接收数据read(new_socket, buffer, BUFFER_SIZE);printf("Client: %s\n", buffer);// 向客户端发送数据send(new_socket, hello, strlen(hello), 0);printf("Hello message sent\n");return 0;
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#define PORT 8080
#define BUFFER_SIZE 1024int main() {int sock = 0;struct sockaddr_in serv_addr;char buffer[BUFFER_SIZE] = {0};const char *hello = "Hello from client";// 创建套接字if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {printf("\n Socket creation error \n");return -1;}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 将IPv4地址从文本转换为二进制形式if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) {printf("\nInvalid address/ Address not supported \n");return -1;}// 连接到服务器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {printf("\nConnection Failed \n");return -1;}// 向服务器发送数据send(sock, hello, strlen(hello), 0);printf("Hello message sent\n");// 从服务器接收数据read(sock, buffer, BUFFER_SIZE);printf("Server: %s\n",buffer);return 0;
}
这个例子中,服务器端创建了一个套接字,绑定到指定的端口,并监听连接请求。当客户端连接到服务器时,服务器接受连接并接收来自客户端的消息,然后向客户端发送一条消息。客户端连接到服务器,向服务器发送一条消息,并接收来自服务器的响应。
👉🏻sockaddr结构
struct sockaddr
struct sockaddr
是一个通用的套接字地址结构体,用于表示套接字的地址信息。在网络编程中,它通常被用于指定套接字的地址,包括 IP 地址和端口号。这个结构体是用于支持不同协议族的通用性。
下面是 struct sockaddr
的定义:
struct sockaddr {unsigned short sa_family; // 地址族(Address Family),如 AF_INET(IPv4)或 AF_INET6(IPv6)char sa_data[14]; // 地址数据,包含具体的地址信息
};
由于 struct sockaddr
是一个通用的结构体,它的大小固定为 16 字节。因此,对于具体的地址族(如 IPv4 或 IPv6),通常会使用它的变体,如 struct sockaddr_in
(IPv4)或 struct sockaddr_in6
(IPv6),它们在 struct sockaddr
的基础上添加了一些额外的字段来表示特定地址族的地址信息。
在实际使用中,通常不直接使用 struct sockaddr
,而是使用具体的地址结构体,如 struct sockaddr_in
或 struct sockaddr_in6
,因为它们提供了更多的字段来存储地址信息,并且更方便使用。
struct sockaddr_in
struct sockaddr_in
是用于表示 IPv4 地址的套接字地址结构体,在网络编程中经常使用。它包含在 <netinet/in.h>
头文件中。
下面是 struct sockaddr_in
的定义:
struct sockaddr_in {short int sin_family; // 地址族(Address Family),通常为 AF_INETunsigned short int sin_port; // 16 位端口号,网络字节序(big-endian)struct in_addr sin_addr; // IPv4 地址结构体unsigned char sin_zero[8]; // 用于填充,使 sockaddr_in 和 sockaddr 的大小相同
};
其中:
sin_family
:表示地址族,通常设置为AF_INET
,表示 IPv4 地址族。sin_port
:表示端口号,是一个 16 位的整数,以网络字节序(big-endian)存储。sin_addr
:是一个struct in_addr
结构体,用于存储 IPv4 地址信息。sin_zero
:用于填充,使struct sockaddr_in
和struct sockaddr
的大小相同,总共占据 8 个字节。
struct in_addr
的定义如下:
struct in_addr {uint32_t s_addr; // IPv4 地址,32 位无符号整数,以网络字节序存储
};
struct sockaddr_in
通常用于存储和表示 IPv4 地址信息,例如在创建套接字、绑定地址和连接远程主机时使用。使用这个结构体可以方便地操作和管理 IPv4 地址。
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长