网络套接字socket

socket

  socket本质是插板和插座的意思,要完成数据的通信的套接字必须是成对出现的,即代表了插板和插座,,如下图所示,IP地址+端口号就对应了一个socket,一端的发送缓冲区对应了一端的接收缓冲区。建立连接的两个进程各自有一个socket来标识,那么这两个socket就组成的socket pair就唯一标识了一个连接,通过同一个文件描述符,进行对应的操作。

网络字节序

  计算机通常采用的都是小端法存储,即高地址存储高位,地地址存地位,而网络字节序是采用大端法,即高位存在低地址,低位存在高地址。所以计算机将数据填到发送缓冲区之前应该将字节序转换成大端字节序(如果发送主机是大端字节序的就不需要进行转换),而接收端解析接收缓冲区的数据前应该将大端字节序的数据转换成小端字节序(如果接收主机是大端字节序的就不需要进行转换)。一些以下是字节序转换函数:

uint32_t htonl(uint32_t hostlong);  // 主机字节序到网络字节序 无符号长整型(IP)
uint16_t htons(uint16_t hostshort);// 主机字节序到网络字节序 无符号短整型 (port)
uint32_t ntohl(uint32_t netlong); // 网络字节序转为主机字节序 无符号长整型(IP)
uint16_t ntohs(uint16_t netshort); // 网络字节序转为主机字节序 无符号短整型(port)

IP地址转换函数

  IP地址是点分十进制的string,转换成网络字节序的过程如下:

192.168.100.102--->atoi--->int--->htonl--->网络字节序

上面的的转换过程很麻烦,所以系统封装如下函数:

inet_pton()
    函数功能:将主机的IP转换成网络字节序IP
    头 文 件:
            #include <arpa/inet.h>
    定义函数:
            int inet_pton(int af,const char *src, void *dst)
    参数分析:
            af:AF_INET(IPV4),AF_INET6(IPV6) 
            src:传入的IP地址
            dst:传出的网络字节序IP地址
    返 回 值:
            成功:1
            异常:0,表明src不是一个有效的IP地址
            失败:-1

inet_ntop()
    函数功能:网络字节序IP转换成主机IP
    头 文 件:
            #include <arpa/inet.h>
    定义函数:
            const char* inet_ntop(int af,const char *src, void *dst, socklen_t size)
    参数分析:
            af:AF_INET(IPV4),AF_INET6(IPV6) 
            src:传入的IP地址
            dst:传出的网络字节序IP地址
    返 回 值:
            成功:dst
            失败:NULL

以上函数可转换IPV4和IPV6类型的地址,以下函数只可以转换IPV4类型的地址

头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// 把cp指向的字符串转换为32位的网络字节序的二进制值存于inp中
int inet_aton(const char *cp, struct in_addr *inp);
inet_aton("192.168.100.102", &inp);
        
// 把cp指向的字符串转换为32位的网络字节序的二进制值并返回
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
        
// 把in中的32位网络字节序的二进制地址转换为点分十进制的字符串
char *inet_ntoa(struct in_addr in);

TCP服务器,客户端模型创建流程图


  注意:上图中创建了三个套接字

相关API

socket()
    函数功能:
            创建套接字
    头 文 件:
            #include <sys/types.h>       
            #include <sys/socket.h>
    定义函数:
            int socket (int domain, int type, int protocol)
    参数分析:
            domain:
                  AF_INET/PF_INET: 网际协议
                  AF_UNIX/PF_UNIX:本地协议,可写成 AF_LOCAL/PF_LOCA
            type:
                  SOCK_STREAM:流式套接字,TCP
                  SOCK_DGRAM:数据报套接字,UDP
            protocol:协议, 一般为 0
    返 回 值:
            成功:待连接套接字
            失败:-1

bind()
    函数功能:
            将套接字绑定上IP地址和端口号
    头 文 件:
            #include <sys/types.h>          
            #include <sys/socket.h>
    定义函数:
            int bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen)   
    参数分析:
            sockfd: 新创建的套接字
              addr: 包含了IP地址和端口的结构体指针,该结构体用于设置它们
            addrlen:结构体addr的大小       
    返 回 值:
            成功: 0
            失败:-1
    备    注:
            参数2被结构体sockaddr_in代替了,因为结构体sockaddr 没有将IP地址和端口号分别用结构体成员保存,设置和操作的时候不太方便。

listen()
    函数功能:
            设置允许同时建立连接的上线数
    头 文 件:
            #include <sys/types.h>          /* See NOTES */
            #include <sys/socket.h>
    定义函数:
            int listen (int sockfd, int backlog)
    参数分析:
             sockfd:待连接套接字
            backlog:允许最大同时接收连接请求个数
    返 回 值:
            成功:0,并将sockfd设置为监听套接字
            失败:-1

accept()
    函数功能:
            等待对端连接请求
    头 文 件:
            #include <sys/types.h>         
            #include <sys/socket.h>
    定义函数:
            int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen)
    参数分析:
            sockfd:监听套接字
              addr:传入参数,用以存储对端地址(IP+PORT)
           addrlen:参数 addr 的存储区域大小
    返 回 值:
            成功:返回一个新的套接字,旧的套接字将用于监听是否有新的连接请求
            失败:-1

connect()
    函数功能:
            连接对端监听套接字
    头 文 件:
            #include <sys/types.h>         
            #include <sys/socket.h>
    定义函数:
            int connect (int sockfd, const struct sockaddr *addr, socklen_taddrlen);
    参数分析:
            sockfd:待连接套接字
              addr:传入参数,用以存储对端地址(IP+PORT)
           addrlen:地址结构体大小
    返 回 值:
            成功:0
            失败:-1

send()
    函数功能:
            向TCP套接字发送数据
    头 文 件:
            #include <sys/types.h>
            #include <sys/socket.h>
    定义函数:
            ssize_t send (int sockfd, const void *buf, size_t len, int flags)
    参数分析:
            sockfd:已连接套接字
               buf:即将被发送的数据
               len:数据长度
             flags:发送标志
                  MSG_NOSIGNAL:当对端已关闭时,不产生 SIGPIPE 信号
                  MSG_OOB:发送紧急(带外)数据,只针对 TCP 连接
    返 回 值:
            成功:已发送字节数
            失败:-1

recv()
    函数功能:
            从TCP套接字中接收数据
    头 文 件:
            #include <sys/types.h>
            #include <sys/socket.h>
    定义函数:
             ssize_t recv (int sockfd, void *buf, size_t len, int flags);
    参数分析:
            sockfd:已连接套接字
               buf:存储数据缓冲区
               len:缓冲区大小
             flags:接收标志
                  MSG_OOB:接收紧急(带外)数据
    返 回 值:
            成功:已接收字节数
            失败:-1
              

TCP实验程序:服务端和客户端互发数据

server.c

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

/**TCP实现数据双向发送和接受 
 */

void *recv_func(void *connect_fd)
{
    int cfd = *(int *)connect_fd;
    char *rev_data = (char *)calloc(100, sizeof(char));
    if (rev_data == NULL)
    {
        printf("calloc error");
        exit(-1);
    }
    while (1)
    {
        bzero(rev_data , 100);
        int ret_val = recv(cfd, rev_data, 100, 0);
        if (ret_val > 0)
        {
            printf("recv client msg: %s", rev_data); 
        }
    }
}

int main(int argc, char const *argv[])
{
    int ret_val;

    
   // printf("bind\n");
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sock_fd)
    {
        perror("socket error");
        return -1;
    }
     
    struct sockaddr_in server_addr;
    bzero(&server_addr , sizeof(server_addr)); 
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(65000);//设置本服务器的端口
    server_addr.sin_addr.s_addr = inet_addr("172.31.42.135");//将服务器IP绑定上本地网卡的其中一个IP

    ret_val = bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (-1 == ret_val)
    {
        perror("bind error");
        return -1;
    }
    printf("bind successfully\n");

    ret_val = listen(sock_fd, 2);
    if (-1 == ret_val)
    {
        perror("listen error");
        return -1;
    }
    
    printf("listen successfully\n");

    struct sockaddr_in other_addr;
    socklen_t addrlen = sizeof(other_addr);
    int connect_fd = accept(sock_fd, (struct sockaddr *)&other_addr, &addrlen);
    if (-1 == connect_fd)
    {
        perror("accept error");
        return -1;
    }
    printf("accept successfully\n");

    pthread_t TID;
    pthread_create(&TID, NULL, recv_func, (void *)&connect_fd);

    char buf [100] ;
    
    while(1)
    {
        fgets(buf , 100 , stdin);
        ret_val = send(connect_fd , buf , strlen(buf) , 0 );
        // if (ret_val > 0)
        // {
        //     printf("cilent port:%d  send data is:%s", 
        //             ntohs(other_addr.sin_port), buf);
        // }
    }

    close(connect_fd);
    close(sock_fd);
    return 0;
}

client.c

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

void *recv_func(void *sock_fd)
{
    int sfd = *(int *)sock_fd;
    char *rev_data = (char *)calloc(100, sizeof(char));
    if (rev_data == NULL)
    {
        printf("calloc error");
        exit(-1);
    }
    while (1)
    {
        bzero(rev_data , 100);
        int ret_val = recv(sfd, rev_data, 100, 0);
        if (ret_val > 0)
        {
            printf("recv server msg: %s", rev_data); 
        }
    }
}


int main(int argc, char const *argv[])
{
  
    int sock_fd = socket( AF_INET, SOCK_STREAM, 0);
    if (-1 == sock_fd)
    {
        perror("socket error");
        return -1 ;
    }
    printf("socket successfully\n");
    
    struct sockaddr_in client_addr ;  // 定义一个结构体变量
    bzero(&client_addr , sizeof(client_addr)); // 清空结构体
    client_addr.sin_family = AF_INET ; // IPV4地址协议
    client_addr.sin_port = htons(65000) ;
    client_addr.sin_addr.s_addr = inet_addr("172.31.42.135"); // 应该设置为服务器的IP地址

    int ret_val= connect(sock_fd, (struct sockaddr *)&client_addr,sizeof(client_addr));
    if (-1 == ret_val )
    {
        perror("connect error");
        return -1 ;
    }
    printf("connect successfully\n");

    pthread_t TID;
    pthread_create(&TID, NULL, recv_func, (void *)&sock_fd);
    
    char buf [100] ;
    struct in_addr addr;
    addr.s_addr = client_addr.sin_addr.s_addr;

    while(1)
    {
        fgets(buf , 100 , stdin);
        ret_val = send(sock_fd , buf , strlen(buf) , 0 );
        if (ret_val > 0)
        {
            // printf("server ip:%s server port:%d  send data is:%s", 
            //         inet_ntoa(addr), ntohs(client_addr.sin_port), buf);
        }
    }

    close(sock_fd);

    return 0;
}

输出结果:

TCP实验程序:TCP通信实现一个服务器接收多个客服端发来的消息

many_accept.c

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

/**TCP通信实现一个服务器接收多个客服端发来的消息
 */

void *recv_func(void *connect_fd)
{
    int cfd = *(int *)connect_fd;
    char *rev_data = (char *)calloc(100, sizeof(char));
    if (rev_data == NULL)
    {
        printf("calloc error");
        close(connect_fd);
        exit(-1);
    }
    while (1)
    {
        bzero(rev_data , 100);
        int ret_val = recv(cfd, rev_data, 100, 0);
        if (ret_val > 0)
        {
            printf("recv client msg: %s", rev_data); 
        }
    }
    close(connect_fd);
}

int main(int argc, char const *argv[])
{
    int ret_val;

    
   // printf("bind\n");
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sock_fd)
    {
        perror("socket error");
        return -1;
    }
     
    struct sockaddr_in server_addr;
    bzero(&server_addr , sizeof(server_addr)); 
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(65000);
    server_addr.sin_addr.s_addr = inet_addr("172.31.42.130");

    ret_val = bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (-1 == ret_val)
    {
        perror("bind error");
        return -1;
    }
    printf("bind successfully\n");

    ret_val = listen(sock_fd, 2); //注意是同时连接的上限数,不是连接的最大上限数
    if (-1 == ret_val)
    {
        perror("listen error");
        return -1;
    }
    
    printf("listen successfully\n");

    struct sockaddr_in other_addr;
    socklen_t addrlen = sizeof(other_addr);

    int connect_fd;

    
    while(1)
    {
        /**循环监听是否有客户端连接进来,当有新的客户端连接则解除阻塞,返回一个新的套接字,作为参数将其传递给新创建的线程
         */
        connect_fd = accept(sock_fd, (struct sockaddr *)&other_addr, &addrlen); 
        if (-1 == connect_fd)
        {
            perror("accept error");
            return -1;
        }
        printf("accept successfully\n");

        pthread_t TID;
        pthread_create(&TID, NULL, recv_func, (void *)&connect_fd);
    }

    close(sock_fd);
    return 0;
}

输出结果:

备注:
  客户端程序直接用上个实验程序的就可以了,注意服务器程序只能收,不能发。

总结

  1.发起TCP连接请求被拒绝是由于目标服务器上无对应的监听套接字,需要检查IP地址和端口号是否对应;
  2.提示错误bind error: Cannot assign requested address,服务器的地址应该与本地网卡任一网卡的IP都不一样;
  3.提示错误bind error: Address already in use,将本地网卡换一个IP地址,注意程序里面也要换;
  4.需要先运行服务端的程序才能运行客户端的程序;
  5.退出程序时,应该先退出客户端程序,再退出服务端程序。

posted @ 2021-01-17 23:09  ding-ding-light  阅读(296)  评论(0编辑  收藏  举报