IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> socket网络编程 -> 正文阅读

[网络协议]socket网络编程

(一) socket编程基础知识

学习socket编程前,我们先来提升一下认知:

(1) 地址

地址由IP地址端口号构成

  1. IP地址:用于设备标识
  2. 端口号:用于标识网络服务(ftp、http、socket) (5000到10000之间选择)

IP地址转换API(IP地址字符串要转换成网络能识别格式

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);  //字符串转网络字节序  注意第二个参数是结构体

char *inet_ntoa(struct in_addr in);		//网络字节序转字符串

字节序转换API (端口号主机字节序转换成网络字节序)

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);	//将无符号整数hostlong从主机字节顺序转换为网络字节顺序

uint16_t htons(uint16_t hostshort);	//将无符号短整数hostshort从主机字节顺序转换为网络字节顺序

uint32_t ntohl(uint32_t netlong);	//将无符号整数netlong从网络字节顺序转换为主机字节顺序

uint16_t ntohs(uint16_t netshort);	//将无符号短整数netshort从网络字节顺序转换为主机字节顺序
h --> host(主机)   	n --> net(网络)      s --> short(小端)	  l --> long(大端)
(2) 传输协议
  1. TCP:面向连接 (先建立连接再发送数据)
    • 可靠性强。用于传输精度高数据量小的交互通信,一对一交互,无差错,不丢失,不重复,且按序到达
    • 全双工可靠信道
  2. UDP:面向报文 (无需连接直接发送数据)
    • 传输能力强。用于传输精度要求不高数据量大的交互通信,会有数据丢失,支持一对一,一对多,多对一,多对多交互
    • 不可靠信道

(二) socket编程步骤

步骤分为客户端和服务器端,详细步骤如下图示:
在这里插入图片描述

(三) socket编程API

(1) socket()函数

作用:获取套接字

//#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol)
  • domain
    指定所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
    • AF_INET ???IPv4 因特网
    • AF_INET6 ? ? IPv6 因特网
    • AF_UNIX ???Uinix域
    • AF_ROUTE ??路由套接字
    • AF_KEY ? ? ?密钥套接字
    • AF_UNSPEC ? 未指定
  • type
    指定socket类型:
    • SOCK_STREAM ?指定TCP协议
      字节流套接字提供可靠的、面向连接的通信流;他使用TCP协议,从而保证了数据传输正确性和顺序性
    • SOCK_DGRAM ? 指定UDP协议
      数据报文套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,使用UDP协议
    • SOCK_RAW ? ? 指定底层协议
      允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用不便,主要用于一些协议的开发- 项目
  • protocol
    通常赋值为0
    • 0 ? ? ?? ????选择type类型的默认协议TCP协议
    • IPPROTO_TCP ? ?TCP传输协议
    • IPPPOTO_UDP ? ?UDP传输协议
    • IPPROTO_SCTP??SCTP传输协议
    • IPPROTO_TIPC? ?TIPC传输协议

(2)bind()函数

功能:绑定IP号和端口号到socketfd

//#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
  • sockfd
    是一个socket描述符
  • addr
    是一个指向包含有本机IP地址端口号等信息的struct sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同
  • addrlen
    结构体add的大小
  • 返回值
    成功返回0 ,失败返回-1,errno被设置

struct sockaddr ----IPv4

struct sockaddr {
      sa_family_t sa_family;		//协议族
      char        sa_data[14];		//IP地址+端口号
};

常用struct sockaddr_in类型强转替换struct sockaddr

struct sockaddr_in {
  __kernel_sa_family_t  sin_family;    //协议族
  __be16                sin_port;      //端口号
  struct in_addr        sin_addr;      //IP地址结构体

  /* 填充  只为了跟sockaddr结构体对齐才能相互转换 */
  unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -
                        sizeof(unsigned short int) - sizeof(struct in_addr)];
};

struct in_addr {
        __be32  s_addr;
};

命令行查找struct sockaddr_in

  1. 进入头文件文件夹
cd /usr/include/
  1. 当前文件夹下显示行数不区分大小写递归查找
grep "struct sockaddr_in {" * -nir
  1. 进入头文件查看
vi linux/in.h +184

(3) listen()函数

功能:设置最大连接数,一直监听是否有其他的客户进程请求连接,响应连接TCP三次握手

//#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);
  • sockfd
    sockfd进行监听的套接字
  • backlog
    指定在请求队列中允许的最大请求数
  • 返回值
    成功返回0 ,失败返回-1,errno被设置

(4) accept()函数

功能:与客户端建立连接,保存客户端套接字对应的“地方”(包括客户端IP和端口信息等),如果已完成请求的队列为空将阻塞到下一个请求到来

//#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd
    监听的套接字
  • addr (可选择不使用填NULL)
    用于保存已连接的客户端的地址
  • addrled (可选择不使用填NULL)
    客户端地址长度
  • 返回值
    返回一个已连接的新的套接字,交互完成后该套接字就会被关闭,第一个参数为监听套接字且服务器只创建一个
    失败返回-1,errno被设置、

(5) 数据收发函数

收发函数有很多套可以使用:

  1. read()、write() 已连接状态使用
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
  1. recv()、send() 已连接状态使用
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
使用与read、write类似, flags控制选项常设置为0
  1. readv()、wirtev() 已连接状态使用
#include <sys/uio.h>

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

struct iovec {
    void *iov_base; /*指向一个缓冲区,这个缓冲区是存放readv()所接收的数据或 //writev()将要发送的数据*/
    size_t iov_len; /*接收的最大长度以及实际写入的长度*/
};
iovcnt  为iov大小
  1. recvmsg()、sendmsg() 未连接状态可使用
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
struct msghdr {
              void         *msg_name;       /* optional address */
              socklen_t     msg_namelen;    /* size of address */
              struct iovec *msg_iov;        /* scatter/gather array */
              size_t        msg_iovlen;     /* # elements in msg_iov */
              void         *msg_control;    /* ancillary data, see below */
              size_t        msg_controllen; /* ancillary data buffer len */
              int           msg_flags;      /* flags on received message */
           };
  1. recvfrom()、sendto() 未连接状态可使用
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

1、2、3套多用于TCP
4、5套多用于UDP

(6) connect()函数

功能:向服务端发出连接请求并进行连接

//#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd
    与服务端连接的套接字
  • addr
    服务端IP地址与端口号地址结构体指针
  • addrlen
    常设置为 sizeof(struct sockaddr)
  • 返回值
    成功返回0 ,失败返回-1,errno被设置

(四) demo

socket编程实现多方收发信息
需要注意的是:

  1. 根据TCP内部算法,发送端发送内容为空的时候,不会发送,但是会往下执行;接收端被阻塞,所以需要进行一个是否为空的判断
  2. 当主动关闭的一端断开连接的时候(不管是CTRL+C,还是调用close()方法等),主动关闭的一端都会发送FIN包给另一端,而被动关闭的一端再收到FIN包之后,不断接收到0字节,不做为空判断并处理,就会出现刷屏现象
服务端
#include <stdio.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

int main()
{
        int s_fd = 0;
        int ret = 0;
        int c_fd = 0;
        int n_read;
        int n_write;
        char w_buf[128];
        char r_buf[128];
        struct sockaddr_in addr;
        struct in_addr sin_addr;

        s_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (s_fd == -1){
                perror("socket ");
                exit(-1);
        }

        addr.sin_family = AF_INET;
        addr.sin_port = htons(8888);
        inet_aton("192.168.1.8", &addr.sin_addr);
        ret = bind(s_fd, (const struct sockaddr*)&addr, sizeof(addr));
        if (ret == -1){
                perror("bind ");
                exit(-1);
        }

        listen(s_fd, 10);
        int addrlen = 0;
        struct sockaddr_in s_addr;
        addrlen = sizeof(s_addr);
        while(1){
                c_fd = accept(s_fd, (struct sockaddr*)&s_addr, &addrlen);
                if (c_fd == -1){
                        perror("accept ");
                        exit(-1);
                }
                printf("get connect IP: %s\n", inet_ntoa(s_addr.sin_addr));
                if (fork() == 0){
                        write(c_fd, "welcome to server!", strlen("welcome to server!"));
                        if (fork() == 0){
                                while(1){
                                        memset(r_buf, 0, 128);
                                        n_read = read(c_fd, r_buf, sizeof(r_buf));
                                        if (n_read == -1){
                                                perror("read ");
                                        } else if (n_read == 0){
                                                printf("IP :%s   clinet quit!\n", inet_ntoa(s_addr.sin_addr));
                                                exit(0);
                                        } else {
                                                printf("IP: %s   %s\n", inet_ntoa(s_addr.sin_addr), r_buf);
                                        }
                                }
                        } else {
                                while(1){
                                        gets(w_buf);
                                        n_write = write(c_fd, w_buf, sizeof(w_buf));
                                        if (n_write == -1){
                                                perror("write ");
                                        }
                                }
                        }
                }
        }

        return 0;
}
客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>

int main(int argc, char **argv)
{
        int s_fd = 0;
        int ret = 0;
        char r_buf[128];
        char w_buf[128];
        int n_read;
        int n_write;
        struct sockaddr_in addr;

        if (argc != 3){
                printf("input false!\n");
                exit(-1);
        }

        s_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (s_fd == -1){
                perror("socket ");
                exit(-1);
        }
        
        addr.sin_family = AF_INET;
        addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1], &addr.sin_addr);
        ret = connect(s_fd, (const struct sockaddr*)&addr, sizeof(addr));
        if (ret == -1){
                perror("connect ");
                exit(-1);
        }
        write(s_fd, "hi server!", strlen("hi server!"));
        if (fork() == 0){
                while(1){
                        memset(r_buf, 0, 128);
                        n_read = read(s_fd, r_buf, sizeof(r_buf));
                        if (n_read == 0){
                                printf("server quit!\n");
                                exit(0);
                        } else if (n_read == -1){
                                perror("read ");
                        } else{
                                printf("get message: %s\n", r_buf);
                        }
                }
        } else {
                while(1){
                        scanf("%s", w_buf);
                        n_write = write(s_fd, w_buf, sizeof(w_buf));
                        if (n_write == -1){
                                perror("write ");
                        }
                }
        }

        return 0;
}

widow命令行telnet

telnet 192.168.1.8 8888
运行结果

在这里插入图片描述

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-07-11 16:55:33  更:2021-07-11 16:55:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/17 2:22:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码