网络通信学习笔记之 ———Socket网络通信

一、套接字

1、什么是套接字

​ 套接字(socket)是一种通信机制,是通信的两方的一种约定,socket屏蔽了各个协议的通信细节, 对用户进程提供了一套可以统一、方便的使用TCP/IP协议的接口。这使得程序员无需关注协议本身,直 接使用socket提供的接口与不同主机间的进程互联通信。
网络通信学习笔记之 ———Socket网络通信_第1张图片

2、创建套接字

​ 套接字描述符类似于文件描述符,UNIX、Linux把网络当文件看待,发送数据即写文件write,接收数 据即读文件read,销毁socket对象即关闭文件close,在UNIX、Linux系统下一切皆文件。

#include 
int socket (int domain, int type, int protocol);
功能:在内核中创建一个套接字对象
domain:通信地址类型
    AF_UNIX/AF_LOCAL/AF_FILE: 本地通信(进程间通信)
    AF_INET: 基于IPv4(32位IP地址)的网络通信
    AF_INET6: 基于IPv6(128位IP地址)的网络通信
type:通信协议类型
    SOCK_STREAM: 数据流协议,即TCP协议
    SOCK_DGRAM: 数据报协议,即UDP协议
protocol:特别通信协议,一般不用,置0即可
返回值:成功返回套接字描述符,失败返回-1

3、套接字的连接

​ 当socket对象创建好以后,它并不能立即被其它socket对象连接并通信,需要把一个地址与Socket对 象进行绑定,这样它才能被其它socket对象连接并通信。

// 基本地址类型,它是socket系列接口的表面参数,而实际使用的是sockaddr_un或sockaddr_in
struct sockaddr
{
	sa_family_t sa_family; // 地址类型,与创建socket对象时的domain参数一至即可
	char sa_data[14]; // 它是只是占位而已
};

// 本地地址类型
#include 
struct sockaddr_un
{
    sa_family_t sun_family;
    char sun_path[]; // 套接字文件路径
};

// 网络地址类型
#include 
struct sockaddr_in
{
    sa_family_t sin_family;
    in_port_t sin_port; // 端口号,用于区分通信方的进程,1024~65535
    struct in_addr sin_addr; // IP地址
};

struct in_addr
{
	in_addr_t s_addr; // 32位IPv4地址
};
typedef uint32_t in_addr_t;

#include 
int bind (int sockfd, const struct sockaddr* addr,socklen_t addrlen);
功能:将套接字和通信地址绑定在一起,由被动连接的Socket对象调用
addr:sockaddr_un、sockaddr_in结构变量的地址
addrlen:地址结构变量的字节数,便于bind区分用户提供的是sockaddr_un或sockaddr_in
返回值:成功返回0,失败返回-1
    
#include 
int connect (int sockfd, const struct sockaddr* addr,socklen_t addrlen);
功能:socket对象A连接socket对象B,由主动连接的Socket对象调用
sockfd:socket对象A的描述符,也就是连接的发起者
addr:socket对象B的通信地址,socket对象B必须与该通信地址绑定过
addrlen:地址结构变量的字节数
返回值:成功返回0,失败返回-1

二、IP地址转换与字节序转换

​ 主机字节序因处理器架构而不同,有的采用小端字节序,有的采用大端字节序,网络字节序则固定采 用大端字节序,所以在网络通信时需要把数据类型大于1字节的数据转换成网络字节序。

#include 

// 32位无符号整数,主机字节序 -> 网络字节序
uint32_t htonl (uint32_t hostlong);

// 16位无符号整数,主机字节序 -> 网络字节序
uint16_t htons (uint16_t hostshort);

// 32位无符号整数,网络字节序 -> 主机字节序
uint32_t ntohl (uint32_t netlong);

// 16位无符号整数,网络字节序 -> 主机字节序
uint16_t ntohs (uint16_t netshort);

​ 日常生活中使用IPv4地址是点分十进制格式,而在程序中使用的是32位4字节无符号整数,并且还要 是采用大端字节序。

#include 

// 点分十进制字符串 -> 网络字节序32位无符号整数
in_addr_t inet_addr (const char* cp);

// 点分十进制字符串 -> 网络字节序32位无符号整数
int inet_aton (const char* cp, struct in_addr* inp);

// 网络字节序32位无符号整数 -> 点分十进制字符串
char* inet_ntoa (struct in_addr in);

三、基于TCP协议的网络Socket通信

​ 由于TCP协议是基于面向连接的,而且在连接还需要进行三次握手,所以在通信前被连接Scoket对象 还需要开启监听和等待连接的步骤。

网络通信学习笔记之 ———Socket网络通信_第2张图片

1、开启监听

​ 在TCP服务器编程中listen函数使主动连接套接字变为被动连接套接口,使得一个进程可以接受其它进 程的连接请求,从而成为一个服务器进程。

#include 
int listen (int sockfd, int backlog);
sockfd: 一个已绑定未被连接的套接字描述符
backlog:连接请求队列(queue of pending connections)的最大长度(一般由24)
	用SOMAXCONN则为系统给出的最大值,TCP为侦听套接字维护的两个队列

网络通信学习笔记之 ———Socket网络通信_第3张图片

2、等待连接
int accept (int sockfd, struct sockaddr* addr,socklen_t* addrlen);
功能:从sockfd参数所标识套接字的未决连接请求队列中,提取第一个连接请求。
	同时创建一个新的套接字,用于在该连接中通信,返回该套接字的描述符。
addr:用于输出连接请求发起者的地址信息
addrlen:既是输入也是输出
    输入:它负责告诉accept函数addr的容量
    输出:它能从accept获取连接者的addr的字节数
返回值:成功返回通信套接字描述符,失败返回-1
3、TCP的数据发送和接收

​ Socket对象的本质就是内核文件对象,所以发送数据可以write函数,接收数据可以使用read函数,关 闭Socket对象close,但Socket在write、read的基础上封装了自己发送数据、接收数据的函数。

ssize_t recv (int sockfd, void* buf, size_t len,int flags);
功能:通过sockfd参数所标识的套接字,期望接收len个字节到buf所指向的缓冲区中。
返回值:成功返回实际接收到的字节数,失败返回-1。
flags:暂时写0即可,后续详细介绍
    
ssize_t send (int sockfd, const void* buf,size_t len, int flags);
功能:通过sockfd参数所标识的套接字,从buf所指向的缓冲区中发送len个字节。
返回值:成功返回实际被发送的字节数,失败返回-1
4、详细编程步骤

网络通信学习笔记之 ———Socket网络通信_第4张图片
网络通信学习笔记之 ———Socket网络通信_第5张图片

​ 服务端代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void error_exit(const char *func)
{
    printf("%s:%m\n", func);
    exit(EXIT_FAILURE);
}
void *run(void *arg)
    {
    int cli_fd = *(int *)arg;
    char buf[4096];
    for (;;)
    {
        // 接收数据
        size_t ret = recv(cli_fd, buf, sizeof(buf), 0);
        if (0 >= ret || 0 == strcmp("quit", buf))
        break;
        printf("recv:%s bytes:%u\n", buf, ret);
        strcat(buf, ":return");
        // 返回数据
        ret = send(cli_fd, buf, strlen(buf) + 1, 0);
        if (0 >= ret)
        break;
	}
	printf("通信结束!\n");
	// 关闭通信socket
	close(cli_fd);
}

int main(int argc, const char *argv[])
{
    if (2 != argc)
    {
    printf("Use:./a.out \n");
    return EXIT_FAILURE;
	}
    // 创建socket对象
    int svr_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (0 > svr_fd)
    	error_exit("socket");
    
    // 准备通信地址
    struct sockaddr_in addr = {};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[1]));
    addr.sin_addr.s_addr = inet_addr("10.0.2.15");
    socklen_t addrlen = sizeof(addr);
    
    // 绑定socket对象和通信地址
    if (bind(svr_fd, (struct sockaddr *)&addr, addrlen))
        error_exit("bind");
    
    // 开启监听
    if (listen(svr_fd, 3))
    	error_exit("listen");
    
    for (;;)
    {
        // 等待连接
        int cli_fd = accept(svr_fd, NULL, NULL);
        if (0 > cli_fd)
        	error_exit("accept");
        
        pthread_t tid;
        pthread_create(&tid, NULL, run, &cli_fd);
        usleep(1000);
    }
    
    // 关闭连接socket
    close(svr_fd);
    return EXIT_SUCCESS;
}

​ 客户端代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void error_exit(const char* func)
{
    printf("%s:%m\n",func);
    exit(EXIT_FAILURE);
}

int main(int argc,const char* argv[])
{
    // 创建socket对象
    int cli_fd = socket(AF_INET,SOCK_STREAM,0);
    if(0 > cli_fd)
    	error_exit("socket");
    
    // 准备server的通信地址
    struct sockaddr_in addr = {};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(2123);
    addr.sin_addr.s_addr = inet_addr("10.0.2.15");
    socklen_t addrlen = sizeof(addr);
    
    // 连接server
    if(connect(cli_fd,(struct sockaddr*)&addr,addrlen))
    	error_exit("connect");
    
    char buf[4096];
    for(;;)
    {
        printf(">>>");
        scanf("%s",buf);
        
        // 发送数据
        size_t ret = send(cli_fd,buf,strlen(buf)+1,0);
        if(0 >= ret || 0 == strcmp("quit",buf))
        	break;
        
        // 接收数据
        ret = recv(cli_fd,buf,sizeof(buf),0);
        if(0 >= ret)
        	break;
        
        printf("recv:%s bytes:%u\n",buf,ret);
    }
    
    printf("通信结束!\n");
    // 关闭socket对象
    close(cli_fd);
    return EXIT_SUCCESS;
}

四、基于UDP协议的网络Socket通信

1、UDP协议的特点
  • UDP是无连接的,即通信时不需要创建连接,发送数据结束时也没有连接可以释放,所以减小了开 销和发送数据前的延时;

  • UDP采用最大努力交付,不保证可靠交付,因此主机不需要维护复杂的连接状态;

  • UDP是面向报文的,只在应用层交下来的报文前增加了首部后就向下交付IP层,不需要解决粘包的 问题。

  • UDP是无阻塞控制的,即使网络中存在阻塞,也不会影响发送端的发送频率

  • UDP支持一对一、一对多、多对一、多对多的交互通信;

  • UDP的首部开销小,只有8个字节,它比TCP的20个字节的首部要短;

2、UDP的数据发送和接收
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
					const struct sockaddr *dest_addr, socklen_t addrlen);
功能:UDP专用的数据发送函数。
dest_addr:收件人的地址。
addrlen:地址长度。
    
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
						struct sockaddr *src_addr, socklen_t *addrlen);
功能:UDP专用的数据接收函数。
src_addr:发件人的地址,也是数据返回时的地址
addrlen:地址长度。
3、详细编程步骤

网络通信学习笔记之 ———Socket网络通信_第6张图片

​ 服务端:

#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
    // 创建socket对象
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (0 > sockfd)
    {
        perror("socket");
        return -1;
    }
    
    // 准备通信地址(本机的)
    struct sockaddr_in addr = {}, src_addr = {};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(5566);
    addr.sin_addr.s_addr = inet_addr("192.168.56.103");
    socklen_t addrlen = sizeof(addr);
    
    // 绑定地址与socket对象
    if (bind(sockfd, (struct sockaddr *)&addr, addrlen))
    {
        perror("bind");
        return -1;
    }
    
    char buf[4096];
    size_t buf_size = sizeof(buf);
    
    for (;;)
    {
        // 接收数据
        int size = recvfrom(sockfd, buf, buf_size, 0, (struct sockaddr
*)&src_addr, &addrlen);
        buf[size] = '\0';
        printf("from:%s recv:%s byte:%d\n", inet_ntoa(src_addr.sin_addr),
buf, size);
        strcat(buf, ":return");
        
        // 返回数据
        sendto(sockfd, buf, strlen(buf) + 1, 0, (struct sockaddr
*)&src_addr, addrlen);
    }
    
    return 0;
}

​ 客户端:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
    // 创建socket对象
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (0 > sockfd)
    {
        perror("socket");
        return -1;
    }
    
    // 准备通信地址(目标的)
    struct sockaddr_in addr = {};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(5566);
    addr.sin_addr.s_addr = inet_addr("192.168.56.103");
    socklen_t addrlen = sizeof(addr);
    
    char buf[4096];
    size_t buf_size = sizeof(buf);
    
    for (;;)
    {
        printf(">>>");
        scanf("%s", buf);
        if (0 == strcmp("quit", buf))
        {
            printf("通信结束!");
            close(sockfd);
            break;
        }
        
        sendto(sockfd, buf, strlen(buf)+1, 0, (struct sockaddr *)&addr,
addrlen);
        int size = recvfrom(sockfd, buf, buf_size, 0, (struct sockaddr
*)&addr, &addrlen);
        printf("from:%s recv:%s byte:%d\n", inet_ntoa(addr.sin_addr), buf,
size);
    }
}

4、UDP的连接操作

​ UPD协议底层是否需要连接操作,客户端但可以在Socket层面进行连接,连接后的Socket对象在后续 的通信过程中就不再需要通信地址了。

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc,const char* argv[])
{
    // 创建socket对象
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(0 > sockfd)
    {
        perror("socket");
        return -1;
    }
    
    // 准备通信地址(目标的)
    struct sockaddr_in addr = {};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(7777);
    addr.sin_addr.s_addr = inet_addr("47.97.229.46");
    socklen_t addrlen = sizeof(addr);
    
    // 连接服务器
    if(connect(sockfd,(struct sockaddr*)&addr,addrlen))
    {
        perror("connect");
        return -1;
    }
    
    char buf[4096];
    size_t buf_size = sizeof(buf);
    
    for(;;)
    {
        printf(">>>");
        scanf("%s",buf);
        
        send(sockfd,buf,strlen(buf)+1,0);
        if(0 == strcmp("quit",buf))
        {
            printf("通信结束!");
            close(sockfd);
            break;
        }
        
        int size = recv(sockfd,buf,buf_size,0);
        printf("from:%s recv:%s
byte:%d\n",inet_ntoa(addr.sin_addr),buf,size);
    }
}

五、本地Socket通信

1、本地Socket通信与网络Socket通信的区别

​ 网络Socket通信是把网卡抽象成Socket文件配合TCP/IP协议簇,能够使当前进程与其它计算机的进程 进行网络通信。

​ 本地Socket通信是在文件系统中创建Socket文件,能够使当前进程与本机的其它进程进行通信(IPC 进程间通信)。

2、创建Socket文件

使用sockaddr_un类型的通信地址,当调用socket对象与通信地址绑定时,会自动创建socket文件。

// 本地地址类型
#include 
struct sockaddr_un
{
    sa_family_t sun_family;
    char sun_path[]; // 套接字文件路径
};

#include 
int bind (int sockfd, const struct sockaddr* addr,socklen_t addrlen);
3、基于TCP协议的本地Socket通信
#include 
#include 
#include 
#include 

int main(int argc,const char* argv[])
{
    int sockfd = socket(AF_LOCAL,SOCK_STREAM,0);
    if(0 > sockfd)
    {
        perror("socket");
        return -1;
    }
    
    struct sockaddr_un addr = {};
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path,"tcp_socket");
    socklen_t addrlen = sizeof(addr);
    
    if(bind(sockfd,(struct sockaddr*)&addr,addrlen))
    {
        perror("bind");
        return -1;
    }
    
    if(listen(sockfd,3))
    {
        perror("listen");
        return -1;
    }
    
    for(;;)
    {
        int clifd = accept(sockfd,NULL,0);
        if(0 > clifd)
        {
            perror("accept");
            sleep(1);
            continue;
        }
        
        if(fork())
       		continue;
        
        char buf[4096];
        
        size_t buf_size = sizeof(buf);
        for(;;)
        {
            printf("recv...\n");
            int ret = recv(clifd,buf,buf_size,0);
            if(0 >= ret || 0 == strcmp("quit",buf))
            {
                printf("结束通信!");
                break;
            }
            
            buf[ret] = '\0';
            printf("recv:%s byte:%d\n",buf,ret);
            strcat(buf,":return");
            
            ret = send(clifd,buf,strlen(buf)+1,0);
            if(0 >= ret)
            {
                printf("结束通信!");
                break;
            }
        }
        
    	close(clifd);
    	return 0;
    }
    return 0;
}

#include 
#include 
#include 
#include 

int main(int argc,const char* argv[])
{
    int sockfd = socket(AF_LOCAL,SOCK_STREAM,0);
    if(0 > sockfd)
    {
        perror("sockfd");
        return -1;
    }
    
    struct sockaddr_un addr = {};
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path,"tcp_socket");
    socklen_t addrlen = sizeof(addr);
    
    if(connect(sockfd,(struct sockaddr*)&addr,addrlen))
    {
        perror("connect");
        return -1;
    }
    
    char buf[4096];
    size_t buf_size = sizeof(buf);
    
    for(;;)
    {
        printf(">>>");
        scanf("%s",buf);
        int ret = send(sockfd,buf,strlen(buf)+1,0);
        if(0 >= ret || 0 == strcmp("quit",buf))
        {
            printf("通信结束!\n");
                break;
        }
        
        ret = recv(sockfd,buf,buf_size,0);
        if(0 >= ret)
        {
            printf("通信结束!\n");
            break;
        }
        
        printf("recv:%s byte:%d\n",buf,ret);
    }
    
    close(sockfd);
    return 0;
}

4、基于UDP协议的网络Socket通信
#include 
#include 
#include 
#include 
#include 

int main(int argc,const char* argv[])
{
    // 创建socket对象
    int sockfd = socket(AF_LOCAL,SOCK_DGRAM,0);
    if(0 > sockfd)
    {
        perror("socket");
        return -1;
    }
    
    // 准备通信地址(本机的)
    struct sockaddr_un addr = {}, src_addr = {};
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path,"udp_ctos");
    socklen_t addrlen = sizeof(addr);
    
    // 绑定地址与socket对象
    if(bind(sockfd,(struct sockaddr*)&addr,addrlen))
    {
        perror("bind");
        return -1;
    }
    
    char buf[4096];
    size_t buf_size = sizeof(buf);
    
    for(;;)
    {
        // 接收数据
        int size = recvfrom(sockfd,buf,buf_size,0,(struct
sockaddr*)&src_addr,&addrlen);
        
        printf("from:%s recv:%s byte:%d\n",src_addr.sun_path,buf,size);
        strcat(buf,":return");
        
        // 返回数据
        sendto(sockfd,buf,strlen(buf)+1,0,(struct
sockaddr*)&src_addr,addrlen);
    }
    
    return 0;
}

#include 
#include 
#include 
#include 
#include 

int main(int argc,const char* argv[])
{
    // 创建socket对象
    int sockfd = socket(AF_LOCAL,SOCK_DGRAM,0);
    if(0 > sockfd)
    {
        perror("socket");
        return -1;
    }
    
    // 准备通信地址(自己的)
    struct sockaddr_un addr = {}, dest_addr = {};
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path,"udp_stoc");
    socklen_t addrlen = sizeof(addr);
    
    // 绑定地址与socket对象
    if(bind(sockfd,(struct sockaddr*)&addr,addrlen))
    {
        perror("bind");
        return -1;
    }
    
    // 目标的
    dest_addr.sun_family = AF_LOCAL;
    strcpy(dest_addr.sun_path,"udp_ctos");
    
    char buf[4096];
    size_t buf_size = sizeof(buf);
    
    for(;;)
    {
        printf(">>>");
        scanf("%s",buf);
        if(0 == strcmp("quit",buf))
        {
            printf("通信结束!");
            close(sockfd);
            break;
        }
        
        sendto(sockfd,buf,strlen(buf)+1,0,(struct
sockaddr*)&dest_addr,addrlen);
        int size = recvfrom(sockfd,buf,buf_size,0,(struct
sockaddr*)&dest_addr,&addrlen);
        printf("recv:%s byte:%d\n",buf,size);
    }
}

你可能感兴趣的:(学习,unix,linux)