网络编程TCP/UDP(socket编程)
Posted 西科陈冠希
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程TCP/UDP(socket编程)相关的知识,希望对你有一定的参考价值。
TCP/UDP
背景知识
源IP地址和目的IP地址
在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址。
应为在链路层传输的时候,只需要去更改MAC的地址但是最终发到哪里是通过识别IP来确定的。
端口号
端口号(port)是传输层协议的内容.
- 端口号是一个2字节16位的整数;
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
- IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用.
源端口号和目的端口号
传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要发给谁
网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可
大小端存储模式:
应为但我们接受信息的时候从高位接收后,传下一个直接连接上一个如果使用小端则需要所有的传完才能知道具体是什么。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换
socket编程
socket 常见API
sockaddr结构
- 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结构体指针做为参数。
- 其实都是运用的一套接口只不过是对其中的一些数据结构进行改变,也就是我们常说的多态传入TCP和UDP就会分别去找对应的框架。
地址转换函数
基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换
字符串转in_addr的函数:
in_addr转字符串的函数:
UDP
基本概念
特点:
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
UDP服务器和客户端框架
服务器(Server)
首先需要应用socket函数来选择进行什么样的链接
DGRAM就是进行UDP通信。
然后对自己的sockaddr进行创建描述。
这里通过htons转换成网络字节序,下面的可以直接绑定IP也可以使用INADDR-ANY来让自己接受所有的IP。
当创建完成我们的ip和端口此时就需要绑定因为,表示唯一主机的唯一进程。
然后就可以通过定义一个sockaddr因为服务器要知道是谁发给我的,运用recvfrom函数来传入参数进行接收。
这里一定要传地址
服务器需要给客户端发送接受成功,让客户端知道,
sendto()传入之前接收的sockaddr然后发送需要发送的内容。
客户端(Client)
客户端首先也要进行UDP的识别,虽然不需要连接但是需要UDP协议。
客户端也要定义自己的sockaddr,但是客户端不需要绑定,因为客户端不需要去占着资源,一个进程可以拥有很多的端口号,然后运用sendto发送给服务器。
然后进行recvfrom接收服务器回来的消息
TCP
基本概念
特点:
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
代码演示(框架)
TCP和UDP不同的就是双方需要共同去维护套接字,和之前一样首先我们要对其进行初始化并给服务器自己的sockaddr。并进行绑定
不同点
这里绑定后需要进行监听,因为双方要时时刻刻的维护着套接字,如果客户端有连接服务器要对其进行监听。并且这里的lsock不是之前我们UDP中所指向的sock这里的lsock指的是监听的客户端的标识符。
之后要进行程序的开始,首先通过获取到的lsock进行accept接收,这里通过返回值sock来进行传输,也就是说lsock是搭建起连接,accept返回的sock才是和udp类似的进行传输信息的sock,剩余的方法就和udp一样了进行recv和send收发和接受。
客户端的不同点
客户端和udp唯一的不同就是需要先进行connect因为套接字是需要双方维护的,客户端需要先连接到服务器的ip和端口才能进行接下来的收发环节。
连接过程
服务器初始化:
调用socket, 创建文件描述符;
调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失败;
调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 为后面的accept做好准备;
调用accecpt, 并阻塞, 等待客户端连接过来
建立连接的过程:
调用socket, 创建文件描述符;
调用connect, 向服务器发起连接请求;connect会发出SYN段并阻塞等待服务器应答; (第一次)
服务器收到客户端的SYN, 会应答一个SYN-ACK段表示"同意建立连接"; (第二次)
客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次)
三次握手
断开连接的过程:
如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次);
此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次);
read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送
一个FIN; (第三次)
客户端收到FIN, 再返回一个ACK给服务器; (第四次)
四次挥手
在学习socket API时要注意应用程序和TCP协议层是如何交互的:
应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段
应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段
以上是关于网络编程TCP/UDP(socket编程)的主要内容,如果未能解决你的问题,请参考以下文章