计算机网络 —— 网络编程(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 (服务端)函数原型返回…...
字玩FontPlayer开发笔记4 性能优化 首屏加载时间优化
字玩FontPlayer开发笔记4 性能优化 首屏加载时间优化 字玩FontPlayer是笔者开源的一款字体设计工具,使用Vue3 ElementUI开发,源代码: github: https://github.com/HiToysMaker/fontplayer gitee: https://gitee.com/toysmaker/fontplayer …...
RabbitMQ案例
1. 导入依赖 <!--AMQP依赖,包含RabbitMQ--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency> 发送消息 注入RabbitTemplate Autowired RabbitT…...
智能工厂的设计软件 应用场景的一个例子:为AI聊天工具添加一个知识系统 之13 方案再探之4:特定于领域的模板 之 div模型(完整版)
前景提要 整个“方案再探”篇 围绕着如何将项目附件文档中Part 1 部分中给出的零散问题讨论整理、重组为一个结构化的设计文档。为此提出了讨论题目: 特定于领域的模板--一个三套接的hoc结构 它是本项目actors 的剧本原型。其地位: 祖传代码脚本模板…...

WebRtc02:WebRtc架构、目录结构、运行机制
整体架构 WebRtc主要分为三层: CAPI层:外层调用Session管理核心层:包括视频引擎、音频引擎、网络传输 可由使用者重写视频引擎:编解码器、视频缓存、视频增强音频引擎:编解码器、音频缓存、回音消除、降噪传输&#x…...

数据结构复习 (顺序查找,对半查找,斐波那契查找,插值查找,分块查找)
查找(检索): 定义:从给定的数据中找到对应的K 1,顺序查找: O(n)的从前向后的遍历 2,对半查找,要求有序 从中间开始查找,每次检查中间的是否正确,不正确就…...

el-input输入框需要支持多输入,最后传输给后台的字段值以逗号分割
需求:一个输入框字段需要支持多次输入,最后传输给后台的字段值以逗号分割 解决方案:结合了el-tag组件的动态编辑标签 那块的代码 //子组件 <template><div class"input-multiple-box" idinputMultipleBox><div>…...

C# 枚举格式字符串
总目录 前言 当前文章为 C# 中的格式设置(格式化字符串) 大全 中的一个小章节。 一、概述 1. 基本信息 可以使用 Enum.ToString 方法,新建表示枚举成员的数字值、十六进制值或字符串值的字符串对象。枚举格式说明符不区分大小写。 二、自定义数字格式说明符详解…...

【51单片机-零基础chapter1】
安装软件(配套的有,不多赘述) 1.管理员身份运行keil和破解软件kegen 将CID代码复制粘贴到 一定要管理员方式,不然会error 插入板子 我的电脑,管理 1.如果是拯救者,查看端口,如果没有则显示隐藏 2.苹果不知道,好像不可以 3.其他电脑在"其他设备找" (注:本人在校已…...

记录:导出功能:接收文件流数据进行导出(vue3)
请求接口:一定要加responseType: blob 后端返回数据: api.js export function export() {return request({url: dev/api/export,method: get,responseType: blob,//一定要加}) } vue: import {export} from /api// 导出 const exportTab…...

基于Spring Boot + Vue3实现的在线汽车保养维修预约管理系统源码+文档
前言 基于Spring Boot Vue3实现的在线汽车保养维修预约管理系统是一种前后端分离架构的应用,它结合了Java后端开发框架Spring Boot和现代JavaScript前端框架Vue.js 3.0的优势。这样的系统可以为汽车服务站提供一个高效的平台来管理客户的预约请求 技术选型 系统…...

PHP框架+gatewayworker实现在线1对1聊天--接收消息(7)
文章目录 接收消息的原理接收消息JavaScript代码 接收消息的原理 接收消息,就是接受服务器转发的客户端消息。并不需要单独创建函数,因为 ws.onmessage会自动接收消息。我们需要在这个函数里进行处理。因为初始化的时候,已经处理的init类型的…...

18.1、网络安全策略分类 流程 内容
目录 网络安全测评概况网络安全测评类型—基于测评目标分类网络安全测评类型—基于实施方式分类网络安全测评类型—基于测评对象保密性分类网络安全等级保护测评内容网络安全测评流程与内容 网络安全测评概况 网络安全测评,它是指参照一定的标准规范要求࿰…...
深入理解连接池:从数据库到HTTP的优化之道
在现代应用开发中,高效的资源管理是关键,其中连接池(Connection Pool)技术起到了至关重要的作用。本文将带你深入了解连接池的概念及其在数据库和HTTP通信中的应用,结合 JDBC 与 Druid 的关系,以及 HttpURL…...

【2025最新计算机毕业设计】基于SpringBoot+Vue智慧养老医护系统(高质量源码,提供文档,免费部署到本地)【提供源码+答辩PPT+文档+项目部署】
作者简介:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容:🌟Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…...
关于使用vue-cropperjs上传一张图后,再次上传时,裁剪的图片不更新的问题
不更新的原因 它与cropperjs不太一样,vue-cropperjs不是一个实例,当页面首次刷新时它就已经创建,即使后面更改了它的某些数据也不会改变,因为浏览器会对dom组件进行缓存。 解决办法 可以使用v-if来控制它的显示和隐藏ÿ…...

学习threejs,导入VTK格式的模型
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.VTKLoader VTK模型加…...

大麦抢票科技狠活
仅供学习参考,切勿再令您所爱的人耗费高昂的价格去购置黄牛票 ⚠️核心内容参考: 据悉,于购票环节,大麦凭借恶意流量清洗技术,于网络层实时甄别并阻拦凭借自动化手段发起下单请求的流量,强化对刷票脚本、刷票软件以及…...
PostgreSQL 表达式
PostgreSQL中的表达式是一种强大的工具,用于在数据库查询中处理和计算数据。它们由一个或多个值、运算符和PostgreSQL函数组合而成,类似于公式,并用于求值【1†source】。 在PostgreSQL中,表达式可以分为不同类型,如布…...

WPF区域导航+导航参数使用+路由守卫+导航日志
背景:使用ContentControl控件实现区域导航是有Mvvm框架的WPF都能使用的,不限于Prism 主要是将ContenControl控件的Content内容在ViewModel中切换成不同的用户控件 下面是MainViewModel: private object body;public object Body {get { retu…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...

C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...

【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...