SOCKET常用的函数和开发模式(2)

本文介绍了使用 Windows Socket 进行网络通信的开发模式,重点讨论了多对一和多对多场景下的实现方法。通过使用 select 函数和线程安全的缓冲区,确保了数据收发的稳定性和安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

想不到我要打这个文章两次,刚才对自己的电脑太自信,直接在网页上写,果然系统死机了。不得不重复的写这个文章。
我开始佩服那些写blog成千上百万次点击率的达人们,因为要坚持做一件事情很难,尤其是你想讲这件事情做好更难,这个第二篇我写的时间已经是2008年了。上一篇写的是windows socket的使用方法,这个我将写一下windows socket的开发模式,.
使用windows socket进行通信时,个人认为无非分为三种情况
:一对一,多队一,多对多。对于“一对一”这种情况而言,应用程序只需要一个端口对应另外一个对应的端口,是所有开发模式中最简单的一个,所以也无所谓任何的开发模式,暂时不提(当然具体的应用可能和后面提及到的开发模式有关系)。对于“多对一”和“多对多”两种模式来说,应用程序都需要管理多个端口的通信,所以两种的开发模式基本上是一致的。具体应用程序采用“一对一”还是“多对一”,由具体的应用场景来决定的,例如防火墙穿透,端口范围限制等等。下面以“多对一”来介绍一种比较实用安全的开发模式。
1.当系统需要控制多个端口时,最好的做法是用select函数来操作,select函数可以用于判断端口的数据状态.需要一个线程安全的数组来统一管理所有需要监听的端口,然后利用结构fd_set来操作.
 在 程序中负责socket通信的需要有两个线程,一个读线程-用于接受数据,一个写线程-用于发送数据.为了保证
 数据的安全性,还需要有两个线程安全的缓冲区,一个用于将从端口读取到数据存放起来交由应用层来进行处理,减少阻塞一个用于应用层需要发送数据的存放(那么在外部的应用层对应的存在两个操作或者说线程). 给出一个具体的例子程序来说明

void UDPTransfer::doRead()                //接收数据线程函数
{
 int wait_fd ;
 fd_set read_fds;
 struct timeval tv;

 while(_RecData->Running() )
 {
  {
   AutoLock pLock(&m_lock);            //自定义的锁,用于线程安全,下同
   wait_fd = _build_fds(&read_fds);  //建立对应的fd_set
  }

  if( Sock_Wait(wait_fd,&read_fds,0,&tv) <= 0)  //select函数判断,是否存在需要读取的数据
   continue;

  ReadData(&read_fds);
 }
}

int UDPTransfer::_build_fds(fd_set *fds)
{
 int maxfd = -1;
 memset(fds,0,sizeof(fd_set));
 {
  AutoLock pLock(&m_lock);
  for(int i= 0;i<FD_SETSIZE;i++)          //注意FD_SETSIZE这个宏
  {
   if( m_sockToMGArray[i] <= 0 )
    continue;
   int socketID =  m_sockToMGArray[i];
   if(socketID > maxfd)
    maxfd = socketID;
   FD_SET(m_sockToMGArray[i],fds);
  }
 }
 return maxfd;
}

int UDPTransfer::Sock_Wait(int maxfd,fd_set * readfds,fd_set * writefds,struct timeval * ptv)
{
 if( maxfd < 0 )
 {
  if( readfds )
   Thread_Sleep(100);    //自定义线程sleep函数
  return 0;
 }
 ptv->tv_sec = 0;
 ptv->tv_usec = 100 *1000; //100ms  设定阻塞的时间
 return select(maxfd+1,readfds,writefds,0,ptv);
}

int UDPTransfer::ReadData(fd_set *fds)
{
 SOCKET sock;
 int len = 0;
 for( int i=0;i<FD_SETSIZE;i++ )
 {
  {
   AutoLock pLock(&m_lock);
   sock = m_sockToMGArray[i];
  }
  if(sock <= 0)
   continue;
  if( !FD_ISSET(sock,fds) )
   continue;
  len =0;
  memset(__RecBuf,0,sizeof(__RecBuf));
  len = recv(sock,__RecBuf,sizeof(__RecBuf),0);
  if (len>0)
  {
   _pLog->writeLog(5,"UDP Receive Data from Socket[%d],size=[%d]",sock,len);
   _pManager->Ondata(sock,__RecBuf,len);        //将数据放入缓存区
  }
 }
 return 1;
}

上述的运行原理,应该比较清楚了,将所有需要监听socket用m_sockToMGArray来管理(如果不要继续监听了,将对应的socket从m_sockToMGArray中移出,所以需要保证m_sockToMGArray的线程安全性)然后建立对应的fd_set,然后调用select函数来检测所有监听端口的状态.如果有数据到达的端口,则读取,将读取的数据放入缓存区交由应用层来处理.
注意FD_SETSIZE在头文件winsock2.h中定义为64,默认一个fd_set,能够支持64个端口,可以直接修改头文件将这个字修改为更大,但是不建议这么做.如果出现了需要监听socket>64的情况,就新建一对,发送和接收的线程,然后由一个总的线程来管理,这就是所谓的线程池的概念,根据需要可以共享发送和接收的缓冲区.
特别的,因为阻塞方式的connect函数,在网络状态比较差的情况(例如经过VPN等),会等待甚至达到30秒.那么需要在作连接操作的时候作好超时保护.

写tech-blog真的好辛苦的事情,每次不希望自己在敷衍,所以用心写,发现耗时很多.只要自己某一天忘记了一下,权当电子记事本好了. 

<think>首先,用户的问题是:“我想了解C++中socket相关函数的作用意义 请问C++ socket编程中常用函数的功能使用方法”。这是一个关于C++ socket编程的查询,具体是关于常用函数的功能使用方法。 根据系统级指令,我需要: - 所有行内数学表达式使用$...$格式(但在这个上下文中,可能没有数学表达式,所以可以忽略)。 - 独立公式使用$$...$$格式并单独成段(同样,可能不适用)。 - LaTeX语法正确(确保任何数学表达式的正确性)。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层指令: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用:提供了四个引用,涉及C++ socket编程的函数解释。我需要参考这些引用,但用户说“上述引用仅供参考,并不是用户真正的需求”,所以我应该基于这些引用构建回答,但确保内容真实可靠。 引用总结: - [^1]: 解释了socket()函数,包括参数。 - [^2]: 关于socket的基本概念。 - [^3]: 提供了socket()函数的详细解释比喻。 - [^4]: 提到了bind()函数。 用户的问题是关于“常用函数的功能使用方法”,所以我需要覆盖多个函数,不仅仅是socket()bind()。 常见的socket编程函数包括: - socket(): 创建套接字。 - bind(): 绑定地址端口。 - listen(): 监听连接(用于服务器)。 - accept(): 接受连接(用于服务器)。 - connect(): 连接到服务器(用于客户端)。 - send()/recv() 或 write()/read(): 发送接收数据。 - close() 或 closesocket(): 关闭套接字。 我应该基于引用一般知识来回答。 回答结构: 1. **引言**:简要介绍socket编程在C++中的重要性。 2. **常用函数的功能使用方法**:逐步解释每个函数。 - 使用中文。 - 从引用中提取信息,并添加引用标识。 - 确保清晰逐步。 3. **生成相关问题**:在回答结束时,添加相关问题部分。 步骤: - 先介绍socket编程的基本概念。 - 然后列出并解释常用函数。 - 每个函数的解释包括功能、参数、返回值。 - 自然地添加引用标识。 例如: - 对于socket()函数,参考[^1][^3]。 - 对于bind()函数,参考[^4]。 - 其他函数需要基于一般知识,但确保真实可靠。 最后,添加相关问题。 相关问题应该基于主题,如: 1. socket编程的常见错误调试方法。 2. 如何实现TCPUDP的socket编程。 3. socket编程在现实中的应用。 确保回答可靠:使用标准C++ socket编程知识,避免错误。</think>### C++ Socket编程常用函数的功能使用方法 在C++网络编程中,Socket是实现网络通信的核心工具,它基于操作系统提供的Berkeley Socket API(如POSIX标准)。C++本身不直接提供socket接口,但可以通过系统库(如Linux的sys/socket.h或Windows的winsock2.h)来实现。Socket编程允许应用程序在网络上传输数据,常用于TCP/IP协议栈。下面我将逐步解释常用函数的功能、参数、返回值基本用法,帮助您理解其作用。所有解释基于标准socket API,确保真实可靠。 #### 1. **socket() 函数:创建套接字** - **功能**:`socket()` 函数用于创建一个新的套接字(socket),它是网络通信的端点。套接字定义了通信的类型(如TCP或UDP)协议(如IPv4或IPv6)。创建后,套接字处于未绑定状态,需要后续函数配置地址端口。 - **参数**: - `int domain`:指定通信域(协议族),常用值: - `AF_INET`:IPv4协议。 - `AF_INET6`:IPv6协议。 - `int type`:指定套接字类型: - `SOCK_STREAM`:面向连接的TCP套接字(可靠传输)。 - `SOCK_DGRAM`:无连接的UDP套接字(高效传输)。 - `int protocol`:指定协议,通常设为0(自动选择默认协议,如TCP或UDP)。 - **返回值**:成功时返回一个非负整数(套接字描述符),失败时返回-1(错误码可通过errno获取)。 - **使用方法**:在程序开头创建套接字,作为后续操作的基础。例如,创建一个TCP套接字: ```cpp #include <sys/socket.h> int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket creation failed"); // 错误处理 } ``` 引用[^1][^3]提供了详细解释:socket是应用程序与网络协议栈的接口,类似于“插座”连接数据流[^1][^3]。 #### 2. **bind() 函数:绑定地址端口** - **功能**:`bind()` 函数将套接字绑定到本地IP地址端口号。这通常用于服务器端,指定监听地址(如0.0.0.0表示所有接口)。绑定后,套接字可以接收数据。 - **参数**: - `int sockfd`:由`socket()`创建的套接字描述符。 - `const struct sockaddr *addr`:指向`sockaddr`结构的指针,包含IP地址端口信息。常用`sockaddr_in`结构(IPv4)或`sockaddr_in6`(IPv6)。 - `socklen_t addrlen`:`addr`结构的大小(字节数)。 - **返回值**:成功时返回0,失败时返回-1(错误码在errno中)。 - **使用方法**:服务器端在创建套接字后调用`bind()`,指定本地地址。例如,绑定到IPv4地址端口8080: ```cpp #include <netinet/in.h> struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有接口 server_addr.sin_port = htons(8080); // 端口号,htons()转换字节序 if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("bind failed"); } ``` 引用[^4]强调了`bind()`对服务器的重要性,确保套接字监听指定端口[^4]。 #### 3. **listen() 函数:监听连接请求** - **功能**:`listen()` 函数将套接字设置为被动监听模式,等待客户端连接请求(仅用于TCP服务器)。它指定等待连接队列的最大长度。 - **参数**: - `int sockfd`:已绑定的套接字描述符。 - `int backlog`:等待连接队列的最大长度(通常设为5-10)。 - **返回值**:成功时返回0,失败时返回-1。 - **使用方法**:服务器在`bind()`后调用`listen()`,准备接受连接: ```cpp if (listen(sockfd, 5) == -1) { // 队列最大5个连接 perror("listen failed"); } ``` #### 4. **accept() 函数:接受连接** - **功能**:`accept()` 函数从监听队列中接受一个客户端连接,并创建一个新套接字用于数据传输。原监听套接字继续等待其他连接。 - **参数**: - `int sockfd`:监听套接字描述符。 - `struct sockaddr *addr`:指向客户端地址信息的指针(可选,设为NULL忽略)。 - `socklen_t *addrlen`:`addr`结构的大小指针。 - **返回值**:成功时返回新套接字描述符(用于数据交换),失败时返回-1。 - **使用方法**:服务器在`listen()`后循环调用`accept()`处理连接: ```cpp struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int new_sock = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); if (new_sock == -1) { perror("accept failed"); } // 使用new_sock发送/接收数据 ``` #### 5. **connect() 函数:连接到服务器** - **功能**:`connect()` 函数用于客户端主动连接到服务器(TCP)或指定目标地址(UDP)。在TCP中,它会发起三次握手。 - **参数**: - `int sockfd`:客户端套接字描述符。 - `const struct sockaddr *addr`:指向服务器地址结构的指针。 - `socklen_t addrlen`:`addr`结构的大小。 - **返回值**:成功时返回0,失败时返回-1。 - **使用方法**:客户端在创建套接字后调用`connect()`,指定服务器地址: ```cpp struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 服务器IP if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("connect failed"); } ``` #### 6. **send() recv() 函数:数据传输** - **功能**:`send()` `recv()` 用于TCP套接字的数据发送接收。它们基于连接,确保数据顺序可靠性。UDP通常使用`sendto()``recvfrom()`。 - **参数**: - `int sockfd`:已连接的套接字描述符。 - `const void *buf`:指向数据缓冲区的指针。 - `size_t len`:数据长度(字节)。 - `int flags`:可选标志(通常设为0)。 - **返回值**:成功时返回发送/接收的字节数,失败时返回-1(错误在errno)。 - **使用方法**:在连接建立后使用: ```cpp char buffer[1024]; // 发送数据 ssize_t bytes_sent = send(new_sock, "Hello", 5, 0); // 接收数据 ssize_t bytes_recv = recv(new_sock, buffer, sizeof(buffer), 0); ``` #### 7. **close() 函数:关闭套接字** - **功能**:`close()`(在Linux)或`closesocket()`(在Windows)关闭套接字,释放资源。它会终止连接并回收描述符。 - **参数**:`int sockfd`:要关闭的套接字描述符。 - **返回值**:成功时返回0,失败时返回-1。 - **使用方法**:在通信结束时调用: ```cpp close(sockfd); // Linux ``` #### 总结 Socket编程的核心是这些函数的组合:创建套接字(socket)、绑定地址(bind)、监听连接(listen)、接受连接(accept)、建立连接(connect)、传输数据(send/recv)关闭资源(close)。TCP强调可靠连接,而UDP更高效但无连接。实际开发中,需处理错误边界条件(如非阻塞模式)。引用[^2]指出,Socket是网络通信的基础,类比于寄快递的源地址目标地址[^2]。确保在代码中包含头文件如`<sys/socket.h>`错误处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值