天津网络网站公司,军人可以做网站吗,金坛建设网站,北京爱空间装修公司1.概念 在Linux中#xff0c;套接字#xff08;socket#xff09;是一种通信机制#xff0c;用于实现不同进程之间或同一主机上的不同线程之间的数据交换。它是网络编程的基础#xff0c;允许应用程序通过网络进行通信#xff0c;也可以在同一台机器上的不同进程间进行通…1.概念 在Linux中套接字socket是一种通信机制用于实现不同进程之间或同一主机上的不同线程之间的数据交换。它是网络编程的基础允许应用程序通过网络进行通信也可以在同一台机器上的不同进程间进行通信。 套接字的概念起源于BSDBerkeley Software Distribution操作系统是由BSD UNIX提出并实现的。后来套接字成为了Unix-like系统包括Linux中网络编程的标准接口。在早期的Unix系统中进程间通信主要通过管道和命名管道FIFO实现这些机制只适用于本地进程通信。为了能够在网络上进行进程间通信套接字作为一种通用的解决方案被引入并且得到了广泛的应用。 套接字可以被视为一种文件描述符它允许进程通过网络发送和接收数据。在Linux中套接字可以基于网络协议如TCP/IP、UDP或本地通信协议如UNIX域套接字工作。它提供了一种统一的接口使得应用程序可以通过不同的传输层协议来进行通信而无需关心底层网络细节。
套接字类型
在Linux中套接字可以根据其类型和地址族的不同而分为多种类型主要包括
流套接字Stream Socket基于TCP协议提供面向连接的可靠数据传输数据传输顺序不会变化适合需要可靠传输的应用。数据报套接字Datagram Socket基于UDP协议提供不可靠的数据传输服务传输速度快但无法保证数据传输的顺序和可靠性适合对传输效率要求较高的应用。原始套接字Raw Socket允许应用程序直接访问网络协议如IP层用于实现自定义网络协议或进行网络数据包分析等特殊用途。UNIX域套接字Unix Domain Socket用于在同一台主机上的进程间通信不涉及网络通信提供了一种高效的本地通信机制。
2.字节序 字节序Byte Order是指多字节数据在存储器中的存放顺序。由于计算机内存和存储器是以字节为最小单位进行寻址的多字节数据比如16位、32位、64位整数在存储器中占据连续的字节空间。字节序定义了这些字节在存储器中的排列顺序。对于单字符来说是没有字节序问题的字符串是单字符的集合因此字符串也没有字节序问题。
大端字节序Big Endian
在大端字节序中数据的高字节Most Significant ByteMSB存储在低地址低字节Least Significant ByteLSB存储在高地址。这种方式类似于把一个多字节整数的数字本身按照从高位到低位的顺序存放在内存中。
小端字节序Little Endian
在小端字节序中数据的低字节LSB存储在低地址高字节MSB存储在高地址。这种方式将一个多字节整数的最低有效字节放在最低地址处。
// 有一个16进制的数, 有32位 (int): 0xab5c01ff
// 字节序, 最小的单位: char 字节, int 有4个字节, 需要将其拆分为4份
// 一个字节 unsigned char, 最大值是 255(十进制) ff(16进制) 内存低地址位 内存的高地址位
---------------------------------------------------------------------------
小端: 0xff 0x01 0x5c 0xab
大端: 0xab 0x5c 0x01 0xff网络通信 大多数网络协议如TCP/IP、HTTP规定数据传输时采用网络字节序即大端字节序。这是因为网络协议需要确保通信双方能够统一数据的解析方式避免因字节序问题导致数据解析错误。在网络中通常使用的是网络字节序大端字节序因此如果要与网络进行数据交换尤其是对于传输整数等多字节数据时使用大端字节序能够简化数据的处理和解析。 个人计算机 大多数个人计算机如x86架构采用小端字节序。因此在开发和编写面向这些平台的应用程序时通常会使用小端字节序。Windows、Linuxx86、x86-64架构、以及大部分现代桌面和移动设备的处理器都是小端字节序。 内存访问优化 小端字节序在访问多字节数据时有时可以更加高效。例如访问一个32位整数的低位字节时可以直接通过该整数的地址加1来获取而不需要进行字节顺序的转换。
相关函数
#include arpa/inet.h
功能将32位主机字节序整数转换为网络字节序大端字节序。
uint32_t htonl(uint32_t hostlong);
参数hostlong待转换的32位主机字节序整数。
返回值返回转换后的32位网络字节序整数。
注意事项
用于将主机字节序数据转换为网络字节序以便进行网络通信。
如果主机字节序和网络字节序相同通常是小端字节序的情况下则 htonl 函数不会进行实际的字节序转换直接返回输入参数本身。
在网络编程中发送数据前通常要使用此函数将数据转换为网络字节序。功能将16位主机字节序短整数转换为网络字节序大端字节序。
uint16_t htons(uint16_t hostshort);
参数hostshort待转换的16位主机字节序短整数。
返回值返回转换后的16位网络字节序短整数。
注意事项
用于将主机字节序数据转换为网络字节序以便进行网络通信。
在网络编程中发送数据前通常要使用此函数将数据转换为网络字节序。功能将32位网络字节序大端字节序整数转换为主机字节序。
uint32_t ntohl(uint32_t netlong);
参数netlong待转换的32位网络字节序整数。
返回值返回转换后的32位主机字节序整数。
注意事项
用于将接收到的网络字节序数据转换为主机字节序以便应用程序正确解析和处理数据。
在接收网络数据后通常要使用此函数将数据转换为主机字节序进行处理。功能将16位网络字节序大端字节序短整数转换为主机字节序。
uint16_t ntohs(uint16_t netshort);
参数netshort待转换的16位网络字节序短整数。
返回值返回转换后的16位主机字节序短整数。
注意事项
用于将接收到的网络字节序数据转换为主机字节序以便应用程序正确解析和处理数据。
在接收网络数据后通常要使用此函数将数据转换为主机字节序进行处理。
示例代码
#include stdio.h
#include arpa/inet.h // 包含网络字节序转换函数的头文件int main() {// 定义一个主机字节序的32位整数uint32_t host_long 0x12345678;// 定义一个主机字节序的16位短整数uint16_t host_short 0x1234;// 将主机字节序的整数转换为网络字节序大端字节序uint32_t network_long htonl(host_long);// 将主机字节序的短整数转换为网络字节序大端字节序uint16_t network_short htons(host_short);// 输出转换前后的整数值和短整数值printf(Original Host Long: 0x%x\n, host_long);printf(Network Long (Big Endian): 0x%x\n, network_long);printf(Original Host Short: 0x%x\n, host_short);printf(Network Short (Big Endian): 0x%x\n, network_short);// 将网络字节序的整数转换回主机字节序uint32_t host_long_back ntohl(network_long);// 将网络字节序的短整数转换回主机字节序uint16_t host_short_back ntohs(network_short);// 输出转换回主机字节序后的整数值和短整数值printf(\nNetwork Long (Big Endian): 0x%x\n, network_long);printf(Back to Host Long: 0x%x\n, host_long_back);printf(Network Short (Big Endian): 0x%x\n, network_short);printf(Back to Host Short: 0x%x\n, host_short_back);return 0;
}3.IP地址转换
虽然IP地址本质是一个整形数但是在使用的过程中都是通过一个字符串来描述下面的函数描述了如何将一个字符串类型的IP地址进行大小端转换
3.1 inet_pton 函数
#include arpa/inet.h
int inet_pton(int af, const char *src, void *dst);
功能将点分十进制字符串形式的IP地址转换为网络字节序的二进制IP地址表示。
参数
af地址族Address Family可以是 AF_INET 表示IPv4或 AF_INET6 表示IPv6。
src待转换的点分十进制字符串形式的IP地址。
dst指向存放转换后二进制IP地址的内存空间的指针。
返回值
如果转换成功返回1IPv4或者1IPv6。
如果传入的字符串不是合法的IP地址返回0。
如果发生错误返回-1并设置 errno 指示具体错误。
注意事项
dst 参数应该足够大来容纳转换后的二进制IP地址。
在使用前需要确保正确设置 af 参数以指明是处理IPv4还是IPv6地址。
函数会自动识别并转换点分十进制的IPv4地址和IPv6的十六进制地址。
3.2 inet_ntop 函数
#include arpa/inet.h
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
功能将网络字节序的二进制IP地址表示转换为点分十进制字符串形式的IP地址。
参数
af地址族Address Family可以是 AF_INET 表示IPv4或 AF_INET6 表示IPv6。
src指向存放二进制IP地址的内存空间的指针。
dst用于存放转换后的点分十进制字符串形式IP地址的缓冲区。
size缓冲区 dst 的大小一般建议使用 INET_ADDRSTRLENIPv4地址的最大长度或 INET6_ADDRSTRLENIPv6地址的最大长度。
返回值
如果转换成功返回指向 dst 的指针即转换后的点分十进制字符串形式IP地址。
如果发生错误返回 NULL并设置 errno 指示具体错误。
注意事项
dst 缓冲区应足够大以容纳转换后的IP地址字符串。
函数根据 af 参数的值自动识别并转换二进制IP地址表示。
在使用前要确保 src 指向的内存区域大小足够。
示例代码
#include stdio.h
#include arpa/inet.h
#include errno.hint main() {char ip4_str[] 192.168.1.1;char ip6_str[] 2001:0db8:85a3:0000:0000:8a2e:0370:7334;struct in_addr ip4_addr;struct in6_addr ip6_addr;char ip_str[INET6_ADDRSTRLEN];// 将IPv4字符串转换为二进制格式if (inet_pton(AF_INET, ip4_str, ip4_addr) 0) {perror(inet_pton);return 1;}// 将二进制IPv4地址转换为字符串格式const char *ip4_str_converted inet_ntop(AF_INET, ip4_addr, ip_str, INET_ADDRSTRLEN);if (ip4_str_converted NULL) {perror(inet_ntop);return 1;}printf(IPv4地址: %s\n, ip4_str_converted);// 将IPv6字符串转换为二进制格式if (inet_pton(AF_INET6, ip6_str, ip6_addr) 0) {perror(inet_pton);return 1;}// 将二进制IPv6地址转换为字符串格式const char *ip6_str_converted inet_ntop(AF_INET6, ip6_addr, ip_str, INET6_ADDRSTRLEN);if (ip6_str_converted NULL) {perror(inet_ntop);return 1;}printf(IPv6地址: %s\n, ip6_str_converted);return 0;
}4.socket套接字
4.1相关操作函数
4.1.1 socket 函数
#include sys/types.h
#include sys/socket.h
int socket(int domain, int type, int protocol);
功能创建一个新的套接字。
参数
domain指定协议族常见的有 AF_INETIPv4和 AF_INET6IPv6还有其他如 AF_UNIXUnix域AF_LOCAL本地通信等。
type指定套接字类型如 SOCK_STREAM流式套接字用于TCPSOCK_DGRAM数据报套接字用于UDPSOCK_RAW原始套接字等。
protocol指定具体的协议通常设为0以选择默认协议。
返回值
如果成功返回一个非负的套接字描述符用于后续的套接字操作。
如果失败返回 -1并设置 errno 指示具体错误。
注意事项
创建套接字时需要确保传入正确的 domain、type 和 protocol 参数。
套接字描述符是一个整数用于唯一标识一个套接字应该小心管理防止资源泄露。
在使用完套接字后应该通过 close 函数关闭套接字释放相关资源。
4.1.2 bind 函数
#include sys/types.h
#include sys/socket.h
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能将一个本地地址IP地址和端口号分配给一个套接字。
参数
sockfd套接字描述符由 socket 函数返回。
addr指向包含要绑定到套接字的地址信息的结构体指针通常是 struct sockaddr 结构体。
addrlenaddr 结构体的长度。
返回值
如果成功返回 0。
如果失败返回 -1并设置 errno 指示具体错误。
注意事项
在使用 bind 函数前确保套接字已经创建成功并且填充了正确的地址信息到 addr 结构体中。
只有未被占用的地址才能成功绑定否则会返回错误。
需要特别注意端口号的使用避免与系统中已有的服务冲突。
4.1.3 struct sockaddr结构体
struct sockaddr 是用于存储各种套接字地址的通用结构体在网络编程中广泛使用。它的设计灵活可以适应不同协议族如IPv4、IPv6、Unix域等的地址表示。在写数据的时候不好用。struct sockaddr 的定义通常在 sys/socket.h 头文件中是一个通用的套接字地址结构体。 struct sockaddr {sa_family_t sa_family; // 地址族Address Familychar sa_data[14]; // 地址数据包括IP地址和端口号端口(2字节) IP地址(4字节) 填充(8字节)
};
sa_family用于指定地址的协议族Address Family可以是以下常见的值之一AF_INETIPv4地址族AF_INET6IPv6地址族AF_UNIX 或 AF_LOCALUnix域本地通信其他协议族的值如AF_PACKET等根据具体需要定义。
sa_data存放套接字地址的实际数据部分包括IP地址和端口号等。由于不同协议的地址数据可能不同这里使用了一个固定长度的数组来存储。
struct sockaddr_in 是用于表示IPv4套接字地址的结构体在网络编程中经常使用。它是 struct sockaddr 结构体的一个特定实现用于IPv4地址族。struct sockaddr_in 的定义通常在 netinet/in.h 头文件中用于表示IPv4地址的套接字地址结构体。
struct in_addr
{in_addr_t s_addr;
}; struct sockaddr_in {sa_family_t sin_family; // 地址族 (AF_INET)in_port_t sin_port; // 端口号 (使用网络字节序)struct in_addr sin_addr; // IPv4地址char sin_zero[8]; // 填充字节用于使结构体与 struct sockaddr 兼容
};
sin_family地址族固定为 AF_INET表示IPv4地址族。
sin_port16位端口号使用网络字节序即大端字节序表示。
sin_addrstruct in_addr 类型的结构体用于存储IPv4地址。
sin_zero填充字段使 struct sockaddr_in 的大小与 struct sockaddr 相同用于兼容性。
4.1.4 listen 函数
#include sys/types.h
#include sys/socket.h
int listen(int sockfd, int backlog);
功能将未连接的套接字转换为被动监听状态用于接受客户端的连接请求。
参数
sockfd套接字描述符由 socket 函数返回并且已经通过 bind 绑定了本地地址。
backlog指定同时等待处理的连接请求的最大数量最大值为128
返回值
如果成功返回 0。
如果失败返回 -1并设置 errno 指示具体错误。
注意事项
在调用 listen 函数前套接字必须已经成功绑定到一个本地地址。
backlog 参数指定内核中连接队列的长度影响服务器可以接受的最大连接数。
当有新的连接请求到达时服务器将从队列中取出并处理。
4.1.5 accept 函数
#include sys/types.h
#include sys/socket.h
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能接受客户端的连接请求创建一个新的套接字用于与客户端通信。
参数
sockfd套接字描述符处于监听状态的套接字。
addr可选指向用于存放客户端地址信息的结构体指针通常是 struct sockaddr 结构体。
addrlen可选addr 结构体的长度指针。
返回值
如果成功返回一个新的套接字描述符用于与客户端通信。
如果失败返回 -1并设置 errno 指示具体错误。
注意事项
accept 函数通常在服务器的主循环中调用用于接受新的客户端连接。
如果不需要获取客户端的地址信息可以将 addr 和 addrlen 参数设置为 NULL。
新创建的套接字用于与特定的客户端进行通信应在通信结束后及时关闭。
4.1.6 接收和发送数据函数
#include sys/types.h
#include sys/socket.h
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能将数据发送到连接的套接字。
参数
sockfd套接字描述符指定要发送数据的套接字。
buf指向要发送数据的缓冲区的指针。
len要发送数据的字节数。
flags指定发送操作的标志通常设为 0。
返回值
如果成功返回发送的字节数。
如果失败返回 -1并设置 errno 指示具体错误。
注意事项
send 函数可能会发送比请求的数据少的字节数部分发送应该在循环中调用直到所有数据都被发送。
需要注意处理信号中断EINTR的情况以确保数据完整性和稳定性。ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能从连接的套接字接收数据。
参数
sockfd套接字描述符指定要接收数据的套接字。
buf指向接收数据的缓冲区的指针。
len要接收数据的最大字节数。
flags指定接收操作的标志通常设为 0。
返回值
如果成功返回接收的字节数。
如果连接关闭对于 TCP 套接字返回 0。
如果失败返回 -1并设置 errno 指示具体错误。
注意事项
recv 函数可能会接收比请求的数据少的字节数部分接收应该在循环中调用直到接收到所需的数据或者达到预期的条件。
对于非阻塞套接字需要处理 EAGAIN 或 EWOULDBLOCK 错误。
在使用前确保套接字已经连接或者绑定并且合适地设置了 buf 和 len。#include unistd.h
ssize_t write(int fd, const void *buf, size_t count);
功能向文件描述符 fd 写入数据。
参数
fd文件描述符可以是套接字描述符。
buf指向要写入数据的缓冲区的指针。
count要写入的字节数。
返回值
如果成功返回实际写入的字节数。
如果失败返回 -1并设置 errno 指示具体错误。
注意事项
write 函数通常用于向已连接的套接字写入数据也可以用于向文件、管道等写入数据。
如果 write 返回值小于 count则可能是由于部分写入或者错误发生。
应该在循环中调用 write 直到所有数据都被写入或者处理写入失败的情况。ssize_t read(int fd, void *buf, size_t count);
功能从文件描述符 fd 读取数据。
参数
fd文件描述符可以是套接字描述符。
buf指向存放读取数据的缓冲区的指针。
count要读取的最大字节数。
返回值
如果成功返回实际读取的字节数。
如果已经到达文件末尾对套接字来说通常表示连接关闭返回 0。
如果失败返回 -1并设置 errno 指示具体错误。
注意事项
read 函数通常用于从已连接的套接字读取数据也可以用于从文件、管道等读取数据。
应该在循环中调用 read 直到接收到所需的数据或者处理读取失败的情况。
对于非阻塞套接字需要处理 EAGAIN 或 EWOULDBLOCK 错误。
在使用 socket 套接字进行网络通信时特别是在 UDP 协议中常用的数据发送和接收函数包括 sendto 和 recvfrom。这两个函数与 send 和 recv 在功能上类似但是更适用于无连接的 UDP 套接字也可以用于有连接的套接字。
#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套接字描述符。
buf指向要发送数据的缓冲区的指针。
len要发送的数据字节数。
flags发送标志通常设置为 0。
dest_addr指向目标地址结构体的指针包含目标地址和端口信息。
addrlendest_addr 结构体的长度。
返回值
如果成功返回实际发送的字节数。
如果失败返回 -1并设置 errno 指示具体错误。
注意事项
sendto 适用于无连接的 UDP 套接字也可以用于有连接的套接字。
如果 dest_addr 是 NULL则需要在之前使用 connect 函数连接套接字。
可以用于向多个目标发送数据通过不同的 dest_addr 参数指定。ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);功能从指定地址接收数据。
参数
sockfd套接字描述符。
buf指向存放接收数据的缓冲区的指针。
len缓冲区的大小即最多接收的数据字节数。
flags接收标志通常设置为 0。
src_addr指向发送方地址结构体的指针用于存放发送方的地址信息。
addrlensrc_addr 结构体的长度指针调用前需设置为结构体的实际长度。
返回值
如果成功返回实际接收的字节数。
如果没有可用数据且对方关闭连接返回 0。
如果失败返回 -1并设置 errno 指示具体错误。
注意事项
recvfrom 适用于无连接的 UDP 套接字也可以用于有连接的套接字。
如果套接字已经连接通过 connect 函数则可以将 src_addr 和 addrlen 设置为 NULL。
可以用于从多个发送方接收数据通过 src_addr 参数获取发送方的地址信息。
4.1.7 connect 函数
#include sys/types.h
#include sys/socket.h
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能与指定地址的服务器建立连接。
参数
sockfd套接字描述符即 socket 函数返回的套接字描述符。
addr指向 struct sockaddr 结构体的指针包含要连接的服务器地址信息。
addrlenaddr 结构体的长度。
返回值
如果成功返回 0。
如果失败返回 -1并设置 errno 指示具体错误。
注意事项
在调用 connect 前需要先创建好套接字并填充好服务器的地址信息。
对于阻塞套接字connect 函数可能会阻塞直到连接建立或超时。
对于非阻塞套接字可能返回 EINPROGRESS需要进一步检查连接状态。
4.2 TCP通信流程
TCP是一个面向连接的安全的流式传输协议这个协议是一个传输层协议。 连接导向 TCP 是面向连接的协议通信双方在传输数据前需要先建立连接确保数据可靠传输。连接的建立包括三次握手过程保证了通信双方的可靠性和数据同步性。 可靠性 TCP 提供可靠的数据传输通过序号、确认应答、重传机制等手段来确保数据的完整性和可靠性。数据传输过程中如果发生丢包、出错或者顺序错乱TCP 会进行重传直到数据正确送达目标。 流量控制 TCP 使用滑动窗口协议进行流量控制通过动态调整发送方的发送窗口大小控制发送数据的速率避免数据包丢失和网络拥塞。 有序性 TCP 保证数据传输的有序性发送的数据包按照顺序到达接收端并且按照发送的顺序重组。 服务端和客户端初始化 socket得到文件描述符服务端调用 bind将 socket 绑定在指定的 IP 地址和端口;服务端调用 listen进行监听服务端调用 accept等待客户端连接客户端调用 connect向服务端的地址和端口发起连接请求服务端 accept 返回用于传输的 socket 的文件描述符客户端调用 write 写入数据服务端调用 read 读取数据客户端断开连接时会调用 close那么服务端 read 读取数据的时候就会读取到了 EOF待处理完数据后服务端调用 close表示连接关闭。
这里需要注意的是服务端调用 accept 时连接成功了会返回一个已完成连接的 socket后续用来传输数据。
所以监听的 socket 和真正用来传送数据的 socket是「两个」 socket一个叫作监听 socket一个叫作已完成连接 socket。
成功连接建立之后双方开始通过 read 和 write 函数来读写数据就像往一个文件流里面写东西一样。
4.2.1 示例代码
TCP回显服务器
//服务端程序
#include stdio.h
#include stdlib.h
#include string.h
#include sys/types.h
#include unistd.h
#include arpa/inet.h
#include netinet/in.h
#include ctype.hint main()
{//创建socket//int socket(int domain, int type, int protocol);int lfd socket(AF_INET, SOCK_STREAM, 0);if(lfd0){perror(socket error);return -1;}//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//绑定struct sockaddr_in serv;bzero(serv, sizeof(serv));serv.sin_family AF_INET;serv.sin_port htons(8888);serv.sin_addr.s_addr htonl(INADDR_ANY); //表示使用本地任意可用IPint ret bind(lfd, (struct sockaddr *)serv, sizeof(serv));if(ret0){perror(bind error); return -1;}//监听//int listen(int sockfd, int backlog);listen(lfd, 3);//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);struct sockaddr_in client;socklen_t len sizeof(client);int cfd accept(lfd, (struct sockaddr *)client, len); //len是一个输入输出参数//const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);//获取client端的IP和端口char sIP[16];memset(sIP, 0x00, sizeof(sIP));printf(client--IP:[%s],PORT:[%d]\n, inet_ntop(AF_INET, client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));printf(lfd[%d], cfd[%d]\n, lfd, cfd);int i 0;int n 0;char buf[1024];while(1){//读数据memset(buf, 0x00, sizeof(buf));n read(cfd, buf, sizeof(buf));if(n0){printf(read error or client close, n[%d]\n, n);break;}printf(n[%d], buf[%s]\n, n, buf); for(i0; in; i){buf[i] toupper(buf[i]);}//发送数据write(cfd, buf, n);}//关闭监听文件描述符和通信文件描述符close(lfd);close(cfd);return 0;
}
//客户端代码
#include stdio.h
#include stdlib.h
#include string.h
#include sys/types.h
#include unistd.h
#include arpa/inet.h
#include netinet/in.hint main()
{//创建socket---用于和服务端进行通信int cfd socket(AF_INET, SOCK_STREAM, 0);if(cfd0){perror(socket error);return -1;}//连接服务端//int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);struct sockaddr_in serv;serv.sin_family AF_INET;serv.sin_port htons(8888);inet_pton(AF_INET, 127.0.0.1, serv.sin_addr.s_addr);printf([%x]\n, serv.sin_addr.s_addr);int ret connect(cfd, (struct sockaddr *)serv, sizeof(serv));if(ret0){perror(connect error);return -1;} int n 0;char buf[256];while(1){//读标准输入数据memset(buf, 0x00, sizeof(buf));n read(STDIN_FILENO, buf, sizeof(buf));//发送数据write(cfd, buf, n);//读服务端发来的数据memset(buf, 0x00, sizeof(buf));n read(cfd, buf, sizeof(buf));if(n0){printf(read error or server closed, n[%d]\n, n);break;}printf(n[%d], buf[%s]\n, n, buf);}//关闭套接字cfdclose(cfd);return 0;
}4.3 UDP通信流程
UDP是一个简单的、无连接的、使用数据报的轻量级的传输层协议。 无连接 UDP 是无连接的协议通信双方在传输数据时不需要建立连接可以直接发送数据包。没有连接建立过程因此UDP的开销比TCP小适合对实时性要求较高的应用。 不可靠性 UDP 不提供数据传输的可靠性保证发送数据后不会确认是否到达目标也不会进行重传。发送的数据包可能丢失或者无序到达接收端需要应用层自行处理数据的丢失和重传。 速度和效率 UDP 相比TCP速度更快没有建立连接和维护状态的开销适合实时性要求高、传输数据量小的应用。UDP 的头部开销小每个数据包仅包含基本的必要信息传输效率较高。 广播和多播 UDP 支持广播和多播可以将数据包发送到一个网络中的多个接收端。 UDP通信流程概述
UDP发送方初始化套接字得到文件描述符UDP接收方初始化套接字得到文件描述符UDP接收方调用bind将套接字绑定在指定的IP地址和端口UDP发送方调用sendto发送数据到接收方的地址和端口UDP接收方调用recvfrom接收数据UDP接收方处理请求并调用sendto发送响应数据到发送方UDP发送方调用recvfrom接收响应数据通信结束后发送方和接收方分别调用close关闭套接字。
4.3.1 示例代码
服务器端
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h
#include ctype.h // 包含toupper函数#define PORT 8080
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in server_addr, client_addr;char buffer[BUFFER_SIZE];socklen_t addr_len;int n;// 创建UDP套接字if ((sockfd socket(AF_INET, SOCK_DGRAM, 0)) 0) {perror(socket creation failed);exit(EXIT_FAILURE);}// 设置服务器地址结构memset(server_addr, 0, sizeof(server_addr));server_addr.sin_family AF_INET;server_addr.sin_addr.s_addr INADDR_ANY;server_addr.sin_port htons(PORT);// 绑定套接字到指定IP地址和端口if (bind(sockfd, (const struct sockaddr *)server_addr, sizeof(server_addr)) 0) {perror(bind failed);close(sockfd);exit(EXIT_FAILURE);}printf(Server listening on port %d...\n, PORT);while (1) {// 接收客户端的数据addr_len sizeof(client_addr);n recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)client_addr, addr_len);buffer[n] \0;// 打印客户端地址信息和接收到的数据char client_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);printf(Received message from %s:%d: %s\n, client_ip, ntohs(client_addr.sin_port), buffer);// 将数据转换为大写for (int i 0; i n; i) {buffer[i] toupper(buffer[i]);}// 发送转换后的数据回客户端sendto(sockfd, buffer, n, 0, (struct sockaddr *)client_addr, addr_len);printf(Sent uppercase message to %s:%d: %s\n, client_ip, ntohs(client_addr.sin_port), buffer);}// 关闭套接字close(sockfd);return 0;
}客户端
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h#define PORT 8080
#define BUFFER_SIZE 1024
#define SERVER_IP 127.0.0.1int main() {int sockfd;struct sockaddr_in server_addr;char buffer[BUFFER_SIZE];char recv_buffer[BUFFER_SIZE];socklen_t addr_len;int n;// 创建UDP套接字if ((sockfd socket(AF_INET, SOCK_DGRAM, 0)) 0) {perror(socket creation failed);exit(EXIT_FAILURE);}// 设置服务器地址结构memset(server_addr, 0, sizeof(server_addr));server_addr.sin_family AF_INET;server_addr.sin_port htons(PORT);inet_pton(AF_INET, SERVER_IP, server_addr.sin_addr);printf(Connected to server at %s:%d\n, SERVER_IP, PORT);while (1) {printf(Enter message to send (or exit to quit): );fgets(buffer, BUFFER_SIZE, stdin);buffer[strcspn(buffer, \n)] 0; // 去掉输入的换行符// 如果输入是 exit则退出循环if (strcmp(buffer, exit) 0) {break;}// 发送数据到服务器sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)server_addr, sizeof(server_addr));printf(Message sent to server: %s\n, buffer);// 接收服务器的响应addr_len sizeof(server_addr);n recvfrom(sockfd, recv_buffer, BUFFER_SIZE, 0, (struct sockaddr *)server_addr, addr_len);recv_buffer[n] \0;printf(Server response: %s\n, recv_buffer);}// 关闭套接字close(sockfd);return 0;
}