计算机网络 —— 网络编程(TCP)
计算机网络 —— 网络编程(TCP)
- TCP和UDP的区别
- TCP (Transmission Control Protocol)
- UDP (User Datagram Protocol)
- 前期准备
- listen (服务端)
- 函数原型
- 返回值
- 使用示例
- 注意事项
- accpect (服务端)
- 函数原型
- 返回值
- 注意事项
- connect (客户端)
- 函数原型
- 返回值
- 注意事项
- send 和 recv
- `send()` 函数
- 函数原型
- 返回值
- `recv()` 函数
- 函数原型
- 返回值
- accpect为啥要返回一个新的文件描述符?
我们之前了解过了UDP的网络编程接口,今天我们要来了解一下TCP网络接口。
TCP和UDP的区别
TCP(传输控制协议)和UDP(用户数据报协议)是两种常用的传输层协议,它们用于在网络中传输数据。尽管它们都是基于IP(互联网协议)之上的传输层协议,但两者在设计目标、功能特性以及应用场景上有着显著的区别。
TCP (Transmission Control Protocol)
- 连接导向:TCP 是面向连接的协议,在数据传输前需要建立连接(三次握手),确保通信双方都准备好接收数据。
- 可靠性:TCP 提供可靠的数据传输服务,通过确认机制(ACK)、重传机制和流量控制来保证数据包按序无误地到达接收端。
- 有序交付:TCP 会按照发送顺序将数据包传递给应用层,即使某些数据包后到也会被正确排序。
- 流控与拥塞控制:TCP 实现了复杂的流量控制和拥塞控制算法,如慢启动、拥塞避免等,以优化网络资源利用并防止网络拥塞。
- 高开销:由于提供了多种保障机制,TCP 的头部较大,处理过程也更复杂,因此相对UDP来说具有更高的CPU和带宽开销。
- 适用于场景:适合对数据完整性要求高的应用,例如文件传输(FTP)、电子邮件(SMTP)、网页浏览(HTTP/HTTPS)等。
UDP (User Datagram Protocol)
- 无连接:UDP 是无连接的协议,不需要在发送数据之前建立连接,可以直接发送数据报文。
- 不可靠性:UDP 不提供可靠性保证,它不会重传丢失的数据包,也不保证数据包的顺序。
- 无序交付:UDP 按照接收到的顺序将数据交给应用层,可能会出现乱序现象。
- 低开销:相比TCP,UDP 头部较小,没有复杂的握手过程和确认机制,所以它的处理速度更快,开销更低。
- 适用于场景:适合对实时性要求较高或对少量数据丢失不敏感的应用,如视频流媒体、在线游戏、DNS查询等。
我们要关注的是TCP在发送数据之前要三次握手建立连接,所以TCP和UDP的网络接口主要差别就是在这个建立连接上,大家如果想详细了解一下TCP和UDP之间的区别可以看看这几篇文章:
https://blog.csdn.net/qq_67693066/article/details/139620649
https://blog.csdn.net/qq_67693066/article/details/139623241
https://blog.csdn.net/qq_67693066/article/details/139626168
前期准备
TCP前期的准备跟UDP是差不多的,所以我们这边按照套路写一下就行了:
#pragma once
#include<iostream>
#include<cstring>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>const static uint16_t defaultport = 8888;class TcpServer
{public:TcpServer(const uint16_t port = defaultport):_port(port){}void Init(){//创建套接字_listen_socketfd = socket(AF_INET,SOCK_STREAM,0);if(_listen_socketfd < 0){std::cout << "Create listensocket fail!" << std::endl;}//初始化本地服务器信息struct sockaddr_in local;local.sin_family = AF_INET; //IPV4local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;//绑定if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local))){std::cout << "Bind fail!" << std::endl;exit(1);}std::cout << "Bind successfully and the listensocketfd is " << _listen_socketfd << std::endl;}void Start(){while(true){}}~TcpServer(){}private://端口号uint16_t _port; //监听套接字int _listen_socketfd = -1;
};
#include<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<cerrno>
#include<stdio.h>
#include<cstring>void Usage()
{std::cout <<"Usage ./TcpClient server_ip server_port"<< std::endl;
}int main(int argc,char* argv[])
{if(argc != 3){Usage();return 1;}//创建客户端的套接字int socketfd = socket(AF_INET,SOCK_STREAM,0);if(socketfd < 0){std::cout << "Create socketfd fail" << std::endl;exit(1);}std::cout << "Create socketfd successfully and the socketfd is " <<socketfd << std::endl;//填充服务器端的信息uint16_t serverport = std::stoi(argv[2]);std::string serverip = argv[1];struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);if(inet_pton(AF_INET,serverip.c_str(),&server.sin_addr) == 1){std::cout << "IPV4 converstion is successful!" << std::endl; }else{perror("Invalid IPv4 address");}
}
#include"TcpServer.hpp"
#include<memory>void Usage()
{std::cout << "Usage ./TcpServer port" << std::endl;
}int main(int argc, char* argv[])
{if(argc != 2){Usage();return 1;}//创建智能指针uint16_t serverport = std::stoi(argv[1]);std::unique_ptr<TcpServer> usr = std::make_unique<TcpServer>(serverport);usr->Init();usr->Start();return 0;
}
接下来我们要写的部分就是和UDP不一样的部分了:
listen (服务端)
TCP建立连接的时候,服务器要进入监听状态,监听客户端是否有链接请求,listen就是完成这部分工作的:
listen()
函数是TCP服务器端编程中的一个重要步骤,它用于将套接字转换为监听状态,以便接受来自客户端的连接请求。一旦调用了 listen()
,该套接字就会开始排队等待连接请求,并准备好通过 accept()
来处理这些请求。
函数原型
#include <sys/types.h>
#include <sys/socket.h>int listen(int sockfd, int backlog);
- sockfd:这是由
socket()
函数创建并已经绑定了地址信息(通过bind()
)的套接字描述符。- backlog:这是监听队列的最大长度,即在服务器开始拒绝新的连接之前,可以有多少个未完成的连接(半连接)。这个值并不是绝对的,操作系统可能会根据实际情况调整它。通常,设置一个合理的值即可,例如5到10之间。
返回值
- 如果成功,
listen()
返回 0。- 如果发生错误,则返回 -1,并设置
errno
以指示具体的错误原因。
使用示例
以下是一个完整的C语言代码片段,展示了如何使用 listen()
函数:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8080
#define BACKLOG 10 // 监听队列的最大长度int main() {int server_fd;struct sockaddr_in address;// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");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");close(server_fd);exit(EXIT_FAILURE);}// 将套接字设置为监听状态if (listen(server_fd, BACKLOG) < 0) {perror("listen failed");close(server_fd);exit(EXIT_FAILURE);}printf("Server is listening on port %d\n", PORT);// 接下来可以调用 accept() 来接收连接// ...// 关闭套接字close(server_fd);return 0;
}
注意事项
- 绑定后监听:确保在调用
listen()
之前已经成功调用了bind()
函数来绑定套接字到特定的地址和端口。- 选择合适的
backlog
值:虽然backlog
参数指定了监听队列的最大长度,但实际的队列长度可能会受到操作系统的限制。一般来说,除非有特殊需求,否则不需要设置非常大的backlog
值。- 非阻塞模式:如果你希望
accept()
不会阻塞,可以在调用listen()
之后将套接字设置为非阻塞模式,但这需要额外的处理逻辑来应对可能的 EAGAIN 或 EWOULDBLOCK 错误。
通过 listen()
函数,服务器能够准备接受客户端的连接请求,并通过后续的 accept()
调用来建立与客户端的实际连接。这使得服务器可以同时处理多个客户端的连接请求,而不会因为等待某个客户端而导致其他客户端被忽视。
我们补全代码:
#pragma once
#include<iostream>
#include<cstring>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>const static uint16_t defaultport = 8888;
const static int BACKLOG = 5;
class TcpServer
{public:TcpServer(const uint16_t port = defaultport):_port(port){}void Init(){//创建套接字_listen_socketfd = socket(AF_INET,SOCK_STREAM,0);if(_listen_socketfd < 0){std::cout << "Create listensocket fail!" << std::endl;}//初始化本地服务器信息struct sockaddr_in local;local.sin_family = AF_INET; //IPV4local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;//绑定if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local))){std::cout << "Bind fail!" << std::endl;exit(1);}std::cout << "Bind successfully and the listensocketfd is " << _listen_socketfd << std::endl;//监听if(listen(_listen_socketfd,BACKLOG) < 0){std::cout << "Listen fail!" << std::endl;exit(1);}std::cout << "Listen successfully! and the listensocketfd is " <<_listen_socketfd << std::endl;}void Start(){while(true){}}~TcpServer(){}private://端口号uint16_t _port; //监听套接字int _listen_socketfd = -1;};
accpect (服务端)
accept()
函数用于TCP服务器端编程中,它从已完成连接队列中提取下一个连接请求,并创建一个新的套接字来与客户端通信。这个新的套接字专用于与特定客户端之间的数据交换,而原始的监听套接字则继续等待其他连接请求。
函数原型
#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd:这是由
socket()
创建并已经调用bind()
和listen()
设置为监听状态的套接字描述符。- addr(可选):这是一个指向
struct sockaddr
结构的指针,用于接收客户端地址信息。如果不需要获取客户端地址信息,可以传入NULL
。- addrlen(可选):这是一个指向
socklen_t
类型变量的指针,表示addr
参数所指向结构的大小。调用accept()
之前应该初始化这个值为结构体的大小;函数返回时,它会被更新为实际填充的地址结构的大小。如果addr
是NULL
,那么addrlen
也应该是NULL
。
返回值
- 如果成功,
accept()
返回一个新套接字描述符,该描述符用于与客户端进行通信。- 如果发生错误,则返回 -1,并设置
errno
以指示具体的错误原因。
注意事项
- 阻塞行为:默认情况下,
accept()
是一个阻塞调用,即如果没有待处理的连接请求,它将一直等待直到有新的连接到来。如果你希望accept()
不会阻塞,可以在调用accept()
之前将套接字设置为非阻塞模式。
- 多线程或多进程处理:为了同时处理多个客户端连接,通常需要在每次接受到新连接后启动一个新的线程或子进程来处理该连接,这样主程序可以继续调用
accept()
来接收更多的连接。
- 关闭连接:当与客户端的通信完成后,记得关闭对应的客户端套接字 (
new_socket
)。监听套接字 (server_fd
) 则保持打开状态,继续接收新的连接请求。
#pragma once
#include<iostream>
#include<cstring>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>const static uint16_t defaultport = 8888;
const static int BACKLOG = 5;
class TcpServer
{public:TcpServer(const uint16_t port = defaultport):_port(port){}void Init(){//创建套接字_listen_socketfd = socket(AF_INET,SOCK_STREAM,0);if(_listen_socketfd < 0){std::cout << "Create listensocket fail!" << std::endl;}//初始化本地服务器信息struct sockaddr_in local;local.sin_family = AF_INET; //IPV4local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;//绑定if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local))){std::cout << "Bind fail!" << std::endl;exit(1);}std::cout << "Bind successfully and the listensocketfd is " << _listen_socketfd << std::endl;//监听if(listen(_listen_socketfd,BACKLOG) < 0){std::cout << "Listen fail!" << std::endl;exit(1);}std::cout << "Listen successfully! and the listensocketfd is " <<_listen_socketfd << std::endl;}void Start(){while(true){//接收(抓取链接)struct sockaddr_in temp;memset(&temp,0,sizeof(temp));socklen_t len = sizeof(temp);int new_socketfd = accept(_listen_socketfd,(struct sockaddr*)&temp,&len);char ip_str[INET_ADDRSTRLEN];const char* result = inet_ntop(AF_INET, &(temp.sin_addr), ip_str, sizeof(ip_str));std::string serverip = result ? ip_str : "Invalid address";if(new_socketfd < 0){std::cout << "Accpect fail but try again" << std::endl;continue;}else{std::cout << "Accpect successfully and the new socketfd is "<<new_socketfd << std::endl;}}~TcpServer(){}private://端口号uint16_t _port; //监听套接字int _listen_socketfd = -1;};
通过 accept()
函数,服务器能够有效地管理并发连接,确保每个客户端都能得到及时的服务。
connect (客户端)
connect()
函数用于TCP客户端编程中,它尝试与指定的服务器建立连接。一旦成功建立了连接,客户端就可以通过这个套接字与服务器进行数据交换。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
结构的指针,包含要连接的服务器的地址信息。通常使用struct sockaddr_in
或struct sockaddr_in6
来表示IPv4或IPv6地址。- addrlen:这是
addr
参数所指向结构体的大小(以字节为单位)。对于sockaddr_in
,这通常是sizeof(struct sockaddr_in)
。
返回值
- 如果成功,
connect()
返回 0。- 如果发生错误,则返回 -1,并设置
errno
以指示具体的错误原因。常见的错误包括:ECONNREFUSED
:连接被服务器拒绝。ETIMEDOUT
:连接超时。EINPROGRESS
:在非阻塞模式下,连接正在尝试建立但尚未完成。
#include<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<cerrno>
#include<stdio.h>
#include<cstring>void Usage()
{std::cout <<"Usage ./TcpClient server_ip server_port"<< std::endl;
}int main(int argc,char* argv[])
{if(argc != 3){Usage();return 1;}//创建客户端的套接字int socketfd = socket(AF_INET,SOCK_STREAM,0);if(socketfd < 0){std::cout << "Create socketfd fail" << std::endl;exit(1);}std::cout << "Create socketfd successfully and the socketfd is " <<socketfd << std::endl;//填充服务器端的信息uint16_t serverport = std::stoi(argv[2]);std::string serverip = argv[1];struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);if(inet_pton(AF_INET,serverip.c_str(),&server.sin_addr) == 1){std::cout << "IPV4 converstion is successful!" << std::endl; }else{perror("Invalid IPv4 address");}//发起连接if(connect(socketfd,(struct sockaddr*)&server,sizeof(server))!=0){std::cout << "Connect fail!" << std::endl;exit(1);}else{std::cout << "Connect successfully! and the socketfd is " <<socketfd << std::endl;}close(socketfd);}
注意事项
- 非阻塞模式:如果你希望
connect()
不会阻塞,可以在调用connect()
之前将套接字设置为非阻塞模式。在这种情况下,如果连接尚未完成,connect()
会立即返回-1
并设置errno
为EINPROGRESS
。你需要使用select()
、poll()
或其他方法来检查连接是否已经建立成功。
- 错误处理:确保对
connect()
的返回值进行适当的错误处理。特别是要注意处理那些可能导致连接失败的情况,如服务器不可达或端口未开放等。
- 超时机制:为了防止程序长时间卡在
connect()
上,可以考虑实现超时机制。这可以通过设置套接字选项(如SO_RCVTIMEO
和SO_SNDTIMEO
)或者使用select()
或poll()
来实现。
- 资源管理:无论连接是否成功,都应该确保在适当的时候关闭套接字以释放系统资源。
通过 connect()
函数,客户端能够主动发起与服务器的连接请求,从而开始双向的数据传输过程。这是TCP客户端编程中的关键步骤之一。
如果一切顺利就可以看到这样的结果:
send 和 recv
send()
和 recv()
是用于TCP套接字通信的两个重要函数,分别用于发送和接收数据。它们是BSD套接字API的一部分,在POSIX兼容的操作系统(如Linux、macOS)中广泛使用。
send()
函数
send()
用于通过已建立连接的套接字发送数据。它类似于标准文件I/O中的 write()
函数,但提供了额外的控制选项。
函数原型
#include <sys/types.h>
#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- sockfd:这是由
socket()
创建并已经通过connect()
或accept()
建立了连接的套接字描述符。- buf:指向要发送的数据缓冲区的指针。
- len:要发送的数据长度(以字节为单位)。
- flags:提供对行为的额外控制,常用的标志包括:
MSG_DONTWAIT
:使操作非阻塞,即使套接字本身是阻塞模式。MSG_NOSIGNAL
:防止SIGPIPE信号在写入已关闭的连接时生成。MSG_MORE
:指示有更多数据将被发送,有助于优化Nagle算法的行为。
返回值
- 如果成功,
send()
返回实际发送的字节数。这个值可能小于请求发送的字节数(例如,当套接字缓冲区满时),因此你可能需要循环调用send()
直到所有数据都被发送。- 如果发生错误,则返回 -1,并设置
errno
以指示具体的错误原因。
recv()
函数
recv()
用于从已建立连接的套接字中读取数据。它类似于标准文件I/O中的 read()
函数,也提供了额外的控制选项。
函数原型
#include <sys/types.h>
#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- sockfd:这是由
socket()
创建并已经通过connect()
或accept()
建立了连接的套接字描述符。- buf:指向用来存储接收到的数据的缓冲区的指针。
- len:要接收的最大数据量(以字节为单位)。
- flags:提供对行为的额外控制,常用的标志包括:
MSG_WAITALL
:等待直到接收到请求的所有数据。MSG_DONTWAIT
:使操作非阻塞,即使套接字本身是阻塞模式。MSG_PEEK
:预览数据而不实际移除它(即数据仍然保留在接收队列中)。
返回值
- 如果成功,
recv()
返回实际接收到的字节数。如果返回值为0,则表示对端已经关闭了连接。- 如果发生错误,则返回 -1,并设置
errno
以指示具体的错误原因。
我们将客户端和服务器端的代码补完:
#pragma once
#include<iostream>
#include<cstring>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>const static uint16_t defaultport = 8888;
const static int BACKLOG = 5;
class TcpServer
{public:TcpServer(const uint16_t port = defaultport):_port(port){}void Init(){//创建套接字_listen_socketfd = socket(AF_INET,SOCK_STREAM,0);if(_listen_socketfd < 0){std::cout << "Create listensocket fail!" << std::endl;}//初始化本地服务器信息struct sockaddr_in local;local.sin_family = AF_INET; //IPV4local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;//绑定if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local))){std::cout << "Bind fail!" << std::endl;exit(1);}std::cout << "Bind successfully and the listensocketfd is " << _listen_socketfd << std::endl;//监听if(listen(_listen_socketfd,BACKLOG) < 0){std::cout << "Listen fail!" << std::endl;exit(1);}std::cout << "Listen successfully! and the listensocketfd is " <<_listen_socketfd << std::endl;}void Start(){while(true){//接收(抓取链接)struct sockaddr_in temp;memset(&temp,0,sizeof(temp));socklen_t len = sizeof(temp);int new_socketfd = accept(_listen_socketfd,(struct sockaddr*)&temp,&len);char ip_str[INET_ADDRSTRLEN];const char* result = inet_ntop(AF_INET, &(temp.sin_addr), ip_str, sizeof(ip_str));std::string serverip = result ? ip_str : "Invalid address";if(new_socketfd < 0){std::cout << "Accpect fail but try again" << std::endl;continue;}else{std::cout << "Accpect successfully and the new socketfd is "<<new_socketfd << std::endl;}//开始服务while(true){char buffer[1024]; //缓冲区int n = recv(new_socketfd,buffer,sizeof(buffer)-1,0);if(n > 0){buffer[n] = 0;std::cout << "[" << serverip << "]# " << buffer << std::endl; }else if(n == 0 || n < 0){std::cout << "Client quit" << std::endl;break;}else{std::cout << "Read fail" << std::endl;break;}}}}~TcpServer(){}private://端口号uint16_t _port; //监听套接字int _listen_socketfd = -1;};
#include<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<cerrno>
#include<stdio.h>
#include<cstring>void Usage()
{std::cout <<"Usage ./TcpClient server_ip server_port"<< std::endl;
}int main(int argc,char* argv[])
{if(argc != 3){Usage();return 1;}//创建客户端的套接字int socketfd = socket(AF_INET,SOCK_STREAM,0);if(socketfd < 0){std::cout << "Create socketfd fail" << std::endl;exit(1);}std::cout << "Create socketfd successfully and the socketfd is " <<socketfd << std::endl;//填充服务器端的信息uint16_t serverport = std::stoi(argv[2]);std::string serverip = argv[1];struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);if(inet_pton(AF_INET,serverip.c_str(),&server.sin_addr) == 1){std::cout << "IPV4 converstion is successful!" << std::endl; }else{perror("Invalid IPv4 address");}//发起连接if(connect(socketfd,(struct sockaddr*)&server,sizeof(server))!=0){std::cout << "Connect fail!" << std::endl;exit(1);}else{std::cout << "Connect successfully! and the socketfd is " <<socketfd << std::endl;}while(true){std::string inbuffer;std::getline(std::cin,inbuffer);//向服务端发送信息int n = send(socketfd,inbuffer.c_str(),inbuffer.size(),0);}close(socketfd);}
我们用本地回环测试一下:
我们也可以用windows来测试一下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include<string>
#include<cstdio>
#include<stdio.h>#pragma comment(lib, "Ws2_32.lib")int main() {// 初始化WinsockWSADATA wsaData;int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0) {std::cerr << "WSAStartup failed: " << iResult << std::endl;return 1;}// 创建套接字SOCKET SendSocket = socket(AF_INET, SOCK_STREAM, 0);if (SendSocket == INVALID_SOCKET) {std::cerr << "socket failed: " << WSAGetLastError() << std::endl;WSACleanup();return 1;}// 设置服务器地址和端口sockaddr_in RecvAddr;RecvAddr.sin_family = AF_INET;RecvAddr.sin_port = htons(8888); // 假设服务器在12345端口监听// 将服务器地址从文本转换为二进制形式inet_pton(AF_INET, "43.138.14.12", &RecvAddr.sin_addr); // 换成自己服务器的ip// 连接到服务器iResult = connect(SendSocket, (SOCKADDR*)&RecvAddr, sizeof(RecvAddr));if (iResult == SOCKET_ERROR) {std::cerr << "connect failed: " << WSAGetLastError() << std::endl;closesocket(SendSocket);WSACleanup();return 1;}// 发送消息到服务器while (true){std::string message;std::cout << "Please enter# ";std::getline(std::cin,message);iResult = send(SendSocket, message.c_str(), message.size(), 0);if (iResult == SOCKET_ERROR) {std::cerr << "send failed: " << WSAGetLastError() << std::endl;closesocket(SendSocket);WSACleanup();return 1;}else {std::cout << "Message sent successfully: " << message << std::endl;}}// 关闭套接字closesocket(SendSocket);// 清理WinsockWSACleanup();return 0;
}
accpect为啥要返回一个新的文件描述符?
我们之前编写代码时,我们一开始定义的socket是listen的socket,但是我们执行accpect时会返回一个新的套接字描述符,这是为什么呢?
这里主要是为了支持并发连接:
服务器通常需要同时处理多个客户端连接。如果
accept()
不返回新的文件描述符,而是使用原始监听套接字进行通信,那么每次只能与一个客户端通信,其他客户端将被阻塞,直到当前通信完成。通过为每个新连接创建一个新的文件描述符,服务器可以同时与多个客户端保持独立的通信会话。
listen套接字是负责“揽客”的,只负责抓客户端发来的连接(有点像饭店门口招揽客人),真正提供服务的,是accpect执行后那个新的套接字(饭店里面的服务员才是真正提供服务)
相关文章:
计算机网络 —— 网络编程(TCP)
计算机网络 —— 网络编程(TCP) TCP和UDP的区别TCP (Transmission Control Protocol)UDP (User Datagram Protocol) 前期准备listen (服务端)函数原型返回值使用示例注意事项 accpect (服务端)函数原型返回…...
[Unity Shader] Shader基础光照3:环境光与自发光
在Unity中,光照是场景渲染的关键组成部分。正确使用环境光和自发光能够大大提高场景的真实感和视觉效果。本篇文章将详细介绍Unity中的环境光和自发光的基本概念,以及如何在编辑器和Shader中进行操作和实现。 1. 环境光(Ambient Light) 1.1 环境光的定义 环境光是场景中…...
云原生安全风险分析
一、什么是云原生安全 云原生安全包含两层含义: 面向云原生环境的安全具有云原生特征的安全 0x1:面向云原生环境的安全 面向云原生环境的安全的目标是防护云原生环境中基础设施、编排系统和微服务等系统的安全。 这类安全机制不一定具备云原生的特性…...
Redis 安装与配置指南
Redis 安装与配置指南 目录 安装说明 Linux 安装 Redis 3.0 压缩包上传服务器编译和安装修改配置启动 Redis关闭 Redis 卸载 RedisRedis 集群配置 Master 主库配置启动 Master 节点的 Redis 和 Sentinel客户登录验证Slave 从库配置查看集群数据验证 安装说明 Linux 安装 R…...
C语言Day13(c程序设计小红书+pta)
目录 (一)用函数调用实现,把最小的数字放在最前面,把最大的放在最后边 (二)使数字向后移m位 (三)用户自定义数据类型: (四)候选人计票数 &am…...
C++二十三种设计模式之迭代器模式
C二十三种设计模式之迭代器模式 一、组成二、特点三、目的四、缺点五、示例代码 一、组成 抽象聚合类:存储集合元素,声明管理集合元素接口。 具体聚合类:实现管理集合元素接口。 抽象迭代器类:声明访问和遍历聚合类元素的接口。 …...
【AI游戏】使用强化学习玩 Flappy Bird:从零实现 Q-Learning 算法(附完整资源)
1. 引言 Flappy Bird 是一款经典的休闲游戏,玩家需要控制小鸟穿过管道,避免碰撞。虽然游戏规则简单,但实现一个 AI 来自动玩 Flappy Bird 却是一个有趣的挑战。本文将介绍如何使用 Q-Learning 强化学习算法来训练一个 AI,使其能够…...
VSCode 中的 launch.json 配置使用
VSCode 中的 launch.json 配置使用 在 VSCode 中,launch.json 文件用于配置调试设置,特别是用来定义如何启动和调试你的应用。它允许你配置不同的调试模式、运行参数和调试选项。 基本结构 launch.json 文件位于 .vscode 文件夹内,可以通过…...
深度学习算法:开启智能时代的钥匙
引言 深度学习作为机器学习的一个分支,近年来在图像识别、自然语言处理、语音识别等多个领域取得了革命性的进展。它的核心在于构建多层的神经网络,通过模仿人脑处理信息的方式,让机器能够从数据中学习复杂的模式。 深度学习算法的基本原理…...
Clojure语言的并发编程
Clojure语言的并发编程 引言 在现代软件开发中,并发编程成为了处理多个任务、提高应用效率和响应速度的重要手段。尤其是在多核处理器逐渐成为主流的今天,如何高效利用这些计算资源是每个开发者面临的挑战。Clojure作为一种函数式编程语言,…...
MySQL学习记录1【DQL和DCL】
SQL学习记录 该笔记从DQL处开始记录 DQL之前值得注意的点 字段 BETWEEN min AND max 可以查询区间[min, max]的数值如果同一个字段需要满足多个OR条件,可以采取 字段 IN(数值1, 数值2, 数值3....)LIKE语句 字段 LIKE ___%%% 表示模糊匹配,_匹配一个字段…...
EasyExcel的应用
一、简单使用 引入依赖: 这里我们可以使用最新的4.0.2版本,也可以选择之前的稳定版本,3.1.x以后的版本API大致相同,新的版本也会向前兼容(3.1.x之前的版本,部分API可能在高版本被废弃)&…...
JS控制对应数据隐藏
首先需要获得到所有的input框,并声明一个空对象来存放,遍历所有的复选框,将他们中选中的放入对象,并设置键值为true,然后执行checkFalseValues(result)函数 function hideItem() {let checkboxes $(.setting_box inp…...
【剑指Offer刷题系列】数据流中的中位数
目录 问题描述示例示例 1: 思路解析方法一:使用两个堆(最大堆和最小堆)核心思路详细步骤示例分析优势适用场景 代码实现Python 实现(方法一:使用两个堆) 测试代码复杂度分析方法一:使…...
RabbitMQ高级篇之MQ可靠性 数据持久化
文章目录 消息丢失的原因分析内存存储的缺陷如何确保 RabbitMQ 的消息可靠性?数据持久化的三个方面持久化对性能的影响持久化实验验证性能对比Spring AMQP 默认持久化总结 消息丢失的原因分析 RabbitMQ 默认使用内存存储消息,但这种方式带来了两个主要问…...
C 语言奇幻之旅 - 第16篇:C 语言项目实战
目录 引言1. 项目规划1.1 需求分析与设计1.1.1 项目目标1.1.2 功能需求1.1.3 技术实现方案 2. 代码实现2.1 模块化编程2.1.1 学生信息模块2.1.2 成绩管理模块 2.2 调试与测试2.2.1 调试2.2.2 测试2.2.4 测试结果 3. 项目总结3.1 代码优化与重构3.1.1 代码优化3.1.2 代码重构 3.…...
[笔记] 使用 Jenkins 实现 CI/CD :从 GitLab 拉取 Java 项目并部署至 Windows Server
随着软件开发节奏的加快,持续集成(CI)和持续部署(CD)已经成为确保软件质量和加速产品发布的不可或缺的部分。Jenkins作为一款广泛使用的开源自动化服务器,为开发者提供了一个强大的平台来实施这些实践。然而…...
Git最便捷的迁移方式
#当公司要求git需要迁移时,你是不是感觉到束手无策。今天带来给大家最快,最便捷的迁移方式 这个命令是用于重命名git仓库中的远程仓库名。在这个命令中,我们将远程仓库的名字从"origin"改为"old-origin"。 git remote …...
【颜色分类--荷兰国旗问题】
问题 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums , 原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。必须在不使用库内置的 sort 函数的情况下…...
xrdp连接闪退情况之一
错误核查 首先使用命令vim ~/.xsession-errors,当里面的报错信息为WARNING **: Could not make bus activated clients aware of XDG_CURRENT_DESKTOPGNOME environment variable:Failed to execute child process “dbus-launch” (No such file or directory)&am…...
KubeVirt 进阶:设置超卖比、CPU/MEM 升降配、在线磁盘扩容
前两篇文章,我们分别介绍 Kubevirt 的安装、基本使用 以及 将 oVirt 虚拟机迁移到 KubeVirt,我们留了两个ToDo,一个是本地磁盘的动态分配,一个是固定 IP 的需求,本期我们先解决第一个,本地磁盘的动态分配。…...
(回溯法)leetcode39组合总和
第一个2开头,下面的子节点的集合元素均为2,5,3 但是在5开头,下面的子节点集合元素均为5,3 带着这个图的思路确定i和index的传递值 backtracking(i, nums,8,sum);用的是i而不是i1 // ConsoleApplication3.cpp : 此文件包含 "main" 函数。程序…...
【数据结构】二叉搜索树
目录 1. 二叉搜索树的概念 2. 二叉搜索树的性能分析 3.二叉搜索树的实现 3. 1.二叉搜索树的插入 3.2. 二叉搜索树的查找 3.3. 二叉搜索树的删除 3.4. 二叉搜索树的实现代码 4. 二叉搜索树key和key/value两种使用场景 4.1 key搜索场景: 4.2 key/value搜索场…...
高可用虚拟IP-keepalived
个人觉得华为云这个文档十分详细:使用虚拟IP和Keepalived搭建高可用Web集群_弹性云服务器 ECS_华为云 应用场景:虚拟IP技术。虚拟IP,就是一个未分配给真实主机的IP,也就是说对外提供数据库服务器的主机除了有一个真实IP外还有一个…...
CSS语言的多线程编程
CSS语言的多线程编程 引言 在现代Web开发中,CSS(层叠样式表)被广泛用于给网页添加样式。然而,CSS本身是一种声明性语言,在设计上并没有直接支持多线程编程的功能。实际上,CSS的解析和应用是由浏览器的渲染…...
电脑之一键备份系统(One Click Backup System for Computer)
电脑之一键备份系统 相信使用电脑的的人都遇到过,电脑系统崩溃,开机蓝屏等原因,这个时候你急着用电脑办公,电脑却给你罢工是多么气人了,其实可以给电脑做一个系统备份。 最近每天都有系统蓝屏崩溃,这个实难…...
R语言的正则表达式
R语言中的正则表达式深度解析 正则表达式(Regular Expressions,简称Regex)是一种用于描述字符串匹配规则的工具,广泛应用于数据处理、文本分析、数据清洗等多个领域。在R语言中,正则表达式被广泛应用于字符串的处理和…...
解决el-table表格数据量过大导致页面卡顿问题 又名《umy-ui---虚拟表格仅渲染可视区域dom的神》
后台管理系统的某个页面需要展示多个列表 数据量过多 页面渲染dom卡顿 经调研发现两个组件 pl-table和umy-ui (也就是u-table) 最终决定使用umy-ui 它是专门基于 Vue 2.0 的桌面端组件库 流畅渲染表格万级数据 而且他是对element-ui的表格做了二次优化…...
《机器学习》——贝叶斯算法
贝叶斯简介 贝叶斯公式,又称贝叶斯定理、贝叶斯法则,最初是用来描述两个事件的条件概率间的关系的公式,后来被人们发现具有很深刻的实际意义和应用价值。该公式的实际内涵是,支持某项属性的事件发生得愈多,则该属性成…...
零基础 监控数据可视化 Spring Boot 2.x(Actuator + Prometheus + Grafana手把手) (上)
一、安装Prometheus Releases prometheus/prometheus GitHubhttps://github.com/prometheus/prometheus/releases 或 https://prometheus.io/download/https://prometheus.io/download/ 1. 下载适用于 Windows 的二进制文件: 找到最新版本的发布页面…...
4.STM32F407ZGT6-独立看门狗
参考: 1.正点原子 前言: 看门狗是一个项目或者产品中肯定需要的功能部分,必须会。常见的两种看门狗类型,独立看门狗和窗口看门狗,各有使用的场景。总结记录独立看门狗一些知识点: 1.独立看门狗的概念。&am…...
RHCE实验-nfs及autofs
本次实验的目的:实现服务端的网络文件共享(配置nfs),且实现客户端的自动挂载(配置autofs) 服务端配置: 关闭防火墙和selinux: 安装软件 [rootlocalhost ~]# yum install nfs-utils -y 创建需要被挂载的目…...
docker代理设置
最近遇到国内镜像无法下载的问题,因此需要配置docker代理来使其能够下载镜像 代理设置方法如下: 编辑 /etc/docker/daemon.json 文件: 配置 HTTP 和 HTTPS 代理: {"proxies": {"http-proxy": "http:/…...
死信交换机
什么是死信?什么是死信交换机? 在MQ中未能成功被消费的消息就被称之为死信,而死信交换机就用于存放死信消息。 消息转变成死信消息的原因: 消息被消费者拒绝或者需要重发(nack、reject) nack:消…...
cat命令详解
🏝️专栏:https://blog.csdn.net/2301_81831423/category_12872319.html 🌅主页:猫咪-9527-CSDN博客 “欲穷千里目,更上一层楼。会当凌绝顶,一览众山小。” cat 是 Linux/Unix 中的一个非常常用的命令&…...
路由器的转发表
【4-24】 已知路由器R₁ 的转发表如表T-4-24 所示。 表T-4-24 习题4-24中路由器R₁的转发表 前缀匹配 下一跳地址 路由器接口 140.5.12.64/26 180.15.2.5 m2 130.5.8/24 190.16.6.2 ml 110.71/16 ----- m0 180.15/16 ----- m2 190.16/16 ----- ml 默认 11…...
腾讯云AI代码助手编程挑战赛-古诗词学习
一、作品介绍 在科技与文化深度交融的当下,“腾讯云 AI 代码助手编程挑战赛 - 每日古诗词” 宛如一颗璀璨的新星,闪耀登场。它绝非一场普通的赛事,而是一座连接编程智慧与古典诗词韵味的桥梁。 这项挑战赛以独特的视角,将每日古…...
积分系统的设计
1. 目的 学习是需要正反馈的,这样学员才能有源源不断的动力去继续学习。 为了激励学员,我们需要设定一个学习积分的排行榜系统。优秀的学员给予一定的奖励,比如奖励优惠券。大家互相比拼的,刺激学员持续学习,互相卷起…...
功能篇:spring事务配置
在 Java 应用程序中配置事务管理通常涉及使用 Spring 框架,因为 Spring 提供了强大的事务管理抽象,可以简化事务的配置和管理。Spring 支持两种类型的事务管理:编程式事务管理和声明式事务管理。 编程式事务管理 编程式事务管理是通过编写代…...
单元测试概述入门
引入 什么是测试?测试的阶段划分? 测试方法有哪些? 1.什么是单元测试? 单元测试:就是针对最小的功能单元(方法),编写测试代码对其正确性进行测试。 2.为什么要引入单元测试&#x…...
PySpark学习笔记2-RDD算子,RDD持久化
RDD定义 RDD是弹性分布式数据集,是spark中的最基本的数据抽象,里面的元素可以并行计算 RDD的五大特性 RDD是有分区的,它的分区是数据存储的最小单位 RDD的方法会作用在所有分区上 RDD之间是有依赖关系的 KV型的RDD可以有分区器 RDD的分区会尽…...
windows10下安装Microsoft SQL Server 2016
一、下载安装包 网站:MSDN, 我告诉你 - 做一个安静的工具站 选择需要的版本,点击详细信息,复制ed2k链接,打开eMule或迅雷,新建下载,粘贴链接,开始下载。 下载好的文件是一个.iso镜像文件。 二、…...
开关不一定是开关灯用 - 命令模式(Command Pattern)
命令模式(Command Pattern) 命令模式(Command Pattern)命令设计模式命令设计模式结构图命令设计模式涉及的角色 talk is cheap, show you my code总结 命令模式(Command Pattern) 命令模式&…...
急速了解什么是GPU服务器
GPU服务器是一种专门配置了高性能图形处理器(GPU)的服务器,旨在提供高性能计算、深度学习、科学计算等多种场景的计算服务。与传统的CPU服务器相比,GPU服务器在处理并行密集型计算任务时具有显著优势。本文将详细介绍GPU服务器的定…...
word论文排版常见问题汇总
word论文排版常见问题汇总 常用快捷键: Alt F9 正常模式与域代码模式切换 Ctrl F9 插入域代码 F9 刷新域代码显示,要注意选定后刷新才会有效果 word中在当前列表的基础上修改列表 在使用word时,我们会定义一个列表,并将其链接…...
作业:IO:day3
思维导图 使用3语言编写一个简易的界面 界面如下 1:标准输出流 2:标准错误流 3:文件流 要求: 按1的时候,通过printf输出数据, 按2的时候,通过perror输出数据, 按3的时候将输入写入文…...
H266/VVC 帧内预测 PDPC 技术
位置决定的帧内预测组合 PDPC 在 VVC 中,对于帧内预测的 Planar 模式、DC 模式和几种角度模式需要使用 PDPC (position dependent intra prediction combination) 方法进一步处理。 PDPC 用于 DC 模式、Planar 模式、小于等于水平模式(模式 18) 的角度模式、大于等于…...
微信小程序mp3音频播放组件,仅需传入url即可
// index.js // packageChat/components/audio-player/index.js Component({/*** 组件的属性列表*/properties: {/*** MP3 文件的 URL*/src: {type: String,value: ,observer(newVal, oldVal) {if (newVal ! oldVal && newVal) {// 如果 InnerAudioContext 已存在&…...
Hadoop3.x 万字解析,从入门到剖析源码
💖 欢迎来到我的博客! 非常高兴能在这里与您相遇。在这里,您不仅能获得有趣的技术分享,还能感受到轻松愉快的氛围。无论您是编程新手,还是资深开发者,都能在这里找到属于您的知识宝藏,学习和成长…...
mysql的一些函数及其用法
mysql 1-来自于leetcode1517的题目 表: Users------------------------ | Column Name | Type | ------------------------ | user_id | int | | name | varchar | | mail | varchar | ------------------------已知一个表,它的…...