C/C++ Socket编程
在当今世界中,计算机网络在数据传输领域起着重要作用。这是每个程序员都应该了解的一个主题。在计算机网络下,socket编程是编程世界中最重要的主题之一。在这个主题中,我们将讨论socket编程以及在C++中实现的不同的socket编程方法。
在C++中,socket编程是一种将两个或多个节点通过网络连接在一起的方法,以便节点之间可以共享数据而不丢失数据。在这个连接中,一个节点监听连接到特定IP地址的一个端口。当客户端到达服务器时,服务器创建socket监听器。
什么是Socket
让我们通过实时示例来理解什么是socket。Socket是一种提供两个设备间连接的介质。Socket可以是手机充电器提供socket与手机之间的连接,也可以是手机与笔记本电脑之间的连接。通过socket,不同的应用程序可以连接到本地网络的不同端口上。每当创建socket时,服务器指定程序,该程序指定socket和域地址。
Socket是一种用于在不同进程之间交换数据的机制。这些进程可以是连接在网络上的不同设备或同一设备上的进程。一旦socket的连接创建成功,数据可以在双向上发送,并持续进行,直到其中一个终点关闭连接。
客户端-服务器通信的过程
建立客户端-服务器通信需要遵循以下过程:
- Socket: 通过 socket,我们可以创建新的通信。
- Bind: 通过此过程,我们可以将本地地址绑定到 socket 上。
- Listen: 通过此过程,我们可以接受连接。
- Accept: 通过此过程,我们可以阻塞传入连接,直到请求到达。
- Connect: 通过此过程,我们可以尝试建立连接。
- Send: 借助此过程,我们可以发送数据到网络上。
- Receive: 通过此过程,我们可以接收网络上的数据。
- Close: 通过此过程,我们可以释放网络连接。
服务器套接字创建的阶段
我们可以按以下步骤创建服务器套接字:
- int socketcr: Socket(domain, type, protocol)
- Socketcr: 它是一种整数类型,类似于文件处理器。
- Domain: 它是一种通信域,是一种整数类型。
- Type: 它是一种通信类型。
- SOCK_DGRAM: 它是一种不可靠且无连接的 UDP 类型。
- Protocol: 它用于为 IP 地址分配协议值,该值为 0。协议值类似于数据包 IP 标头中的协议字段中的值。
什么是连接
连接是两台机器之间的一种关系,这两台机器上的软件彼此了解。这两个软件知道如何与对方建立连接;换句话说,我们可以说这两个软件知道如何在网络上发送比特。套接字的连接意味着两台机器应该彼此了解所有的信息,如电话号码、IP 地址和 TCP 端口。
套接字是一种类似于文件的对象,它允许程序接受传入连接并允许它们发送或接收传入连接。此外,它是分配给服务器进程的一种资源。
服务器可以借助 socket() 创建套接字。该套接字不能与其他处理器共享。
- Setsockopt: 通过Setsockopt,我们可以操作套接字的各种选项,这些选项由套接字的文件描述符引用。该过程是完全可选的。通过Setsockopt,我们可以重用客户端和服务器的端口和地址。当服务器出现“地址已经在使用中”的错误时,我们可以通过Setsockopt来防止。
- Bind: 我们可以使用bind函数将套接字与地址和端口绑定。此操作在创建套接字之后进行。例如,如果我们尝试将服务器与本地主机绑定,则使用INADDR_ANY来定义服务器的IP地址。
- Listen: 我们可以使用listen()函数创建一个连接模式套接字。连接模式套接字的示例是SOCK_STREAM。这可以通过套接字参数进行定义。它用于接受传入连接并执行传入连接的队列操作和传入连接的积压。当传入连接请求服务器确认时,套接字被放置为被动模式。服务器的backlog参数指的是它不能同时允许多个连接到服务器。如果有新的传入连接且队列已满,则服务器会返回“ECONNREFUSED”错误。使用listen()将传入连接保持在等待状态,当队列为空时,调用所有传入连接到服务器。
- Accept: 通过accept()系统调用,我们可以创建基于连接的套接字。一些基于连接的套接字是SOCK_STREAM和SOCK_SEQPACKET。它提取所有首次到达的传入连接并允许其请求发送到服务器。通过创建新套接字的另一个参数,无法倾听新连接。
客户端的阶段
- 套接字连接: 与创建服务器的方法完全相同。
- 连接: 我们可以使用connect()系统调用来初始化与套接字的连接。如果套接字的参数是SOCK_DGRAM类型,则可以使用connect()将数据报定义为永久性的。如果套接字是SOCK_STREAM类型,则可以尝试与服务器建立另一个连接。使用connect()函数,我们还可以为外部关联创建一个连接。如果套接字未绑定,则系统为本地关联分配唯一值。当系统成功完成调用时,套接字准备好发送或接收任何类型的数据。
- 发送/接收: send()和recv()函数可以执行以下操作:
- 可以通过套接字进行数据通信。
- 存储缓冲区可以存储有关地址的数据,如数据的地址和缓冲区的地址。
- 处理缓冲区的大小,如数据的长度和缓冲区的长度。
- 处理标志,指示数据将如何发送。
建立套接字连接的步骤
它在不同客户端和服务器之间建立连接。但是客户端和服务器都可以处理套接字连接。每个进程都必须为自己的套接字建立连接。
在客户端建立套接字的步骤如下:
- 使用socket()系统调用创建套接字。
- 然后,我们必须使用system()调用与服务器的套接字地址建立连接。
- 然后我们必须发送和接收数据。我们可以使用各种方法来实现这一点。我们可以使用read()和write()函数来实现。
在服务器端建立套接字的步骤如下:
- 首先,使用socket()系统调用创建套接字。
- 然后,使用bind()系统调用将套接字绑定到地址上。地址包含主机机器上服务器套接字的端口号。
- 然后,使用listening()系统调用监听连接。
- 然后,服务器使用accept()系统调用接受传入连接。它还会阻塞所有传入命令,直到客户端连接到服务器为止。
- 然后开始发送和接收数据的过程。
连接多个客户端而不使用多线程
在各种示例中,我们可以看到单个用户如何与服务器连接。在当今的编程世界中,通过不同的套接字连接多个用户到服务器上。
有各种方法可以实现这一点。其中之一是多线程。通过多线程的帮助,我们可以实现这一点。我们可以使用select()函数来实现多线程处理。
示例
客户端代码:
// Client side C/C++ program to demonstrate Socket
// programming
#include
#include
#include
#include
#include
#define PORT 8080
int main(int argc, char const* argv[])
{
int sock = 0, valread, client_fd;
struct sockaddr_in serv_addr;
char* hello = "Hello from client";
char buffer[1024] = { 0 };
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);
// Convert IPv4 and IPv6 addresses from text to binary
// form
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)
<= 0) {
printf(
"\nInvalid address/ Address not supported \n");
return -1;
}
if ((client_fd
= 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");
valread = read(sock, buffer, 1024);
printf("%s\n", buffer);
// closing the connected socket
close(client_fd);
return 0;
}
Code for server:
// Server side C/C++ program to demonstrate Socket
// programming
#include
#include
#include
#include
#include
#include
#define PORT 8080
int main(int argc, char const* argv[])
{
int server_fd, new_socket, valread;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = { 0 };
char* hello = "Hello from server";
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// Forcefully attaching socket to port 8080
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);
// Forcefully attaching socket to port 8080
if (bind(server_fd, (struct sockaddr*)&address,
sizeof(address))
< 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
if ((new_socket
= accept(server_fd, (struct sockaddr*)&address,
(socklen_t*)&addrlen))
< 0) {
perror("accept");
exit(EXIT_FAILURE);
}
valread = read(new_socket, buffer, 1024);
printf("%s\n", buffer);
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent\n");
// closing the connected socket
close(new_socket);
// closing the listening socket
shutdown(server_fd, SHUT_RDWR);
return 0;
}
编译中:
输出结果:
Socket编程的用途
Socket程序用于不同系统上运行的各个进程之间的通信。它通常用于创建客户端-服务器环境。本文提供了创建服务器和客户端程序以及示例程序所使用的各种函数。
在示例中,客户端程序向服务器发送文件名,服务器将文件的内容发送回客户端。Socket编程通常涉及基本通信协议,如TCP/UDP和ICMP之类的原始套接字。与底层协议(如HTTP/DHCP/SMTP等)相比,这些协议的通信开销较小。
客户端和服务器之间的一些基本数据通信包括:
- 文件传输:发送文件名并获取文件。
- 网页:发送URL并获取页面。
- 回显:发送一条消息并获取回复。
缺点
- C++只能与所请求的计算机进行通信,不能与网络上的其他计算机通信。
- Socket只允许发送原始数据。这意味着客户端和服务器需要机制来解释数据。