linux网络编程基础api
-
socket
地址api:ip地址和端口对,成为soccket
地址。 -
socket
基础api:sys/socket.h
中,包括创建、命名、监听socket
;接受连接、发起连接、读写数据、获取地址信息、检测带外标记、读取设置socket
选项 -
网络信息api:主机名和
ip
地址之间的转换、服务名称和端口号之间的转换。netdb.h
中。
socket通信过程
- server端
创建socket()->绑定地址和端口bind()->监听连接listen()->接受连接accept()->发送数据send()->关闭socket close() - client
创建socket()->IP地址转换inet_pton()->连接到服务端connect()->接收数据read()->关闭socket close()
-
TCP:可靠传输,三次握手建立连接,传出去一定接受的到(如聊天软件);
-
UDP:不可靠传输,不需要建立连接,只管发送,实时性好(如视频会议);
-
套接字:表示通信的端点。就像用电话通信,套接字相当于电话,IP地址相当于总机号码,而端口号则相当于分机号码。
TCP:
UDP:
socket地址
-
协议族和地址
+=========+=========+==============+
| 协议族 | 地址族 | 描 述 |
+=========+=========+==============+
| PF_UNIX | AF_UNIX |UNIX本地域协议族|
+=========+=========+==============+
| PF_INET | AF_INET | TCP/IPv4协议族 |
+=========+=========+==============+
| PF_INET6 | AF_INET6 | TCP/IPv6协议族 |
+=========+=========+==============+ -
tcp/ip
协议族有sockaddr_in
和sockaddr_in6
两个专用socket
地址结构体,分别用于ipv4和ipv6
。
// TCP/IPv4协议族
struct sockaddr_in {
sa_family_t sin_family; /* 地址族 */
in_port_t sin_port; /* Port number.*/
struct in_addr sin_addr; /*Internet address. */
};
struct in_addr {
in_addr_t s_addr;
};
// TCP/IPv6协议族
struct sockaddr_in6 {
sa_family_t sin6_family; /* 地址族 */
in_port_t sin6_port; /* Transport layer port # */
uint32_t sin6_flowinfo; /* IPv6 flow information = 0*/
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* IPv6 scope-id */
};
struct in6_addr {
unsigned charsa_addr[16]; /* IPv6要用网络字节序表示 */
};
ip转换函数
-
将点分十进制的 IPv4 地址转换为网络字节序整数表示的 IPv4 地址,是为了方便在网络通信中使用。整数形式的 IP 地址更容易存储、传输和比较。
-
将
“192.168.1.1”
转换为010101···的值
# include <arpa/inet.h>
in_addr_t inet_addr( const char* strptr); //将点分十进制的ipv4转化为网络字节序整数表示的ipv4
int inet_aton(const char * cp, struct in_addr* inp); //与上功能相同,但是转化结果存储与参数imp的地址结构中。返回1或0 表示成功或失败。
char* inet_ntoa( struct in_addr in ); //将网络字节序整数转化为点分十进制ipv4,该函数内部用一个静态变量存储静态结果,因此是不可重入的。
创建socket
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
-
domain
:指定要使用的协议族,常用的有AF_INET
(IPv4 地址族)和AF_INET6
(IPv6 地址族)。 -
type
:指定套接字的类型,常用的有SOCK_STREAM
(流套接字,用于TCP
)和SOCK_DGRAM
(数据报套接字,用于UDP
)。 -
protocol
:指定要使用的协议,通常为 0,表示使用默认协议。 -
返回值:成功创建返回 0,失败返回 -1。
函数作用:socket
函数用于创建一个套接字(socket
),并返回一个文件描述符,该描述符可以用于后续的套接字操作,如绑定地址、监听连接、发送和接收数据等。
绑定地址和端口
struct sockaddr_in address;
address.sin_family = AF_INET; // TCP/IPv4 AF_INET6:TCP/IPv6
address.sin_addr.s_addr = INADDR_ANY; // 地址
address.sin_port = htons(8080); // 端口号
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) == -1) {
std::cerr << "Error: Failed to bind\n";
return 1;
}
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
sockfd
:要绑定地址的套接字文件描述符。 -
addr
:要绑定的地址信息,通常是一个 struct sockaddr 结构体指针,需要进行类型转换。 -
addrlen
:地址信息的长度。 -
返回值:成功绑定返回 0,失败返回 -1。
函数作用:bind
函数将一个套接字绑定到一个地址,使得该套接字可以在网络中被标识。对于服务器程序来说,通常在调用 bind 后还需要调用 listen
函数开始监听连接。对于客户端程序来说,通常不需要显式调用 bind
函数。
监听连接
int listen(int sockfd, int backlog);
if (listen(server_fd, 3) == -1) {
std::cerr << "Error: Failed to listen\n";
return 1;
}
sockfd
:套接字文件描述符,即要监听的套接字。backlog
:指定在拒绝新连接之前,操作系统可以挂起的最大连接数。- 返回值:成功返回 0,失败返回 -1。
函数作用:listen
函数将指定的套接字设置为监听状态,使其可以接受连接。backlog
参数指定操作系统可以挂起的最大连接数,超过这个数目的连接将被拒绝。通常在服务器端使用,用于开始接受客户端的连接请求。
接受连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int addrlen = sizeof(address);
int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
if (new_socket == -1) {
std::cerr << "Error: Failed to accept connection\n";
return 1;
}
-
sockfd
:监听套接字的文件描述符,即调用listen
函数后返回的套接字。 -
addr
:指向sockaddr
结构的指针,用于存储连接到的客户端的地址信息。 -
addrlen
:sockaddr
结构的大小,在调用accept
函数前,需将其初始化为sizeof(struct sockaddr)
。 -
返回值:返回一个新的套接字文件描述符,用于与客户端通信。如果出现错误,则返回 -1。
函数作用:accept
函数用于接受客户端的连接请求。当有客户端连接到服务器时,accept
函数会创建一个新的套接字,并返回该套接字的文件描述符,服务器可以使用该文件描述符与客户端进行通信。同时,addr
参数用于存储连接到的客户端的地址信息。
发送数据给客户端
const char *message = "Hello from server";
send(new_socket, message, strlen(message), 0);
std::cout << "Message sent to client\n";
sockfd
:要发送数据的套接字文件描述符。buf
:指向要发送数据的缓冲区。len
:要发送数据的长度。flags
:发送标志,通常设置为 0。- 返回值:成功时返回发送的字节数,失败时返回 -1。
函数作用:send
函数用于向指定套接字发送数据。它将缓冲区 buf
中的 len
字节数据发送到套接字 sockfd
。函数的 flags
参数通常设置为 0,表示没有特殊标志
关闭socket
close(new_socket);
close(server_fd);
使用 close 函数关闭这个套接字,释放相关的资源。
- 释放系统资源:关闭套接字会释放操作系统为其分配的资源,包括文件描述符等。
- 避免资源泄漏:如果不关闭套接字,可能会导致资源泄漏,消耗系统资源并降低性能。
- 表示通信结束:关闭套接字可以告知对方通信结束,同时也能清理本地的通信状态。
在这段代码中,new_socket
是一个整数类型的变量,但它关联了一个与客户端通信的套接字文件描述符。在调用 accept
函数后,操作系统会创建一个新的套接字并返回其文件描述符,我们将这个文件描述符保存在 new_socket
中。因此,虽然 new_socket
是一个整数类型的变量,但它实际上是用来代表与客户端通信的套接字的文件描述符。
连接到服务端
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
std::cerr << "Invalid address/ Address not supported\n";
return 1;
}
if (connect(client_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
std::cerr << "Connection failed\n";
return 1;
}
int inet_pton(int af, const char *src, void *dst);
af
:指定地址族,常用的有AF_INET
(IPv4 地址族)和AF_INET6
(IPv6 地址族)。src
:要转换的点分十进制或十六进制字符串形式的 IP 地址。dst
:用于存储转换后的二进制形式 IP 地址的内存地址。- 返回值:如果转换成功,返回 1(对于 IPv4 地址)或 0(对于 IPv6 地址)。如果发生错误,返回 -1。
函数作用:inet_pton
函数用于将点分十进制或十六进制字符串形式的 IP
地址转换为二进制形式的 IP 地址表示。在这个示例中,它将字符串形式的 IPv4
地址 "127.0.0.1"
转换为二进制形式,并将结果存储在 serv_addr.sin_addr
中。如果转换成功,则条件 inet_pton(...) <= 0
将为假,否则为真。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
:套接字文件描述符,表示要连接的套接字。addr
:指向要连接的目标地址信息的 sockaddr 结构体指针。addrlen
:sockaddr
结构体的大小。- 返回值:成功返回 0,失败返回 -1。
函数作用:connect
函数用于向指定的目标地址发起连接请求。在客户端中,通常在创建套接字后调用 connect
函数,指定要连接的服务器的地址和端口号,以建立与服务器的连接。如果连接成功建立,则可以通过该套接字进行数据传输。
从服务端接收数据
ssize_t read(int fd, void *buf, size_t count);
read(client_fd, buffer, 1024);
std::cout << "Message from server: " << buffer << std::endl;
fd
:文件描述符,即要从中读取数据的文件或套接字。buf
:指向存储读取数据的缓冲区的指针。count
:要读取的最大字节数。- 返回值:返回实际读取的字节数。如果返回 0,表示已经读到文件末尾(对套接字则表示连接已关闭)。如果返回 -1,表示出现错误。
函数作用:read
函数用于从文件描述符 fd
指定的文件或套接字中读取数据,将读取的数据存储到 buf
指向的缓冲区中。它会尽可能读取 count
指定的字节数,但可能会因为文件末尾或其他原因而读取少于 count
字节的数据。
代码
server.cpp
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
// 创建socket
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
std::cerr << "Error: Failed to create socket\n";
return 1;
}
// 绑定地址和端口
struct sockaddr_in address;
int addrlen = sizeof(address);
address.sin_family = inet_addr("100.10.10.1");//AF_INET; // TCP/IPv4 AF_INET6:TCP/IPv6
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) == -1) {
std::cerr << "Error: Failed to bind\n";
return 1;
}
// 监听连接
if (listen(server_fd, 3) == -1) {
std::cerr << "Error: Failed to listen\n";
return 1;
}
// 接受连接
int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
if (new_socket == -1) {
std::cerr << "Error: Failed to accept connection\n";
return 1;
}
// 发送数据给客户端
const char *message = "Hello from server";
send(new_socket, message, strlen(message), 0);
std::cout << "Message sent to client\n";
// 关闭socket
close(new_socket);
close(server_fd);
return 0;
}
client.cpp
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
// 创建socket
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1) {
std::cerr << "Error: Failed to create socket\n";
return 1;
}
// 连接到服务端
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { //IP地址转换
std::cerr << "Invalid address/ Address not supported\n";
return 1;
}
if (connect(client_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
std::cerr << "Connection failed\n";
return 1;
}
// 从服务端接收数据
char buffer[1024] = {0};
read(client_fd, buffer, 1024);
std::cout << "Message from server: " << buffer << std::endl;
// 关闭socket
close(client_fd);
return 0;
}
上面内容比较简单基础,这篇socket网络通信包含TCP和UDP,以供参考
【C++】基础:网络编程介绍与TCP&UDP示例_c++网络编程-CSDN博客
抛弃别人的目光才是自由的开始!