【C++】socket相关函数

21 篇文章 0 订阅
19 篇文章 0 订阅

Socket本意是(电源)插座,在计算机通信领域中被翻译为“套接字”,是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。通过Socket,两台计算机可以通过网络进行信息的传递。

本篇主要介绍了Socket相关函数的一些基本操作,并给出了客户端和服务器之间通信的例子,其环境是Ubuntu18.04 LTS。

一、Socket相关函数的基本操作

(一)socket()函数

在Linux/Ubuntu环境下,使用socket()函数建立套接字需要引用头文件<sys/socket.h>,其返回值为一个文件描述符(由于一般情况下0、1、2分别表示标准输入、标准输出、标准错误,因此该返回值为在这以后的数字),函数原型如下:

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。

具体使用方法如下:

int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

(二)bind()和connect()函数

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

int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);
int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);

1. __fd为socket返回的文件描述符。

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

3. __len为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填充
};

具体使用方法如下:

struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //先使用0元素对结构体进行初始化
serv_addr.sin_family = AF_INET;  //使用IPv4
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //定义IP地址
serv_addr.sin_port = htons(1234);  //定义端口号
如果是服务器:
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))
客户端:
    connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

(三)listen()和accept()函数

针对服务器端,在绑定了套接字后,还需要使用listen()函数进入到监听状态,然后再调用accept函数进行接收,当接收到来自客户端的请求后就可以建立连接了。

listen()函数返回值表示是否成功(0表示成功,-1表示错误),函数原型如下:

int listen (int __fd, int __n);

1. __fd为socket返回的文件描述符。

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

accept()函数返回一个新的文件描述符(由于一般情况下0、1、2分别表示标准输入、标准输出、标准错误,因此该返回值为在这以后的数字)表示和对应的客户端进行通信,后续具体的通信都使用这个文件描述符进行数据的传输,函数原型如下:

int accept (int __fd, __SOCKADDR_ARG __addr, socklen_t* __addr_len);

这个函数的参数类似于bind()和connect()的参数,详细的解释大家可以参考上文,只不过这里的sockaddr_in并不需要具体的给出,另外最后的长度参数也是通过指针的方式传进去的。

具体使用方法如下:

listen(serv_sock, 20);
struct sockaddr_in client_addr;
socklen_t client_addr_size = sizeof(client_addr);
int client_sock = accept(serv_sock, (struct sockaddr*)&client_addr, &client_addr_size);

注:listen() 函数只是让socket进入监听的状态,程序会继续执行,直到遇到 accept()函数。但accept() 默认是阻塞模式,会阻塞程序执行(即后面代码不能继续进行),直到有请求的到来。

(四)read()和write()函数

至此,服务器和客户端的连接已经建立好了,就剩调用网络I/O函数进行读写操作了。其中最简单的就属read()和write()函数了,它输入的参数较少,能够适应一般的应用场景,其函数返回值为接受/写入的字节数,函数原型如下:

ssize_t read (int __fd, void *__buf, size_t __nbytes)
ssize_t write (int __fd, const void *__buf, size_t __n)

1. __fd为对应的文件描述符(服务器端为accept建立的,客户端为socket建立的)。

2. __buf为要读取和写入的缓冲区地址,由于数据的传输只能以字符串的形式,因此这里要强制类型转换为(char*)。

3. __n/__nbytes为要读取/写入数据的字节数。

具体使用方法如下:

vector<int> test_array(3);
如果是客户端:
    write(client_sock, (char*)test_array.data(), sizeof(int)*test_array.size());
如果是服务器:
    read(client_sock, (char*)test_array.data(), sizeof(int)*test_array.size());

(五)close()函数

最后,当完成了数据的传输,就应该关闭相应的socket文件描述符,以备下一次正常的使用。socket提供了close()函数来进行该项操作,其函数返回值表示是否成功(0表示成功,-1表示错误),函数原型如下,输入参数即为相应的文件描述符:

int close (int __fd);

二、Socket通信例程

这里,我们利用上面的知识,给出了服务器订阅客户端发送的vector<int> test_arry = {1, 2, 3}的数据,并显示在终端上的例子。

(一)server.cpp

#include <iostream>
#include <vector>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

using namespace std;

vector<int> test_array(3);

int main(){
    //creat socket
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(serv_sock == -1) {
        cout << "server_socket failed" << endl;
        return -1;
    }
    //bind the socket to IP and port
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr)); //use 0 to initialize every byte
    serv_addr.sin_family = AF_INET;  //use IPV4
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //define IP
    serv_addr.sin_port = htons(1234);  //define port
    if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))) { cout << "bind failed" << endl; return -1; }

    //enter listening state, wait for the request from client
    if (listen(serv_sock, 20) < 0) { cout << "listen failed" << endl; return -1; }
    
    //accept client's request 
    struct sockaddr_in client_addr;
    socklen_t client_addr_size = sizeof(client_addr);
    int client_sock = accept(serv_sock, (struct sockaddr*)&client_addr, &client_addr_size);
    if(client_sock == -1) { cout << "client_socket failed" << endl; return -1; }     

    read(client_sock, (char*)test_array.data(), sizeof(int)*test_array.size());
    for(int i=0;i<test_array.size();i++){
        cout << test_array[i] << " ";
    }
    cout << endl;

    close(client_sock);
    close(serv_sock);
    return 0;
}

(二)client.cpp

#include <iostream>
#include <vector>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

using namespace std;
vector<int> test_array = {1, 2, 3};

int main(){
    //send request to the server with its IP and port
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr)); //use 0 to initialize every byte
    serv_addr.sin_family = AF_INET;  //use IPV4
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //define IP
    serv_addr.sin_port = htons(1234);  //define port

    //creat socket and establish connection with server
    int client_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    write(client_sock, (char*)test_array.data(), sizeof(int)*test_array.size());    //send data to server 

    close(client_sock);
    return 0;
}

构建完上述两个cpp文件后,对其进行编译生成可执行文件,然后运行即可得到结果。

g++ client.cpp -o client
g++ server.cpp -o server
./server
./client

 

  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mumu_wangwei

主修"红尘道--红尘练心"

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值