• Linux系统编程—socket网络编程


    理论概念

    1. TCP与UDP对比

    • TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需 要建立连接

    • TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

    • TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
      UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

    • 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

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

    • TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

    端口号作用

    • 一台拥有IP地址的主机可以提供许多服
      ,比如Web服务、FTP服务、SMTP服务等

    • 通过“IP地址+端口号”来区 分不同的服务,端口提供了一种访问通道,服务器一般都是通过端口号来识别的。

    socket开发过程

    在这里插入图片描述

    服务端

    1. socket 创建套接字

    函数原型

    	#include           
    	#include 
    	
    	int socket(int domain, int type, int protocol);
    
    • 1
    • 2
    • 3
    • 4

    返回值: 成功返回网络标识符,失败返回 -1
    参数分析
    domain: 指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族):

    • AF_INET IPv4因特网域
    • AF_INET6 IPv6 因特网域
    • AF_UNIX Unix 域
    • AF_ROUTE 路由套接字
    • AF_KEY 密钥套接字
    • AF_UNSPEC 未指定

    type: 指定socket类型

    • SOCK_STREAM
        流式套接字提供可靠的、面向连接的通信流,它使用 TCP 协议,从而保证了数据传输的正确性和顺序性
    • SOCK_DGRAM
        数据报套接字定义了一种无连接的数据,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP
    • SOCK_RAW
        允许程序使用低层协议,原始套接字允许对底层协议如 IP 或ICMP 进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。

    protocol:
    通常赋值 0

    • 0 选择 type 类型对应的默认协议
    • IPPROTO_TCP TCP 传输协议
    • IPPROTO_UDP UDP 传输协议
    • IPPROTO_SCTP SCTP 传输协议
    • IPPROTO_TIPC TIPC 传输协议

    2. bind 绑定IP+端口

    • 功能:用于绑定IP地址和端口号到sockfd

    函数原型

           #include           
           #include 
    
           int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
           //成功返回0,失败返回-1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    参数分析

    参数类型/值
    sockfd服务端网络标识符,即 socket 的返回值
    struct sockaddr *addr绑定服务器端 网络协议、IP 地址和端口号的地址结构指针
    socklen_t addrlen结构体大小
    • addr
        一个指向包含有本机 IP 地址及端口号等信息的 sockaddr 类型的指针,指向要绑定给 sockfd 的协议地址结构,这个地址结构根据地址创建socket 时的地址协议族的不同而不同

    注:
    IPV4的实际结构体如下

    struct sockaddr {
    	sa_family_t sa_family;
    	char        sa_data[14];
    }
    
    • 1
    • 2
    • 3
    • 4

    同等替换为:

    		struct sockaddr_in {
    		 sa_family_t       sin_family;    //协议族
    		 in_port_t         sin_port;      //端口号
    		 struct in_addr    sin_port;      //IP地址结构体
    		 unsigned char     sin_zero[8];   //填充 无实际意义
    		};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    struct sockaddr_in 结构体已在Linux内核系统中定义,成员参数配置如下;

    • sa_family_t  sin_family
      协议族, TCP协议族,AF_INET

    • in_port_t    sin_port
      端口号, 类型为网络字节序,需利用htons函数进行转化为网络字节数
      例:s_addr.sin_port = htons(8888);

    • struct in_addr    sin_port
      IP地址结构体,struct in_addr 类型结构体
      系统中定义的struct in_addr 结构体

      	struct in_addr {
      	        __be32  s_addr;
      	};
      
      • 1
      • 2
      • 3

      将IP地址转化为网络能识别的格式函数原型:

      	#include 
      	#include 
      	
      	int inet_aton(const char* straddr,struct in_addr *addrp);
      
      • 1
      • 2
      • 3
      • 4

      转化典例

      	inet_aton("127.0.0.1", &s_addr.sin_addr );
      
      • 1

      地址转化API

      	 int inet_aton(char *straddr,struct in_addr *addr)
      	 //将字符串形式的"127.0.0.1"转化为网络能识别的格式
      	 
      	 char *inet_ntoa(struct in_addr inaddr);
      	 //把网络格式的ip地址转化为字符串形式
      
      • 1
      • 2
      • 3
      • 4
      • 5

    结构体配置及bin函数整合实例

    	struct sockaddr_in s_addr;		//结构体变量定义
    	s_addr.sin_family = AF_INET;	//配置网络协议族,TCP协议
    	inet_aton("127.0.0.1", &s_addr.sin_addr );	//配置结构体成员IP地址
    	s_addr.sin_port = htons(8888);				//配置结构体成员端口号
    	bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));	//绑定IP+端口号
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3. listen 监听客户端

    函数原型

    	#include           /* See NOTES */
    	#include 
    	
    	int listen(int sockfd, int backlog);
    
    • 1
    • 2
    • 3
    • 4

    参数分析

    参数类型/值
    sockfd服务端网络标识符
    backlog允许的最大客户单请求个数

    功能

    • 设置能处理的最大连接数,listen()并未开始接受连线,只是设置 socket 的 listen 模式,listen 函数只用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连接数。
    • 内核为任何一个给定监听套接字维护两个队列:
      • 未完成连接队列,每个这样的 SYN 报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的 TCP 三次握手过程。这些套接字处于SYN REVD 状态
      • 已完成连接队列,每个已完成 TCP 三次握手过程的客户端对应其中一项。这些套接字处于ESTABLISHED 状态

    4. accept 接收客户端

    函数原型

    	#include           /* See NOTES */
    	#include 
    	
    	int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    
    • 1
    • 2
    • 3
    • 4

    返回值: 成功返回接入的客户端网络标识符,失败返回-1
    参数分析

    参数类型/值
    sockfd服务端网络标识符
    struct sockaddr *addrstruct sockaddr 结构体类型地址,用来返回已连接的对端(客户端)的协议地址
    socklen_t *addrlen客户端地址长度,但为地址,需先取长度,后取地址

    例:

    	addrlen = sizeof(struct sockaddr_in);
    	c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &addrlen);
    
    • 1
    • 2

    5. read / write 数据传输

    	#include 
    	ssize_t read(int c_fd, void *buf, size_t count);
    	ssize_t write(int c_fd, const void *buf, size_t count);  
    
    • 1
    • 2
    • 3

    客户端

    1. socket 创建套接字

    函数原型

    	#include           
    	#include 
    	
    	int socket(int domain, int type, int protocol);
    
    • 1
    • 2
    • 3
    • 4

    返回值: 成功返回网络标识符,失败返回 -1
    参数分析

    • domain: 指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族):
    • type: 指定socket类型,SOCK_STREAM 流式套接字提供可靠的、面向连接的通信流,它使用 TCP 协议,
    • protocol: 通常赋值 0 ,选择 type 类型对应的默认协议

    2. connect 连接服务

    • 功能:该函数用于绑定之后的client 端(客户端),与服务器建立连接

    函数原型

    	#include           /* See NOTES */
    	#include 
    	
    	int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    	//成功返回0,失败返回-1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    参数分析

    参数类型/值
    sockfd服务端网络标识符
    struct sockaddr *addr服务器端的 IP 地址和端口号的地址结构指针
    socklen_t addrlen地址长度常被设置为 sizeof(struct sockaddr_in)

    结构体实际配置同服务端,案例如下

        struct sockaddr_in c_addr;		//定义结构体变量
        c_addr.sin_family = AF_INET;	//配置成员网络协议族
        inet_aton("127.0.0.1", &c_addr.sin_addr );	//配置结构体成员IP地址
        c_addr.sin_port = htons(8888);				//配置结构体成员端口号
        connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr_in));	//绑定IP+端口号
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3. read / write 数据传输

    	#include 
    	ssize_t read(int c_fd, void *buf, size_t count);
    	ssize_t write(int c_fd, const void *buf, size_t count);  
    
    • 1
    • 2
    • 3
    • 函数使用方法同Linux文件编程用法,参数主要为:网络标识符、读写Buf以及读写字节数

    server.c与client.c程序案例

    server.c

    #include 
    #include           /* See NOTES */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main()
    {
            int s_fd, c_fd;		//定义网络标识符
            int nread;			//读取字节数
            char readBuf[128]={0};	//读取缓存
            char msg[]="Return from server: I got your message!";	//发送缓存
    
    		//定义网络结构体并初始化
            struct sockaddr_in s_addr;
            struct sockaddr_in c_addr;
            memset(&s_addr, 0, sizeof(struct sockaddr_in));
            memset(&c_addr, 0, sizeof(struct sockaddr_in));
    
            //1. socket 创建套接字
            s_fd  = socket(AF_INET, SOCK_STREAM, 0);	//创建套接字
            if(s_fd == -1){
                    printf("socket error!\n");
                    exit(-1);
            }
    
            //2.bind   绑定IP+端口号
            s_addr.sin_family = AF_INET;				//配置网络协议
            inet_aton("127.0.0.1", &s_addr.sin_addr );	//配置IP地址
            s_addr.sin_port = htons(8888);				//配置端口号
            bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));	//绑定IP+端口号
    
            //3.linsten  监听客户端接入
            listen(s_fd, 10);
            printf("listing......\n");
            //4.accept  接收客户端接入
            int addrlen = sizeof(struct sockaddr_in);
            c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &addrlen);
            if(c_fd == -1){
                    printf("c_fd error!\n");
                    exit(-1);
            }
            else
                    printf("connect client: %s\n",inet_ntoa(c_addr.sin_addr));
    
            //5.read 
            memset(readBuf, '\0', 128);
            nread = read(c_fd, readBuf, 128);	//从客户端读取128字节数据到readBuf缓存
            if(nread == -1)
            {
                    printf("nread error\n");
                    exit(-1);
            }
            printf("Receive: %d Byte context:%s\n",nread, readBuf);
    
            //6.write
            write(c_fd, msg, strlen(msg));	//应答客户端
            memset(msg,'\0',128);
    
            return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    client.c

    #include 
    #include           /* See NOTES */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
            int c_fd;
            int nread;
            int addrlen;
            char readBuf[128]={0};
            char msg[]="hello world";
            struct sockaddr_in c_addr;
            memset(&c_addr, 0, sizeof(struct sockaddr_in));
    
            //1. socket
            c_fd  = socket(AF_INET, SOCK_STREAM, 0);
            if(c_fd == -1){
                    printf("socket error!\n");
                    exit(-1);
            }
    
            //2. connect 与服务器建立连接
            c_addr.sin_family = AF_INET;
            inet_aton("127.0.0.1", &c_addr.sin_addr );
            c_addr.sin_port = htons(8888);
            if( connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr_in)) == -1)
            {
                    printf("connect error\n");
                    exit(-1);
            }
    
            //3.write
            write(c_fd, msg, strlen(msg));
            memset(msg, '\0', 128);
    
            //4.read
            memset(readBuf,'\0',128);
            nread = read(c_fd, readBuf, 128);
            if(nread == -1){
                    printf("nread error\n");
                    exit(-1);
            }
            printf("read from server: %d Byte  context:\n%s\n",nread, readBuf);
            
            return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    试问:如何实现双方自由聊天

    • 服务器通过创建子进程用于和每个不同的客户端进行数据交互,父进程负责接收后续接入的客户端
    • 子进程内部再次创建子进程用来发送数据,父进程则实时接收来自服务器的数据

    server.c 核心代码

    //4.accept
    while(1)
    {
    		//父进程用于接收其他接入的客户端
            c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &addrlen);
            if(c_fd == -1){
                    printf("c_fd error!\n");
                    exit(-1);
            }
            else
                    printf("connect client: %s\n",inet_ntoa(c_addr.sin_addr));
            cnt++;
    
            if(fork() == 0)			//创建进程,子进程用于和客户端进行数据交互
            {
    
                    if(fork() == 0)		//子进程用于向客户端发送数据
                    {
                            //write
                            while(1)
                            {
                                    printf("Inputs: ");
                                    scanf("%s",msg);
                                    write(c_fd, msg, strlen(msg));
                                    memset(msg,'\0',128);
                            }
                    }
                    else		//父进程用于接收来自客户端发送的数据
                    {
                            //read
                            while(1)
                            {
                                    memset(readBuf, '\0', 128);
                                    nread = read(c_fd, readBuf, 128);
                                    if(nread == -1)
                                    {
                                            printf("nread error\n");
                                            exit(-1);
                                    }
                                    printf("Receive: %d Byte context:%s\n",nread, readBuf);
                            }
                    }
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    client.c 核心代码

    while(1)
    {
            if(fork() == 0)			//子进程用于向服务器发送数据
            {
                    while(1)        //write
                    {
                            printf("Input: ");
                            scanf("%s",msg);
                            //gets(msg);
                            write(c_fd, msg, strlen(msg));
                            memset(msg, '\0', 128);
                    }
            }
            else					
            {						//父进程用于接收服务器发送的数据
                    while(1)    	//read    
                    {
                    		
                            memset(readBuf,'\0',128);
                            nread = read(c_fd, readBuf, 128);
                            if(nread == -1){
                                    printf("nread error\n");
                                    exit(-1);
                            }
                            printf("read from server: %d Byte  context:\n",nread);
                            printf("%s\n",readBuf);
                    }
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  • 相关阅读:
    如何优雅部署OpenStack私有云I--Kolla
    从零开始操作系统-09:键盘实现
    linux安装redis
    数据分析 第二周 (条形图,散点图,直方图,numpy运算和数组广播机制)笔记
    MySQL基础
    kali linux将默认的非root提升为root权限
    Java实现快速排序
    Lua与Python:深度解析两者之间的核心差异
    MATLAB使用速成 第三章(MATLAB绘图)
    Python学习基础笔记八——字典
  • 原文地址:https://blog.csdn.net/weixin_46216674/article/details/108084020