【网络篇】套接字编程

文章目录

  • 1、前提知识铺垫
    • 1.1 认识端口---port
    • 1.2 网络数据的五元组信息
    • 1.3 网络字节序
    • 1.4 主机字节序与网络字节序的相互转换
    • 1.5 TCP协议与UDP协议的特性和区别
  • 2、UDP_socket编程
    • 2.1 编程流程
    • 2.2 编程接口
      • 2.2.1 创建套接字
      • 2.2.2 绑定接口
      • 2.2.3 发送接口
      • 2.2.4 接收接口
      • 2.2.5 关闭接口
    • 2.3 编程代码
  • 3、TCP_socket编程
    • 3.1 编程流程
    • 3.2 编程接口(较UDP编程新增的接口)
      • 3.2.1 监听接口
      • 3.2.2 获取新连接
      • 3.2.3 发起连接
      • 3.2.4 收发数据
    • 3.3 编程代码
      • 3.3.1 TCP_demo
      • 3.3.2 TCP + 多进程
      • 3.3.3 TCP + 多线程

1、前提知识铺垫

1.1 认识端口—port

本质端口号是一个2字节16位的无符号整数,范围是[0,65535]

作用端口号是用来标识一个进程,告诉操作系统,当前的数据要交给那一个进程来处理

注意事项

一个端口只能被一个进程占用
一个进程可以占用多个端口

一些知名端口

[0,1023] 范围内的端口已经被一些知名的协议所使用,我们在编写代码的时候不要使用该范围内的数据作为端口号
MySQL----3306端口
Oracle----1521端口

1.2 网络数据的五元组信息

{源IP、 目的IP 、源端口、目的端口、 协议}

名称 作用
源IP 标识网络数据是从哪台主机发出的
目的IP 标识数据要去往哪一台主机
源端口 标识网络数据是从“源IP”对应的这台主机的哪个进程产生的
目的端口 通过目的IP找到目的主机之后,需要利用目的端口找到对应的进程
协议 标定双方传输数据时使用的协议,一般是UDP/TCP

1.3 网络字节序

  字节序又称为端序或者尾序指的是多字节数据在内存中存放的顺序

我们接触的字节序分为两类:小端字节序大端字节序
他们是两种数据在内存中存放顺序的不同规则

字节序 特点
小端字节序 数据的低权值位对应空间的低地址
大端字节序 数据的低权值位对应空间的高地址

如何判断自己的机器遵守的是哪一种存储规则?

方式一:指针+变量+强制类型转换验证
【网络篇】套接字编程_第1张图片
方式二:利用联合体的存储特性来判断
【网络篇】套接字编程_第2张图片
有了上面的铺垫,我们来认识一下主机字节序与网络字节序

主机字节序:指的是机器本身的字节序,如果是大端,则主机字节序为大端,如果是小端,则主机字节序为小端

网络字节序规定网络传输数据的时候采用大端字节序进行传输

1.4 主机字节序与网络字节序的相互转换

既然网络字节序是大端字节序,现在假设有AB两台主机,他们之间需要通过网络进行通信,我们分析A向B发送消息这一单程。

A向B发送的数据,通过网络传输时一定要转换为网络字节序,否则传输的数据可能会出错(这取决于主机A是大端还是小端机器)

B从网络中接收A发送的数据时,也需要将数据从网络字节序转换为B主机的主机字节序

这个具体的转换过程并不需要我们自己实现,OS为我们提供了转换的接口

【网络篇】套接字编程_第3张图片

1.5 TCP协议与UDP协议的特性和区别

UDP:
【网络篇】套接字编程_第4张图片
TCP
【网络篇】套接字编程_第5张图片

2、UDP_socket编程

2.1 编程流程

服务端

Ⅰ、创建套接字
Ⅱ、绑定地址信息
Ⅲ、收发消息
Ⅳ、使用完毕后关闭套接字

客户端

Ⅰ、创建套接字
Ⅱ、不推荐绑定地址信息
  不推荐在代码手动绑定地址信息
Ⅲ、收发消息
Ⅳ、使用完毕后关闭套接字

图示:【网络篇】套接字编程_第6张图片
总结:
1、为什么要创建套接字?
  将进程和网卡进行绑定,进程可以从网卡中接收消息,也可以通过网卡发送消息。
2、绑定地址信息具体干了什么?
  绑定IP和端口。目的是为了在网络中表示一台主机和一个进程。这样一来,对于接收方而言,发送数据的人就知道接受方的在哪台机器的哪个进程了;对于发送方而言,能够标识网络数据是从哪台机器的哪个进程发送出去的。

2.2 编程接口

2.2.1 创建套接字

#include

int socket(int domain, int type, int protocol)

参数:
【网络篇】套接字编程_第7张图片
返回值:
【网络篇】套接字编程_第8张图片

2.2.2 绑定接口

int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);

参数:
sockfd-----创建套接字时返回的套接字描述符
addr-----绑定的地址信息(IP + port)
addrlen----绑定的地址信息的长度

注意:这里的 struct sockaddr是一个通用的数据结构!结构如下:
【网络篇】套接字编程_第9张图片
我们在组织参数的时候,传递的并不是上面的这个通用数据结构,而是struct sockaddr_in这个结构体变量,具体内容如下:
【网络篇】套接字编程_第10张图片

2.2.3 发送接口

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

参数:
【网络篇】套接字编程_第11张图片
返回值:
【网络篇】套接字编程_第12张图片

2.2.4 接收接口

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

参数:
【网络篇】套接字编程_第13张图片

2.2.5 关闭接口

int close(int fd)

2.3 编程代码

【网络篇】套接字编程_第14张图片
具体代码---->点这里

3、TCP_socket编程

3.1 编程流程

服务端

创建套接字
绑定地址信息
监听
获取新连接

收发数据
关闭连接

客户端

创建套接字
不推荐绑定地址信息
发起连接
收发数据
关闭连接

图示:
【网络篇】套接字编程_第15张图片
总结:
监听的含义
监听TCP客户端新的连接,同客户端建立TCP连接。(此时,TCP的建立在内核中就完成了)
获取新连接的含义
获取新连接的套接字描述符,每一个TCP连接会产生一个新的套接字描述符
发起连接的含义
向服务端发起连接

3.2 编程接口(较UDP编程新增的接口)

3.2.1 监听接口

int listen(int sockfd, int backlog)

参数
【网络篇】套接字编程_第16张图片返回值
成功—>0
失败—>-1

3.2.2 获取新连接

int accept(int sockfd, struct sockaddr* addr, socklen_t * addrlen);

【网络篇】套接字编程_第17张图片
关于获取新连接这一接口的理解:
【网络篇】套接字编程_第18张图片

3.2.3 发起连接

int connect (int sockfd, const struct sockaddr* addr, socklen_t addrlen)

【网络篇】套接字编程_第19张图片
【网络篇】套接字编程_第20张图片

3.2.4 收发数据

接收数据:

ssize_t recv(int sockfd, void* buf, size_t len, int flags);

【网络篇】套接字编程_第21张图片
注意:返回值为0表示对端关闭连接了,如果此时的对端指的是客户端,则服务端需要将对应的新套接字描述符关闭!

发送数据

ssize_t send(int sockfd, const void* buf, size_t len, int flags)

【网络篇】套接字编程_第22张图片
注意:
服务端在发送数据的时候,第一个参数sockfd传递的是新创建的套接字描述符,并不是侦听套接字的套接字描述符

3.3 编程代码

期望完成的功能:服务端和多个客户端之间能够正常的通信!

3.3.1 TCP_demo

服务端主要代码:
【网络篇】套接字编程_第23张图片
客户端主要代码 :
【网络篇】套接字编程_第24张图片
运行结果分析:
【网络篇】套接字编程_第25张图片
分析出现这种情况的原因:
 首先,第二个客户端的代码能够执行到提示输入语句处,说明此时该客户端与服务端已经建立了连接。并且客户端发送数据后没有报错,说明客户端数据发送是成功的。因此问题肯定是出在服务端的代码。
我们可以通过命令netstat来查看当前网络连接状态以及相关信息。
【网络篇】套接字编程_第26张图片
综上所述,我们单线程的TCP代码就目前而言,只能服务于单个客户端的情况。(注意:后续可以通过多路转接IO模型实现服务器与客户端是一对多的现象)

TCP_demo客户端代码
TCP_demo服务端代码

基于这种情况,我们需要找到一种方法,能够同时让多个客户端都享受服务。
因此,我们可以让服务端的一个进程(线程)只负责与客户端建立连接,剩下的一批进程(线程)可以各自与一个客户端进行沟通。这样就可以达到我们的目标了。因此TCP结合多进程/多线程就脱颖而出~

下面分别介绍TCP 与多进程和多线程结合的代码

3.3.2 TCP + 多进程

首先,对于客户端的代码而言,不需要做出任何的改动!因为客户端只需要一直和服务端进行通信即可!

主要的更改是在服务端,我们通过创建子进程的方式来实现职责的分离,也就是父进程只负责与客户端建立连接,而子进程负责与客户端进行收发消息。
具体核心代码如下:
【网络篇】套接字编程_第27张图片
注意几点细节:
1、子进程是拷贝父进程的PCB,因此需要父进程先与客户端建立连接,也即在父进程的PCB中的fd_array中有了该套接字的文件描述符之后再创建子进程
2、子进程创建成功过,由于它只需要和客户端进行收发消息,因此只需要accept返回的新套接字描述符即可,所以需要将拷贝自父进程的侦听套接字关闭
3、客户端如果将连接关闭,则子进程需要将对应的套接字即文件描述符关闭,然后该进程需要退出。
但是注意:退出时,一定要通知父进程来回收子进程的退出状态信息,否则子进程就会编程僵尸进程!但是我们不能采用wait || waitpid来回收。因为wait具有阻塞属性,而waitpid需要搭配循环来使用,均不符合我们的预期。我们可以通过信号量的方式来处理,即改写SIGCHLD信号!

具体代码参考 TCP_process服务端

3.3.3 TCP + 多线程

【网络篇】套接字编程_第28张图片
【网络篇】套接字编程_第29张图片
具体代码参考—TCP_thread服务端代码

以上就是关于socket编程的相关总结~感觉有所帮助的友友们欢迎评论转发!!
【网络篇】套接字编程_第30张图片

你可能感兴趣的:(Linux,网络,网络,tcp/ip,网络协议)