来自AI助手的总结
C++ Socket编程通过TCP和UDP协议实现网络通信,适用于文件传输、网页浏览、在线游戏等场景。
基本概念
C++ Socket 编程是实现网络通信的一种方式,利用 Socket 接口,程序可以通过计算机网络进行数据的传输与接收。Socket 是一种用于实现通信的抽象概念,它可看作是连接网络中两个通信端点的工具。在网络编程中,Socket 被广泛用于客户端和服务器之间的数据交换。
主要协议
在 C++ Socket 编程中,最常用的传输层协议有两种:TCP(传输控制协议)和 UDP(用户数据报协议)。
-
TCP(Transmission Control Protocol):
- 连接导向: TCP 是面向连接的协议,在数据传输之前,客户端和服务器之间需要建立一个连接(即三次握手)。
- 可靠性: TCP 提供可靠的数据传输,确保数据包按顺序到达且不丢失。
- 流量控制: TCP 是面向字节流的,能够进行流量控制和拥塞控制。
-
UDP(User Datagram Protocol):
- 无连接: UDP 是无连接的协议,数据传输时不需要建立连接。
- 较低的延迟: 由于没有建立连接的开销,UDP 比 TCP 的延迟更低,但不保证数据包的顺序和完整性。
- 适用场景: UDP 适用于一些对实时性要求高但可以容忍丢包的应用,例如视频会议和在线游戏。
协议区别
特性 | TCP | UDP |
---|---|---|
连接方式 | 面向连接 | 无连接 |
可靠性 | 可靠,确保数据的顺序与完整性 | 不可靠,数据包可能丢失或乱序 |
传输方式 | 字节流 | 数据报(Datagram) |
开销 | 较大,需进行三次握手和连接维护 | 较小,没有连接维护开销 |
适用场景 | 文件传输、网页浏览、电子邮件等需要可靠传输的场景 | 实时视频、语音通话、在线游戏等 |
适用场景
-
TCP 适用场景:
- 文件传输: 在 FTP(文件传输协议)中,可靠性是关键,因此 TCP 是首选协议。
- 网络浏览: HTTP 和 HTTPS 协议基于 TCP,当用户浏览网页时,信息的完整性和顺序非常重要。
- 电子邮件: SMTP 和 IMAP 协议基于 TCP,它们确保电子邮件在发送和接收过程中的可靠性。
-
UDP 适用场景:
- 视频流媒体: 在视频会议或在线直播中,实时性是首要考虑因素,使用 UDP 可以减少延迟。
- 在线游戏: 许多实时在线游戏使用 UDP 进行通信,以提高游戏体验,尽量减少延迟。
- DNS 查询: DNS(域名系统)的查询通常使用 UDP,因为它们是短小的数据包,要求迅速回应。
Windows:
TCP:
TCP 服务器的主要步骤包括:
- 加载套接字库,创建套接字(WSAStartup()/socket());
- 创建 Socket: 使用
socket()
函数创建一个用于网络通信的套接字。 - 绑定(Bind): 将服务器的地址和端口绑定到创建的套接字上。
- 监听(Listen): 开始监听客户端的连接请求。
- 接受连接(Accept): 接受来自客户端的连接请求,并返回一个新的套接字用于与该客户端进行通信。
- 处理请求: 读取客户端发送的数据,并发送响应。
- 关闭连接: 完成通信后,关闭套接字。
实现代码(MSVC – C++14 – Release – x64):
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#pragma warning(disable:4996)
int main(int argc, char* argv[])
{
//初始化WSA
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != 0)
{
return 0;
}
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(23333);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//开始监听
if (listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
//循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
printf("等待连接...\n");
sClient = accept(slisten, (SOCKADDR*)&remoteAddr, &nAddrlen);
if (sClient == INVALID_SOCKET)
{
printf("accept error !");
continue;
}
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
//接收数据
int ret = recv(sClient, revData, 255, 0);
if (ret > 0)
{
revData[ret] = '\0';
printf("收到消息:%s\n", revData);
}
//发送数据
char sendData[255];
strcpy_s(sendData, 255, revData);
send(sClient, sendData, strlen(sendData), 0);
closesocket(sClient);
}
closesocket(slisten);
WSACleanup();
return 0;
}
TCP 客户端的主要步骤包括:
- 加载套接字库,创建套接字(WSAStartup()/socket());
- 创建 Socket: 使用
socket()
函数创建一个套接字。 - 设置服务器地址: 设置要连接的服务器的地址和端口号。
- 连接(Connect): 使用
connect()
函数连接到服务器。 - 发送请求: 向服务器发送数据。
- 接收响应: 从服务器接收数据。
- 关闭连接: 完成通信后,关闭套接字。
实现代码(MSVC – C++14 – Release – x64):
#include<winsock2.h>
#include<stdio.h>
#include<iostream>
#include<cstring>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
#pragma warning(disable: 4996)
int main()
{
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
while (true) {
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sclient == INVALID_SOCKET)
{
printf("invalid socket!");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(目标端口号,int);
serAddr.sin_addr.S_un.S_addr = inet_addr(你的服务器IP,字符串形式);
if (connect(sclient, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{ //连接失败
printf("connect error !");
closesocket(sclient);
return 0;
}
string data;
cin >> data;
const char* sendData;
sendData = data.c_str(); //string转const char*
//char * sendData = "你好,TCP服务端,我是客户端\n";
send(sclient, sendData, strlen(sendData), 0);
//send()用来将数据由指定的socket传给对方主机
//int send(int s, const void * msg, int len, unsigned int flags)
//s为已建立好连接的socket,msg指向数据内容,len则为数据长度,参数flags一般设0
//成功则返回实际传送出去的字符数,失败返回-1,错误原因存于error
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if (ret > 0) {
recData[ret] = '\0';
printf("收到回复:%s\n", recData);
}
closesocket(sclient);
}
WSACleanup();
return 0;
}
UDP
UDP服务器流程:
- 加载套接字库,创建套接字(WSAStartup()/socket());
- socket():与客户端一样,服务器首先创建一个UDP套接字,通过调用
socket()
函数。 - bind():将套接字与指定的IP地址和端口绑定。服务器必须绑定到一个特定的端口上,这样才能接收来自客户端的数据。
bind()
是服务器端特有的操作,客户端通常不需要显式调用bind()
。 - recvfrom():服务器使用
recvfrom()
接收客户端发送的数据报,并进入阻塞状态,直到接收到数据为止。 - 处理请求:收到数据后,服务器可以处理这个请求。例如,解析数据、执行相关操作.
- sendto():处理完成后,服务器通过
sendto()
向客户端发送响应数据。 - 继续等待:服务器可以继续调用
recvfrom()
来接收下一个数据请求。
实现代码(MSVC – C++14 – Release – x64):
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2,2);
if(WSAStartup(sockVersion, &wsaData) != 0)
{
return 0;
}
SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(serSocket == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) ==SOCKET_ERROR)
{
printf("bind error !");
closesocket(serSocket);
return 0;
}
sockaddr_in remoteAddr;
int nAddrLen = sizeof(remoteAddr);
while (true)
{
char recvData[255];
int ret = recvfrom(serSocket, recvData, 255, 0, (sockaddr*)&remoteAddr, &nAddrLen);
if (ret > 0)
{
recvData[ret] = 0x00;
printf("接受到一个连接:%s \r\n",inet_ntoa(remoteAddr.sin_addr));
printf(recvData);
}
const char * sendData = "一个来自服务端的UDP数据包\n";
sendto(serSocket, sendData,strlen(sendData), 0, (sockaddr *)&remoteAddr, nAddrLen);
}
closesocket(serSocket);
WSACleanup();
return 0;
}
UDP客户端流程:
- 加载套接字库,创建套接字(WSAStartup()/socket());
- socket():创建一个UDP套接字(Socket)。这是启动UDP通信的第一步,客户端通过调用
socket()
函数生成一个用于通信的套接字。 - sendto():向服务器发送数据。客户端使用
sendto()
函数来将数据报发送到指定的服务器IP地址和端口。这是一个无连接的操作,不需要事先建立连接。 - 等待响应:客户端调用
recvfrom()
函数,进入阻塞状态,等待从服务器返回的数据。recvfrom()
会接收来自服务器的数据报,函数会在接收到数据后解除阻塞。 - recvfrom():接收到服务器返回的数据后,继续处理该数据。
- close():通信完成后,关闭客户端套接字,释放系统资源
实现代码:
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
WORD socketVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(socketVersion, &wsaData) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int len = sizeof(sin);
const char * sendData = "来自客户端的数据包.\n";
sendto(sclient, sendData, strlen(sendData), 0, (sockaddr *)&sin,len);
char recvData[255];
int ret = recvfrom(sclient, recvData, 255, 0, (sockaddr *)&sin,&len);
if(ret > 0)
{
recvData[ret] = 0x00;
printf(recvData);
}
closesocket(sclient);
WSACleanup();
return 0;
}
Linux:
TCP:
TCP 服务器的主要步骤包括:
- 创建 Socket: 使用
socket()
函数创建一个用于网络通信的套接字。 - 绑定(Bind): 将服务器的地址和端口绑定到创建的套接字上。
- 监听(Listen): 开始监听客户端的连接请求。
- 接受连接(Accept): 接受来自客户端的连接请求,并返回一个新的套接字用于与该客户端进行通信。
- 处理请求: 读取客户端发送的数据,并发送响应。
- 关闭连接: 完成通信后,关闭套接字。
实现代码:
#include <iostream> // 引入输入输出库
#include <sys/socket.h> // 引入 Socket 库
#include <netinet/in.h> // 引入 Internet 地址族
#include <unistd.h> // 引入 UNIX 标准库
#include <cstring> // 引入字符串操作库,用于 memset()
int main() {
int server_fd, new_socket; // 声明服务器 Socket 和新的客户端 Socket
struct sockaddr_in address; // 声明用于存储地址信息的结构体
int opt = 1; // 设置选项
int addrlen = sizeof(address); // 地址结构体的大小
const int PORT = 8080; // 服务器监听的端口号
// 创建 Socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == 0) { // 检查 Socket 创建是否成功
std::cerr << "Socket creation failed!" << std::endl; // 输出错误信息
return 1; // 退出程序
}
// 设置 Socket 选项,允许重用地址
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 设置服务器地址信息
address.sin_family = AF_INET; // 使用 IPv4 地址族
address.sin_addr.s_addr = INADDR_ANY; // 允许接收来自任何 IP 地址的连接
address.sin_port = htons(PORT); // 设置监听的端口号
// 将 Socket 绑定到指定地址和端口
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
std::cerr << "Bind failed!" << std::endl; // 输出绑定失败的信息
return 1; // 退出程序
}
// 开始监听传入的连接请求,队列长度为 3
if (listen(server_fd, 3) < 0) {
std::cerr << "Listen failed!" << std::endl; // 输出监听失败的信息
return 1; // 退出程序
}
std::cout << "Server is listening on port " << PORT << "..." << std::endl; // 输出服务器监听状态
// 无限循环,接受客户端连接
while (true) {
// 接受客户端的连接请求
new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
if (new_socket < 0) { // 检查连接是否成功
std::cerr << "Accept failed!" << std::endl; // 输出接受连接失败的信息
continue; // 继续下一次循环
}
// 接收客户端发送的消息
char buffer[1024] = {0}; // 创建接收缓冲区
ssize_t bytes_read = recv(new_socket, buffer, sizeof(buffer), 0); // 接收数据
if (bytes_read > 0) { // 检查是否成功接收数据
std::cout << "Client: " << buffer << std::endl; // 输出客户端发送的消息
// 发送响应消息给客户端
const char* message = "Hello from server"; // 响应消息
send(new_socket, message, strlen(message), 0); // 发送数据
}
// 关闭与客户端的连接
close(new_socket); // 关闭客户端 Socket
}
// 关闭服务器 Socket
close(server_fd); // 关闭服务器 Socket
return 0; // 正常退出程序
}
TCP 客户端的主要步骤包括:
- 创建 Socket: 使用
socket()
函数创建一个套接字。 - 设置服务器地址: 设置要连接的服务器的地址和端口号。
- 连接(Connect): 使用
connect()
函数连接到服务器。 - 发送请求: 向服务器发送数据。
- 接收响应: 从服务器接收数据。
- 关闭连接: 完成通信后,关闭套接字。
实现代码:
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int sock;
struct sockaddr_in server;
// 创建 Socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
std::cerr << "Socket creation failed!" << std::endl;
return 1;
}
// 设置服务器地址
server.sin_family = AF_INET;
server.sin_port = htons(8080);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
// 连接到服务器
if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) {
std::cerr << "Connection failed!" << std::endl;
return 1;
}
// 发送消息
const char* message = "Hello from client";
send(sock, message, strlen(message), 0);
// 接收响应
char buffer[1024] = {0};
recv(sock, buffer, sizeof(buffer), 0);
std::cout << "Server: " << buffer << std::endl;
// 关闭 Socket
close(sock);
return 0;
}
UDP:
UDP服务器流程:
- socket():与客户端一样,服务器首先创建一个UDP套接字,通过调用
socket()
函数。 - bind():将套接字与指定的IP地址和端口绑定。服务器必须绑定到一个特定的端口上,这样才能接收来自客户端的数据。
bind()
是服务器端特有的操作,客户端通常不需要显式调用bind()
。 - recvfrom():服务器使用
recvfrom()
接收客户端发送的数据报,并进入阻塞状态,直到接收到数据为止。 - 处理请求:收到数据后,服务器可以处理这个请求。例如,解析数据、执行相关操作。
- sendto():处理完成后,服务器通过
sendto()
向客户端发送响应数据。 - 继续等待:服务器可以继续调用
recvfrom()
来接收下一个数据请求。
实现代码(Ubuntu 22 C/C++):
#include <iostream>
#include <cstring> // for memset
#include <sys/socket.h> // for socket functions
#include <arpa/inet.h> // for sockaddr_in and inet_ntoa
#include <unistd.h> // for close
#define PORT 8081
#define BUFFER_SIZE 1024
int main() {
int sockfd;
char buffer[BUFFER_SIZE];
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
// 创建UDP Socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 配置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET; // IPv4
server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定到本地所有IP地址
server_addr.sin_port = htons(PORT); // 指定端口号
// 绑定Socket到地址
if (bind(sockfd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
std::cout << "UDP server is listening on port " << PORT << std::endl;
while (true) {
// 接收消息
int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &addr_len);
buffer[n] = '\0'; // 将接收到的数据转换为字符串格式
std::cout << "Client: " << buffer << std::endl;
// 响应消息
const char *response = "Message received";
sendto(sockfd, response, strlen(response), 0, (const struct sockaddr *)&client_addr, addr_len);
}
close(sockfd);
return 0;
}
UDP客户端流程:
- socket():创建一个UDP套接字(Socket)。这是启动UDP通信的第一步,客户端通过调用
socket()
函数生成一个用于通信的套接字。 - sendto():向服务器发送数据。客户端使用
sendto()
函数来将数据报发送到指定的服务器IP地址和端口。这是一个无连接的操作,不需要事先建立连接。 - 等待响应:客户端调用
recvfrom()
函数,进入阻塞状态,等待从服务器返回的数据。recvfrom()
会接收来自服务器的数据报,函数会在接收到数据后解除阻塞。 - recvfrom():接收到服务器返回的数据后,继续处理该数据。
- close():通信完成后,关闭客户端套接字,释放系统资源。
实现代码:
#include <iostream>
#include <string>
#include <cstring> // 使用strerror
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h> // 使用close()
#define PORT 8081 // 定义端口号
#define BUFFER_SIZE 1024 // 定义缓冲区大小
#define IP "110.41.83.50" // 定义服务器IP地址
int main() {
int sockfd;
char buffer[BUFFER_SIZE];
struct sockaddr_in server_addr;
// 创建UDP Socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
std::cerr << "Socket creation failed: " << strerror(errno) << std::endl;
return 1;
}
// 配置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
// 使用inet_pton将IP地址从字符串转换为网络字节顺序
if (inet_pton(AF_INET, IP, &server_addr.sin_addr) <= 0) {
std::cerr << "Invalid address/ Address not supported" << std::endl;
close(sockfd);
return 1;
}
while (true) {
// 发送消息到服务器
std::string message;
std::cout << "Enter message: ";
std::getline(std::cin, message);
// 发送消息
int send_result = sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (send_result < 0) {
std::cerr << "sendto failed: " << strerror(errno) << std::endl;
break;
}
// 接收服务器的响应
socklen_t addr_len = sizeof(server_addr); // 地址长度参数
int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&server_addr, &addr_len);
if (n < 0) {
std::cerr << "recvfrom failed: " << strerror(errno) << std::endl;
break;
}
buffer[n] = '\0'; // 添加字符串结束符
std::cout << "Server: " << buffer << std::endl; // 输出服务器响应
}
// 关闭Socket
close(sockfd);
return 0;
}
© 版权声明
THE END
暂无评论内容