《Linux操作系统 - 高级编程》第三部分 网络编程 (第2章 网络编程(socket)))

2.1网络编程基础

2.1.1套接字概述

套接字就是网络编程的ID。网络通信,归根到底还是进程间的通信(不同计算机上的进程间的通信)。在网络中,每一个节点(计算机或路由器)都有一个网络地址,也就是IP地址,两个进程通信时,首先要确定各自所在网络节点的网络地址。但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中哪一个进程通信,因此套接口中还需要有其他的信息,也就是端口号(port)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应的关系。所以,使用端口号和网络地址的组合就能唯一确定整个网络中的一个网络进程。

把网络地址和端口号信息放在一个结构体中,也就是套接口地址结构,大多数的套接口函数都需要一个指向套接口地址结构的指针作为参数,并以此来传递地址信息。每个协议族都定义了它自己的套接口地址结构,套接字地址结构都以"sockaddr_” 开头,并以每个协议族名中两个字母作为结尾。

下面是socket所在位置:
这里写图片描述

图1

可以看到套接口有3种类型:
1)流式套接字(SOCK_STREAM)
流式套接字提供可靠的、面向连接的通信刘,保证数据传输的可靠性和按序收发。TCP通信使用的就是流式套接字。

2)数据包套接字(SOCK_DGRAM)
数据报套接字实现了一种不可靠、无连接的服务。数据通过相互独立的报文进行传输,是无序的,并且不保证可靠的传输。UDP通信使用的就是数据报套接字。

3)原始套接字(SOCK_RAW)
原始套接字允许对底层协议(如IP或ICMP)进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。

2.1.2端口号

这里的端口号是逻辑意义上的端口,一般是指TCP/IP 协议中的端口,端口号的范围为0~65535,比如用于浏览网页服务(HTTP协议)的80端口,用于FTP服务的21端口等。其中, 0 到1023 一般被系统程序所使用。

那么TCP/IP协议中的端口指的是什么呢?举个例子,如果IP地址唯一指定了地球上某个地理位置的一间房子,端口号就是出入这间房子的门,只不过这个房子的门有65536个之多,端口是通过端口号来标记的,端口号是一个16位的整数,范围是从0~65535。
端口号只具有本地意义,即端口号只是为了标识本地计算机上的各个进程。在互联网中不同计算机的相同端口号是没有联系的。16bit 的端口号可允许有64K个端口号,这个数目对一个计算机来说是足够用的。

2.1.3 IP地址

1)IP地址的作用
IP地址用来表示网络中的一台主机。准确的说,IP地址是一台主机到一个网络的一个连接,因为现在一个主机中会有多个网卡。

IP地址包含两部分:网络号和主机号。其中,网络号和主机号根据子网掩码来区分。简单的说,有了源IP 和目标 IP,数据包就能在不同主机之间传输。

2)IP地址格式转换
IP地址有两种不同格式:十进制点分形式和32位二进制形式。前者是用户熟悉的形式,而后者则是网络传输中IP地址的存储方式。

这里主要介绍IPV4地址转换函数,主要有 inet_addr() 、inet_aton() 、inet_ntoa() 。前两者的功能都是将字符串转换成32位网络字节序二进制值,第三个将32位网络字节序二进制地址转换成点分十进制的字符串。

inet_addr() 函数语法如下:

表1 inet_addr() 函数

这里写图片描述
inet_pton() 函数语法如下:

表2 inet_pton() 函数

这里写图片描述

inet_ntop() 函数语法如下:

表3 inet_ntop()

这里写图片描述

2.1.4字节序

字节序又称为主机字节序 Host Byte Order,HBO,是指计算机中多字节整型数据的存储方式。字节序有两种:大端(高位字节存储在低位地址,低位字节存储在高位地址)和小端(和大端序相反,PC通常采用小端模式)。

为什么需要字节序?在网络通信中,发送方和接收方有可能使用不同的字节序;

这里写图片描述

图2

为了保证数据接受后能被正确的解析处理,统一规定:数据以高位字节优先顺序在网络上传输。因此数据在发送前和接收后都需要在主机字节序和网络字节序之间转换。

这里写图片描述

图3

1)函数说明
字节序转换涉及4个函数:htons() 、ntohs() 、htonl() 和 ntohl() 。这里的 h 代表 host , n 代表 network , s 代表 short , l 代表 long 。通常 16bit 的IP端口号用前两个函数处理,而 IP 地址用后两个函数来转换。调用这些函数只是使其得到相应的字节序,用户不需要知道该系统的主机字节序和网络字节序是否真的相等。如果两个相同不需要转换的话,该系统的这些函数会定义成空宏。

2)函数格式

表4

这里写图片描述

2.1.5 TCP编程

socket()编程的基本函数有socket() 、bind()、listen()、accept()、send()、sendto()、recv()以及recvfrom()等。下面先简单介绍上述函数的功能,再结合流程图具体说明

1)socket() :该函数用于创建一个套接字,同时指定协议和类型。
2)bind() :该函数将保存在相应地址结构中的地址信息与套接字进行绑定。它主要用于服务器端,客户端创建的套接字可以不绑定地址。
3)listen() :在服务端程序成功建立套接字并与地址进行绑定以后,通过调用listen() 函数将TCP连接后,该函数会返回一个新的已连接套接字。
5)connect():客户端通过该函数向服务器端的监听套接字发送连接请求。
6)send() 和 recv():这两个函数通常在TCP通信过程中用于发送和接收数据,也可用于UDP中。
7)sendto()和recvfrom() :这两个函数一般在UDP通信过程中用于发送和接受数据。当用于TCP时,后面的几个与地址有关的参数不起作用,函数作用等同于 send() 和 recv()
服务器端和客户端使用TCP的流程如下:

这里写图片描述

图4服务器端和客户端使用TCP的流程

可以看到通信工作的大致流程如下:
1)服务器先用socket() 函数来建立一个套接口,用这个套接口完成通信的监听及数据的收发;
2)服务器用bind() 函数来绑定一个端口号和IP地址,使套接口与制定的端口号和IP地址相关联;
3)服务器调用listen()函数,使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求。
4)客户机调用socket()函数建立一个套接口,设定远程IP和端口。
5)客户机调用 connect() 函数链接远程计算机指定的端口。
6)服务器调用 accept() 函数来接受远程计算机的连接请求,建立起与客户机之间的通信连接。
7)建立连接以后,客户机用write() 函数 (或send()函数)向socket() 中写入数据,也可以用 read() 函数(或recv()函数)读取服务器发送来的数据。
8)服务器用 read()函数(或recv()函数)读取客户机发送来的数据,也可以用 write() 函数(或send()函数)来发送数据。
9)完成通信以后,使用close()函数关闭socket 连接。

函数格式:
1)创建套接口 socket() 函数

表5 socket()函数

这里写图片描述

参数domain指明协议族,取值如:
AF_INET:IPv4协议
AF_INET6:IPv6协议
AF_LOCAL:UNIX域协议
AF_ROUTE:路由套接字
AF_KEY:密钥套接字
这里“AF”代表“Adress Family”(地址族)
types指明通信字节流类型,其取值如:
SOCK_STREAM:流式套接字(TCP方式)
SOCK_DGRAM:数据包套接字(UDP方式)
SOCK_RAM:原始套接字

2)绑定端口 bind()函数
用socket() 函数创建一个套接口后,需要使用bind 函数在这个套接口上绑定一个指定的端口号和IP地址。原型如下:

表6 bind()函数

这里写图片描述

这里my_addr是IPv4地址,IPv4 套接口地址数据结构以socketaddr_in 命名,定义在 <netinet/in.h>头文件中,形式如下:

struct sockaddr_in   
sa_family_t    sin_family; /* address family: AF_INET */  
in_port_t      sin_port;   /* port in network byte order */  
struct in_addr sin_addr;   /* internet address */  
;  
sin_famliy 为套接字结构协议族,如IPv4为AF_INET;
sin_port  是16位 TCP或UDP端口号,网络字节顺序;
结构体成员in_addr也是一个结构体,定义如下:
struct in_addr  
{  
    uint32_t s_addr;     /* address in network byte order */  
};  

这里s_addr为32位IPv地址,网络字节顺序;本地地址可以用INADDR_ANY;
【参见附件/socket.c】

#include <stdio.h>  
#include <string.h>  
#include <stdlib.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <unistd.h>  
#define PORT 2345  
  
int main()  
{  
    int sockfd;  
    struct sockaddr_in addr;  
    int addr_len = sizeof(struct sockaddr_in);  
  
    if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)  
    {  
        perror("socket fail");  
        exit(-1);  
    }  
    else  
    {  
        printf("socket created successfully!\nsocket id is %d\n",sockfd);  
    }  
  
    memset(&addr,0,addr_len);  
    addr.sin_family = AF_INET;  
    addr.sin_port = htons(PORT);  
    addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  
    if(bind(sockfd,(struct sockaddr *)(&addr),addr_len) < 0)  
    {  
        perror("bind error");  
        exit(-1);  
    }  
    else  
    {  
        printf("bind port successfully!\nlocal port:%d\n",PORT);  
    }  
    return 0;  
}  

执行结果如下:
这里写图片描述
3)等待监听函数
所谓监听,指的是socket 的端口一直处于等待的状态,监听网络中的所有客户机,耐心等待某一客户机发送请求。如果客户端有连接请求,端口就会接受这个连接。listen 函数用于实现服务器的监听等待功能,它的函数原型如下:

表7 listen()函数

这里写图片描述

需要注意的是listen 并未真正的接受连接,只是设置socket 的状态为监听模式,真正接受客户端连接的是accept 函数。通常情况下,listen 函数会在 socket ,bind 函数之后调用,然后才会调用 accept 函数。

listen函数只适用于SOCK_STREAM或SOCK_SEQPACKET 的socket 类型。如果socket 为 AF_INET ,则参数 backlog 最大值可设至128,即最多可以同时接受128个客户端的请求。

4)接受连接函数
服务器处于监听状态时,如果模式可获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时,再处理客户机的连接请求,接受连接请求的函数时accept,函数原型如下:

表8 accept()函数

这里写图片描述

当 accept 函数接受一个连接时,会返回一个新的 socket 标识符,以后的数据传输与读取就是通过这个新的socket 编号来处理,原来参数中的 socket 也可以继续使用。接受连接以后,远程主机的地址和端口信息会保存在 addr 所指的结构体内。
下面是个实例,体验listen 、accept函数的使用:
【参见附件/lisacc .c】

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <unistd.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#define PORT 2345  
  
int main()  
{  
    int sockfd,newsockfd;  
    struct sockaddr_in addr,caddr;  
    int addr_len = sizeof(struct sockaddr_in);  
    int caddr_len = sizeof(struct sockaddr_in);  
  
    if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)  
    {  
        perror("socket error");  
        exit(-1);  
    }  
    else  
    {  
        printf("socket successfully!\n");  
        printf("socket id : %d\n",sockfd);  
    }  
    memset(&addr,0,addr_len);  
    addr.sin_family = AF_INET;  
    addr.sin_port = htons(PORT);  
    addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  
    if(bind(sockfd,(struct sockaddr *)&addr,addr_len) == -1)  
    {  
        perror("bind error");  
        exit(-1);  
    }  
    else  
    {  
        printf("bind successfully!\n");  
        printf("local port : %d\n",PORT);  
    }  
    if(listen(sockfd,5) == -1)  
    {  
        perror("listen error");  
        exit(-1);  
    }  
    else  
    {  
        printf("listening...\n");  
    }  
    if((newsockfd = accept(sockfd,(struct sockaddr *)&caddr,&caddr_len)) == -1)  
    {  
        perror("accept error");  
        exit(-1);  
    }  
    else  
    {  
        printf("accepted a new connection ..\n");  
        printf("new socket id : %d\n",newsockfd);  
    }  
    return 0;  
}  

执行程序,得到输出结果:
这里写图片描述
程序运行到这停止,并一直在这里等待,说明本机计算机的 2345号端口正处于监听的状态,等待本机上的连接服务请求。此时打开浏览器,在浏览器地址栏中输入下列形式的地址:
http://192.168.3.51:2345/
这个地址是笔者个人IP地址,按"ENTER" 键,这样浏览器会请求连接本地计算机上的2345号端口。此时终端中显示如下结果:
accepted a new connection …
new socket id : 4
表明程序已经接受了这个连接,并创建了一个新的套接口(ID为4),然后退出了程序。

2.1.6请求连接函数

所谓请求连接,是指在客户机向服务器发送信息之前,需要先发送一个连接请求,请求与服务器建立TCP通信连接。connect 函数可以完成这项功能,函数原型如下:

表9 connect()函数

这里写图片描述

这里ser_addr 是一个结构体指针,指向一个sockaddr 结构体,这个结构体存储着远处服务器的IP与端口号信息。

2.1.7数据读写函数

TCP/UDP读写函数总结,注意函数要成对使用。

表10

这里写图片描述

1)send函数
建立套接口并完成通信连接以后,可以把信息传送到远程主机上,这个过程就是信息的发送。而对于远程主机发送来的信息,本地主机需要进行接收处理。下面开始讲述这种面向连接的套接口信息发送与接收操作。

用connect 函数连接到远程计算机以后,可以用 send 函数将应答信息发送给请求服务的本地主机,通信时双向的,并且通信的双方是对等的。

send() 函数原型如下:

表11 send()函数

这里写图片描述

2)recv()函数函数recv 可以接收远程主机发送来的数据,并将这些数据保存到一个数组中,函数原型如下:

表12 recv()函数

这里写图片描述

本章参考代码

点击进入

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
第1节 python高级编程.zip 是一门关于Python编程语言的高级应用的教程。Python是一种易于学习且功能强大的编程语言,广泛应用于各行各业。这门课程将帮助学习者掌握Python的高级编程技巧,包括函数式编程、面向对象编程、装饰器等。通过学习这门课程,学习者可以进一步提升自己的Python编程能力,更加熟练地应用Python解决实际问题。 第2节 linux系统编程.zip 是一门关于Linux操作系统的系统级编程的课程。Linux操作系统是一种开源的、免费的操作系统,广泛应用于服务器、嵌入式设备等领域。该课程将帮助学习者理解Linux操作系统的基本原理和核心概念,并教授如何进行系统级编程,包括文件操作、进程管理、内存管理等。学习这门课程可以帮助学习者深入理解Linux系统,并掌握开发Linux应用程序的技能。 第3节 网络编程.zip 是一门关于网络编程的课程。在现代社会中,网络已经成为人与人、人与机器之间进行信息交流和数据传输的重要手段。这门课程将帮助学习者理解网络编程的基本原理和核心技术,并通过实例演示如何使用Python语言进行网络编程。学习者将学习如何使用Socket编程进行网络通信、如何实现客户端和服务器端的交互、如何处理网络通信中的异常等。通过学习这门课程,学习者可以掌握网络编程的基本技能,为开发网络应用程序打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bruceoxl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值