套接字Socket——TCP、UDP通信

套接字(socket:插座)
是一种可以进行网络通信的内核对象,它有一个唯一的标识符,一般称它为socket描述符,跟文件描述符类似,也可以用read/write/close操作


功能:创建socket对象,返回socket描述符
domain:通信地址类型,AF_UNIX (本地进程间通信) 、AF_INET (使用ipv4地址通信) 、AF_INET6 (使用ipv6地址通信);
type:
  SOCK_STREAM:数据流协议,TCP面向连接的通信协议,优点是安全可靠,数据不会丢失,但速度慢。一般常用于安全性较高的场景。
  SOCK_DGRAM:数据报协议,UDP面向无连接的通信协议,优点是速度快,数据可能丢失,安全性和可靠性与tcp相比不同。一般用于安全性要求不高但是对速度有要求的场景。 

protocol:通常是0,表示为给定的域和套接字类型选择默认协议。

准备通信地址:

本地通信地址
struct sockaddr_un
{
  sun_family_t sun_family;   // 通信地址类型,AF_UNIX、AF_INET、AF_INET6等

  char sun_path[108];  // socket文件的路径,本地通信的socket文件
};


网络通信地址
struct sockaddr_in
{
  short int sin_family;  // 通信地址类型,AF_UNIX、AF_INET、AF_INET6等
  in_port_t sin_port;  // 端口号,以网络字节序表示的16位整数
  struct in_addr sin_addr;  // ip地址,32位的无符号整数
}

服务器绑定socket与通信地址:

一个socket对象只能绑定一个地址。

功能:把socket对象与通信地址建立联系

sockfd:socket函数返回的socket描述符

addr:通信地址

addrlen:通信地址的长度

客户端连接通信目标:

个人计算机系统数据的存储方式可能是大端,也可能是小端,网络通信时需要的是大端数据,必须把数据转换成大端。
uint32_t htonl(uint32_t hostlong);  功能:把32位的主机字节序转换成32位网络字节序
uint16_t htons(uint16_t hostshort);  功能:把16位的主机字节序转换成16位网络字节序
uint32_t ntohl(uint32_t netlong);  功能:把32位网络字节序转换成32位的主机字节序
uint16_t ntohs(uint16_t netshort);  功能:把16位网络字节序转换成16位的主机字节序

生成端口号:
端口号就是一个16位的无符整数因此:uint16_t htons(uint16_t hostshort);

生成ip地址:in_addr_t inet_addr(const char *cp);
功能:把点分十进制的字符串ip地址转换成32位的无符号整数

char *inet_ntoa(struct in_addr in);
功能:把32的的网络字节序的ip地址转换成点分十进制的字符串ip地址。

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据并获取发送端的地址

如果addr非空,它将包含数据发送者的套接字端点地址。当调用recvfrom时,需要设置addrlen参数指向一个整数,该整数包含addr所指向的套接字缓冲区的字节 长度。返回时,该整数设为该地址的实际字节长度。因为可以获得发送者的地址,recvfrom通常用于无连接的套接字。否则,recvfrom等同于recv

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送数据到指定的目标,面对无连接的套接字。

当socket对象被全部关闭,会在内核中停留一段时间(会给重新连接的机会),如果再使用同样的ip地址和端口号时就会失败(延时关闭)。

示例:

udp_client

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

typedef struct sockaddr* sockaddrp;

int main()
{
    // 创建socket对象
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(0 > sockfd)
    {
        perror("socket");
        return -1;
    }

    // 准备通信地址
    struct sockaddr_in addr = {AF_INET};
    addr.sin_port = htons(6666);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    while(1)
    {
        char buf[255] = {};
        // 发送数据到目标
        printf("请输入要返回的数据:");
        gets(buf);
        sendto(sockfd,buf,strlen(buf),0,(sockaddrp)&addr,sizeof(addr));
        if(0 == strcmp(buf,"q")) break;

        // 接收数据与来时的路径
        socklen_t addr_len;
        recvfrom(sockfd,buf,sizeof(buf),0,(sockaddrp)&addr,&addr_len);
        printf("接收到数据:%s
",buf);
        if(0 == strcmp(buf,"q")) break;

    }

    // 关闭
    close(sockfd);
}

udp_server

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

typedef struct sockaddr* sockaddrp;

int main()
{
    // 创建socket对象
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(0 > sockfd)
    {
        perror("socket");
        return -1;
    }

    // 准备通信地址
    struct sockaddr_in addr = {AF_INET};
    addr.sin_port = htons(6666);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 绑定对象与地址
    int ret = bind(sockfd,(sockaddrp)&addr,sizeof(addr));
    if(0 > ret)
    {
        perror("bind");
        return -1;
    }
    
    struct sockaddr_in src_addr = {};
    socklen_t addr_len = sizeof(addr);

    while(1)
    {
        char buf[255] = {};
        // 接收数据与来时的路径
        recvfrom(sockfd,buf,sizeof(buf),0,(sockaddrp)&src_addr,&addr_len);
        printf("接收到数据:%s
",buf);
        if(0 == strcmp(buf,"q")) break;

        // 发送数据到目标
        printf("请输入要返回的数据:");
        gets(buf);
        ret = sendto(sockfd,buf,strlen(buf),0,(sockaddrp)&src_addr,addr_len);
        if(0 == strcmp(buf,"q")) break;
    }

    // 关闭
    close(sockfd);
}

面向连接的通信TCP

功能:声明sockfd处于监听状态,成功返回0,失败返回-1

sockfd:socket函数返回的socket描述符

backlog:提示系统该进程所要入队的未完成连接请求数量。一旦队列满,系统就会拒绝多余的连接请求。

功能:当有客户端发起连接时,服务器就调用accept()返回并接收这个连接,如果有大量客户端发起请求,服务器来不及处理,还没有accept的客户端就处于连接等待状态。 
addr:准备的通信地址  addrlen:通信地址长度

类似于文件操作的read/write,只是多了一个flags标志位,通常填写0,表示为默认的阻塞操作,如果指定为MSG_DONTWAIT则允许非阻塞操作

tcp_client

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <netinet/in.h>

typedef struct sockaddr* sockaddrp;
int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    
    struct sockaddr_in addr = {AF_INET};
    addr.sin_port = htons(6666);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    connect(sockfd,(sockaddrp)&addr,sizeof(addr));
    while(1)
    {
        char buf[255] ={};
        printf("请输入要发送的数据:");
        gets(buf);
        send(sockfd,buf,strlen(buf),0);
        if(0 == strcmp("q",buf)) break;

        bzero(buf,sizeof(buf));
        int ret = recv(sockfd,buf,sizeof(buf),0);
        printf("读取到%d字节,内容:%s
",ret,buf);
        if(0 == strcmp("q",buf)) break;
        

    }
    close(sockfd);
}

tcp_server

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <netinet/in.h>

typedef struct sockaddr* sockaddrp;
int main()
{
    //建立socket对象
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    
    }

    //准备通讯地址
    struct sockaddr_in addr = {AF_INET};
    addr.sin_port = htons(6666);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    //绑定对象与地址
    int ret = bind(sockfd,(sockaddrp)&addr,sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return -1;
    }

    //设置排队数量
    listen(sockfd,1024);

    //等待连接
    struct sockaddr_in src_addr = {};
    socklen_t addr_len = sizeof(src_addr);

    bool flag = true;
    while(flag)
    {
        int clifd = accept(sockfd,(sockaddrp)&src_addr,&addr_len);

        //通信
        while(1)
        {
            char buf[255] = {};
            ret = recv(clifd,buf,sizeof(buf),0);
            printf("接收到%d字节数据,内容:%s
",ret,buf);
            if(0 == strcmp("q",buf))
            {
                flag = false;
                break;
            }

            printf("请输入返回内容:");
            gets(buf);
            send(clifd,buf,strlen(buf),0);
            if(0 == strcmp("q",buf))
            {
                flag = false;
                break;
            }
        }
        close(clifd);
    }
    //关闭socket
    close(sockfd);
}
原文地址:https://www.cnblogs.com/xiehuan-blog/p/9418913.html