网络编程套接字(一)

学习任务:

我们先来认识端口号,区分好主机IP和端口号的区别,以及涉及到进程PID和端口号的区别。

然后简单认识一下TCP协议和UDP协议,这两个协议都是传输层的。接着了解什么是网络字节序,它有什么作用。然后是网络编程的一些接口。最后写代码简单实践一下。

目录

1、认识端口号,区分IP/port,PID/port

2、认识TCP协议,认识UDP协议

3、认识网络字节序

4、socket编程接口

5、代码示例


1、认识端口号,区分IP/port,PID/port

IP地址(公网IP)是用来唯一标识互联网中的一台主机,一台主机一个IP。而IP分源IP和目的IP,源IP和目的IP对一个报文来讲,是起从哪里来,到哪里去的作用,其最大的意义是指导报文该如何进行路径的选择,而路径中,每一个“站点”就是MAC地址的变化。

认识端口号port

数据从计算机A到达计算机B,并不是真正的目的,而是到达计算机B的某一个进程,提供数据处理的服务,才是网络传输数据最终的目的。

数据本身并不是由计算机产生的,而是由人,即用户通过特定的客户端等等输入进去的,因此本质上,所有的网络通信,站在人的角度上,就是人与人之间的通信,这是一个比较好的理解方向,站在计算机角度上,是进程间通信!只不过通信的进程不在一台计算机上。就比如抖音的app客户端,它是一个进程,抖音的服务器,也是一个进程。我们通过抖音客户端达到网络通信,在抖音的服务器上获取信息,便是进程间通信。

而IP地址,仅仅是解决了两台物理机器之间的相互通信的识别问题,我们还要解决是在这两台计算机之间的进程间的通信,就是怎么知道计算机A发出的信息是要传给计算机B中的某个进程呢?这就需要端口号了!网络编程套接字(一)_第1张图片

 

因此,端口号的作用是唯一标识一台机器上的唯一一个进程!通过IP+端口号port,就能够标识互联网中的唯一一个进程!

我们可以将整个网络看成是一个大的OS,所有的网络行为,几乎都是在这一个大的OS进行进程间通信!

既然说端口号port是进程的一个身份,那么进程的PID按理论上来说,也能通过PID来进行网络上的进程间通信,那么为什么还需要一个port呢?

区分IP/PORT,PID/PORT

上面我们已经很清楚了,IP的标识物理机器的,port是标识进程的。而PID也是用来标识进程的,也是唯一性的!其实PID跟port,都属于进程的身份,就好像学生由身份证,也有他的学生证,一句话来说,将进程的PID和port分开来使用,是为了解耦!

一个进程可以关联多个端口号,而一个端口号不能关联多个进程。

网络是一份共享资源

要在网络上进行进程间通信,我们首先需要找到目标主机,然后找到该主机上的服务(进程),完成进程间通信。我们可以说网络世界,是一个进程间通信的世界。而进程要通信的话,由于进程具有独立性,因此不同的进程必须看到同一份资源,即共享资源!所有,网络便是一份共享资源!

2、认识TCP协议,认识UDP协议

这里先简单得对TCP和UDP来一个直观的认识:

TCP协议和UDP协议都是传输层的控制协议,以下是两种协议的特定,我们需要根据它们的特定,在不同场景下,权衡使用哪种协议。

TCP协议:

*传输层协议  *有连接  *可靠传输  *面向字节流

YDP协议:

*传输层协议  *无连接  *不可靠传输  *面向数据报

3、认识网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

网络数据流觉得这样分来分去太麻烦了,这样吧!我就使用大端的形式吧!如果你的数据流本来就是大端,那你就直接传输,如果你的数据流是小端,那么麻烦你先转换成大端,再来传输!

因此,网络字节序指的就是在网络上的采用的大端形式,先发出的数据是低地址,后发出的数据是高地址。

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换:

网络编程套接字(一)_第2张图片

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

总结一下网络字节序:

⭐发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出。
⭐接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。
⭐因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。
⭐TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
⭐不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据。
⭐如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可。

4.socket编程接口

socket的意思是套接字,即

socket 常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

我们逐一来理解一下这些接口:

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

网络编程套接字(一)_第3张图片

第一个参数:domain:协议域。就是需要用哪种协议,我们最常用的就两种->AF_INET (IPV4协议),AF_INET6 (IPV6协议)。

  

第二个参数:套接字的类型,即SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)。

  

第三个参数:这个我们置为0即可,它是用来制定某个协议的特定类型,即type类型中的某个类型。通常一种协议只有一种类型,那样该参数可以直接被设置为0;如果协议有多种类型,则需要指定协议类型。

 

返回值:返回一个文件描述符。

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);

网络编程套接字(一)_第4张图片

 第一个参数:socket函数返回的文件描述符。

 

第二个参数:指定想要绑定的IP和端口。下面将分析sockadder结构体。

 

第三个参数:address的长度。

 

返回值:成功为0,失败-1

sockaddr结构:

网络通信的方式有很多种,比如基于网IP的网络通信,AF_INET,原始套接字,域间套接字等等。有那么多方式,那么在绑定IP和端口的时候,就需要很多种方法了,因此系统需要将其统一一下结构,就有了sockadder。

网络编程套接字(一)_第5张图片

IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址。

 
IPv4、 IPv6地址类型分别定义为常数AF_INET、 AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。

 
socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。

sockaddr 结构

 sockaddr_in 结构

网络编程套接字(一)_第6张图片

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。

我们简化看看表示IPV4的结构体:

struct sockaddr_in
{
    sa_family_t       sin_family;//地址族
    uint16_t          sin_port;//TCP/UDP端口号,16位整型
    struct in_addr    sin_addr;//IP地址,32位整型
    char              sin_sero[8];//别管它了
};

其中sin_famile:

地址族 含义
AF_INET IPV4网络协议中的使用的地址族
AF_INET6 IPV6网络协议中使用的地址族
AF_LOCAL 本地通信中采用的UNIX协议的地址族

in_addr结构

in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数。

我们使用这两个函数,再补充两个函数:recvfrom和sendto就可以写一个示例了(UDP的)。

 recvfrom:适用于UDP协议

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

网络编程套接字(一)_第7张图片

 本函数用于从(已连接)套接口上接收数据,并捕获数据发送源的地址

第一个参数:套接字文件描述符

 

第二个参数:指明一个缓冲区,该缓冲区用来存放recvfrom函数接收到的数据

 

第三个参数:buf的长度

 

第四个参数:一般置0,即false。

 

第五个参数:是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号

 

第六个参数:第五个参数的sizeof

 

返回值:成功返回接收到的字节数。失败返回-1。

sendto:适用于UDP协议

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

网络编程套接字(一)_第8张图片

·第一个参数:套接字文件描述符。

 

第二个参数:指明一个存放应用程序要发送数据的缓冲区。

 

第三个参数:buf的长度

 

第四个参数:置为0吧。

 

第五个参数:dest_addr表示目地机的IP地址和端口号信息

 

第六个参数:dest_addr的长度

 

返回值:成功返回接收到的字节数。失败返回-1。

示例代码:

客户端client代码:

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

void Usage(std::string proc)
{
    std::cout<<"Usage: \n\t"<>message;

        sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&server, sizeof(server));

        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        char buffer[1024];
        recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr*)&tmp, &len);
        std::cout<<"server echo# "<

服务器server代码:

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

const uint16_t port = 8080;

int main()
{
    //1.创建套接字,打开网络文件
    int sock = socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        std::cerr<<"socket create error: "<

你可能感兴趣的:(网络,服务器,tcp/ip)