【Linux网络编程】02---网络编程套接字---socket编程常用接口

发布于:2024-08-12 ⋅ 阅读:(162) ⋅ 点赞:(0)
🍁你好,我是 RO-BERRY
📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识
🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油

请添加图片描述



1. socket 常见API

1.1 socket函数-- 创建 socket 文件描述符

  • 函数原型

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);

Socket()为通信创建一个端点,并返回一个指向该端点的文件描述符。成功调用返回的文件描述符将是当前未为进程打开的编号最低的文件描述符。

  • 参数详解
  1. domain

作用:用于设置网络通信的域,函数根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义。

名称 含义 名称 含义
PF_UNIX,PF_LOCAL 本地通讯 PF_X25 ITU-T X25 / ISO-8208协议
AF_INET,PF_INET IPv4 Internet协议 PF_AX25 Amateur radio AX.25
PF_INET6 IPv6 Internet协议 PF_ATMPVC 原始ATM PVC访问
PF_IPX IPX-Novell协议 PF_APPLETALK Appletalk
PF_NETLINK 内核用户界面设备 PF_PACKET 底层包访问
  1. type

作用: type用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等。

名称 含义
SOCK_STREAM Tcp连接,序列化、可靠、双向连接的字节流。支持带外数据传输
SOCK_DGRAM 支持UDP连接(无连接状态的消息)
SOCK_SEQPACKET 序列化包,提供一个序列化、可靠、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出
SOCK_RAW RAW类型,提供原始网络协议访问
SOCK_RDM 提供可靠的数据报文,不过可能数据会有乱序
SOCK_PACKET 这是一个专用类型,不能呢过在通用程序中使用

并不是所有的协议族都实现了这些协议类型,例如,AF_INET协议族就没有实现SOCK_SEQPACKET协议类型。

  1. protocol

用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。

  • 类型为SOCK_STREAM的套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用**connect()**函数进行。一旦连接,可以使用read()或者write()函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收,当数据在一段时间内任然没有接受完毕,可以认为这个连接已经死掉。
  • SOCK_DGRAM和SOCK_RAW 这个两种套接字可以使用函数**sendto()**来发送数据,使用recvfrom()函数接受数据,recvfrom()接受来自制定IP地址的发送方的数据。
  • SOCK_PACKET是一种专用的数据包,它直接从设备驱动接受数据。
  1. errno
含义
EACCES 没有权限建立制定的domain的type的socket
EAFNOSUPPORT 不支持所给的地址类型
EINVAL 不支持此协议或者协议不可用
EMFILE 进程文件表溢出
ENFILE 已经达到系统允许打开的文件数量,打开文件过多
ENOBUFS/ENOMEM 内存不足。socket只有到资源足够或者有进程释放内存
EPROTONOSUPPORT 制定的协议type在domain中不存在

1.2 bind函数 — 绑定端口号

  • 函数原型

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

  • 参数详解
  1. sockfd 需要绑定的socket。

  2. addr 一个存放了服务端用于通信的地址和端口的结构体。

  3. addrlen 表示 addr 结构体的大小

返回值:

成功则返回0 ,失败返回-1,错误原因存于 errno 中。如果绑定的地址错误,或者端口已被占用,bind 函数一定会报错,否则一般不会返回错误。

struct sockaddr{
    sa_family_t  sin_family;   //地址族(Address Family),也就是地址类型
    char         sa_data[14];  //IP地址和端口号
};

sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,但没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值。正是由于通用结构体 sockaddr 使用不便,才针对不同的地址类型定义了不同的结构体。

如ipv6对应的是:

struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};

struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};

Unix域对应的是:

#define UNIX_PATH_MAX    108

struct sockaddr_un { 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
};
  • 作用:

服务端用于将把用于通信的地址和端口绑定到 socket 上。所以可以猜出,这个函数的参数应该包含:用于通信的 socket 和服务端的 IP 地址和端口号。ip地址和端口号是放在 socketaddr_in 结构体里面的。

通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

返回值:
如无错误发生,则bind()返回0。否则的话,将返回-1,应用程序可通过WSAGetLastError()获取相应错误代码。

1.3 listen函数—开始监听

  • 函数原型

#include <sys/types.h>
#include <sys/socket.h>
int listen(int socket, int backlog);

  • 参数详解
  1. socket

表示已经绑定好地址并且打开监听模式的套接字描述符。

  1. backlog

一个整数,它指定了队列中可以等待连接的最大客户端数目。这是一个临时缓冲区,当服务器繁忙,无法立即处理新连接时,这些连接会被放入这个队列中。

listen() 函数的作用是在指定的套接字上开启监听,一旦有新的连接请求到达,系统会将这些请求暂存在队列中,直到服务器有了处理能力。返回值通常是0表示成功,非零表示失败,此时可以根据错误码判断具体原因。

socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

返回值:

如无错误发生,listen()返回0。否则的话,返回-1,应用程序可通过WSAGetLastError()获取相应错误代码。

1.4 accept函数 —接收请求

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

int accept(int socket, struct sockaddr* address,socklen_t* address_len);

参数详解

  1. socket:

服务器的socket描述字

  1. address

指向struct sockaddr *的指针,用于返回客户端的协议地址,

  1. address_len

客户端协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

返回值:

如果没有错误产生,则accept()返回一个描述所接受包的SOCKET类型的值。否则的话,返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()来获得特定的错误代码。

注意:

  • accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;
  • 而accept函数返回的是已连接的socket描述字。两个套接字不一样。
  • 一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

2. 套接字编程

socket套接字编程比较复杂,这里只提供TCP的示例:

服务器

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  
#define BUF_SIZE 1024  
#define PORT 8080  
int main() {  
    int server_fd, new_socket;  
    struct sockaddr_in address;  
    int opt = 1;  
    int addrlen = sizeof(address);  
    char buffer[BUF_SIZE] = {0};  
    const char *hello = "Hello from server";  
    // 创建socket文件描述符  
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {  
        perror("socket failed");  
        exit(EXIT_FAILURE);  
    }  
    // 设置socket选项  
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {  
        perror("setsockopt");  
        exit(EXIT_FAILURE);  
    }  
    address.sin_family = AF_INET;  
    address.sin_addr.s_addr = INADDR_ANY;  
    address.sin_port = htons(PORT);  
    // 绑定socket到端口  
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {  
        perror("bind failed");  
        exit(EXIT_FAILURE);  
    }  
  
    // 开始监听  
    if (listen(server_fd, 3) < 0) {  
        perror("listen");  
        exit(EXIT_FAILURE);  
    }  
    // 等待客户端连接  
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {  
        perror("accept");  
        exit(EXIT_FAILURE);  
    }  
    // 发送一些数据  
    write(new_socket, hello, strlen(hello));  
    // 读取客户端数据并回显  
    while ((read(new_socket, buffer, BUF_SIZE - 1)) > 0) {  
        // 发送数据回客户端  
        write(new_socket, buffer, strlen(buffer));  
        memset(buffer, 0, BUF_SIZE);  
    }  
    if (read(new_socket, buffer, 0) < 0) {  
        perror("read failed");  
    }  
    // 关闭连接  
    close(new_socket);  
    close(server_fd);  
    return 0;  
}

客户端

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  
  
#define BUF_SIZE 1024  
#define SERVER_IP "127.0.0.1" // 服务器IP地址,这里使用本地回环地址  
#define SERVER_PORT 8080        // 服务器端口号,与服务器设置的端口一致  
  
int main() {  
    int sockfd;  
    struct sockaddr_in server_addr;  
    char buffer[BUF_SIZE] = {0};  
    char *message = "Hello from client";  
    // 创建socket文件描述符  
    sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    if (sockfd < 0) {  
        perror("socket creation failed");  
        exit(EXIT_FAILURE);  
    }  
    memset(&server_addr, 0, sizeof(server_addr));  
    // 配置服务器地址信息  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_port = htons(SERVER_PORT);  
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {  
        perror("Invalid server address");  
        exit(EXIT_FAILURE);  
    }  
    // 连接到服务器  
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {  
        perror("connection failed");  
        exit(EXIT_FAILURE);  
    }  
    // 接收服务器的欢迎消息  
    read(sockfd, buffer, BUF_SIZE - 1);  
    printf("Server: %s\n", buffer);  
    // 向服务器发送消息  
    write(sockfd, message, strlen(message));  
    printf("Client: %s\n", message);  
    // 读取服务器的响应  
    memset(buffer, 0, BUF_SIZE); // 清空缓冲区以便接收新数据  
    read(sockfd, buffer, BUF_SIZE - 1);  
    printf("Server echo: %s\n", buffer);  
    // 关闭连接  
    close(sockfd);  
    return 0;  
}


网站公告

今日签到

点亮在社区的每一天
去签到