c++ socket、 listen、accept、recv 、send、 connect函数记录

本文详细介绍了C++中TCP网络编程的关键函数,包括socket、bind、connect、listen、accept、recv、send的使用。讨论了它们在TCP客户端和服务端的应用,以及在数据传输过程中的作用。

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

socket

int socket (int __domain, int __type, int __protocol);
  1. __domain为协议域,又称协议族,我们最常用的有AF_INET、AF_INET6(也可以写作为PF_INET、PF_INET6),分别代表IPv4地址和IPv6地址。

  2. __type为数据传输方式或套接字类型,最常见的有SOCK_STREAM和 SOCK_DGRAM,其中SOCK_STREAM为面向连接的数据传输方式,是基于TCP的协议,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢;而SOCK_DGRAM是无连接的数据传输方式,是基于UDP的协议,即只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。

  3. __protocol为传输协议,对应上述的__type,常用的有IPPROTO_TCP 和 IPPTOTO_UDP分别代表TCP和UDP协议。而系统会根据__type的值自行选择,因此该项一般可直接指定为0。

  4. socket返回的值是一个文件描述符,由于0,1,2,都被占用,所以是从3开始,错误时返回-1;

bind()和connect()函数

bind()函数用于服务器端,用来绑定套接字和自己的ip地址和端口;
connect()函数用于客户端,旨在连接套接字和服务器端的IP地址和端口。
两个函数的返回值表示是否成功(0表示成功,-1表示错误)
函数原型如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
  1. sockfd为socket返回的文件描述符。

  2. addr为一个const struct sockaddr *指针,由于兼容性的原因,这里我们只能先使用sockaddr_in 结构体来定义相应的IP地址和端口号,然后再强制转换为 sockaddr 类型的方式。

  3. addrlen为2中结构体的大小,可由sizeof()计算给出。

在使用上述两个函数之前,需要先给sockaddr_in 结构体中的成员赋值,其中sockaddr_in 结构体的成员变量如下:

struct sockaddr_in{
    sa_family_t     sin_family;   //协议族,和socket()函数中一致即可
    uint16_t        sin_port;     //16位的端口号,尽量保证端口号在1024~65536之间
    struct in_addr  sin_addr;     //32位IP地址
    char            sin_zero[8];  //不使用,一般用0填充
};

listen()和accept()函数

在服务器端,绑定了套接字后,还需通过listen()函数进入到监听状态。
监听状态下调用accept()函数进行接收,当收到来自客户端的请求后就可以建立连接了。
listen()函数返回值0表示成功,-1表示失败

int listen(int __fd, int __n);
  1. __fd为socket返回的文件描述符。

  2. __n为请求队列的最大长度,也即缓冲区大小。当socket正在处理客户端请求时,如果有新的请求到来,只能被放进缓冲区,这个参数就是表明能接受多少个客户端请求。

accept()函数返回一个新的文件描述符,表示和对应的客户端进行通信。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  1. __fd为socket返回的文件描述符。

  2. addr为一个const struct sockaddr *指针,由于兼容性的原因,这里我们只能先使用sockaddr_in 结构体来定义相应的IP地址和端口号,然后再强制转换为 sockaddr 类型的方式。

  3. addrlen为一个指针,解引用后值为2中结构体的大小,可由sizeof()计算给出。

send()、recv()、read()和write()函数

send()和write()返回值为向fd写入的字节数,=0时断开连接,<0时出错。
recv()和read()返回值为从fd读取的字节数,=0时断开连接或读取结束,<0时出错。

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void*buf,size_t nbytes);
  • read:负责从描述符fd中读取内容,当读取成功时,read返回实际所读的字节数(大于0);如果返回的值为0,表示已经读到文件结束了;如果返回的值小于0,则出现错误,如果错误为EINTR,说明错误是由中断引起的,如果是ECONNREST表示网络连接出了问题。

  • write:将buf中的nbytes字节内容写入到文件描述符fd,成功时返回写的字节数,失败的时候返回-1;在实际的程序中,写入有两种可能:

    • write的返回值大于0,表示写了部分或者是全部的数据。这样我们用一个while循环来不停的写入,但是循环过程中的buf参数和nbyte参数得由我们来更新。也就是说,网络写函数是不负责将全部数据写完之后在返回的。
    • 返回的值小于0,此时出现了错误。我们要根据错误类型来处理.如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接).
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

第四个参数可以是

flags描述
MSG_DONTROUTE不查找表
MSG_OOB接受或者发送带外数据
MSG_PEEK查看数据,并不从系统缓冲区移走数据
MSG_WAITALL等待所有数据

当recv/send的flag参数设置为0时,则和read/write是一样的。
如果有如下几种需求,则read/write无法满足,必须使用recv/send:

  • 为接收和发送进行一些选项设置
  • 从多个客户端中接收报文
  • 发送带外数据(out-of-band data)

TCP客户端

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>

using namespace std;

int main(int argc, char *argv[]){
    if(argc!=3){
        cout<<"Example:./myTceClient 127.0.0.1 5005"<<endl;
    }

    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd==-1){
        perror("socket");
        return -1;
    }

    struct hostent *h;
    if((h=gethostbyname(argv[1])) == 0){
        cout<<"gethostbyname failed"<<endl;
        close(sockfd);
        return -1;
    }

    // struct hostent  {  
    //     char *h_name;         //正式主机名  
    //     char **h_aliases;     //主机别名  
    //     int h_addrtype;       //主机IP地址类型:IPV4-AF_INET  
    //     int h_length;         //主机IP地址字节长度,对于IPv4是四字节,即32位  
    //     char **h_addr_list;   //主机的IP地址列表  
    // }; 


    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(atoi(argv[2]));
    memcpy(&servaddr.sin_addr, h->h_addr, h->h_length);

    int conres = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    if(conres != 0){
        perror("connect failed");
        close(sockfd);
        return -1;
    }

    char buffer[1024];

    for(int ii=0; ii<3; ii++){
        int iret;
        memset(buffer,0, sizeof(buffer));
        sprintf(buffer, "this is  test");
        iret = send(sockfd, buffer, strlen(buffer),0);
        if(iret<=0){
            perror("send");
            break;
        }
        cout<<buffer<<endl;
        memset(buffer,0,sizeof(buffer));

        iret = recv(sockfd, buffer, sizeof(buffer), 0);
        if(iret<=0){
            cout<<"recv nothing"<<endl;
            break;
        }
        cout<<"recv="<<buffer<<endl;
    }
    close(sockfd);
    return 0;
}

Tcp服务端

#include<iostream>
#include<bits/stdc++.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<sys/socket.h>
using namespace std;
int main(int argc, char *argv[] ){
	if(argc!=2){
	cout<<"Example:./myTcpServer 5005 "<<endl;
	return -1;
	}
	int listenfd;
	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	if(listenfd==-1){
		perror("socket");
		return -1;	
	}
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(atoi(argv[1]));
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	int bres=bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	if(bres!=0){
		perror("bind");
		close(listenfd);
		return -1;
	}

	int lisres = listen(listenfd, 5);
	if(lisres != 0){
		perror("listen");
		close(listenfd);
		return -1;
	}

	int clientfd;
	int socklen=sizeof(struct sockaddr_in);
	struct sockaddr_in clientaddr;
	clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, (socklen_t*)&socklen);
	cout<<"客户端"<<inet_ntoa(clientaddr.sin_addr)<<"已连接"<<endl;

	char buffer[1024];
	while(true){
		int iret;
		memset(buffer,0,sizeof(buffer));
		iret = recv(clientfd, buffer, sizeof(buffer), 0);
		if(iret<=0){
			cout<<"iret="<<iret<<endl;
			break;
		}
		cout<<"message="<<buffer<<endl;
		
		strcpy(buffer, "ok");
		iret = send(clientfd,buffer,strlen(buffer),0);
		if(iret<=0){
			perror("sent");
			break;
		}
		cout<<"发送:"<<buffer<<endl;
		
	}
	close(listenfd);
	close(clientfd);

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值